v3: the lake processed + /data page + glossary terms

- scripts/processStatsbomb.ts: one pass over the 17GB StatsBomb lake (4,235
  matches) -> lakestats.json (12.82GB events / 14.9M events / 6,919 players),
  scorestate.json (goal-rate multipliers by game state — shipped as an insight,
  NOT wired into in-play: the raw effect is selection-biased toward stronger
  teams and would distort a calibrated model), fingerprints.json (40/48 nations'
  shots/xG/set-piece share from real event data). Artifacts committed; raw lake
  stays local (gitignored).
- /data page + /api/data/stats: the gigabytes made visible — lake counters,
  historical archive, live-collection counts (odds lines, frozen forecasts,
  enrichment), per-source health with freshness. Footer links.
- Term component: plain-language glossary tooltips (RPS, Brier, ECE, Elo, xG,
  de-vig, ensemble) attached where metrics appear.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 17:41:43 +02:00
parent dc52a908c0
commit dda8e1ae42
13 changed files with 430 additions and 7 deletions
+14
View File
@@ -177,6 +177,20 @@ export function healthRecord(source: string, ok: boolean, note?: string, openMs?
});
}
/** Row counts for the /data page — everything the live layer has collected. */
export function dbCounts(): Record<string, number> {
const c = (sql: string) => (db().prepare(sql).get() as { n: number }).n;
return {
oddsLines: c('SELECT COUNT(*) n FROM odds_history'),
oddsFixtures: c('SELECT COUNT(DISTINCT fixture_num) n FROM odds_history'),
frozenForecasts: c('SELECT COUNT(*) n FROM prediction_snapshots'),
enrichedMatches: c('SELECT COUNT(*) n FROM match_ext'),
mappedFixtures: c('SELECT COUNT(*) n FROM source_map'),
cachedResponses: c('SELECT COUNT(*) n FROM kv_cache'),
ingestEvents: c('SELECT COUNT(*) n FROM ingest_log'),
};
}
export function logIngest(source: string, job: string, ok: boolean, ms: number, note?: string): void {
db().prepare('INSERT INTO ingest_log (source, job, ok, ms, note, at) VALUES (?, ?, ?, ?, ?, ?)')
.run(source, job, ok ? 1 : 0, Math.round(ms), note ?? null, Date.now());