v3: Model-vs-Market scoreboard — frozen forecasts, fair scoring, live page
- prediction_snapshots: model + de-vigged market frozen ≤15min pre-kickoff (or at first sight of a live match, flagged 'late' and excluded from totals). - src/lib/model/scoring.ts: ONE shared RPS/Brier/log-loss implementation for the backtest and the live scoreboard. - /api/scoreboard + /scoreboard page: per-match model-vs-bookmaker bars, RPS for both after FT, 'closer' badges, running head-to-head with honest rules printed (small-sample caveat included). Nav: 'vs Market'. - Methodology page now shows the three-way split + ensemble bake-off variants. - Verified end-to-end with a simulated match: snapshot froze model 80.7% and DraftKings 67.0% (de-vig correct), both scored at FT, late-flag honored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,11 @@ function dateUTC(iso: string): string {
|
||||
return `${d.getUTCFullYear()}${String(d.getUTCMonth() + 1).padStart(2, '0')}${String(d.getUTCDate()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
export function startScheduler(state: TournamentState, onChange: () => void): () => void {
|
||||
export function startScheduler(
|
||||
state: TournamentState,
|
||||
onChange: () => void,
|
||||
onTick?: () => void,
|
||||
): () => void {
|
||||
const token = process.env.FOOTBALL_DATA_TOKEN?.trim();
|
||||
const sofa = process.env.ENABLE_SOFASCORE === 'true';
|
||||
let stopped = false;
|
||||
@@ -88,6 +92,7 @@ export function startScheduler(state: TournamentState, onChange: () => void): ()
|
||||
const changed = state.mergeLive(matches, source);
|
||||
if (changed.length) { console.log(`[ingest] ${changed.length} fixture(s) updated via ${source}`); onChange(); }
|
||||
}
|
||||
onTick?.(); // e.g. freeze pre-kickoff forecast snapshots
|
||||
|
||||
// Keep live matches' stats + event timeline fresh at the live cadence.
|
||||
for (const f of state.allFixtures()) {
|
||||
|
||||
Reference in New Issue
Block a user