Show Eurostar Standard prices and total journey cost on results page

Fetches prices via the site-api.eurostar.com GraphQL gateway
(NewBookingSearch operation, discovered with Playwright). Adds
fetch_prices() to scraper/eurostar.py using requests, caches results,
annotates each trip with eurostar_price and total_price, and shows an
ES Std column plus total cost (duration + price) in the results table.
The Transfer column is hidden on small screens for mobile usability.

Closes #4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Edward Betts 2026-04-04 10:38:09 +01:00
parent 804fcedfad
commit 0dee942e16
4 changed files with 191 additions and 12 deletions

29
app.py
View file

@ -8,6 +8,7 @@ from cache import get_cached, set_cached
import scraper.eurostar as eurostar_scraper
import scraper.realtime_trains as rtt_scraper
from trip_planner import combine_trips, find_unreachable_morning_eurostars
from scraper.eurostar import fetch_prices as fetch_eurostar_prices
RTT_PADDINGTON_URL = (
"https://www.realtimetrains.co.uk/search/detailed/"
@ -80,10 +81,12 @@ def results(slug, travel_date):
rtt_cache_key = f"rtt_{travel_date}"
es_cache_key = f"eurostar_{travel_date}_{destination}"
prices_cache_key = f"eurostar_prices_{travel_date}_{destination}"
cached_rtt = get_cached(rtt_cache_key)
cached_es = get_cached(es_cache_key)
from_cache = bool(cached_rtt and cached_es)
cached_prices = get_cached(prices_cache_key)
from_cache = bool(cached_rtt and cached_es and cached_prices)
error = None
@ -108,7 +111,28 @@ def results(slug, travel_date):
msg = f"Could not fetch Eurostar times: {e}"
error = f"{error}; {msg}" if error else msg
if cached_prices:
eurostar_prices = cached_prices
else:
try:
eurostar_prices = fetch_eurostar_prices(destination, travel_date)
set_cached(prices_cache_key, eurostar_prices)
except Exception as e:
eurostar_prices = {}
msg = f"Could not fetch Eurostar prices: {e}"
error = f"{error}; {msg}" if error else msg
trips = combine_trips(gwr_trains, eurostar_trains, travel_date, min_connection, max_connection)
# Annotate each trip with Eurostar Standard price and total cost
for trip in trips:
es_price = eurostar_prices.get(trip['depart_st_pancras'])
trip['eurostar_price'] = es_price
if es_price is not None:
trip['total_price'] = trip['ticket_price'] + es_price
else:
trip['total_price'] = None
unreachable_morning_services = find_unreachable_morning_eurostars(
gwr_trains,
eurostar_trains,
@ -116,6 +140,9 @@ def results(slug, travel_date):
min_connection,
max_connection,
)
for svc in unreachable_morning_services:
svc['eurostar_price'] = eurostar_prices.get(svc['depart_st_pancras'])
result_rows = sorted(
[{'row_type': 'trip', **trip} for trip in trips]
+ [{'row_type': 'unreachable', **service} for service in unreachable_morning_services],