4cd933d83e
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>
50 lines
2.2 KiB
JavaScript
50 lines
2.2 KiB
JavaScript
'use strict';
|
|
|
|
// Job 1 — per-sport snapshot cadence config. Locks the posting-rhythm map and
|
|
// the two quota-discipline invariants so a future edit can't silently
|
|
// reintroduce baseball's one-size-fits-all rhythm.
|
|
|
|
const cadence = require('../../src/config/sportCadence');
|
|
const { HOURS_UTC } = require('../../src/snapshotScheduler');
|
|
|
|
describe('sportCadence', () => {
|
|
test('every sport is a subset of the scheduler firing grid (else it never runs)', () => {
|
|
// The scheduler only fires at HOURS_UTC; a sport hour outside that set would
|
|
// be silently dropped. This invariant is the whole reason the design keeps
|
|
// the union inside the grid.
|
|
for (const orphan of cadence.ALL_HOURS.filter((h) => !HOURS_UTC.includes(h))) {
|
|
throw new Error(`hour ${orphan} is scheduled for some sport but not in HOURS_UTC=${HOURS_UTC}`);
|
|
}
|
|
expect(cadence.ALL_HOURS.every((h) => HOURS_UTC.includes(h))).toBe(true);
|
|
});
|
|
|
|
test('WNBA skips 14:00 UTC (props not posted) — the wasted "wnba:0" slot', () => {
|
|
expect(cadence.sportsForHour(14)).toContain('mlb');
|
|
expect(cadence.sportsForHour(14)).not.toContain('wnba');
|
|
// but WNBA DOES run at its real hours
|
|
expect(cadence.sportsForHour(22)).toContain('wnba');
|
|
expect(cadence.sportsForHour(1)).toContain('wnba');
|
|
});
|
|
|
|
test('soccer runs only its two lean odds-api slots, never the full grid', () => {
|
|
expect(cadence.hoursFor('soccer')).toEqual([14, 19]);
|
|
expect(cadence.sportsForHour(22)).not.toContain('soccer');
|
|
expect(cadence.sportsForHour(1)).not.toContain('soccer');
|
|
expect(cadence.sportsForHour(3)).not.toContain('soccer');
|
|
});
|
|
|
|
test('soccer is excluded from intraday (protects the 500/mo odds-api key)', () => {
|
|
expect(cadence.intradaySports()).not.toContain('soccer');
|
|
expect(cadence.intradaySports()).toEqual(expect.arrayContaining(['mlb', 'nba', 'wnba']));
|
|
});
|
|
|
|
test('sportsForHour respects a candidate subset and unknown hours yield none', () => {
|
|
expect(cadence.sportsForHour(19, ['wnba', 'soccer'])).toEqual(expect.arrayContaining(['wnba', 'soccer']));
|
|
expect(cadence.sportsForHour(7)).toEqual([]); // 7 UTC — nobody posts
|
|
});
|
|
|
|
test('mlb keeps the full baseball grid', () => {
|
|
expect(cadence.hoursFor('mlb')).toEqual([14, 19, 22, 1, 3]);
|
|
});
|
|
});
|