Session 61 (pre-merge): live-verification fixes from day-one rows

1. [settlement] armed boot line — settlement WAS scheduled (the tick runs
   settleAllOutcomes + settleAllLedgers FIRST at every snapshot slot,
   src/snapshotScheduler.js) but was invisible at boot. Now verifiable
   from logs forever.
2. Null locked_odds root cause (4 day-one rows, all betmgm SB unders):
   genuinely absent in the DB — the first-seen book row carried no juice
   for that side. indexOdds/indexProps now PREFER a book row with both
   sides priced (still a real row, never synthesized). Display was
   correct: absent beats wrong.
3. Verified retry-safe: 'Odds data temporarily unavailable' is the
   odds-api quota gate (oddsService:423) — thrown BEFORE any spend or
   cache write; runSnapshot errors write nothing; the next cron retries
   fresh with PropLine first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-11 10:16:27 -04:00
parent 7a35c96781
commit 4d2b27d1d0
4 changed files with 37 additions and 4 deletions
+6 -2
View File
@@ -100,13 +100,17 @@ function teamOpponentFor(g, prop) {
return { team, opponent: null };
}
/** Index odds props by nameKey|stat for lock/closing lookups. */
/** Index odds props by nameKey|stat for lock/closing lookups.
* Session 61 — prefer a book row with BOTH sides priced (same rule as
* snapshotService.indexOdds): fewer genuinely-absent locked/closing odds
* when another book carried the side. Real rows only, never synthesized. */
function indexProps(props) {
const map = {};
const bothSides = (p) => p && p.over_odds != null && p.under_odds != null;
for (const p of props || []) {
if (!p || !p.player || !p.stat_type) continue;
const k = `${nameKey(p.player)}|${String(p.stat_type).toLowerCase()}`;
if (!map[k]) map[k] = p;
if (!map[k] || (!bothSides(map[k]) && bothSides(p))) map[k] = p;
}
return map;
}