Wave 1: NBA/WNBA settlement — grades settle vs free ESPN game logs
Unblocks the self-learning loop for basketball. Once an NBA/WNBA grade
exists (Wave 0), it now settles against the FREE ESPN per-game log
(espnStatsAdapter.getPlayerGameLog) — the same {found, last10:[{date,stat}]}
contract MLB settlement already consumes. accuracy:{sport} + by_tier
calibration + the Wave-3 TierRecord light up automatically.
- outcomeService/ledgerService: defaultGetPlayerStats routes nba/wnba to
espnStatsAdapter.getPlayerGameLog; MLB stays on mlbStatsAdapter.
- outcomeService: sport-aware statValue + a SEPARATE NBA_BOX_KEY/NBA_COMBO
map (S11 three-map-split kept — never merged with MLB_LOG_FIELD). Combos
(pts_reb_ast, reb_ast, stl_blk, …) sum components; a missing component
never fabricates a total.
- logRowOnDate: ESPN gamelog rows carry a FULL ISO timestamp (a late tip
rolls past UTC midnight), so basketball date-matches on UTC OR ET date;
MLB keeps exact YYYY-MM-DD compare. Outcome `date` is normalized to the
ET calendar day so the accuracy window filter + idempotency key behave
identically across sports.
- Final-honesty guard: never settle a basketball row whose ET date is
today (an in-progress partial box). MLB is final-only + settles same-day,
so the guard is scoped to basketball. The ledger path is already guarded
(.lt('game_date', today)) for all sports.
- opsWatch: nba/wnba added to SETTLEABLE_SPORTS; zeroSettleAlarm gates them
behind a real-finals probe (finalsBySport) so an offseason/off-day's
stale pendings never false-page "settled 0". snapshotScheduler counts
yesterday's ESPN state==='post' events and feeds the map; MLB unchanged.
- snapshotScheduler: boot announce per settleable sport
([settle:mlb] [settle:nba] [settle:wnba]). Thrown-error paging already
covers the new sports (settleAll* loop every sport).
Tests: tests/unit/nbaSettlement.test.js (16) — WNBA hit/miss/push, combo
pra, idempotent re-run, unplayed/today game does NOT settle, accuracy:wnba
+ byGrade + by_tier populate, ledger WNBA settle. opsWatch (+5) — finals
off-day no page, finals present DOES page. MLB suites unregressed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,9 @@ function startSnapshotScheduler(opts = {}) {
|
||||
const healthIssues = opts.healthIssues || require('./services/systemHealth').healthIssues;
|
||||
const getQuotaStatus = opts.getQuotaStatus || require('./services/quotaTracker').getQuotaStatus;
|
||||
const countLedgerRows = opts.countLedgerRows || require('./services/ledgerService').countRowsForDate;
|
||||
// Wave 1 — real-finals probe for the zero-settle alarm (NBA/WNBA only page
|
||||
// when yesterday actually had games). Reuses the free, cached ESPN scoreboard.
|
||||
const getSchedule = opts.getSchedule || require('./services/scheduleService').getSchedule;
|
||||
const failureTracker = opts.failureTracker || opsWatch.createFailureTracker();
|
||||
let lastFiredSlot = null;
|
||||
let lastOverdueSlot = null;
|
||||
@@ -178,7 +181,17 @@ function startSnapshotScheduler(opts = {}) {
|
||||
// once per ET date; a genuinely empty yesterday (0 rows fetched) never fires.
|
||||
try {
|
||||
if (h === opsWatch.morningHourUtc(HOURS_UTC) && ledgerResults) {
|
||||
const z = opsWatch.zeroSettleAlarm(ledgerResults);
|
||||
// Wave 1 — probe yesterday's ESPN scoreboard so an NBA/WNBA off-day
|
||||
// (0 finals) never false-pages "settled 0" on stale pending rows.
|
||||
const yEt = opsWatch.dateET(new Date(d.getTime() - 24 * 3600 * 1000));
|
||||
const finalsBySport = {};
|
||||
for (const fsp of opsWatch.FINALS_GATED_SPORTS) {
|
||||
try {
|
||||
const games = await getSchedule(fsp, yEt);
|
||||
finalsBySport[fsp] = Array.isArray(games) && games.some((g) => g && g.status === 'post');
|
||||
} catch { finalsBySport[fsp] = false; }
|
||||
}
|
||||
const z = opsWatch.zeroSettleAlarm(ledgerResults, { finalsBySport });
|
||||
if (z.alarm) {
|
||||
const dk = `ops:settle_zero:${opsWatch.dateET(d)}`;
|
||||
if (!(await cacheGet(dk))) {
|
||||
@@ -254,7 +267,10 @@ function startSnapshotScheduler(opts = {}) {
|
||||
// settleAllLedgers run FIRST at every snapshot slot, before grading). It
|
||||
// was invisible at boot, which made "is settlement scheduled?" unanswerable
|
||||
// from logs. This line makes it verifiable forever.
|
||||
console.log(`[settlement] armed — outcomes + ledger settle pass runs FIRST at each snapshot slot (${HOURS_UTC.join(',')} UTC), idempotent re-runs`);
|
||||
// Wave 1 — announce settlement PER settleable sport so "does NBA settle?" is
|
||||
// answerable from boot logs (mlb=statsapi, nba/wnba=ESPN game logs).
|
||||
const settleTags = require('./services/opsWatch').SETTLEABLE_SPORTS.map((s) => `[settle:${s}]`).join(' ');
|
||||
console.log(`[settlement] armed — ${settleTags} outcomes + ledger settle pass runs FIRST at each snapshot slot (${HOURS_UTC.join(',')} UTC), idempotent re-runs`);
|
||||
// Session 8 — same verifiability rule: every watchdog states itself at boot.
|
||||
console.log(`[opsWatch] armed — settle alarms (throw + morning zero-settle), failure pager (${failureTracker.threshold} consecutive), quota daily check, pulse ${PULSE_HOUR_UTC}:00 UTC`);
|
||||
return { interval, tick, refreshTick, pulseTick };
|
||||
|
||||
Reference in New Issue
Block a user