/** * NOTE ON `engine_grade` (2026-08-07 grade cutover). * * The user-facing `grade` now derives from `p_win`, not engine1's additive * factor index -- that index carried 0.16x the information of p_win on 3,417 * settled props and its A hit worse than its F. The engine's OWN decision * (graded vs suppressed) is preserved as `engine_grade`, so behaviour * assertions read that; suppression assertions still read `grade`, since a * suppressed prop has no letter either way. */ 'use strict'; // Model Train — value engine: takeable gate, value flag, and the end-to-end // wiring in analyzeViaEngine1 (de-vig + EV + triplet + flags). const ve = require('../../src/config/valueEngine'); describe('valueEngine config (steps 3-4)', () => { test('takeable band is -160..+200 by default', () => { expect(ve.isTakeable(-110)).toBe(true); expect(ve.isTakeable(-160)).toBe(true); // ceiling inclusive expect(ve.isTakeable(+200)).toBe(true); // max inclusive expect(ve.isTakeable(-200)).toBe(false); // too chalky expect(ve.isTakeable(+250)).toBe(false); // too long expect(ve.isTakeable(null)).toBe(false); }); test('value = takeable AND ev above threshold', () => { expect(ve.isValue(-120, 5)).toBe(true); // takeable + 5% EV expect(ve.isValue(-120, 1)).toBe(false); // takeable but EV below 2% expect(ve.isValue(-900, 20)).toBe(false); // huge EV but not takeable }); }); // ── analyzeViaEngine1 value fields ────────────────────────────────────────── const mockCompute = { current: null }; jest.mock('../../src/services/intelligence/computeFeatures', () => ({ computeFeaturesForProp: async () => mockCompute.current, })); jest.mock('../../src/services/intelligence/engine1', () => ({ gradeProp: () => ({ grade: 'B', confidence: 0.55, top_factors: [], all_factors: [] }), })); const mockPOver = { current: 0.6 }; jest.mock('../../src/services/intelligence/probabilityEstimator', () => ({ estimateProbability: () => ({ p_over: mockPOver.current }), })); const { analyzeViaEngine1 } = require('../../src/services/intelligence/analyzeViaEngine1'); beforeEach(() => { mockCompute.current = { features: { l5_avg: 1.4, l20_avg: 1.3 }, trap: {}, consistency: { consistency: 'reliable', score: 0.7 }, prop: { line: 0.5, direction: 'over' }, meta: { sport: 'mlb', gameLogs: [{ hits: 1 }], errors: [] }, }; mockPOver.current = 0.6; // model P(over) = 60% }); describe('analyzeViaEngine1 — de-vig + EV + triplet (steps 1,2,6)', () => { test('computes the value triplet, EV, and flags from both-sided odds', async () => { // book: over -130 / under +110. model P(over) 0.60. const out = await analyzeViaEngine1({ player: 'X', stat_type: 'hits', line: 0.5, direction: 'over', over_odds: -130, under_odds: 110, }); expect(out.engine_grade).toBe('B'); // still a real read expect(out.book_odds).toBe(-130); // book price expect(typeof out.fair_odds).toBe('number'); // de-vigged fair price present (both sides) expect(out.model_odds).toBe(-150); // impliedProbToAmerican(0.60) expect(out.p_win).toBe(0.6); // EV at -130 with p 0.60: 0.60*(1+100/130) - 1 = +6.15% expect(out.ev_pct).toBeGreaterThan(5); expect(out.takeable).toBe(true); // -130 in band expect(out.value).toBe(true); // takeable + EV > 2% expect(out.devig_method).toBe('multiplicative'); }); test('one-sided odds → fair UNAVAILABLE, but book price + EV still ship', async () => { const out = await analyzeViaEngine1({ player: 'X', stat_type: 'hits', line: 0.5, direction: 'over', over_odds: -130, // no under_odds }); expect(out.book_odds).toBe(-130); expect(out.fair_odds).toBeUndefined(); // never faked from one side expect(out.ev_pct).toBeGreaterThan(0); // EV from model + actual price expect(out.takeable).toBe(true); }); test('a chalky price is not takeable → not value even at positive EV', async () => { mockPOver.current = 0.92; const out = await analyzeViaEngine1({ player: 'X', stat_type: 'hits', line: 0.5, direction: 'over', over_odds: -600, under_odds: 400, }); // -600 is past the JUICE_ODDS_FLOOR (-400) → refused before we even get here expect(out.grade).toBeNull(); expect(out.suppressed_reason).toBe('juiced_no_edge'); }); });