- Persistence via Node's built-in node:sqlite (zero native deps) on a Docker
volume: response cache, source health, fixture↔provider id map, per-fixture
enrichment, ingest log. Runtime bumped to node:24-slim + --experimental-sqlite.
- Resilient fetcher: DB cache + per-source rate-limit + jittered backoff +
circuit-breaker (blocked sources trip + skip; UI reads DB, never breaks).
- ESPN hidden API as the PRIMARY rich source (works from the VPS where SofaScore
403s): scoreboard (live scores w/ clock) + summary (venue, H2H, recent form,
lineups, team stats). football-data / SofaScore are fallbacks.
- Scheduler: maps all 72 group fixtures to ESPN event ids, polls live on a
dynamic cadence, enriches imminent fixtures into match_ext. /api/sources/health.
- Verified: DB populates (real H2H + form for opening matches), 22 tests pass,
Docker image runs node:sqlite on the volume and persists across restart.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- buildStatsbombViz.ts: processes the 2022 WC Final (Argentina 3-3 France) from
StatsBomb open data → shots+xG, cumulative xG race, starting-XI pass networks,
totals, shootout result. Output committed (11KB) to avoid build-time fetch
- Custom SVG viz (no chart lib): reusable Pitch (StatsBomb 120x80 coords),
ShotMap (xG-sized dots, teams attacking opposite goals), XgRace (stepped
cumulative xG with goal markers, d3-scale), PassNetwork (nodes ∝ involvement,
links ∝ pass volume)
- StoryPage: narrative with hero scoreline, by-the-numbers bars, and the three
visualizations with explanatory copy
- Verified in browser: all three render correctly, no console errors
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>