const mockState = { rows: new Map(), upserts: [] }; function mockKeyOf(s, g, p) { return `${s}|${g}|${p}`; } jest.mock('../../src/utils/supabase', () => ({ getSupabaseServiceClient: () => ({ from() { const ctx = { filters: {} }; const proxy = { select() { return proxy; }, eq(col, val) { ctx.filters[col] = val; return proxy; }, maybeSingle() { const row = mockState.rows.get(mockKeyOf(ctx.filters.sport, ctx.filters.grade, ctx.filters.period)); return Promise.resolve({ data: row || null, error: null }); }, upsert(row) { mockState.upserts.push(row); mockState.rows.set(mockKeyOf(row.sport, row.grade, row.period), { ...row }); return Promise.resolve({ error: null }); }, then(resolve) { // For getAccuracyDashboard — list all rows with period filter. const period = ctx.filters.period; const all = Array.from(mockState.rows.values()).filter((r) => !period || r.period === period); return resolve({ data: all, error: null }); }, }; return proxy; }, }), })); const at = require('../../src/services/intelligence/accuracyTracker'); beforeEach(() => { mockState.rows.clear(); mockState.upserts.length = 0; }); describe('accuracyTracker.recordResolution', () => { test('increments hit + total_graded across all three periods', async () => { await at.recordResolution('nba', 'A', 'hit'); expect(mockState.upserts).toHaveLength(3); // all_time + 30d + 7d for (const u of mockState.upserts) { expect(u.total_graded).toBe(1); expect(u.total_hit).toBe(1); expect(u.total_miss).toBe(0); } }); test('push/void don\'t count toward hit rate', async () => { await at.recordResolution('nba', 'A', 'push'); await at.recordResolution('nba', 'A', 'void'); const row = mockState.rows.get('nba|A|all_time'); expect(row.total_hit).toBe(0); expect(row.total_miss).toBe(0); expect(row.total_push).toBe(1); expect(row.total_void).toBe(1); expect(row.hit_rate).toBeNull(); }); test('hit_rate is hits/(hits+misses) only', async () => { for (let i = 0; i < 7; i += 1) await at.recordResolution('nba', 'A', 'hit'); for (let i = 0; i < 3; i += 1) await at.recordResolution('nba', 'A', 'miss'); const row = mockState.rows.get('nba|A|all_time'); expect(row.hit_rate).toBeCloseTo(0.7, 5); }); }); describe('accuracyTracker — baseline lock', () => { test('baseline locks at 100 decisive resolutions', async () => { for (let i = 0; i < 99; i += 1) await at.recordResolution('nba', 'A', 'hit'); let row = mockState.rows.get('nba|A|all_time'); expect(row.baseline_locked).toBe(false); await at.recordResolution('nba', 'A', 'hit'); row = mockState.rows.get('nba|A|all_time'); expect(row.baseline_locked).toBe(true); expect(row.baseline_hit_rate).toBe(1.0); }); test('only locks the all_time period', async () => { for (let i = 0; i < 100; i += 1) await at.recordResolution('nba', 'A', 'hit'); expect(mockState.rows.get('nba|A|all_time').baseline_locked).toBe(true); expect(mockState.rows.get('nba|A|last_30d').baseline_locked).toBe(false); }); test('pushes do not contribute to baseline trigger', async () => { for (let i = 0; i < 99; i += 1) await at.recordResolution('nba', 'A', 'hit'); await at.recordResolution('nba', 'A', 'push'); expect(mockState.rows.get('nba|A|all_time').baseline_locked).toBe(false); }); }); describe('accuracyTracker.getAccuracy', () => { test('reports expected hit rate by grade', async () => { const r = await at.getAccuracy('nba', 'A+', 'all_time'); expect(r.expected).toBe(0.65); }); test('returns deltas relative to baseline once locked', async () => { for (let i = 0; i < 80; i += 1) await at.recordResolution('nba', 'A', 'hit'); for (let i = 0; i < 20; i += 1) await at.recordResolution('nba', 'A', 'miss'); const r = await at.getAccuracy('nba', 'A', 'all_time'); expect(r.locked).toBe(true); expect(r.baseline).toBeCloseTo(0.8, 5); expect(r.delta).toBe(0); }); }); describe('accuracyTracker.getAllAccuracy', () => { test('returns one entry per known grade tier', async () => { const all = await at.getAllAccuracy('nba'); const tiers = all.map((r) => r.grade); expect(tiers).toContain('A+'); expect(tiers).toContain('F'); expect(all.length).toBe(Object.keys(at.EXPECTED_HIT_RATES).length); }); });