diff --git a/app.py b/app.py index 03eff12..ed9ed86 100644 --- a/app.py +++ b/app.py @@ -681,6 +681,32 @@ def _results(station_crs, slug, travel_date, journey_type, return_date): next_date, **{**common_url_args, "return_date": next_return_date}, ) + prev_outbound_url = _results_url( + station_crs, slug, prev_date, **common_url_args + ) + next_outbound_url = _results_url( + station_crs, slug, next_date, **common_url_args + ) + prev_return_url = ( + _results_url( + station_crs, + slug, + travel_date, + **{**common_url_args, "return_date": prev_return_date}, + ) + if return_date + else None + ) + next_return_url = ( + _results_url( + station_crs, + slug, + travel_date, + **{**common_url_args, "return_date": next_return_date}, + ) + if return_date + else None + ) destination_links = [ ( destination_slug, @@ -775,6 +801,10 @@ def _results(station_crs, slug, travel_date, journey_type, return_date): next_date=next_date, prev_results_url=prev_results_url, next_results_url=next_results_url, + prev_outbound_url=prev_outbound_url, + next_outbound_url=next_outbound_url, + prev_return_url=prev_return_url, + next_return_url=next_return_url, destination_links=destination_links, results_base_url=results_base_url, travel_date_display=travel_date_display, diff --git a/circle_line.py b/circle_line.py index c0d945c..76baaf9 100644 --- a/circle_line.py +++ b/circle_line.py @@ -165,13 +165,17 @@ def next_service( def upcoming_services( - earliest_board: datetime, count: int = 2, direction: str = 'pad_to_kx' + earliest_board: datetime, + count: int = 2, + direction: str = 'pad_to_kx', + preceding: int = 0, ) -> list[tuple[datetime, datetime]]: """ - Return up to *count* Circle line services for *direction*, starting from - *earliest_board*. + Return Circle line services for *direction* around *earliest_board*. - Each element is (depart_origin, arrive_destination) as datetimes. + Returns up to *preceding* services before earliest_board followed by up to + *count* services at or after earliest_board. Each element is + (depart_origin, arrive_destination) as datetimes. """ timetable = _get_timetable().get(direction, {})[_day_type(earliest_board.weekday())] board_secs = ( @@ -180,13 +184,17 @@ def upcoming_services( + earliest_board.second ) midnight = earliest_board.replace(hour=0, minute=0, second=0, microsecond=0) + pre_results = [] results = [] for pad_secs, kxp_secs in timetable: - if pad_secs >= board_secs: - results.append(( - midnight + timedelta(seconds=pad_secs), - midnight + timedelta(seconds=kxp_secs), - )) + entry = ( + midnight + timedelta(seconds=pad_secs), + midnight + timedelta(seconds=kxp_secs), + ) + if pad_secs < board_secs: + pre_results.append(entry) + else: + results.append(entry) if len(results) == count: break - return results + return pre_results[-preceding:] + results if preceding else results diff --git a/templates/base.html b/templates/base.html index 53b3f70..5009f0c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -246,6 +246,7 @@ /* Results page layout */ .back-link { margin-bottom: 1rem; } .date-nav { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; } + .date-nav-label { min-width: 6rem; font-weight: 600; font-size: 0.9rem; } .switcher-section { margin: 0.9rem 0 1rem; } .section-label { font-size: 0.9rem; font-weight: 600; margin-bottom: 0.45rem; } .filter-row { margin-top: 0.75rem; display: flex; gap: 1.5rem; align-items: center; flex-wrap: wrap; } diff --git a/templates/results.html b/templates/results.html index 96b2e8a..067a1e7 100644 --- a/templates/results.html +++ b/templates/results.html @@ -20,13 +20,28 @@ {{ departure_station_name }} → {{ destination }} {% endif %} + {% if journey_type == 'return' %} +
+ Outbound: + ← Prev + {{ travel_date_display }} + Next → +
+
+ Return: + ← Prev + {{ return_date_display }} + Next → +
+ {% else %}
← Prev - {{ travel_date_display }}{% if return_date_display %} to {{ return_date_display }}{% endif %} + {{ travel_date_display }} Next →
+ {% endif %}
Switch destination for {{ travel_date_display }}
@@ -496,11 +511,14 @@ {{ row.connection_duration }}{% if row.connection_minutes < 45 %} ⚠️{% endif %} {% if row.circle_services %} - {% set c = row.circle_services[0] %} -
Circle {{ c.depart }} → PAD {{ c.arrive_pad }} · £{{ "%.2f"|format(c.fare) }} {% if row.circle_services | length > 1 %} - {% set c2 = row.circle_services[1] %} -
next {{ c2.depart }} → PAD {{ c2.arrive_pad }} · £{{ "%.2f"|format(c2.fare) }} + {% set c_early = row.circle_services[0] %} + {% set c = row.circle_services[1] %} +
earlier {{ c_early.depart }} → PAD {{ c_early.arrive_pad }} · £{{ "%.2f"|format(c_early.fare) }} +
Circle {{ c.depart }} → PAD {{ c.arrive_pad }} · £{{ "%.2f"|format(c.fare) }} + {% else %} + {% set c = row.circle_services[0] %} +
Circle {{ c.depart }} → PAD {{ c.arrive_pad }} · £{{ "%.2f"|format(c.fare) }} {% endif %} {% endif %} diff --git a/tests/test_app.py b/tests/test_app.py index 1adca0f..13cdae4 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -599,7 +599,7 @@ def test_results_return_renders_outbound_and_inbound_tables(monkeypatch): monkeypatch.setattr( trip_planner_module.circle_line, 'upcoming_services', - lambda earliest_board, count=2, direction='pad_to_kx': ( + lambda earliest_board, count=2, direction='pad_to_kx', preceding=0: ( [ (datetime(2026, 4, 10, 9, 10), datetime(2026, 4, 10, 9, 25)), (datetime(2026, 4, 10, 9, 15), datetime(2026, 4, 10, 9, 30)), @@ -619,16 +619,19 @@ def test_results_return_renders_outbound_and_inbound_tables(monkeypatch): assert resp.status_code == 200 assert 'Outbound: Bristol Temple Meads → Paris Gare du Nord' in html assert 'Return: Paris Gare du Nord → Bristol Temple Meads' in html - assert 'Friday 10 April 2026 to Friday 17 April 2026' in html - assert '/results/BRI/paris/2026-04-09/return/2026-04-16' in html - assert '/results/BRI/paris/2026-04-11/return/2026-04-18' in html + assert 'Friday 10 April 2026' in html + assert 'Friday 17 April 2026' in html + assert '/results/BRI/paris/2026-04-09/return/2026-04-17' in html + assert '/results/BRI/paris/2026-04-11/return/2026-04-17' in html + assert '/results/BRI/paris/2026-04-10/return/2026-04-16' in html + assert '/results/BRI/paris/2026-04-10/return/2026-04-18' in html assert "/results/BRI/paris/2026-04-10/return/2026-04-17" in html assert 'journey_type=return' not in html assert 'return_date=2026-04-17' not in html assert 'Circle 09:10 → KX 09:25' in html assert 'next 09:15 → KX 09:30' in html - assert 'Circle 16:40 → PAD 16:55' in html - assert 'next 16:45 → PAD 17:00' in html + assert 'earlier 16:40 → PAD 16:55' in html + assert 'Circle 16:45 → PAD 17:00' in html assert 'title="Tight connection">⚠️' in html assert 'ES 9014' in html assert 'ES 9035' in html diff --git a/trip_planner.py b/trip_planner.py index 50de19d..d03b767 100644 --- a/trip_planner.py +++ b/trip_planner.py @@ -48,7 +48,7 @@ def _circle_line_services_to_paddington(arrive_st_pancras: datetime) -> list[dic earliest_board = arrive_st_pancras + timedelta( minutes=KX_WALK_TO_UNDERGROUND_MINUTES ) - services = circle_line.upcoming_services(earliest_board, count=2, direction='kx_to_pad') + services = circle_line.upcoming_services(earliest_board, count=1, direction='kx_to_pad', preceding=1) return [ { "depart": dep.strftime(TIME_FMT),