Files
vyndr/tests/unit/intradayRefresh.test.js
T
builtbykev 9ae64516e6 Session D (night2): Phase 2.5 — intraday refresh + directional movement
Odds-only refresh every 20 min during slate hours (noon–midnight ET),
in-process (INTRADAY_REFRESH=0 kill switch; skips full-snapshot slots):
- signed delta RELATIVE TO THE GRADED SIDE; direction is the signal.
- WITH the grade → STEAM badge, never a re-grade.
- AGAINST >=1.0 → re-grade THAT PROP ONLY at the real current line:
  holds/refuses → VALUE (better entry, same read); drops → PUBLIC revision
  (grade updates, revised_from_grade preserves the ORIGINAL forever, UI
  strikethrough on slate strips + ledger cards).
- Every run recaptures closing_line/odds → the close is now refresh-
  fidelity; SYNC goes live by dropping SNAPSHOT_EXPECTED_INTERVAL to 1200.
- Ticker MOVE events feed from the refresh (>=1.0 moves).
- QUOTA MATH: 36 runs/day/sport x 4 sports <= 144 PropLine calls/day vs
  9,000/day free capacity (3 keys x 3,000). Re-grades are internal compute.
- POST /api/internal/refresh/all for manual runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 00:51:00 -04:00

134 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Session 60 (night2/D) — Phase 2.5 intraday refresh. Direction is the
// signal: WITH the grade = STEAM (no re-grade); AGAINST ≥1.0 = re-grade
// that prop only (holds → VALUE, drops → PUBLIC revision). All lines are
// real feed values; the refresh recaptures the close.
const { runIntradayRefresh, inSlateHours, __internals } = require('../../src/services/intradayRefreshService');
const { signedDelta } = __internals;
const NOW = '2026-07-11T20:00:00.000Z';
function harness({ grades, oddsProps, analyzeResult }) {
const store = new Map([[`snapshot:mlb:latest`, { sport: 'mlb', updated_at: NOW, grades, deltas: [] }]]);
const calls = { analyze: [], revisions: [], closing: 0, ticker: [] };
const deps = {
cacheGet: async (k) => store.get(k) ?? null,
cacheSet: async (k, v) => { store.set(k, v); },
getOdds: async () => ({ props: oddsProps }),
analyze: async (prop) => { calls.analyze.push(prop); return analyzeResult; },
ledger: {
applyRevision: async (sport, rev) => { calls.revisions.push(rev); return { revised: true }; },
captureClosing: async () => { calls.closing += 1; return { updated: 0 }; },
},
pushTickerItems: async (events) => { calls.ticker.push(...events); },
now: () => NOW,
};
return { deps, store, calls };
}
const GRADE = {
player: 'Aaron Judge', stat_type: 'home_runs', line: 0.5, direction: 'over',
grade: 'A', confidence: 78, gradedAt: { line: 0.5, odds: -115, timestamp: NOW },
};
const prop = (line) => ({ player: 'Aaron Judge', stat_type: 'home_runs', line, book: 'draftkings' });
describe('signedDelta — relative to the graded side', () => {
test('over: rising line = positive (market chasing the over)', () => {
expect(signedDelta('over', 1.5, 2.5)).toBe(1);
expect(signedDelta('over', 1.5, 1.0)).toBe(-0.5);
});
test('under: falling line = positive', () => {
expect(signedDelta('under', 1.5, 1.0)).toBe(0.5);
expect(signedDelta('under', 1.5, 2.5)).toBe(-1);
});
test('missing values → null, never a fabricated zero', () => {
expect(signedDelta('over', null, 2)).toBeNull();
});
});
describe('runIntradayRefresh — directional handling', () => {
test('moved WITH the grade → STEAM, NO re-grade', async () => {
const { deps, store, calls } = harness({ grades: [GRADE], oddsProps: [prop(1.5)], analyzeResult: null });
const res = await runIntradayRefresh('mlb', deps);
expect(res).toMatchObject({ status: 'ok', steam: 1, value: 0, revised: 0 });
expect(calls.analyze).toHaveLength(0); // steam never re-grades
const snap = store.get('snapshot:mlb:latest');
expect(snap.grades[0].movement).toMatchObject({ kind: 'steam', delta: 1, currentLine: 1.5 });
expect(calls.ticker.length).toBeGreaterThan(0); // ≥1.0 move → ticker MOVE
expect(calls.ticker[0].text).toContain('o0.5 → o1.5');
});
test('moved AGAINST ≥1.0, re-grade HOLDS → VALUE (better entry)', async () => {
const { deps, store, calls } = harness({
grades: [{ ...GRADE, gradedAt: { line: 2.5, odds: -110, timestamp: NOW }, line: 2.5 }],
oddsProps: [prop(1.5)],
analyzeResult: { grade: 'A', confidence: 70 }, // same grade at the new line
});
const res = await runIntradayRefresh('mlb', deps);
expect(res).toMatchObject({ value: 1, revised: 0 });
expect(calls.analyze).toHaveLength(1);
expect(calls.analyze[0].line).toBe(1.5); // re-graded at the REAL current line
expect(store.get('snapshot:mlb:latest').grades[0].movement.kind).toBe('value');
expect(calls.revisions).toHaveLength(0);
});
test('moved AGAINST ≥1.0, grade DROPS → PUBLIC revision (original preserved)', async () => {
const { deps, store, calls } = harness({
grades: [{ ...GRADE, gradedAt: { line: 2.5, odds: -110, timestamp: NOW }, line: 2.5 }],
oddsProps: [prop(1.5)],
analyzeResult: { grade: 'C', confidence: 40 },
});
const res = await runIntradayRefresh('mlb', deps);
expect(res).toMatchObject({ revised: 1 });
const g = store.get('snapshot:mlb:latest').grades[0];
expect(g.grade).toBe('C');
expect(g.revised_from_grade).toBe('A'); // the original, forever
expect(calls.revisions[0]).toMatchObject({ newGrade: 'C', fromGrade: 'A', line: 2.5, side: 'over' });
});
test('a second drop keeps the ORIGINAL revised_from_grade', async () => {
const already = { ...GRADE, grade: 'B', revised_from_grade: 'A', gradedAt: { line: 2.5, odds: null, timestamp: NOW }, line: 2.5 };
const { deps, store } = harness({ grades: [already], oddsProps: [prop(1.5)], analyzeResult: { grade: 'C' } });
await runIntradayRefresh('mlb', deps);
expect(store.get('snapshot:mlb:latest').grades[0].revised_from_grade).toBe('A');
});
test('an insufficient re-read HOLDS the original grade (never blanks it)', async () => {
const { deps, store } = harness({
grades: [{ ...GRADE, gradedAt: { line: 2.5, odds: null, timestamp: NOW }, line: 2.5 }],
oddsProps: [prop(1.5)],
analyzeResult: { grade: null, insufficient_data: true },
});
const res = await runIntradayRefresh('mlb', deps);
expect(res.value).toBe(1);
expect(store.get('snapshot:mlb:latest').grades[0].grade).toBe('A');
});
test('prop gone from the feed → frozen untouched; close recaptured every run', async () => {
const { deps, store, calls } = harness({ grades: [GRADE], oddsProps: [prop(0.5)], analyzeResult: null });
const res = await runIntradayRefresh('mlb', deps);
expect(res.checked).toBe(1);
expect(store.get('snapshot:mlb:latest').grades[0].movement).toBeNull(); // <0.5 noise
expect(calls.closing).toBe(1); // refresh-fidelity closing capture
});
test('no snapshot → honest skip, zero quota spent', async () => {
const calls = { odds: 0 };
const res = await runIntradayRefresh('mlb', {
cacheGet: async () => null,
cacheSet: async () => {},
getOdds: async () => { calls.odds += 1; return { props: [] }; },
ledger: {}, pushTickerItems: async () => {}, now: () => NOW,
});
expect(res.status).toBe('skipped');
expect(calls.odds).toBe(0);
});
});
describe('inSlateHours — noonmidnight ET', () => {
test('3 PM ET yes, 4 AM ET no', () => {
expect(inSlateHours(new Date('2026-07-11T19:00:00Z'))).toBe(true); // 3 PM EDT
expect(inSlateHours(new Date('2026-07-11T08:00:00Z'))).toBe(false); // 4 AM EDT
});
});