const engine1 = require('../../src/services/intelligence/engine1'); function withProp(features = {}, trap = {}, consistency = {}, prop = { line: 25.5, direction: 'over' }) { return engine1.gradeProp({ features, trap, consistency, prop }); } describe('engine1.gradeProp', () => { test('hot recent form + weak D + home + rested → A-tier', () => { const result = withProp( { l5_avg: 32, l20_avg: 27, opp_rank_stat: 0.85, home_away: 1.0, rest_days: 2 }, { composite: 0.1 }, { consistency: 'reliable', score: 0.7 }, ); expect(['A+', 'A', 'A-']).toContain(result.grade); expect(result.top_factors.length).toBeGreaterThan(0); }); test('cold L5 + strong opponent D + back-to-back → D/F tier', () => { const result = withProp( { l5_avg: 18, l20_avg: 21, opp_rank_stat: 0.1, home_away: 0.0, rest_days: 0 }, { composite: 0.6 }, { consistency: 'boom_bust', score: 0.1 }, ); expect(['F', 'D', 'C-']).toContain(result.grade); }); test('high trap composite downgrades a hot prop to C-tier', () => { const hotNoTrap = withProp( { l5_avg: 30, l20_avg: 26, opp_rank_stat: 0.8, home_away: 1.0, rest_days: 2 }, { composite: 0.1 }, { consistency: 'reliable' }, ); const hotWithTrap = withProp( { l5_avg: 30, l20_avg: 26, opp_rank_stat: 0.8, home_away: 1.0, rest_days: 2 }, { composite: 0.7 }, { consistency: 'reliable' }, ); // The trap version should land at least one step lower on the scale. const idxHigh = engine1.GRADE_SCALE.indexOf(hotNoTrap.grade); const idxLow = engine1.GRADE_SCALE.indexOf(hotWithTrap.grade); expect(idxLow).toBeLessThan(idxHigh); }); test('UNDER direction inverts the L5 polarity', () => { // Player averaging 18 with line 25.5 over → cold for OVER. const over = withProp( { l5_avg: 18, l20_avg: 19 }, { composite: 0.1 }, { consistency: 'reliable' }, { line: 25.5, direction: 'over' }, ); const under = withProp( { l5_avg: 18, l20_avg: 19 }, { composite: 0.1 }, { consistency: 'reliable' }, { line: 25.5, direction: 'under' }, ); const overIdx = engine1.GRADE_SCALE.indexOf(over.grade); const underIdx = engine1.GRADE_SCALE.indexOf(under.grade); expect(underIdx).toBeGreaterThan(overIdx); }); test('returns confidence proportional to grade', () => { const a = withProp({ l5_avg: 32, l20_avg: 27, opp_rank_stat: 0.85, home_away: 1.0, rest_days: 2 }, { composite: 0.05 }, { consistency: 'elite', score: 1.0 }); const c = withProp({}, {}, {}); expect(a.confidence).toBeGreaterThan(c.confidence); }); test('with no features at all, returns neutral C', () => { const result = withProp({}, {}, {}); expect(result.grade).toBe('C'); expect(result.top_factors).toEqual([]); }); test('all_factors lists every contributing factor, top_factors slices to 3', () => { const result = withProp( { l5_avg: 32, l20_avg: 27, opp_rank_stat: 0.85, home_away: 1.0, rest_days: 2, coach_pace_delta: 1.2 }, { composite: 0.1 }, { consistency: 'elite', score: 1.0 }, ); expect(result.top_factors.length).toBeLessThanOrEqual(3); expect(result.all_factors.length).toBeGreaterThanOrEqual(result.top_factors.length); }); }); describe('GRADE_SCALE invariants', () => { test('11 grades in ascending order F → A+', () => { expect(engine1.GRADE_SCALE).toEqual(['F', 'D', 'C-', 'C', 'C+', 'B-', 'B', 'B+', 'A-', 'A', 'A+']); }); test('confidence values strictly ascending', () => { const conf = engine1.GRADE_SCALE.map((g) => engine1.GRADE_TO_CONFIDENCE[g]); for (let i = 1; i < conf.length; i += 1) { expect(conf[i]).toBeGreaterThan(conf[i - 1]); } }); });