Files
vyndr/tests/unit/intradayRefresh.test.js
builtbykev d3637e7abd S6 (a1): display — the full picture under the grammar
- specs/ROW-GRAMMAR.md: the row grammar law (slot order, color law, mark
  law, mobile stacking, no-truncation), locked by tests/unit/rowGrammar.
  StatStrip violations fixed: MovementChip before the grade (market
  context before model output); ViabilityChips after the archetype
  (identity is one contiguous run).
- Line-movement sparklines: intradayRefreshService.trackHistory captures
  real {t,line} points per grade (seeded with the lock, deduped when
  flat, capped 24) inside the snapshot write-back; StatStrip.LineSparkline
  renders at >=3 points (green toward / amber against / dim flat).
- Last-10 dot strips: services/last10Dots (streaksService accessors) ->
  /api/snapshot/:sport attaches last10_dots from rosterlogs:{sport};
  StatStrip.DotStrip renders vs the LOCKED line, newest first.
- CLV distribution: getModelAggregate emits clv_distribution (7 signed
  buckets, outliers clamped) only past the centralized n>=20 gate;
  ledger MODEL header renders the bar strip.
- Global search: SearchModal (cmd-K via GlobalHosts + window.__search),
  players via /api/players/search per sport + static lib/teams.js
  (soccer deliberately absent); Nav search icon + Search first in the
  mobile More sheet. Explore tab untouched.
- Landing LCP: fade-up floored at opacity .6 (hero h1 contentful on
  first frame, S33 visible-floor rule) + IBM Plex Mono preload:false
  (4 decorative font files off the slow-4G critical path).

2654 -> 2698 tests (226 suites) green; web build exit 0.

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

195 lines
9.3 KiB
JavaScript
Raw Permalink 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, trackHistory, HISTORY_CAP } = __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);
});
});
// S6 (A1 board) — sparkline fuel: {t, line} history capture on each grade's
// movement tracking, persisted inside the snapshot write-back (no new keys).
describe('trackHistory — real {t, line} points, seeded, deduped, capped', () => {
const LOCK_TS = '2026-07-11T14:00:00.000Z';
const g = (extra = {}) => ({ ...GRADE, gradedAt: { line: 0.5, odds: -115, timestamp: LOCK_TS }, ...extra });
test('first capture seeds the LOCKED line at its own timestamp, then the move', () => {
const h = trackHistory(g(), 1.5, NOW);
expect(h).toEqual([{ t: LOCK_TS, line: 0.5 }, { t: NOW, line: 1.5 }]);
});
test('a flat line never appends a duplicate point (a point means it MOVED)', () => {
const h = trackHistory(g(), 0.5, NOW); // current == locked
expect(h).toEqual([{ t: LOCK_TS, line: 0.5 }]);
const again = trackHistory(g({ history: h }), 0.5, '2026-07-11T21:00:00.000Z');
expect(again).toHaveLength(1);
});
test('an unparseable current line appends nothing (real points only)', () => {
expect(trackHistory(g(), null, NOW)).toBeUndefined();
const prior = [{ t: LOCK_TS, line: 0.5 }];
expect(trackHistory(g({ history: prior }), 'n/a', NOW)).toBe(prior);
});
test('capped at 24 points, newest kept', () => {
const prior = Array.from({ length: HISTORY_CAP }, (_, i) => ({ t: `t${i}`, line: i }));
const h = trackHistory(g({ history: prior }), 99, NOW);
expect(h).toHaveLength(HISTORY_CAP);
expect(h[HISTORY_CAP - 1]).toEqual({ t: NOW, line: 99 });
expect(h[0]).toEqual({ t: 't1', line: 1 }); // oldest point aged out
});
});
describe('runIntradayRefresh — history rides the snapshot write-back', () => {
test('a steam move persists history on the grade (locked point + current)', async () => {
const { deps, store } = harness({ grades: [GRADE], oddsProps: [prop(1.5)], analyzeResult: null });
await runIntradayRefresh('mlb', deps);
const snap = store.get('snapshot:mlb:latest');
expect(snap.grades[0].history).toEqual([
{ t: NOW, line: 0.5 }, // seeded from the lock (gradedAt.timestamp === NOW here)
{ t: NOW, line: 1.5 },
]);
// grades:{sport} carries the same enriched grades.
expect(store.get('grades:mlb').grades[0].history).toHaveLength(2);
});
test('a flat run keeps a single seeded point — the sparkline stays hidden (<3)', async () => {
const { deps, store } = harness({ grades: [GRADE], oddsProps: [prop(0.5)], analyzeResult: null });
await runIntradayRefresh('mlb', deps);
expect(store.get('snapshot:mlb:latest').grades[0].history).toEqual([{ t: NOW, line: 0.5 }]);
});
test('a prop gone from the feed leaves prior history untouched', async () => {
const withHist = { ...GRADE, stat_type: 'hits', history: [{ t: 'x', line: 1 }] };
const { deps, store } = harness({ grades: [withHist], oddsProps: [prop(1.5)], analyzeResult: null });
await runIntradayRefresh('mlb', deps);
const frozen = store.get('snapshot:mlb:latest').grades.find((g) => g.stat_type === 'hits');
expect(frozen.history).toEqual([{ t: 'x', line: 1 }]);
});
});
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
});
});