from __future__ import annotations from app.journey import _filter_dominated_journeys from app.journey_search import _rank_journeys, _select_diverse_journeys def test_progressive_selection_keeps_fast_and_low_walk_transit_options(): fast_with_walk = _journey( "fast", arrival=30_000, transfers=0, legs=[ _leg("tram", "22", "A", "B"), _leg("walk", "walk", "B", "Z", distance_m=1200), ], ) slower_low_walk = _journey( "low-walk", arrival=31_200, transfers=1, legs=[ _leg("tram", "22", "A", "Hbf"), _leg("bus", "34", "Hbf", "Y"), _leg("walk", "walk", "Y", "Z", distance_m=120), ], ) walk_only = _journey( "walk-only", arrival=30_900, transfers=0, legs=[_leg("walk", "walk", "A", "Z", distance_m=1800)], ) ranked = _rank_journeys([slower_low_walk, walk_only, fast_with_walk], "recommended") selected = _select_diverse_journeys(ranked, limit=3) assert len(selected) <= 3 assert {journey["id"] for journey in selected} == {"fast", "low-walk", "walk-only"} def test_progressive_selection_collapses_same_journey_from_duplicate_gtfs_feeds(): rnv = _journey( "rnv", arrival=29_820, transfers=0, legs=[ _leg("walk", "walk", "Jakob-Neu-Straße 2", "Eppelheim Rathaus", dataset_id=10, source_name=None), _leg("tram", "RNV 22", "Eppelheim Rathaus", "Heidelberg Hbf", dataset_id=10, source_name="DE Verkehrsverbund Rhein-Neckar GTFS"), ], ) delfi = _journey( "delfi", arrival=29_820, transfers=0, legs=[ _leg("walk", "walk", "Jakob-Neu-Straße 2", "Eppelheim Rathaus", dataset_id=4, source_name=None), _leg("tram", "RNV 22", "Eppelheim Rathaus", "Heidelberg Hbf", dataset_id=4, source_name="DELFI / GTFS.de Germany", stop_count=8), ], ) ranked = _rank_journeys([rnv, delfi], "recommended") selected = _select_diverse_journeys(ranked, limit=3) assert [journey["id"] for journey in selected] == ["delfi"] def test_dominance_removes_only_slower_more_transfer_same_endpoint_options(): direct = _journey( "direct", arrival=30_000, transfers=0, legs=[ _leg("tram", "22", "A", "platform-a", canonical_to=500), _leg("walk", "walk", "platform-a", "Z", distance_m=800), ], ) faster_transfer = _journey( "faster-transfer", arrival=29_500, transfers=1, legs=[ _leg("bus", "34", "A", "X"), _leg("tram", "5", "X", "platform-b", canonical_to=500), _leg("walk", "walk", "platform-b", "Z", distance_m=800), ], ) slower_transfer = _journey( "slower-transfer", arrival=31_000, transfers=1, legs=[ _leg("bus", "713", "A", "Y"), _leg("tram", "22", "Y", "platform-c", canonical_to=500), _leg("walk", "walk", "platform-c", "Z", distance_m=800), ], ) filtered = _filter_dominated_journeys([slower_transfer, faster_transfer, direct]) assert {journey["id"] for journey in filtered} == {"direct", "faster-transfer"} def _journey(journey_id: str, *, arrival: int, transfers: int, legs: list[dict]) -> dict: departure = 28_800 return { "id": journey_id, "departure_seconds": departure, "arrival_seconds": arrival, "duration_seconds": arrival - departure, "duration_minutes": (arrival - departure + 59) // 60, "transfers": transfers, "legs": legs, } def _leg( mode: str, route: str, from_stop: str, to_stop: str, *, distance_m: float = 0, canonical_to: int | None = None, dataset_id: int = 1, source_name: str | None = "Test GTFS", stop_count: int = 2, ) -> dict: to_payload = {"stop_id": to_stop} stops = [{"stop_id": from_stop}, {"stop_id": to_stop}] if canonical_to is not None: stops[-1]["canonical_stop"] = {"id": canonical_to} return { "dataset_id": dataset_id, "source_name": source_name, "mode": mode, "route_id": route, "route_ref": route, "trip_id": f"{route}-trip" if mode != "walk" else None, "from": {"stop_id": from_stop}, "to": to_payload, "departure_time": "08:00:00", "arrival_time": "08:10:00", "distance_m": distance_m, "stop_count": stop_count if mode != "walk" else None, "intermediate_stop_count": max(0, stop_count - 2) if mode != "walk" else None, "stops": stops, }