Files
meubility-workbench/tests/test_journey_search.py

148 lines
4.6 KiB
Python

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,
}