Add multi-station support, GWR fares API, and Circle line improvements
- Support any station with direct trains to Paddington; station CRS code is now part of the URL (/results/<crs>/<slug>/<date>) - Load station list from data/direct_to_paddington.tsv; show dropdown on index page; 404 for unknown station codes - Fetch live GWR walk-on fares via api.gwr.com for all stations (SSS/SVS/SDS with restrictions already applied per train); cache 30 days - Scrape Paddington arrival platform numbers from RTT - Show unreachable morning Eurostars (before first reachable service only) - Circle line: show actual KX St Pancras arrival times (not check-in estimate) and add a second backup service in the transfer column - Widen page max-width to 1100px for longer station names Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
71be0dd8cf
commit
3c787b33d3
12 changed files with 810 additions and 262 deletions
|
|
@ -43,7 +43,7 @@
|
|||
}
|
||||
|
||||
main {
|
||||
max-width: 960px;
|
||||
max-width: 1100px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@
|
|||
<div class="card">
|
||||
<h2>Plan your journey</h2>
|
||||
<form method="get" action="{{ url_for('search') }}">
|
||||
<div class="form-group">
|
||||
<span class="field-label">Departure point</span>
|
||||
<div class="fixed-station" aria-label="Departure point">
|
||||
<strong>Bristol Temple Meads</strong>
|
||||
<span>Fixed starting station for all journeys</span>
|
||||
</div>
|
||||
<div class="form-group-lg">
|
||||
<label for="station_crs" class="field-label">Departure point</label>
|
||||
<select id="station_crs" name="station_crs" class="form-control">
|
||||
{% for name, crs in stations %}
|
||||
<option value="{{ crs }}" {% if crs == 'BRI' %}selected{% endif %}>{{ name }} ({{ crs }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}Bristol to {{ destination }} via Eurostar{% endblock %}
|
||||
{% block og_title %}Bristol to {{ destination }} via Eurostar{% endblock %}
|
||||
{% block og_description %}Train options from Bristol Temple Meads to {{ destination }} on {{ travel_date_display }} via Paddington, St Pancras, and Eurostar.{% endblock %}
|
||||
{% 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 title %}{{ departure_station_name }} to {{ destination }} via Eurostar{% endblock %}
|
||||
{% block og_title %}{{ departure_station_name }} to {{ destination }} via Eurostar{% endblock %}
|
||||
{% block og_description %}Train options from {{ departure_station_name }} to {{ destination }} on {{ travel_date_display }} via Paddington, St Pancras, and Eurostar.{% endblock %}
|
||||
{% block twitter_title %}{{ departure_station_name }} to {{ destination }} via Eurostar{% endblock %}
|
||||
{% block twitter_description %}Train options from {{ departure_station_name }} to {{ destination }} on {{ travel_date_display }} via Paddington, St Pancras, and Eurostar.{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<p class="back-link">
|
||||
|
|
@ -12,13 +12,13 @@
|
|||
|
||||
<div class="card" style="margin-bottom:1.5rem">
|
||||
<h2>
|
||||
Bristol Temple Meads → {{ destination }}
|
||||
{{ departure_station_name }} → {{ destination }}
|
||||
</h2>
|
||||
<div class="date-nav">
|
||||
<a href="{{ url_for('results', slug=slug, travel_date=prev_date, min_connection=min_connection, max_connection=max_connection) }}"
|
||||
<a href="{{ url_for('results', station_crs=station_crs, slug=slug, travel_date=prev_date, min_connection=min_connection, max_connection=max_connection) }}"
|
||||
class="btn-nav">← Prev</a>
|
||||
<strong>{{ travel_date_display }}</strong>
|
||||
<a href="{{ url_for('results', slug=slug, travel_date=next_date, min_connection=min_connection, max_connection=max_connection) }}"
|
||||
<a href="{{ url_for('results', station_crs=station_crs, slug=slug, travel_date=next_date, min_connection=min_connection, max_connection=max_connection) }}"
|
||||
class="btn-nav">Next →</a>
|
||||
</div>
|
||||
<div class="switcher-section">
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
{% else %}
|
||||
<a
|
||||
class="chip-link"
|
||||
href="{{ url_for('results', slug=destination_slug, travel_date=travel_date, min_connection=min_connection, max_connection=max_connection) }}"
|
||||
href="{{ url_for('results', station_crs=station_crs, slug=destination_slug, travel_date=travel_date, min_connection=min_connection, max_connection=max_connection) }}"
|
||||
>{{ destination_name }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
@ -66,19 +66,13 @@
|
|||
function applyConnectionFilter() {
|
||||
var min = document.getElementById('min_conn_select').value;
|
||||
var max = document.getElementById('max_conn_select').value;
|
||||
window.location = '{{ url_for('results', slug=slug, travel_date=travel_date) }}?min_connection=' + min + '&max_connection=' + max;
|
||||
window.location = '{{ url_for('results', station_crs=station_crs, slug=slug, travel_date=travel_date) }}?min_connection=' + min + '&max_connection=' + max;
|
||||
}
|
||||
</script>
|
||||
<p class="card-meta">
|
||||
{{ gwr_count }} GWR service{{ 's' if gwr_count != 1 }}
|
||||
·
|
||||
{{ eurostar_count }} Eurostar service{{ 's' if eurostar_count != 1 }}
|
||||
{% if unreachable_morning_services %}
|
||||
·
|
||||
<span class="text-muted">
|
||||
{{ unreachable_morning_services | length }} Eurostar service{{ 's' if unreachable_morning_services | length != 1 }} unavailable from Bristol
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if from_cache %}
|
||||
· <span class="text-muted text-sm">(cached)</span>
|
||||
{% endif %}
|
||||
|
|
@ -100,7 +94,7 @@
|
|||
<table class="results-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="nowrap">Bristol</th>
|
||||
<th class="nowrap">{{ departure_station_name }}</th>
|
||||
<th class="nowrap">Paddington</th>
|
||||
<th class="nowrap">GWR Fare</th>
|
||||
<th class="col-transfer nowrap">Transfer</th>
|
||||
|
|
@ -141,14 +135,26 @@
|
|||
<td>
|
||||
{{ row.arrive_paddington }}
|
||||
<span class="text-sm text-muted nowrap">({{ row.gwr_duration }})</span>
|
||||
{% if row.arrive_platform %}<br><span class="text-xs text-muted">Plat {{ row.arrive_platform }}</span>{% endif %}
|
||||
</td>
|
||||
<td class="nowrap">
|
||||
£{{ "%.2f"|format(row.ticket_price) }}
|
||||
<br><span class="text-xs text-muted">{{ row.ticket_name }}</span>
|
||||
{% if row.ticket_price is not none %}
|
||||
£{{ "%.2f"|format(row.ticket_price) }}
|
||||
<br><span class="text-xs text-muted">{{ row.ticket_name }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">–</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="col-transfer nowrap" style="color:#4a5568">
|
||||
{{ row.connection_duration }}{% if row.connection_minutes < 80 %} <span title="Tight connection">⚠️</span>{% endif %}
|
||||
<br><span class="text-xs text-muted">Circle {{ row.circle_line_depart }} → STP {{ row.circle_arrive_checkin }}</span>
|
||||
{% if row.circle_services %}
|
||||
{% set c = row.circle_services[0] %}
|
||||
<br><span class="text-xs text-muted">Circle {{ c.depart }} → KX {{ c.arrive_kx }}</span>
|
||||
{% if row.circle_services | length > 1 %}
|
||||
{% set c2 = row.circle_services[1] %}
|
||||
<br><span class="text-xs text-muted" style="opacity:0.7">next {{ c2.depart }} → KX {{ c2.arrive_kx }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="font-bold">
|
||||
{{ row.depart_st_pancras }}
|
||||
|
|
@ -184,8 +190,8 @@
|
|||
{% else %}
|
||||
<td class="font-bold">—</td>
|
||||
<td>—</td>
|
||||
<td>—</td>
|
||||
<td class="col-transfer">—</td>
|
||||
<td>n/a</td>
|
||||
<td class="font-bold">
|
||||
{{ row.depart_st_pancras }}
|
||||
{% if row.train_number %}<br><span class="text-xs font-normal text-dimmed">{% for part in row.train_number.split(' + ') %}<span class="nowrap">{{ part }}</span>{% if not loop.last %} + {% endif %}{% endfor %}</span>{% endif %}
|
||||
|
|
@ -206,7 +212,7 @@
|
|||
{% endif %}
|
||||
</td>
|
||||
<td class="font-bold">
|
||||
<span title="No same-day Bristol connection" class="text-dimmed nowrap">Too early</span>
|
||||
<span title="Too early to reach from {{ departure_station_name }}" class="text-dimmed nowrap">Too early</span>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
|
@ -217,11 +223,12 @@
|
|||
|
||||
<p class="footnote">
|
||||
Paddington → St Pancras connection: {{ min_connection }}–{{ max_connection }} min.
|
||||
GWR walk-on single prices for Bristol Temple Meads → Paddington.
|
||||
GWR walk-on single prices from
|
||||
<a href="https://www.gwr.com/" target="_blank" rel="noopener">gwr.com</a>.
|
||||
Eurostar Standard prices are for 1 adult in GBP; always check
|
||||
<a href="{{ eurostar_url }}" target="_blank" rel="noopener">eurostar.com</a> to book.
|
||||
·
|
||||
<a href="{{ rtt_bristol_url }}" target="_blank" rel="noopener">Bristol departures on RTT</a>
|
||||
<a href="{{ rtt_station_url }}" target="_blank" rel="noopener">{{ departure_station_name }} departures on RTT</a>
|
||||
·
|
||||
<a href="{{ rtt_url }}" target="_blank" rel="noopener">Paddington arrivals on RTT</a>
|
||||
</p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue