Quota guard: close the silent odds-api drain + reserve floor for MLB
Diagnosis (why 500/500 went unpaged): the only regular odds-api burner was
futuresService, which called axios DIRECTLY — bypassing the gateway, so it
never hit recordCall (the ONE place the WARN/BLOCK pager fires) and never
respected the 95% block. It only syncFromHeaders, which updated the counter's
number SILENTLY. oddsService (which does go through the gateway) only touches
odds-api when PropLine fails, so recordCall for odds-api effectively never ran.
Result: the counter could reach 100% with neither pager firing.
Fixes (a silent drain is now impossible, not just guarded):
- futuresService routes through gateway.fetch('odds-api', …) → counted, blocked
at 95%, and reserve-gated. Closes the raw-axios bypass.
- Reserve floor in the gateway: a DISCRETIONARY call (futures/soccer) passes
reserve=ODDS_API_RESERVE (default 50) and is refused while remaining <= reserve.
The ESSENTIAL MLB prop-backup passes no reserve and may spend to the 95% block.
→ a futures/soccer drain can NEVER starve MLB's backup path.
- quotaTracker.syncFromHeaders (the AUTHORITATIVE number) now fires the same
once-per-period WARN/BLOCK alert on a crossing — extracted fireThresholdAlert
shared with recordCall. The header-only drain now pages.
- POST /api/internal/quota/test-alert (internal-key) test-fires the pager
end-to-end so ntfy delivery is verifiable on demand.
Also (reality-corrected cadence): WNBA restored to the full grid. 2026-07-15
had two AFTERNOON WNBA games finished before the 22 UTC slot — 14 UTC (10am ET)
is the only slot early enough for a 1pm ET game's props, and on PropLine the
extra slots cost a rounding error. Soccer stays the only trimmed sport (the
real odds-api discipline). Assumption corrected by observed data.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,10 +38,15 @@
|
||||
* - mlb — games + props all day; the full grid is correct. Keep 14/19/22/1/3.
|
||||
* - nba — off-season now (self-skips empty on PropLine, ~0 cost); in-season
|
||||
* posts morning→afternoon ET. The full grid fits both. Keep.
|
||||
* - wnba — games tip evening ET; props post ~afternoon ET. 19 UTC (3pm ET)
|
||||
* catches early games, 22 UTC (6pm ET) the bulk, 1 UTC (9pm ET) the
|
||||
* west-coast lock. 14 UTC (10am ET) and 3 UTC (11pm ET, games done)
|
||||
* are dropped — that's the wasted "wnba:0" slot removed.
|
||||
* - wnba — PropLine-backed (cheap), and games span EARLY AFTERNOON (weekend/
|
||||
* holiday 1–3pm ET) through LATE-EVENING west-coast (10pm ET). A
|
||||
* 1pm ET game's props post ~morning ET, so the 14 UTC (10am ET) slot
|
||||
* is the ONLY one early enough to catch them; 3 UTC (11pm ET) is the
|
||||
* west-coast lock. Observed 2026-07-15: two afternoon WNBA games had
|
||||
* already finished by the 22 UTC slot. So WNBA keeps the FULL grid —
|
||||
* the earlier "drop 14/3 as waste" was wrong: on PropLine it costs a
|
||||
* rounding error and the coverage is real. (An empty slot is an
|
||||
* honest 'skipped', never a paged failure.)
|
||||
* - soccer— World Cup live now (final Jul 19), club leagues resume Aug. Lines
|
||||
* post well ahead and barely move; two well-placed odds-api reads
|
||||
* (14 UTC morning research, 19 UTC pre-afternoon-match lock) cover a
|
||||
@@ -53,7 +58,7 @@
|
||||
const SPORT_CADENCE = Object.freeze({
|
||||
mlb: { hours: [14, 19, 22, 1, 3], intraday: true, source: 'propline' },
|
||||
nba: { hours: [14, 19, 22, 1, 3], intraday: true, source: 'propline' },
|
||||
wnba: { hours: [19, 22, 1], intraday: true, source: 'propline' },
|
||||
wnba: { hours: [14, 19, 22, 1, 3], intraday: true, source: 'propline' },
|
||||
soccer: { hours: [14, 19], intraday: false, source: 'odds-api' },
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user