Compare commits

..

No commits in common. "ae859e0db476788b875a2f849272af8462091c45" and "2004155e51e0016c91cdcdedebbb813023e8b8ee" have entirely different histories.

2 changed files with 60 additions and 65 deletions

109
app.py
View File

@ -120,11 +120,8 @@ def parse_trains(flush: bool = False, verbose: bool = False):
with open('tgvmax.csv') as f: with open('tgvmax.csv') as f:
first_line = True first_line = True
already_seen = set() already_seen = set()
already_updated = set(x[0] for x in db.session.query(Train).filter(Train.last_modification > last_modification)\
.values(Train.id))
for line in (tqdm if verbose else lambda x: x)(csv.reader(f, delimiter=';')): for line in (tqdm if verbose else lambda x: x)(csv.reader(f, delimiter=';')):
if first_line: if first_line:
# Skip first line
first_line = False first_line = False
continue continue
@ -137,10 +134,6 @@ def parse_trains(flush: bool = False, verbose: bool = False):
print("Duplicate:", train_id) print("Duplicate:", train_id)
continue continue
if train_id in already_updated:
# Already updated by the simulator
continue
train = Train( train = Train(
id=train_id, id=train_id,
day=date.fromisoformat(line[0]), day=date.fromisoformat(line[0]),
@ -169,57 +162,62 @@ def parse_trains(flush: bool = False, verbose: bool = False):
def find_routes(day: date | datetime, origin: str, destination: str | None, def find_routes(day: date | datetime, origin: str, destination: str | None,
verbose: bool = False, min_dep: time = time(0, 0), verbose: bool = False):
explored: dict | None = None):
if isinstance(day, datetime): if isinstance(day, datetime):
day = day.date() day = day.date()
if explored is None: trains = db.session.query(Train).filter_by(day=day, tgvmax=True).all()
explored = {}
if origin not in explored:
explored[origin] = (min_dep, None)
valid_routes = []
max_dep = time(23, 59)
else:
max_dep, valid_routes = explored[origin]
if max_dep < min_dep:
# Already parsed these trains
return {destination: valid_routes}
explored[origin] = min_dep, None
trains = db.session.query(Train).filter_by(day=day, tgvmax=True, orig=origin)\
.filter(Train.dep >= min_dep, Train.dep < max_dep).all()
if not trains:
# No train in the requested interval
explored[origin] = (min_dep, valid_routes)
return {destination: valid_routes}
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
# This is not exhaustive, but can be a good approximation
queue_routes(day, origin=origin, verbose=verbose, autocommit=False)
if destination:
queue_routes(day, destination=destination, verbose=verbose, autocommit=False)
db.session.commit() db.session.commit()
for train in (t := tqdm(trains, desc=origin) if verbose else trains): per_arr_explore = {}
if train.dest == destination: valid_routes = []
# We hope that we have a direct train
valid_routes.append([train])
else:
if train.dest in explored and explored[train.dest][1] is None:
# This is a loop
continue
elif train.arr < min_dep:
# The train is not direct and arrives on the next day, we avoid that
continue
find_routes(day, train.dest, destination, verbose, train.arr, explored)
# Filter unusuable trains for train in (t := tqdm(trains) if verbose else trains):
valid_routes += [[train] + it for it in explored[train.dest][1] if it[0].dep >= train.arr] if train.orig == origin:
# Update from the TGVMax simulator
queue_route(day, train.orig_iata, train.dest_iata, verbose, False)
explored[origin] = (min_dep, valid_routes) it = [train]
if train.dest == destination:
# We hope that we have a direct train
valid_routes.append(it)
else:
per_arr_explore.setdefault(train.dest, [])
per_arr_explore[train.dest].append(it)
continue
for it in list(per_arr_explore.get(train.orig, [])):
if any(train.dest == tr.dest or train.dest == origin for tr in it):
# Avoid loops
continue
last_train = it[-1]
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, verbose, False)
new_it = it + [train]
if train.dest == destination:
# Goal is achieved
valid_routes.append(new_it)
else:
per_arr_explore.setdefault(train.dest, [])
per_arr_explore[train.dest].append(new_it)
# Send queued trains to the database # Send queued trains to the database
db.session.commit() db.session.commit()
return {destination: valid_routes} if destination else {} return {destination: valid_routes} if destination else per_arr_explore
# Don't use the decorator to keep the function callable # Don't use the decorator to keep the function callable
@ -243,14 +241,19 @@ def queue_route(day: date | datetime, origin: str, destination: str, verbose: bo
if isinstance(day, datetime): if isinstance(day, datetime):
day = day.date() day = day.date()
query = db.session.query(RouteQueue).filter_by(day=day, origin=origin, destination=destination)\ query = db.session.query(RouteQueue).filter_by(day=day, origin=origin, destination=destination, response_time=None)
.filter((RouteQueue.response_time == None) | (RouteQueue.expiration_time >= datetime.now(timezone('UTC')))) if query.count():
return
query = db.session.query(RouteQueue).filter(RouteQueue.day == day,
RouteQueue.origin == origin,
RouteQueue.destination == destination,
RouteQueue.expiration_time >= datetime.now(timezone('UTC')))
if query.count(): if query.count():
return return
db.session.add(RouteQueue(day=day, origin=origin, destination=destination)) db.session.add(RouteQueue(day=day, origin=origin, destination=destination))
if autocommit: db.session.commit()
db.session.commit()
# Don't use the decorator to keep the function callable # Don't use the decorator to keep the function callable
@ -267,10 +270,6 @@ def queue_routes(day: date | datetime, origin: str | None = None,
if isinstance(day, datetime): if isinstance(day, datetime):
day = day.date() day = day.date()
valid_routes = set(db.session.query(RouteQueue).filter_by(day=day)\
.filter((RouteQueue.response_time == None) | (RouteQueue.expiration_time >= datetime.now(timezone('UTC'))))\
.values(RouteQueue.origin, RouteQueue.destination))
query = db.session.query(Train).filter((Train.day == day)) query = db.session.query(Train).filter((Train.day == day))
if origin: if origin:
query = query.filter((Train.orig_iata == origin) | (Train.orig == origin)) query = query.filter((Train.orig_iata == origin) | (Train.orig == origin))
@ -280,9 +279,7 @@ def queue_routes(day: date | datetime, origin: str | None = None,
for train in (t := tqdm(query) if verbose else query): for train in (t := tqdm(query) if verbose else query):
if verbose: if verbose:
t.set_description(f"{day}: {train.orig} --> {train.dest}") t.set_description(f"{day}: {train.orig} --> {train.dest}")
if (train.orig_iata, train.dest_iata) not in valid_routes: queue_route(day, train.orig_iata, train.dest_iata, verbose, autocommit)
queue_route(day, train.orig_iata, train.dest_iata, verbose, autocommit)
valid_routes.add((train.orig_iata, train.dest_iata))
# Same as above # Same as above

View File

@ -5,37 +5,35 @@
<link href="/static/bootstrap5/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/bootstrap5/css/bootstrap.min.css" rel="stylesheet">
</head> </head>
<body> <body>
<main class="container"> <form id="form" action="#">
<form id="form" action="#">
<datalist id="iataCodes"> <datalist id="iataCodes">
<option value="Chargement…"> <option value="Chargement…">
</datalist> </datalist>
<div class="mb-3"> <div class="mb-3">
<label for="origin" class="form-label">Origine :</label> <label for="origin" class="form-label">Origine :</label>
<input type="text" class="form-control" list="iataCodes" id="origin" <input type="text" class="form-control" list="iataCodes" id="origin"
placeholder="Origine…" aria-describedby="originHelp"> placeholder="Origine…" aria-describedby="originHelp">
<input type="hidden" class="form-control" id="originIata"> <input type="hidden" class="form-control" id="originIata">
<div id="originHelp" class="form-text">Le point de départ de votre trajet.</div> <div id="originHelp" class="form-text">Le point de départ de votre trajet.</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="destination" class="form-label">Destination :</label> <label for="destination" class="form-label">Destination :</label>
<input type="text" class="form-control" list="iataCodes" id="destination" <input type="text" class="form-control" list="iataCodes" id="destination"
placeholder="Destination…" aria-describedby="destinationHelp"> placeholder="Destination…" aria-describedby="destinationHelp">
<input type="hidden" class="form-control" id="destinationIata"> <input type="hidden" class="form-control" id="destinationIata">
<div id="destinationHelp" class="form-text">Le point d'arrivée de votre trajet.</div> <div id="destinationHelp" class="form-text">Le point d'arrivée de votre trajet.</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="day" class="form-label">Jour de départ :</label> <label for="day" class="form-label">Jour de départ :</label>
<input type="date" class="form-control" id="day" aria-describedby="dayHelp" <input type="date" class="form-control" id="day" aria-describedby="dayHelp"
min="{{ today }}" max="{{ max_day }}" value="{{ today }}"> min="{{ today }}" max="{{ max_day }}" value="{{ today }}">
<div id="dayHelp" class="form-text">Le jour de votre départ.</div> <div id="dayHelp" class="form-text">Le jour de votre départ.</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<input type="submit" class="form-control btn btn-primary" value="Rechercher…"> <input type="submit" class="form-control btn btn-primary" value="Rechercher…">
</div> </div>
</form> </form>
<div id="result"></div> <div id="result"></div>
</main>
</body> </body>
<script src="/static/bootstrap5/js/bootstrap.bundle.min.js"></script> <script src="/static/bootstrap5/js/bootstrap.bundle.min.js"></script>
@ -135,7 +133,7 @@
let text = route[0].origin let text = route[0].origin
for (let train of route) { for (let train of route) {
text += " (" + train.departure + ") --> (" + train.arrival + ") " + train.destination + " " text += " (" + train.departure + ") --> (" + train.arrival + ") " + train.destination + ", "
} }
route_elem.textContent = text route_elem.textContent = text
} }