v3 Phase A (urgent): bookmaker odds capture — the Model-vs-Market benchmark

- docs/V3-GIGA-PLAN.md: the v3 plan (data lake to 7+GB, GIGA ensemble, scoreboard,
  UI clarity) with the honest reframe: perfect accuracy = perfect calibration;
  'beat the betting sites' = a live public head-to-head, scored with proper rules.
- src/lib/odds.ts: American-ml → decimal, de-vig (normalize away the margin),
  overround; 3 vitest cases incl. tonight's real opening lines.
- ESPN core odds adapter (server/src/ingest/espnOdds.ts): captures DraftKings
  1X2 moneylines + O/U + spread per fixture; follows $ref pointer items.
- odds_history table (insert-if-changed → clean line-movement history);
  scheduler odds sweep every 3h over a 14-day horizon, first sweep at boot;
  /api/odds + /api/odds/:num. Odds are a benchmark ONLY — never a model input.
- Boot speed-up: fixture↔event mapping skips dates already fully mapped.
- Verified: 54 fixtures captured with real moneylines locally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 17:03:15 +02:00
parent 51dfe00216
commit d887664fce
8 changed files with 295 additions and 5 deletions
+55
View File
@@ -0,0 +1,55 @@
# Cup26 v3 — "GIGA": the data lake, the ensemble, the scoreboard
## The three asks, translated honestly
| Ask | Reality | What we build |
|---|---|---|
| **Perfect accuracy** | Impossible — match outcomes are irreducibly random. A true 60% favourite loses 40% of the time. | Perfect **calibration** (ECE → ~0.01, proven on the reliability diagram) + maximal sharpness from an ensemble. "Perfect" = our probabilities mean exactly what they say. |
| **More accurate than betting sites** | The closing line is the strongest public forecaster; nobody beats it systematically. | A **live public Model-vs-Market scoreboard**: capture DraftKings odds (free via ESPN core API) for every WC match, de-vig them, score market vs our model with RPS/Brier after each result. Target: within noise of the market; stretch: beat it on slices (draws, knockouts). Nobody else shows this publicly — win or lose, we show the gap. The model itself stays **pure** (odds are a benchmark, never an input — per the earlier decision). |
| **Literal gigabytes of data** | Verified: StatsBomb open data alone is **7.06 GB** of event JSON. | A real data lake: 7 GB StatsBomb corpus + ESPN historical sweep + odds-history capture + Transfermarkt squads/values/injuries. Raw lake lives on the dev machine; compact aggregates (tens of MB) ship to prod; a `/data` page shows live counters (bytes, events, matches, players, freshness) — "show all the data." |
## Pillar A — The Data Lake (Phase A)
1. **StatsBomb full corpus** (`github.com/statsbomb/open-data`, ~7 GB): clone locally, process EVERY competition (WC 2018/2022, Euros, La Liga, EPL, WSL, Copa, …) into:
- `player_profiles` — per-90 xG, xA, shots, key passes, pressures, duels, set-piece involvement for every player in the corpus; matched to 2026 squads by name.
- `team_fingerprints` — directness, press height, set-piece share, xG-by-zone (the "gaps & strengths" engine, now from real event data).
- `score_state_rates` — empirical goal rates by score state/minute (powers in-play v2).
- `shootout/penalty priors` — conversion rates (with martj42 `shootouts.csv`).
2. **ESPN historical sweep**: scoreboards + summaries for WC 20102022 (verified working) → fills H2H/form archive with venues, stats.
3. **ESPN odds capture** (`sports.core.api.espn.com/.../odds`, verified live): VPS scheduler snapshots odds for every 2026 fixture a few times daily + near kickoff → `odds_history` (line movement!). Benchmark only.
4. **Transfermarkt adapter** (planned in v2, now built): squads, market values, injuries/suspensions → `squad`, `injuries` tables (daily cadence).
5. **`/api/data/stats` + `/data` page**: total bytes processed, events, matches, players, per-source freshness/health. The gigabytes, visible.
## Pillar B — Model v3: the GIGA ensemble (Phase B)
Three independent ratings, ensembled:
1. **EloDixon-Coles v2** (current model + covariates): tuned time-decay, rest days, venue geo/altitude, stage effects.
2. **Squad-value model**: Transfermarkt market values + age curves + injury deductions → goal expectations (literature: rivals Elo; low correlation with it → great ensemble partner).
3. **Player-aggregate model**: squad xG production/defence from StatsBomb player profiles (coverage-weighted; honest about sparse squads).
Then: **log-opinion-pool ensemble** (weights tuned on 20182022 via the existing backtest harness) → **isotonic calibration** on held-out years → only ship what wins the bake-off. Plus: shootout model in the Monte Carlo (knockouts), in-play v2 (score-state intensity, red cards).
Measurement: extended walk-forward backtest scores every variant (RPS/Brier/log-loss/ECE); the Methodology page shows the bake-off table. Live: the scoreboard (below) is the ultimate test.
## Pillar C — UI: clear, clean, labelled (Phase C)
1. **Glossary + tooltips**: every metric (xG, Elo, RPS, ECE…) gets an inline "?" → plain-language explainer; one shared `<Term>` component.
2. **Provenance chips everywhere**: "ESPN · 2 min ago", "150-year archive", "StatsBomb corpus", "model v3".
3. **`/scoreboard`** — Model vs Market: per-match cards (our probs vs de-vigged DraftKings), running RPS/Brier totals, "who was closer" badges. The headline feature.
4. **`/data`** — the lake, visible: counters, coverage, freshness, browsable source tables.
5. **Squad views** on team pages: players with club, age, market value, injury status, corpus stats where available.
6. **Clarity pass**: consistent units/labels, collapsible advanced sections, density/mobile polish.
## Phasing
- **Phase A — Lake** (StatsBomb ingest + Transfermarkt + ESPN history/odds capture + /data) ← start here; odds capture must begin ASAP (every day of line history is unrecoverable).
- **Phase B — Ensemble** (covariates, squad-value + player models, ensemble + calibration, bake-off, shootout, in-play v2).
- **Phase C — UI** (scoreboard, data explorer, glossary/tooltips, squads, clarity pass).
- **Phase D — Deploy & live validation** through the group stage; scoreboard accumulates.
## Honest success criteria
- Calibration: ECE ≤ 0.015 out-of-sample (already 0.01; keep it while adding sharpness).
- Sharpness: beat v2's RPS 0.171 out-of-sample (every point matters; the market sits only a little better).
- Market: publish the head-to-head; target = statistical tie; stretch = ahead on any meaningful slice after ≥48 matches.
- Data: > 7 GB processed, with the proof on `/data`.