Fix the ROOT: bind props to their real game, never date by the grade clock
Order 1.5 Phase 1. PropLine emits NO commence_time (grep-verified: zero hits in proplineAdapter), so ledgerService's `dateET(prop.game_time) || dateET(gradedTs)` always fell through to the GRADE timestamp — and a 01:00/03:00 UTC snapshot is 21:00/23:00 ET the PREVIOUS day. Tonight's props were filed under yesterday, settlement correctly found no game there, and Order 1's void logic turned that into 64 destroyed results. gameBinder.attachGameTimes() now matches every prop to a scheduled game by TEAMS across the plausible ET window (grade date, +1, -1) and attaches the GAME'S OWN time/date/id. It runs in snapshotService before grading and before the ledger write, so ledger, retention and settlement all inherit the correct date from one place. HARD CONTRACT: an unbindable prop returns NOTHING. ledgerService no longer has a grade-clock fallback — a row with no real game time is SKIPPED and counted, because a mis-dated row is fabricated data and the ledger holds real values or nothing. A slate that binds nothing pages. DOUBLEHEADERS are reported, never guessed: two games with the same teams on one date mark the binding `ambiguous` so settlement can decline rather than attribute a prop to the wrong game. (Real example already in the data: mlb:2026-07-11:MilwaukeeBrewers@PittsburghPirates(Game1).) Also fixes retention, which had the SAME bug from last night — I had dated model_snapshots rows with the snapshot clock. Rows now take the ET date of the bound game_time. Suite 282/3381 green, build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
This commit is contained in:
@@ -264,6 +264,26 @@ async function runSnapshot(sport, opts = {}) {
|
||||
return { sport: sp, status: 'skipped', reason: 'no props', gradeCount: 0 };
|
||||
}
|
||||
|
||||
// Session 64 (Order 1.5) — BIND EVERY PROP TO ITS REAL GAME before anything
|
||||
// downstream dates it. PropLine emits no commence_time, so ledgerService's
|
||||
// `dateET(prop.game_time) || dateET(gradedTs)` always fell through to the
|
||||
// GRADE timestamp — and a 01:00/03:00 UTC snapshot is the previous ET day,
|
||||
// so props for tonight were filed under yesterday. Everything downstream
|
||||
// (ledger, retention, settlement) reads prop.game_time, so fixing it here
|
||||
// fixes all of them at once.
|
||||
try {
|
||||
const binder = deps.gameBinder || require('./gameBinder');
|
||||
const b = await binder.attachGameTimes(sp, props, { gradedAt: ts });
|
||||
console.log(`[snapshot] game binding ${sp}: ${b.bound} bound, ${b.alreadyHad} already had times, ${b.unresolved} UNRESOLVED, ${b.ambiguous} ambiguous(doubleheader)`);
|
||||
if (b.unresolved > 0 && b.bound === 0 && b.alreadyHad === 0) {
|
||||
await deps.notify(`Game binding produced NOTHING for ${sp.toUpperCase()} — ${b.unresolved} props could not be tied to a scheduled game. Their rows will be skipped rather than mis-dated.`, {
|
||||
title: 'VYNDR pipeline', priority: 'high', tags: ['rotating_light'],
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[snapshot] game binding failed for ${sp} (rows without a real game time will be skipped):`, e.message);
|
||||
}
|
||||
|
||||
// Session 63 — REFRESH TEAM STATS BEFORE GRADING.
|
||||
// `refreshTeamStats` is the ONLY writer of `team_stats:{sport}:{abbr}`, which
|
||||
// is the ONLY source of `opp_rank_stat` — and it had zero production callers,
|
||||
|
||||
Reference in New Issue
Block a user