Session 56: Full audit — PropLine + boxscore + pipeline + sport coverage (2289 tests)

Research (verified against live MLB Stats / ESPN / The Odds APIs):
- specs/propline-audit.md — every stat_type mapped against our 4-layer pipeline;
  real MLB boxscore fields; sport coverage status; pipeline gap analysis.
- specs/vyndr-roadmap.md — priority-ordered Sessions 57–64 + coverage targets.
- scripts/propline-audit.js + specs/audit-data/ (raw capture).

Headline bug: oddsNormalizer mapped batter_rbis → 'rbis' while the whole
grade/feature/outcome chain keys on 'rbi' — every PropLine RBI prop silently
failed to grade AND settle. Fixed (+ regression test).

Phase 4 — wired missing MLB stats end-to-end:
- PropLine MLB markets 6 → 12 (+runs, walks, doubles, earned_runs, hits_allowed,
  outs — same request, no extra quota).
- doubles/outs/triples added to featureCache + outcomeService MLB_LOG_FIELD and
  all three grade whitelists (analyze/scan/validation.py).

Phase 6 — pipeline resilience:
- opsNotify.js: ntfy alerts (never throws, test-disabled). Snapshot success/
  stale/failure alerts; retry-once on hard odds error (not on empty slate).
- Missed-cron watchdog (mostRecentExpectedSlot/isSnapshotOverdue); status probe
  now returns `overdue`.

Coverage truth: MLB is the only end-to-end-live sport; outcome settlement is
MLB-only (WNBA/NBA/soccer never settle) — documented as the #1 roadmap gap.

Backend 2276 → 2289 tests (+13). Web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-10 17:00:29 -04:00
parent d09a06c054
commit 2ae8a5697e
21 changed files with 1607 additions and 11 deletions
+4 -1
View File
@@ -112,7 +112,7 @@ router.post('/snapshot/all', async (req, res) => {
*/
router.get('/snapshot/status', async (req, res) => {
const { cacheGet } = require('../utils/redis');
const { HOURS_UTC } = require('../snapshotScheduler');
const { HOURS_UTC, isSnapshotOverdue } = require('../snapshotScheduler');
const SPORTS = ['mlb', 'nba', 'wnba'];
try {
const redis_keys = {};
@@ -135,10 +135,13 @@ router.get('/snapshot/status', async (req, res) => {
}
const ticker = await cacheGet('ticker:items');
redis_keys['ticker:items'] = !!ticker;
// Session 56 — surface the missed-cron signal in the health probe.
const mlbTs = last_snapshot.mlb && last_snapshot.mlb.updated_at;
return res.json({
cron_armed: process.env.SNAPSHOT_CRON === '1',
cron_hours_utc: HOURS_UTC,
last_snapshot,
overdue: isSnapshotOverdue(mlbTs),
redis_keys,
ticker_count: Array.isArray(ticker) ? ticker.length : 0,
});