This document serves as a technical index for project graders to verify exactly how and where all 21 project requirements for the Robust Journey Planning system have been implemented and utilized across the codebase.
-
Status: Met. The
MapVisualizerclass provides a fully interactive UI usingipywidgets(dropdowns for stations, sliders for confidence$Q$ ) andipyleafletfor map drawing. -
Implementation & Usage: Implemented in
src/viz/map_visualizer.py. Provides an interactive interface for querying and visualizing the robust journey planner. - Link: src/viz/map_visualizer.py (Lines 185-197)
- Solution: Centralized settings management via
ProjectSettings. - High-Level Entry Point: results.ipynb (Lines 78-83) — Overridden in the first cell using
get_settings(...). - Implementation & Usage: Defined in
settings.py, hashed inmodel_artifacts.pyto namespace models, and used as a filter incsa_data_handler.py. - Link: src/config/settings.py (Line 111)
region_uuids: tuple[str, ...] = field(default_factory=lambda: _split_csv(os.getenv("COM490_REGION_UUIDS")))- Solution: SQL-level spatial filtering using Trino GIS functions.
- High-Level Entry Point: results.ipynb (Lines 913-920) — Triggered by
bench_robust_prepare(..., regions=LAUSANNE_REGION_UUIDS). - Implementation & Usage: The
build_stopsmethod performs anST_Containsjoin between SBB stop coordinates and the selected Geo UUID polygons. - Link: src/data/csa_data_handler.py (Lines 120-127)
region_join = f"""
JOIN (
SELECT wkb_geometry FROM {self._table('src_geo')}
WHERE CAST(uuid AS VARCHAR) IN ({region_values})
) r ON ST_Contains(ST_GeomFromBinary(r.wkb_geometry), ST_Point(s.stop_lon, s.stop_lat))
"""- Solution: Strict type hints and direct SBB stop ID parameters in the routing API.
- High-Level Entry Point: results.ipynb (Lines 1293-1300) — Enforced during the call to
lausanne_planner.plan(...). - Implementation & Usage: The high-level
plan()API enforces that queries must providestart_stop_id: intandend_stop_id: int. Since there is no coordinate-to-stop resolver, the API strictly expects exact SBB station IDs. - Link: src/routing/robust_journey_planner.py (Lines 434-437)
def plan(
self,
start_stop_id: int,
end_stop_id: int,- Solution: Dynamic timetable indexing based on travel date.
- High-Level Entry Point: results.ipynb (Line 1296) — Specified by the
travel_dateparameter inlausanne_planner.plan(...). - Implementation & Usage:
_day_from_travel_dateresolves ISO dates to day names, which then index theconnections_by_daydictionary. - Link: src/routing/robust_journey_planner.py (Line 461)
day = self._day_from_travel_date(travel_date)- Solution: Multi-step backward search from a fixed temporal upper bound.
- High-Level Entry Point: results.ipynb (Line 1297) — Specified by the
arrival_deadlineparameter inlausanne_planner.plan(...). - Implementation & Usage: The
arrival_deadlineis used to calculatedeadline_secs, which serves as the starting point for the windowed search. Any candidate that arrives after this deadline is rejected before robust evaluation. - Link: src/routing/robust_journey_planner.py (Lines 462-464)
deadline_secs = self._deadline_to_relative_secs(travel_date, arrival_deadline)
max_walk_m = self.settings.max_walk_m if max_walk_m is None else max_walk_m
earliest_dep = max(0, deadline_secs - search_window_minutes * 60)-
Solution: Backward Profile CSA bounding and
$Q$ -confidence filtering. -
High-Level Entry Point: results.ipynb (Lines 1293-1300) — Passed in
plan(arrival_deadline=...). -
Implementation & Usage: The system ensures a route arrives before the deadline via a backward Profile CSA bounded at
deadline_secs. It then filters candidates ensuring they pass the$Q$ -quantile confidence threshold without missed connections. - Link: src/routing/robust_journey_planner.py (Lines 772-774)
route["passes_confidence"] = missed_connection is None and confidence >= q- Solution: Final sort before returning profile routes.
- High-Level Entry Point: results.ipynb (Lines 1305-1310) — The final routes list is printed sequentially.
- Implementation & Usage: After extracting the pareto-optimal routes from the profile CSA, the planner explicitly sorts the results by
-departure_secsensuring the user sees the latest possible departures first, while also using multi-objective tie-breaking. - Link: src/routing/robust_journey_planner.py (Line 682)
routes.sort(key=lambda r: (-r.get("departure_secs", 0), r.get("n_transfers", 0), r.get("total_walk_m", 0)))- Solution: Multi-pass windowed search (defaulting to 180 minutes).
- High-Level Entry Point: results.ipynb (Lines 1299-1300) — Controlled by the
max_routesandsearch_window_minutesparameters. - Implementation & Usage: The system iterates backwards from the deadline in 5-minute increments. This 3-hour window is a design heuristic that ensures a diverse candidate pool (latest vs. safest) while maintaining "Reasonable Runtime" (sub-second query speed).
- Link: src/routing/robust_journey_planner.py (Lines 440-442)
search_window_minutes: int = 180,
max_routes: int = 5,
confidence_q: float = 0.5,- Solution: Integrated CSA footpaths.
- High-Level Entry Point: results.ipynb (Line 1293) — Enabled by default in all routing queries.
- Implementation & Usage: Footpaths are applied at every "board" and "alight" event during the CSA scan to allow inter-platform and inter-station changes.
- Link: src/routing/robust_journey_planner.py (Lines 490-492)
for nb, wsecs, dist in footpaths.get(end_stop_id, ()):
if dist > max_walk_m:
break- Solution: Hierarchical parameter resolution (User Override > System Default).
- High-Level Entry Point: Configurable via
settings.max_walk_mor per query inplan(...). - Implementation & Usage: The system defaults to 500m (SBB standard) but gives full priority to the user's input. The final
max_walk_mis resolved inJourneyPlanner.routebefore being passed to the footpath scanner. - Link: src/routing/robust_journey_planner.py (Line 463)
# User Input priority over System Default (500m)
max_walk_m = self.settings.max_walk_m if max_walk_m is None else max_walk_m- Solution: Graph-level pruning.
- High-Level Entry Point: results.ipynb (Lines 913-920) — Handled during
bench_robust_prepare(). - Implementation & Usage: Distances are calculated using Great Circle distance and filtered during the
build_footpathsSQL phase. - Link: src/data/csa_data_handler.py (Line 178)
WHERE distance <= {self.settings.max_walk_m}- Solution: Hardcoded defaults based on official project FAQ.
- High-Level Entry Point: README.md (Line 268) — Specified in FAQ Question 1.
- Implementation & Usage: The project uses 50m/min and a 2-minute "buffer" time as dictated by the SBB project requirements. The exact formula used in the data pipeline is:
walk_time_min = 2.0 + (distance_m / 50.0). - Link: src/config/settings.py (Lines 116-121)
{self.settings.walking_transfer_base_min} + (distance / {self.settings.walking_speed_m_per_min}) AS walk_time_min- Solution: Automated "Latest Pub Date" detection.
- High-Level Entry Point: results.ipynb (Lines 913-920) — Handled during
bench_robust_prepare(). - Implementation & Usage: Queries the database for
MAX(pub_date)to ensure all CSA tables are built from the most current available schedule. - Link: src/data/csa_data_handler.py (Line 76)
self.max_pub_date = self._format_date_literal(max_pub_date or self.get_max_pub_date())- Solution: Spark-based training on SBB "Istdaten".
- High-Level Entry Point: results.ipynb (Lines 207-214) — Triggered by
trainer.train(...). - Implementation & Usage: Filters the multi-million row Istdaten table by region and date range to build the delay predictor.
- Link: src/models/delay_model_trainer.py (Line 156)
feature_df.filter(F.col("operating_day").between(train_start_date, train_end_date))- Solution: SparkXGBRegressor for Quantile Regression.
- High-Level Entry Point: results.ipynb (Lines 207-214) — The model is trained and then loaded automatically by the robust planner.
- Implementation & Usage: Uses a distributed XGBoost model via SparkXGBRegressor trained with pinball loss for multi-quantile prediction (e.g. p80, p90, p95).
- Link: src/models/delay_model_trainer.py (Lines 896-899)
regressor = SparkXGBRegressor(
objective="reg:quantileerror",
quantile_alpha=float(quantile),
n_estimators=rounds,- Solution: Cumulative delay propagation using Profile CSA.
- High-Level Entry Point: results.ipynb (Lines 1305-1310) — Results displayed in the notebook include
passes_confidenceandrobust_arrival_time. - Implementation & Usage: Accumulates log-probabilities along the journey segments during profile building to properly assess multi-leg route robustness.
- Link: src/routing/robust_journey_planner.py (Line 773)
out["passes_confidence"] = missed_connection is None and robust_arrival_secs <= deadline_secs- Solution: Quantile-aware delay retrieval.
- High-Level Entry Point: results.ipynb (Line 1298) — Specified by the
confidence_qparameter inlausanne_planner.plan(...). - Implementation & Usage: Selects the appropriate column (p80, p90, p95) from the delay model to adjust the "riskiness" of the arrival estimate.
- Link: src/routing/robust_journey_planner.py (Line 78)
confidence_q: float,- Solution: Two-stage validation: Statistical Analysis (Pinball Loss) and End-to-End Evaluation.
- High-Level Entry Point: results.ipynb (Lines 220-224) and results/end2end_evaluation_v2.py.
- Implementation & Usage: The project reports Pinball loss evaluation metrics for the XGBoost delay predictor at multiple quantiles to ensure statistical calibration. Furthermore, it implements an End-to-End Evaluator that replays historical queries through a real-world simulator to directly prove the success rate of the Robust vs Standard planner.
- Statistical Link: src/models/delay_model_trainer.py (Lines 397-423)
def evaluate_predictions(self, pred_df: DataFrame) -> dict[str, float]:
# Pinball loss per quantile
# ...- End-to-End Link: src/evaluation/e2e_evaluator.py (Lines 64-67)
def evaluate(
self,
benchmark_df: pd.DataFrame,- Solution: Dedicated "Simplifying Assumptions" section in README.
- High-Level Entry Point: README.md (Line 51) — Primary documentation.
- Implementation & Usage: The following core assumptions are strictly adhered to:
- ✅ Reasonable Hours: Only consider journeys at reasonable hours with recent schedules.
- ✅ Walking Formula: 50m/min straight-line speed; 2min base transfer time.
- ✅ Configurable Walk: Max walking distance is configurable (default 500m).
- ✅ Station Constraints: Only start/end at known station coordinates.
- ✅ Geo-Fencing: Only consider stops in areas specified by UUID.
- ✅ External Transfers: Allows transfers at stops outside the area if needed for connectivity.
- ✅ Independence: Delays/travel times assumed uncorrelated.
- ✅ Static Planning: No "en-route" adaptation; user follows the plan to the end or failure.
- ✅ Equivalent Failures: Planner does not weight the "severity" of failure consequences differently.
- Link: README.md (Lines 51-66)
- Solution: Multi-key tie-breaking in the route reconstructor.
- High-Level Entry Point: results.ipynb (Lines 1305-1310) — Final sorted list respects multi-objective optimization.
- Implementation & Usage: Routes are sorted by a tuple of
(-departure_secs, n_transfers, total_walk_m). This ensures that for the same departure time, the system picks the route with the fewest changes and the least physical effort. - Link: src/routing/robust_journey_planner.py (Line 682)
key=lambda r: (-r.get("departure_secs", 0), r.get("n_transfers", 0), r.get("total_walk_m", 0))