// international-results.csv + goalscorers.csv → public/data/{h2h.json,scorers.json} // Precomputes match-preview inputs that need the full historical corpus: every // World-Cup-team pairing's head-to-head record, and each team's top historical // scorers. ESPN supplies live form/lineups/venue at runtime; these are the deep // historical layers. import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; import { canonicalTeam, ALL_TEAMS } from '../src/lib/teams'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); const RESULTS = join(ROOT, 'data', 'raw', 'international-results.csv'); const SCORERS = join(ROOT, 'data', 'raw', 'goalscorers.csv'); const OUT_DIR = join(ROOT, 'public', 'data'); const WC = new Set(ALL_TEAMS); const pairKey = (a: string, b: string) => [a, b].sort().join('|'); interface H2HMeeting { date: string; home: string; away: string; hs: number; as: number } interface H2HRecord { a: string; b: string; games: number; aWins: number; bWins: number; draws: number; aGoals: number; bGoals: number; last: H2HMeeting[]; } function buildH2H(): Record { const lines = readFileSync(RESULTS, 'utf8').split('\n'); const rec: Record = {}; for (let i = 1; i < lines.length; i++) { const c = lines[i]?.split(','); if (!c || c.length < 9) continue; const hs = Number(c[3]); const as = Number(c[4]); if (!Number.isFinite(hs) || !Number.isFinite(as)) continue; const home = canonicalTeam(c[1]!); const away = canonicalTeam(c[2]!); if (!WC.has(home) || !WC.has(away)) continue; const [a, b] = [home, away].sort() as [string, string]; const key = pairKey(home, away); let r = rec[key]; if (!r) { r = { a, b, games: 0, aWins: 0, bWins: 0, draws: 0, aGoals: 0, bGoals: 0, last: [] }; rec[key] = r; } // goals from a's/b's perspective const aIsHome = home === a; const aScore = aIsHome ? hs : as; const bScore = aIsHome ? as : hs; r.games++; r.aGoals += aScore; r.bGoals += bScore; if (aScore > bScore) r.aWins++; else if (aScore < bScore) r.bWins++; else r.draws++; r.last.push({ date: c[0]!, home, away, hs, as }); } // keep only the 6 most recent meetings per pair for (const r of Object.values(rec)) { r.last.sort((x, y) => y.date.localeCompare(x.date)); r.last = r.last.slice(0, 6); } return rec; } interface Scorer { name: string; goals: number; pens: number } function buildScorers(): Record { const lines = readFileSync(SCORERS, 'utf8').split('\n'); const byTeam = new Map>(); for (let i = 1; i < lines.length; i++) { const c = lines[i]?.split(','); if (!c || c.length < 8) continue; const team = canonicalTeam(c[3]!); const scorer = c[4]; if (!scorer || !WC.has(team)) continue; if ((c[6] ?? '').toUpperCase() === 'TRUE') continue; // own goal const pen = (c[7] ?? '').toUpperCase() === 'TRUE'; if (!byTeam.has(team)) byTeam.set(team, new Map()); const m = byTeam.get(team)!; const e = m.get(scorer) ?? { goals: 0, pens: 0 }; e.goals++; if (pen) e.pens++; m.set(scorer, e); } const out: Record = {}; for (const [team, m] of byTeam) { out[team] = [...m.entries()] .map(([name, v]) => ({ name, goals: v.goals, pens: v.pens })) .sort((x, y) => y.goals - x.goals) .slice(0, 10); } return out; } function main(): void { const h2h = buildH2H(); const scorers = buildScorers(); mkdirSync(OUT_DIR, { recursive: true }); writeFileSync(join(OUT_DIR, 'h2h.json'), JSON.stringify(h2h)); writeFileSync(join(OUT_DIR, 'scorers.json'), JSON.stringify(scorers)); console.log(`preview data → h2h.json (${Object.keys(h2h).length} pairings), scorers.json (${Object.keys(scorers).length} teams)`); } main();