Add Eurostar Plus prices and NR advance fare support

- Eurostar scraper now fetches both Standard and Plus (PLUS class code)
  prices/seats in a single API call; each service dict gains plus_price
  and plus_seats fields
- GWR fares scraper gains fetch_advance() which makes two sets of
  paginated calls (standard advance + first-class advance) and returns
  cheapest per departure; shared _run_pages() generator reduces
  duplication in fetch()
- New /api/advance_fares/<station_crs>/<travel_date> endpoint returns
  advance fares as JSON, cached for 24 hours
- Results page gains NR ticket selector (Walk-on / Std Advance / 1st
  Advance) and Eurostar selector (Standard / Plus); total column is
  JS-computed from the selected combination with cheapest/priciest
  highlighting
- Load advance prices button fetches the API lazily; if advance fares
  are already cached they are embedded in the page and applied on load
  so the button is hidden automatically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Edward Betts 2026-04-11 16:22:24 +01:00
parent 5583a20143
commit 89a536dfd3
8 changed files with 515 additions and 83 deletions

View file

@ -248,8 +248,19 @@
.date-nav { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; }
.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; }
.filter-row { margin-top: 0.75rem; display: flex; gap: 1.5rem; align-items: center; flex-wrap: wrap; }
.filter-label { font-size: 0.9rem; font-weight: 600; margin-right: 0.5rem; }
.btn-secondary {
padding: 0.3rem 0.75rem;
border: 1px solid #00539f;
border-radius: 4px;
color: #00539f;
background: #fff;
cursor: pointer;
font-size: 0.9rem;
}
.btn-secondary:hover { background: #f8fbff; }
.btn-secondary:disabled { opacity: 0.5; cursor: default; }
.card-meta { color: #4a5568; margin: 0; }
.footnote { margin-top: 1rem; font-size: 0.82rem; color: #718096; }