/** * Session 64 Order 1 — date-targeted settlement + terminal states. * * The bug: a lookup miss did `pending += 1` with no terminal state, so a row * that could NEVER settle (DNP, or the rolling window passed the game) was * indistinguishable from one settling tomorrow. These lock the distinctions. */ const src = require('../../src/services/settleSource'); const matchesDate = (r, d) => r.date === d; const statValue = (s, st) => (s && s[st] != null ? Number(s[st]) : null); const base = { sport: 'mlb', playerName: 'X', gameDate: '2026-07-17', statType: 'doubles', matchesDate, statValue, }; describe('classifyGameState', () => { test('final states', () => { expect(src.classifyGameState({ status: 'Final' })).toBe('final'); expect(src.classifyGameState({ state: 'post' })).toBe('final'); }); test('postponed/cancelled are void', () => { expect(src.classifyGameState({ detailedState: 'Postponed' })).toBe('void'); expect(src.classifyGameState({ status: 'Cancelled' })).toBe('void'); }); test('SUSPENDED is NOT final — it resumes and must stay pending', () => { expect(src.classifyGameState({ detailedState: 'Suspended' })).toBe('not_final'); }); test('scheduled / in progress are not final', () => { expect(src.classifyGameState({ status: 'Scheduled' })).toBe('not_final'); expect(src.classifyGameState({ status: 'In Progress' })).toBe('not_final'); }); test('unknown when there is nothing to read', () => { expect(src.classifyGameState({})).toBe('unknown'); }); }); describe('absenceMeaning — what a missing player row MEANS', () => { test('any unplayed game → pending (never void a bet that may still settle)', () => { expect(src.absenceMeaning([{ status: 'Final' }, { status: 'Suspended' }])).toBe('pending'); }); test('all games postponed → void', () => { expect(src.absenceMeaning([{ status: 'Postponed' }])).toBe('void'); }); test('game final but player absent → void (confirmed DNP)', () => { expect(src.absenceMeaning([{ status: 'Final' }])).toBe('void'); }); test('no schedule → unknown, not a guess', () => { expect(src.absenceMeaning([])).toBe('unknown'); }); }); describe('resolveOutcome', () => { test('player has a line on that date → SETTLED with the real value', async () => { const r = await src.resolveOutcome({ ...base, getFullLog: async () => [{ date: '2026-07-17', stat: { doubles: 1 } }], getSchedule: async () => [{ status: 'Final' }], }); expect(r.state).toBe('settled'); expect(r.value).toBe(1); }); test('a ZERO value still settles (0 is a real result, not absence)', async () => { const r = await src.resolveOutcome({ ...base, getFullLog: async () => [{ date: '2026-07-17', stat: { doubles: 0 } }], getSchedule: async () => [{ status: 'Final' }], }); expect(r.state).toBe('settled'); expect(r.value).toBe(0); }); // Session 64 — this MUST NOT void. Player-absence from a labelled date can // equally mean the row's date is wrong (ledger derives game_date from the // GRADE time when the feed has no game_time, so late-UTC snapshots label the // previous ET day). Verified live: Freddie Freeman's 97-game log has no // 2026-07-18 because he played the 17th and 19th. Voiding on absence // destroyed real results. test('player absent on a FINAL game → VOID (dates proven correct; DNP is real)', async () => { const r = await src.resolveOutcome({ ...base, getFullLog: async () => [{ date: '2026-07-16', stat: { doubles: 0 } }], getSchedule: async () => [{ status: 'Final' }], }); expect(r.state).toBe('void'); expect(r.reason).toBe('player_dnp'); }); test('postponed game → VOID with the postponed reason', async () => { const r = await src.resolveOutcome({ ...base, getFullLog: async () => [], getSchedule: async () => [{ status: 'Postponed' }], }); expect(r.state).toBe('void'); expect(r.reason).toBe('game_postponed_or_cancelled'); }); test('SUSPENDED game stays PENDING — never voided', async () => { const r = await src.resolveOutcome({ ...base, getFullLog: async () => [], getSchedule: async () => [{ status: 'Suspended' }], }); expect(r.state).toBe('pending'); }); test('a game OUTSIDE a rolling window still settles from the full log', async () => { // The exact live failure: Jul 12 game, window now starts Jul 6. const full = Array.from({ length: 40 }, (_, i) => ({ date: `2026-07-${String(i + 1).padStart(2, '0')}`, stat: { doubles: i % 3 }, })); const r = await src.resolveOutcome({ ...base, gameDate: '2026-07-12', getFullLog: async () => full, getSchedule: async () => [{ status: 'Final' }], }); expect(r.state).toBe('settled'); }); test('player PLAYED but the stat is missing from the box row → unknown, NEVER void', async () => { const r = await src.resolveOutcome({ ...base, getFullLog: async () => [{ date: '2026-07-17', stat: { hits: 2 } }], getSchedule: async () => [{ status: 'Final' }], }); expect(r.state).toBe('unknown'); expect(r.reason).toBe('stat_not_in_box_row'); }); test('a throwing source degrades to unknown, never throws', async () => { const r = await src.resolveOutcome({ ...base, getFullLog: async () => { throw new Error('net'); }, getSchedule: async () => { throw new Error('net'); }, }); expect(r.state).toBe('unknown'); }); });