Files

99 lines
3.7 KiB
JavaScript

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]);
}
});
});