Job 1: per-sport snapshot cadence (config, not baseball's rhythm for all)

Every ACTIVE sport was graded at all five MLB slots (14/19/22/1/3 UTC). Sports
post lines on different clocks, so that inheritance was wasteful both ways:
WNBA props aren't posted at 14:00 UTC (10am ET) → that slot always graded 0
(the audit's "wnba:0"); soccer odds come from the 500/MONTH odds-api key, so
five slots/day is a third of the budget for 1-2 matches.

New src/config/sportCadence.js is the single source of truth (config-over-
constants). Mapped from reality + quota headroom (PropLine 9k/day abundant,
odds-api 500/mo scarce):
  mlb    14/19/22/1/3  intraday   (full grid — games+props all day)
  nba    14/19/22/1/3  intraday   (in-season fits; off-season self-skips empty)
  wnba   19/22/1       intraday   (afternoon→evening ET; drops the 14/3 waste)
  soccer 14/19         NO intraday (WC live; 2 lean odds-api reads, key-protected)

The scheduler still fires at HOURS_UTC and the missed-cron watchdog still
references MLB (which runs every grid hour) — each slot now grades only
sportsForHour(h), and only intradaySports() get the 20-min refresh. Every
sport's hours are kept a subset of the firing grid (a boot-time guard + a test
warn if that's ever violated). Retune a sport by editing one table row.

Adaptive, not constant: near-zero when a sport is quiet, protecting the scarce
odds-api quota from being drained by noon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-15 16:17:55 -04:00
parent 7712f0a442
commit 4cd933d83e
4 changed files with 180 additions and 4 deletions
+8 -1
View File
@@ -457,9 +457,16 @@ async function runSnapshot(sport, opts = {}) {
}
/** Run snapshots for every active sport sequentially (cron entrypoint). */
// Job 1 — `opts.sports` scopes the run to a subset (the scheduler passes the
// hour's per-sport cadence). Absent → every ACTIVE sport (the on-demand
// /api/internal/snapshot/all behaviour is unchanged). runSnapshot ignores the
// extra key.
async function runAllSnapshots(opts = {}) {
// An EXPLICIT array is honored verbatim (even empty = run nothing); only an
// ABSENT `sports` key means "every active sport" (on-demand /all).
const sports = Array.isArray(opts.sports) ? opts.sports : ACTIVE_SPORTS;
const results = [];
for (const sp of ACTIVE_SPORTS) {
for (const sp of sports) {
results.push(await runSnapshot(sp, opts));
}
return results;