Various improvements
This commit is contained in:
parent
876eb6a759
commit
1fa2e68b31
5 changed files with 101 additions and 8 deletions
37
app.py
37
app.py
|
|
@ -31,12 +31,28 @@ def index():
|
|||
return render_template('index.html', destinations=DESTINATIONS, today=today)
|
||||
|
||||
|
||||
VALID_MIN_CONNECTIONS = {50, 60, 70, 80, 90, 100, 110, 120}
|
||||
VALID_MAX_CONNECTIONS = {60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180}
|
||||
|
||||
|
||||
@app.route('/search')
|
||||
def search():
|
||||
slug = request.args.get('destination', '')
|
||||
travel_date = request.args.get('travel_date', '')
|
||||
try:
|
||||
min_conn = int(request.args.get('min_connection', 50))
|
||||
except ValueError:
|
||||
min_conn = 50
|
||||
if min_conn not in VALID_MIN_CONNECTIONS:
|
||||
min_conn = 50
|
||||
try:
|
||||
max_conn = int(request.args.get('max_connection', 110))
|
||||
except ValueError:
|
||||
max_conn = 110
|
||||
if max_conn not in VALID_MAX_CONNECTIONS:
|
||||
max_conn = 110
|
||||
if slug in DESTINATIONS and travel_date:
|
||||
return redirect(url_for('results', slug=slug, travel_date=travel_date))
|
||||
return redirect(url_for('results', slug=slug, travel_date=travel_date, min_connection=min_conn, max_connection=max_conn))
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
||||
|
|
@ -46,6 +62,19 @@ def results(slug, travel_date):
|
|||
if not destination or not travel_date:
|
||||
return redirect(url_for('index'))
|
||||
|
||||
try:
|
||||
min_connection = int(request.args.get('min_connection', 50))
|
||||
except ValueError:
|
||||
min_connection = 50
|
||||
if min_connection not in VALID_MIN_CONNECTIONS:
|
||||
min_connection = 50
|
||||
try:
|
||||
max_connection = int(request.args.get('max_connection', 110))
|
||||
except ValueError:
|
||||
max_connection = 110
|
||||
if max_connection not in VALID_MAX_CONNECTIONS:
|
||||
max_connection = 110
|
||||
|
||||
user_agent = request.headers.get('User-Agent', rtt_scraper.DEFAULT_UA)
|
||||
|
||||
rtt_cache_key = f"rtt_{travel_date}"
|
||||
|
|
@ -78,7 +107,7 @@ def results(slug, travel_date):
|
|||
msg = f"Could not fetch Eurostar times: {e}"
|
||||
error = f"{error}; {msg}" if error else msg
|
||||
|
||||
trips = combine_trips(gwr_trains, eurostar_trains, travel_date)
|
||||
trips = combine_trips(gwr_trains, eurostar_trains, travel_date, min_connection, max_connection)
|
||||
|
||||
dt = date.fromisoformat(travel_date)
|
||||
prev_date = (dt - timedelta(days=1)).isoformat()
|
||||
|
|
@ -103,6 +132,10 @@ def results(slug, travel_date):
|
|||
error=error,
|
||||
eurostar_url=eurostar_url,
|
||||
rtt_url=rtt_url,
|
||||
min_connection=min_connection,
|
||||
max_connection=max_connection,
|
||||
valid_min_connections=sorted(VALID_MIN_CONNECTIONS),
|
||||
valid_max_connections=sorted(VALID_MAX_CONNECTIONS),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Bristol to Europe via Eurostar</title>
|
||||
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,30 @@
|
|||
style="width:100%;padding:0.6rem 0.8rem;font-size:1rem;border:1px solid #cbd5e0;border-radius:4px">
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:1.2rem">
|
||||
<label for="min_connection" style="display:block;font-weight:600;margin-bottom:0.4rem">
|
||||
Minimum connection time (Paddington → St Pancras)
|
||||
</label>
|
||||
<select id="min_connection" name="min_connection"
|
||||
style="width:100%;padding:0.6rem 0.8rem;font-size:1rem;border:1px solid #cbd5e0;border-radius:4px">
|
||||
{% for mins in [50, 60, 70, 80, 90, 100, 110, 120] %}
|
||||
<option value="{{ mins }}" {% if mins == 50 %}selected{% endif %}>{{ mins }} min</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:1.5rem">
|
||||
<label for="max_connection" style="display:block;font-weight:600;margin-bottom:0.4rem">
|
||||
Maximum connection time (Paddington → St Pancras)
|
||||
</label>
|
||||
<select id="max_connection" name="max_connection"
|
||||
style="width:100%;padding:0.6rem 0.8rem;font-size:1rem;border:1px solid #cbd5e0;border-radius:4px">
|
||||
{% for mins in [60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180] %}
|
||||
<option value="{{ mins }}" {% if mins == 110 %}selected{% endif %}>{{ mins }} min</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
style="background:#00539f;color:#fff;border:none;padding:0.75rem 2rem;
|
||||
font-size:1rem;font-weight:600;border-radius:4px;cursor:pointer">
|
||||
|
|
|
|||
|
|
@ -10,14 +10,47 @@
|
|||
Bristol Temple Meads → {{ destination }}
|
||||
</h2>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:0.5rem">
|
||||
<a href="{{ url_for('results', slug=slug, travel_date=prev_date) }}"
|
||||
<a href="{{ url_for('results', slug=slug, travel_date=prev_date, min_connection=min_connection, max_connection=max_connection) }}"
|
||||
style="padding:0.3rem 0.75rem;border:1px solid #cbd5e0;border-radius:4px;
|
||||
text-decoration:none;color:#00539f;font-size:0.9rem">← Prev</a>
|
||||
<strong>{{ travel_date_display }}</strong>
|
||||
<a href="{{ url_for('results', slug=slug, travel_date=next_date) }}"
|
||||
<a href="{{ url_for('results', slug=slug, travel_date=next_date, min_connection=min_connection, max_connection=max_connection) }}"
|
||||
style="padding:0.3rem 0.75rem;border:1px solid #cbd5e0;border-radius:4px;
|
||||
text-decoration:none;color:#00539f;font-size:0.9rem">Next →</a>
|
||||
</div>
|
||||
<div style="margin-top:0.75rem;display:flex;gap:1.5rem;align-items:center">
|
||||
<div>
|
||||
<label for="min_conn_select" style="font-size:0.9rem;font-weight:600;margin-right:0.5rem">
|
||||
Min connection:
|
||||
</label>
|
||||
<select id="min_conn_select"
|
||||
onchange="applyConnectionFilter()"
|
||||
style="padding:0.3rem 0.6rem;font-size:0.9rem;border:1px solid #cbd5e0;border-radius:4px">
|
||||
{% for mins in valid_min_connections %}
|
||||
<option value="{{ mins }}" {% if mins == min_connection %}selected{% endif %}>{{ mins }} min</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="max_conn_select" style="font-size:0.9rem;font-weight:600;margin-right:0.5rem">
|
||||
Max connection:
|
||||
</label>
|
||||
<select id="max_conn_select"
|
||||
onchange="applyConnectionFilter()"
|
||||
style="padding:0.3rem 0.6rem;font-size:0.9rem;border:1px solid #cbd5e0;border-radius:4px">
|
||||
{% for mins in valid_max_connections %}
|
||||
<option value="{{ mins }}" {% if mins == max_connection %}selected{% endif %}>{{ mins }} min</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
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;
|
||||
}
|
||||
</script>
|
||||
<p style="color:#4a5568;margin:0">
|
||||
{{ gwr_count }} GWR service{{ 's' if gwr_count != 1 }}
|
||||
·
|
||||
|
|
@ -96,7 +129,7 @@
|
|||
</div>
|
||||
|
||||
<p style="margin-top:1rem;font-size:0.82rem;color:#718096">
|
||||
Paddington → St Pancras connection: 60 min minimum, 2h maximum.
|
||||
Paddington → St Pancras connection: {{ min_connection }}–{{ max_connection }} min.
|
||||
Eurostar times are from the general timetable and may vary; always check
|
||||
<a href="{{ eurostar_url }}" target="_blank" rel="noopener">eurostar.com</a> to book.
|
||||
·
|
||||
|
|
@ -114,7 +147,7 @@
|
|||
{% elif eurostar_count == 0 %}
|
||||
No Eurostar services found for {{ destination }} on this date.
|
||||
{% else %}
|
||||
No GWR + Eurostar combination allows an 80-minute connection at Paddington/St Pancras.
|
||||
No GWR + Eurostar combination has at least a {{ min_connection }}-minute connection at Paddington/St Pancras.
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ def combine_trips(
|
|||
gwr_trains: list[dict],
|
||||
eurostar_trains: list[dict],
|
||||
travel_date: str,
|
||||
min_connection_minutes: int = MIN_CONNECTION_MINUTES,
|
||||
max_connection_minutes: int = MAX_CONNECTION_MINUTES,
|
||||
) -> list[dict]:
|
||||
"""
|
||||
Return a list of valid combined trips, sorted by Bristol departure time.
|
||||
|
|
@ -53,7 +55,7 @@ def combine_trips(
|
|||
if int((arr_pad - dep_bri).total_seconds() / 60) > MAX_GWR_MINUTES:
|
||||
continue
|
||||
|
||||
earliest_eurostar = arr_pad + timedelta(minutes=MIN_CONNECTION_MINUTES)
|
||||
earliest_eurostar = arr_pad + timedelta(minutes=min_connection_minutes)
|
||||
|
||||
# Find only the earliest viable Eurostar for this GWR departure
|
||||
for es in eurostar_trains:
|
||||
|
|
@ -69,7 +71,7 @@ def combine_trips(
|
|||
|
||||
if dep_stp < earliest_eurostar:
|
||||
continue
|
||||
if (dep_stp - arr_pad).total_seconds() / 60 > MAX_CONNECTION_MINUTES:
|
||||
if (dep_stp - arr_pad).total_seconds() / 60 > max_connection_minutes:
|
||||
continue
|
||||
|
||||
total_mins = int((arr_dest - dep_bri).total_seconds() / 60)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue