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:
+18
-1
@@ -10,7 +10,8 @@ import { startScheduler } from './ingest/scheduler';
|
||||
import { ModelEngine } from './model';
|
||||
import { buildPreview, buildTeamProfile } from './preview';
|
||||
import { buildScoreboard, snapshotCheck } from './scoreboard';
|
||||
import { db, healthAll, oddsFor, latestOddsAll } from './db/db';
|
||||
import { db, healthAll, oddsFor, latestOddsAll, dbCounts } from './db/db';
|
||||
import { loadStatic } from './preview';
|
||||
import { canonicalTeam } from '../../src/lib/teams';
|
||||
import type { MatchStatus, ServerMessage } from '../../src/lib/types';
|
||||
|
||||
@@ -61,6 +62,22 @@ export function buildServer() {
|
||||
// Bookmaker odds (benchmark for the Model-vs-Market scoreboard; not a model input)
|
||||
app.get('/api/odds', async () => ({ odds: latestOddsAll() }));
|
||||
app.get('/api/scoreboard', async () => buildScoreboard(state));
|
||||
// The data lake, quantified — counters for the /data page.
|
||||
app.get('/api/data/stats', async () => {
|
||||
const safe = <T,>(fn: () => T): T | null => { try { return fn(); } catch { return null; } };
|
||||
return {
|
||||
lake: safe(() => loadStatic('lakestats.json')),
|
||||
archive: safe(() => {
|
||||
const r = loadStatic<{ matchesProcessed: number; asOf: string }>('ratings.json');
|
||||
const h = loadStatic<Record<string, unknown>>('h2h.json');
|
||||
return { results: r.matchesProcessed, asOf: r.asOf, h2hPairings: Object.keys(h).length };
|
||||
}),
|
||||
fingerprints: safe(() => Object.keys(loadStatic<{ teams: Record<string, unknown> }>('fingerprints.json').teams).length),
|
||||
squadValues: safe(() => Object.keys(loadStatic<{ teams: Record<string, unknown> }>('squadvalues.json').teams).length),
|
||||
db: dbCounts(),
|
||||
sources: healthAll(),
|
||||
};
|
||||
});
|
||||
app.get('/api/odds/:num', async (req, reply) => {
|
||||
const num = Number((req.params as { num: string }).num);
|
||||
if (!Number.isFinite(num)) return reply.code(400).send({ error: 'bad num' });
|
||||
|
||||
Reference in New Issue
Block a user