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

View file

@ -5,6 +5,11 @@
{% block twitter_title %}Bristol to {{ destination }} via Eurostar{% endblock %}
{% block twitter_description %}Train options from Bristol Temple Meads to {{ destination }} on {{ travel_date_display }} via Paddington, St Pancras, and Eurostar.{% endblock %}
{% block content %}
<style>
@media (max-width: 640px) {
.col-transfer { display: none; }
}
</style>
<p style="margin-bottom:1rem">
<a href="{{ url_for('index') }}">&larr; New search</a>
@ -100,10 +105,10 @@
<th style="padding:0.6rem 0.8rem;white-space:nowrap">Bristol</th>
<th style="padding:0.6rem 0.8rem;white-space:nowrap">Paddington</th>
<th style="padding:0.6rem 0.8rem;white-space:nowrap">GWR Fare</th>
<th style="padding:0.6rem 0.8rem;white-space:nowrap">Transfer</th>
<th class="col-transfer" style="padding:0.6rem 0.8rem;white-space:nowrap">Transfer</th>
<th style="padding:0.6rem 0.8rem;white-space:nowrap">Depart St&nbsp;Pancras</th>
<th style="padding:0.6rem 0.8rem">{{ destination }}
</th>
<th style="padding:0.6rem 0.8rem">{{ destination }}</th>
<th style="padding:0.6rem 0.8rem;white-space:nowrap">ES Std</th>
<th style="padding:0.6rem 0.8rem;white-space:nowrap">Total</th>
</tr>
</thead>
@ -138,7 +143,7 @@
£{{ "%.2f"|format(row.ticket_price) }}
<br><span style="font-size:0.75rem;color:#718096">{{ row.ticket_name }}</span>
</td>
<td style="padding:0.6rem 0.8rem;color:#4a5568">
<td class="col-transfer" style="padding:0.6rem 0.8rem;color:#4a5568">
{{ row.connection_duration }}
</td>
<td style="padding:0.6rem 0.8rem;font-weight:600">
@ -149,7 +154,14 @@
{{ row.arrive_destination }}
<span style="font-weight:400;color:#718096;font-size:0.85em">(CET)</span>
</td>
<td style="padding:0.6rem 0.8rem;font-weight:600">
<td style="padding:0.6rem 0.8rem;white-space:nowrap">
{% if row.eurostar_price is not none %}
£{{ row.eurostar_price }}
{% else %}
<span style="color:#718096">&ndash;</span>
{% endif %}
</td>
<td style="padding:0.6rem 0.8rem;font-weight:600;white-space:nowrap">
{% if row.total_minutes <= best_mins + 5 and trips | length > 1 %}
<span style="color:#276749" title="Fastest option">{{ row.total_duration }} ⚡</span>
{% elif row.total_minutes >= worst_mins - 5 and trips | length > 1 %}
@ -157,11 +169,14 @@
{% else %}
<span style="color:#00539f">{{ row.total_duration }}</span>
{% endif %}
{% if row.total_price is not none %}
<br><span style="font-size:0.8rem;font-weight:700;color:#276749">£{{ "%.2f"|format(row.total_price) }}</span>
{% endif %}
</td>
{% else %}
<td style="padding:0.6rem 0.8rem;font-weight:600">&mdash;</td>
<td style="padding:0.6rem 0.8rem">&mdash;</td>
<td style="padding:0.6rem 0.8rem">&mdash;</td>
<td class="col-transfer" style="padding:0.6rem 0.8rem">&mdash;</td>
<td style="padding:0.6rem 0.8rem">Unavailable</td>
<td style="padding:0.6rem 0.8rem;font-weight:600">
{{ row.depart_st_pancras }}
@ -171,8 +186,15 @@
{{ row.arrive_destination }}
<span style="font-weight:400;color:#a0aec0;font-size:0.85em">(CET)</span>
</td>
<td style="padding:0.6rem 0.8rem;white-space:nowrap">
{% if row.eurostar_price is not none %}
<span style="color:#a0aec0">£{{ row.eurostar_price }}</span>
{% else %}
<span style="color:#a0aec0">&ndash;</span>
{% endif %}
</td>
<td style="padding:0.6rem 0.8rem;font-weight:600">
<span title="No same-day Bristol connection">Unavailable from Bristol</span>
<span title="No same-day Bristol connection" style="color:#a0aec0">Unavailable from Bristol</span>
</td>
{% endif %}
</tr>
@ -186,7 +208,8 @@
{% if unreachable_morning_services %}
Morning means Eurostar departures before 12:00 from St&nbsp;Pancras.
{% endif %}
Eurostar times are from the general timetable and may vary; always check
GWR walk-on single prices for Bristol Temple Meads&nbsp;&rarr;&nbsp;Paddington.
Eurostar Standard prices are for 1 adult in GBP; always check
<a href="{{ eurostar_url }}" target="_blank" rel="noopener">eurostar.com</a> to book.
&nbsp;&middot;&nbsp;
<a href="{{ rtt_url }}" target="_blank" rel="noopener">Paddington arrivals on RTT</a>