# Cup26 "v2" — the data leap (internally v7) Drafted June 12, 2026 (matchday 2) from a six-source reconnaissance, probed from both the residential IP and the production VPS. The visual overhaul is handled separately in Claude Design; this plan reserves explicit handoff slots for it and keeps all new features design-ready (data + logic first, plain markup that a design pass can reskin). ## Reconnaissance results (verified June 12) | Source | Local | VPS | Verdict | |---|---|---|---| | **FotMob** `/api/data/*` | ✅ 200 | ✅ 200 — **no signed header anymore** | **INGEST NOW** | | **FIFA official** `api.fifa.com/api/v3` | ✅ | ✅ — no auth, no block | **INGEST NOW** | | **ESPN deep fields** (already cached!) + propBets | ✅ | ✅ | **INGEST NOW** | | **Open-Meteo** weather+elevation | ✅ | ✅ — keyless | **INGEST NOW** | | SofaScore | browser-only (403 to scripts) | ❌ hard IP block | defer (FotMob supersedes) | | FBref | ❌ Cloudflare wall | ❌ | skip (no 2026 xG yet anyway) | What the three rich sources carry (all verified against real June 2026 matches): - **FotMob `matchDetails`** (~244KB/match): per-shot xG + xGOT with pitch x/y, team xG split (open play/set play), **player ratings** (e.g. 7.5) for all 52 players, player-of-the-match, momentum curve, attacking zones, match facts/insights, lineups with formations. Works for live matches too. Key ids: league 77; matches by `?date=YYYYMMDD` (e.g. 4667751 = MEX–RSA). - **FIFA v3**: IdCompetition=17, IdSeason=285023. Official **confirmed lineups with stable player IDs + headshot URLs**, typed event timeline with pitch X/Y, official attendance (80,824 at the opener), team tactics, officials, standings with QualificationStatus, live match clock. - **ESPN, unused but already stored**: per-player match stat lines (14 stats: shots, on-target, saves, fouls, cards, goals conceded…), formation + formationPlace (pitch positions), team leaders, ~109-entry commentary ticker, broadcasters, match recap articles. Plus one cheap new fetch: **propBets — DraftKings any/first-goalscorer odds, ~962 props per match**. - **Open-Meteo**: one batched call covers all 16 venues; returns elevation (Azteca 2,245m!), hourly temp/precip/wind in venue-local time; archive API for played matches. ## Phase D — capture first (urgent: live data is unrecoverable) FotMob is unofficial — it could re-lock any day. Capture beats features. - `server/src/ingest/fotmob.ts`: daily `matches?date=` sweep → id map (fixture ↔ matchId via team-pair+date, the ESPN pattern); `matchDetails` per finished match (once, re-fetch at FT+2h for settled ratings) and per live match every ~90s. Store raw-ish normalized JSON in new `match_fotmob` table (whole payload is fine — ~25MB for all 104 matches). Respectful cadence: ≥2s between calls, circuit breaker, health-tracked like ESPN. - `server/src/ingest/fifa.ts`: calendar sweep (id map), `live/football/...` per live match (clock/lineups), `timelines` at FT. Tables: `match_fifa` (lineups/officials/attendance/tactics), reuse health. - `server/src/ingest/espnExtra`: widen `espnNormalize` to keep player stat lines, formation+formationPlace, leaders, commentary, attendance when present (normalizer v3 + the existing boot backfill re-stores everything). New `scorer_props` table fed from propBets on the odds cadence (3h) — line history is unrecoverable, same urgency as 1X2 odds was in v3. - `server/src/ingest/weather.ts`: forecast at enrich cadence for fixtures <48h away; archive once at FT. `match_weather` table. Venue lat/lon/alt as a committed static (`scripts/buildVenues.ts` → `public/data/venues.json`). - Storage: everything also lands in the nightly storage-box snapshot. ## Phase M — model upgrades (CV-gated, same discipline as v4/v5) Every experiment runs through `scripts/cvSearch.ts` (5-fold rolling CV, pre-registered ship bar ≥0.0005 mean-fold RPS) — ship only what validates. - **M1 xG-informed Team-DC**: backfill FotMob `matchDetails` for recent *international* matches (last ~4 years; local script into the lake, weeks of polite fetching — start immediately, run in background). Experiment: fit attack/defence on a goals/xG blend (xG is less noisy than goals). This is the single largest model upside available. - **M2 lineup-aware kickoff refresh**: when FIFA confirmed lineups land (~75min before kickoff), adjust the match forecast for missing key players (availability share from minutes×ratings, NOT market values). Snapshot for the scoreboard stays frozen at T-15min as today — honesty unchanged. - **M3 in-play v2**: blend the live model with score-state rates from the StatsBomb lake, stratified by pre-match strength gap (fixes the v3 selection-bias concern), validated on lake matches; FotMob momentum as a display layer, not a model input. - **M4 scorer model (new headline)**: per-player goal-rate model (minutes × team λ share from historical scorer data + tournament form) → predicted Golden Boot table, benchmarked against DraftKings scorer props on a **Scorers: model vs market** scoreboard — the existing honest-benchmark franchise, extended to players. ## Phase F — features (each lands design-ready; final skin = design handoff) Tier 1 (data already flowing after D): 1. **Match center, full**: xG race chart (reuse the Story page viz), shotmap (per-shot xG, x/y), attack-momentum strip, lineup pitch with formations + headshots (FIFA), per-player stat lines + ratings table, POTM card, officials + attendance + broadcasters, weather/altitude badge, commentary ticker. This page becomes the product's core. 2. **Player pages + tournament leaderboards**: G+A (assists verified structured in ESPN participants), ratings-based Best XI, saves/cards boards from ESPN stat lines; FIFA headshots; links everywhere. 3. **Luck-adjusted table**: group standings re-computed on xG ("the table if chances decided games") next to the real one — very shareable. 4. **Golden Boot: model vs market** (M4 + scorer_props). 5. **Weather strip** on Live/match pages; altitude notes (Azteca 2,245m). Tier 2 (after M experiments settle): 6. xG-aware form chips and team pages (xG trend last 5). 7. In-play v2 probability + momentum fusion on live pages. 8. Lineup-drop push notification ("XI is in — model moved to 54/24/22"). ## Design handoff slots (Claude Design → me for implementation) - Match center layout (shotmap + xG race + momentum + lineup pitch + ticker) - Player page / player card / Best XI grid - Leaderboards (boot race, G+A, ratings) and the luck-adjusted table - Weather/altitude badges and the match-info panel - Any reskin of existing pages — all current components are plain Tailwind with tokenized colors (`--app-*`), so a token+component redesign drops in. ## Order & verification D (capture-first, deploy same day) → M1 backfill kicked off in background → F1–F5 in batches → M experiments as data accrues → Tier 2. Per batch: tests + exit-code build gate + preview matrix (EN/DE × mobile/desktop × themes) + prod verification against the live tournament. Honest framing everywhere: xG and ratings are third-party estimates; scorer odds remain benchmark-only. ## Risks - FotMob is unofficial: could re-add signed headers or block datacenter IPs any day (SofaScore precedent). Mitigations: capture-first, raw payload storage, circuit breaker + health, app never depends on it being live. - FIFA API is unauthenticated but official-internal; same mitigations. - Identity mapping (fixture ↔ fotmobId ↔ fifaId ↔ espnId) is the foundation — one `source_map` per provider, team-pair+date matching, tested. - Player identity across sources (names vs FIFA IdPlayer): normalize via name+team with a manual alias map fallback; keep per-source ids.