Compare commits
No commits in common. "4f326626bff46186d18605cad2d26d7b7e9c6195" and "995a320208415f730d4bf005fdc8b5798a18b74c" have entirely different histories.
4f326626bf
...
995a320208
95
app.py
95
app.py
@ -18,11 +18,13 @@ from tqdm import tqdm
|
|||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
cli = AppGroup('tgvmax', help="Manage the TGVMax dataset.")
|
cli = AppGroup('tgvmax', help="Manage the TGVMax dataset.")
|
||||||
app.cli.add_command(cli)
|
app.cli.add_command(cli)
|
||||||
|
|
||||||
|
|
||||||
app.config |= config.FLASK_CONFIG
|
app.config |= config.FLASK_CONFIG
|
||||||
|
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
@ -76,8 +78,7 @@ def update_dataset():
|
|||||||
modified_date = datetime.fromisoformat(info['dateModified'])
|
modified_date = datetime.fromisoformat(info['dateModified'])
|
||||||
|
|
||||||
utc = timezone('UTC')
|
utc = timezone('UTC')
|
||||||
last_modified = datetime.utcfromtimestamp(os.path.getmtime('tgvmax.csv')).replace(tzinfo=utc) if os.path.isfile(
|
last_modified = datetime.utcfromtimestamp(os.path.getmtime('tgvmax.csv')).replace(tzinfo=utc) if os.path.isfile('tgvmax.csv') else datetime(1, 1, 1, tzinfo=utc)
|
||||||
'tgvmax.csv') else datetime(1, 1, 1, tzinfo=utc)
|
|
||||||
|
|
||||||
if last_modified < modified_date:
|
if last_modified < modified_date:
|
||||||
print("Updating tgvmax.csv…")
|
print("Updating tgvmax.csv…")
|
||||||
@ -155,30 +156,23 @@ def parse_trains(flush: bool = False):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def find_routes(day: date, origin: str, destination: str | None):
|
def find_routes(day, origin, destination):
|
||||||
trains = db.session.query(Train).filter_by(day=day, tgvmax=True).all()
|
trains = db.session.query(Train).filter_by(day=day, tgvmax=True).all()
|
||||||
|
|
||||||
trains.sort(key=lambda train: train.dep)
|
trains.sort(key=lambda train: train.dep)
|
||||||
|
|
||||||
# For better results later, fetch all trains from the origin or to the destination
|
explore = []
|
||||||
# This is not exhaustive, but can be a good approximation
|
|
||||||
queue_routes(day, origin=origin)
|
|
||||||
if destination:
|
|
||||||
queue_routes(day, destination=destination)
|
|
||||||
|
|
||||||
per_arr_explore = {}
|
per_arr_explore = {}
|
||||||
valid_routes = []
|
valid_routes = []
|
||||||
|
|
||||||
for train in tqdm(trains):
|
for train in tqdm(trains):
|
||||||
if train.orig == origin:
|
if train.orig == origin:
|
||||||
# Update from the TGVMax simulator
|
|
||||||
queue_route(day, train.orig_iata, train.dest_iata)
|
|
||||||
|
|
||||||
it = [train]
|
it = [train]
|
||||||
if train.dest == destination:
|
if train.dest == destination:
|
||||||
# We hope that we have a direct train
|
# We hope that we have a direct train
|
||||||
valid_routes.append(it)
|
valid_routes.append(it)
|
||||||
else:
|
else:
|
||||||
|
explore.append(it)
|
||||||
per_arr_explore.setdefault(train.dest, [])
|
per_arr_explore.setdefault(train.dest, [])
|
||||||
per_arr_explore[train.dest].append(it)
|
per_arr_explore[train.dest].append(it)
|
||||||
continue
|
continue
|
||||||
@ -191,21 +185,30 @@ def find_routes(day: date, origin: str, destination: str | None):
|
|||||||
last_train = it[-1]
|
last_train = it[-1]
|
||||||
|
|
||||||
if last_train.arr <= train.dep:
|
if last_train.arr <= train.dep:
|
||||||
# Update from the TGVMax simulator, this line can be useful later
|
|
||||||
queue_route(day, train.orig_iata, train.dest_iata)
|
|
||||||
|
|
||||||
new_it = it + [train]
|
new_it = it + [train]
|
||||||
if train.dest == destination:
|
if train.dest == destination:
|
||||||
# Goal is achieved
|
# Goal is achieved
|
||||||
valid_routes.append(new_it)
|
valid_routes.append(new_it)
|
||||||
else:
|
else:
|
||||||
|
explore.append(new_it)
|
||||||
per_arr_explore.setdefault(train.dest, [])
|
per_arr_explore.setdefault(train.dest, [])
|
||||||
per_arr_explore[train.dest].append(new_it)
|
per_arr_explore[train.dest].append(new_it)
|
||||||
|
|
||||||
return {destination: valid_routes} if destination else per_arr_explore
|
return valid_routes
|
||||||
|
|
||||||
|
|
||||||
def queue_route(day: date | datetime, origin: str, destination: str, verbose: bool = False):
|
def print_route(route: list[Train]):
|
||||||
|
s = f"{route[0].orig} "
|
||||||
|
for tr in route:
|
||||||
|
s += f"({tr.dep}) --> ({tr.arr}) {tr.dest}, "
|
||||||
|
print(s[:-2])
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command('queue-route')
|
||||||
|
@click.argument('day', type=click.DateTime(formats=['%Y-%m-%d']))
|
||||||
|
@click.argument('origin', type=str)
|
||||||
|
@click.argument('destination', type=str)
|
||||||
|
def queue_route(day: date | datetime, origin: str, destination: str):
|
||||||
"""
|
"""
|
||||||
Fetch the TGVMax simulator to refresh data.
|
Fetch the TGVMax simulator to refresh data.
|
||||||
|
|
||||||
@ -220,7 +223,6 @@ def queue_route(day: date | datetime, origin: str, destination: str, verbose: bo
|
|||||||
|
|
||||||
query = db.session.query(RouteQueue).filter_by(day=day, origin=origin, destination=destination, response_time=None)
|
query = db.session.query(RouteQueue).filter_by(day=day, origin=origin, destination=destination, response_time=None)
|
||||||
if query.count():
|
if query.count():
|
||||||
if verbose:
|
|
||||||
print("Already queued")
|
print("Already queued")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -229,7 +231,6 @@ def queue_route(day: date | datetime, origin: str, destination: str, verbose: bo
|
|||||||
RouteQueue.destination == destination,
|
RouteQueue.destination == destination,
|
||||||
RouteQueue.expiration_time >= datetime.now(timezone('UTC')))
|
RouteQueue.expiration_time >= datetime.now(timezone('UTC')))
|
||||||
if query.count():
|
if query.count():
|
||||||
if verbose:
|
|
||||||
print("Using recent value")
|
print("Using recent value")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -237,38 +238,8 @@ def queue_route(day: date | datetime, origin: str, destination: str, verbose: bo
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
# Don't use the decorator to keep the function callable
|
|
||||||
cli.command('queue-route')(click.argument('day', type=click.DateTime(formats=['%Y-%m-%d']))
|
|
||||||
(click.argument('origin', type=str)
|
|
||||||
(click.argument('destination', type=str)
|
|
||||||
(click.option('--verbose', '-v', type=bool, is_flag=True, help="Display errors.")
|
|
||||||
(queue_route)))))
|
|
||||||
|
|
||||||
|
|
||||||
def queue_routes(day: date | datetime, origin: str | None = None,
|
|
||||||
destination: str | None = None, verbose: bool = False):
|
|
||||||
if isinstance(day, datetime):
|
|
||||||
day = day.date()
|
|
||||||
|
|
||||||
query = db.session.query(Train).filter((Train.day == day))
|
|
||||||
if origin:
|
|
||||||
query = query.filter((Train.orig_iata == origin) | (Train.orig == origin))
|
|
||||||
if destination:
|
|
||||||
query = query.filter((Train.dest_iata == destination) | (Train.dest == destination))
|
|
||||||
for train in query.all():
|
|
||||||
queue_route(day, train.orig_iata, train.dest_iata, verbose)
|
|
||||||
|
|
||||||
|
|
||||||
# Same as above
|
|
||||||
cli.command('queue-routes')(click.argument('day', type=click.DateTime(formats=['%Y-%m-%d']))
|
|
||||||
(click.option('--origin', '-o', default=None)
|
|
||||||
(click.option('--destination', '-d', default=None)
|
|
||||||
(click.option('--verbose', '-v', type=bool, is_flag=True, help="Display errors.")
|
|
||||||
(queue_routes)))))
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command('process-queue', help="Process the waiting list to refresh from the simulator.")
|
@cli.command('process-queue', help="Process the waiting list to refresh from the simulator.")
|
||||||
@click.argument('number', default=30, type=int)
|
@click.argument('number', default=5, type=int)
|
||||||
def process_queue(number: int):
|
def process_queue(number: int):
|
||||||
queue = db.session.query(RouteQueue).filter_by(response_time=None).order_by(RouteQueue.queue_time)
|
queue = db.session.query(RouteQueue).filter_by(response_time=None).order_by(RouteQueue.queue_time)
|
||||||
if number > 0:
|
if number > 0:
|
||||||
@ -289,7 +260,6 @@ def process_queue(number: int):
|
|||||||
req.response_time = datetime.now()
|
req.response_time = datetime.now()
|
||||||
req.expiration_time = datetime.now() + timedelta(hours=1)
|
req.expiration_time = datetime.now() + timedelta(hours=1)
|
||||||
db.session.add(req)
|
db.session.add(req)
|
||||||
db.session.commit()
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
@ -300,16 +270,12 @@ def process_queue(number: int):
|
|||||||
req.expiration_time += timedelta(hours=3) # By default 5 minutes, extend it to 3 hours to be safe
|
req.expiration_time += timedelta(hours=3) # By default 5 minutes, extend it to 3 hours to be safe
|
||||||
db.session.add(req)
|
db.session.add(req)
|
||||||
|
|
||||||
db.session.query(Train).filter_by(day=req.day, orig_iata=req.origin, dest_iata=req.destination) \
|
db.session.query(Train).filter_by(day=req.day, orig_iata=req.origin, dest_iata=req.destination)\
|
||||||
.update(dict(tgvmax=False, remaining_seats=-1))
|
.update(dict(tgvmax=False, remaining_seats=-1))
|
||||||
|
|
||||||
for proposal in data['proposals']:
|
for proposal in data['proposals']:
|
||||||
train = db.session.query(Train).filter_by(day=req.day, number=int(proposal['trainNumber']),
|
train = db.session.query(Train).filter_by(day=req.day, number=int(proposal['trainNumber']),
|
||||||
orig_iata=req.origin, dest_iata=req.destination).first()
|
orig_iata=req.origin, dest_iata=req.destination).first()
|
||||||
if train is None:
|
|
||||||
# In a city with multiple stations
|
|
||||||
print(proposal)
|
|
||||||
continue
|
|
||||||
train.tgvmax = True
|
train.tgvmax = True
|
||||||
train.remaining_seats = proposal['freePlaces']
|
train.remaining_seats = proposal['freePlaces']
|
||||||
train.last_modification = req.response_time
|
train.last_modification = req.response_time
|
||||||
@ -338,17 +304,9 @@ def iata_codes():
|
|||||||
|
|
||||||
|
|
||||||
@app.get('/api/routes/<day>/<origin>/<destination>/')
|
@app.get('/api/routes/<day>/<origin>/<destination>/')
|
||||||
def get_routes(day: date | str, origin: str, destination: str):
|
def get_routes(day: date, origin: str, destination: str):
|
||||||
if isinstance(day, str):
|
|
||||||
day = date.fromisoformat(day)
|
|
||||||
|
|
||||||
if destination == 'undefined':
|
|
||||||
destination = None
|
|
||||||
|
|
||||||
routes = find_routes(day, origin, destination)
|
routes = find_routes(day, origin, destination)
|
||||||
|
return [
|
||||||
return {
|
|
||||||
city: [
|
|
||||||
[{
|
[{
|
||||||
'origin': tr.orig,
|
'origin': tr.orig,
|
||||||
'origin_iata': tr.orig_iata,
|
'origin_iata': tr.orig_iata,
|
||||||
@ -358,9 +316,8 @@ def get_routes(day: date | str, origin: str, destination: str):
|
|||||||
'arrival': tr.arr.isoformat(),
|
'arrival': tr.arr.isoformat(),
|
||||||
'number': tr.number,
|
'number': tr.number,
|
||||||
'free_seats': tr.remaining_seats,
|
'free_seats': tr.remaining_seats,
|
||||||
} for tr in route] for route in city_routes
|
} for tr in route] for route in routes
|
||||||
] for city, city_routes in routes.items()
|
]
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -78,71 +78,27 @@
|
|||||||
let result_elem = document.getElementById('result')
|
let result_elem = document.getElementById('result')
|
||||||
document.getElementById('form').addEventListener('submit', () => {
|
document.getElementById('form').addEventListener('submit', () => {
|
||||||
result_elem.innerHTML = 'Chargement…'
|
result_elem.innerHTML = 'Chargement…'
|
||||||
fetch('/api/routes/' + day_elem.value + '/' + origin_elem.value + '/' + (destination_elem.value || 'undefined') + '/')
|
fetch('/api/routes/' + day_elem.value + '/' + origin_elem.value + '/' + destination_elem.value + '/')
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.then(routes => {
|
.then(routes => {
|
||||||
|
console.log(routes)
|
||||||
result_elem.innerHTML = ''
|
result_elem.innerHTML = ''
|
||||||
|
|
||||||
let cities_number = Object.keys(routes).length
|
|
||||||
|
|
||||||
let accordion = document.createElement('div')
|
|
||||||
accordion.classList.add('accordion')
|
|
||||||
accordion.setAttribute('id', 'result-accordion')
|
|
||||||
result_elem.appendChild(accordion)
|
|
||||||
|
|
||||||
for (let city in routes) {
|
|
||||||
let city_routes = routes[city]
|
|
||||||
|
|
||||||
let city_id = city.replaceAll(' ', '_')
|
|
||||||
|
|
||||||
let city_elem = document.createElement('div')
|
|
||||||
city_elem.classList.add('accordion-item')
|
|
||||||
accordion.appendChild(city_elem)
|
|
||||||
|
|
||||||
let city_header = document.createElement('h2')
|
|
||||||
city_header.classList.add('accordion-header')
|
|
||||||
city_header.setAttribute('id', 'collapse-header-' + city_id)
|
|
||||||
city_elem.appendChild(city_header)
|
|
||||||
|
|
||||||
let city_name_button = document.createElement('button')
|
|
||||||
city_name_button.classList.add('accordion-button')
|
|
||||||
if (cities_number > 1)
|
|
||||||
city_name_button.classList.add('collapsed')
|
|
||||||
city_name_button.setAttribute('type', 'button')
|
|
||||||
city_name_button.setAttribute('data-bs-toggle', 'collapse')
|
|
||||||
city_name_button.setAttribute('data-bs-target', '#collapse-' + city_id)
|
|
||||||
city_name_button.setAttribute('aria-expanded', cities_number > 1 ? 'false' : 'true')
|
|
||||||
city_name_button.setAttribute('aria-controls', 'collapse-' + city_id)
|
|
||||||
city_name_button.textContent = city
|
|
||||||
city_header.appendChild(city_name_button)
|
|
||||||
|
|
||||||
let collapse = document.createElement('div')
|
|
||||||
collapse.setAttribute('id', 'collapse-' + city_id)
|
|
||||||
collapse.classList.add('accordion-collapse')
|
|
||||||
if (cities_number > 1)
|
|
||||||
collapse.classList.add('collapse')
|
|
||||||
collapse.setAttribute('aria-labelled-by', 'collapse-header-' + city_id)
|
|
||||||
collapse.setAttribute('data-bs-parent', '#result-accordion')
|
|
||||||
city_elem.appendChild(collapse)
|
|
||||||
|
|
||||||
let collapse_body = document.createElement('div')
|
|
||||||
collapse_body.classList.add('accordion-body')
|
|
||||||
collapse.appendChild(collapse_body)
|
|
||||||
|
|
||||||
let routes_elem = document.createElement('ul')
|
let routes_elem = document.createElement('ul')
|
||||||
collapse_body.appendChild(routes_elem)
|
result_elem.appendChild(routes_elem)
|
||||||
|
for (let route in routes) {
|
||||||
for (let route of city_routes) {
|
route = routes[route]
|
||||||
|
console.log(route)
|
||||||
let route_elem = document.createElement('li')
|
let route_elem = document.createElement('li')
|
||||||
routes_elem.appendChild(route_elem)
|
routes_elem.appendChild(route_elem)
|
||||||
|
|
||||||
let text = route[0].origin
|
let text = route[0].origin
|
||||||
for (let train of route) {
|
for (let train in route) {
|
||||||
|
train = route[train]
|
||||||
|
console.log(train)
|
||||||
text += " (" + train.departure + ") --> (" + train.arrival + ") " + train.destination + ", "
|
text += " (" + train.departure + ") --> (" + train.arrival + ") " + train.destination + ", "
|
||||||
}
|
}
|
||||||
route_elem.textContent = text
|
route_elem.textContent = text
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user