'use strict'; // MLB grade-degradation regression locks (2026-07-17). Three bugs the phone // audit surfaced, fixed in engine1 + analyzeViaEngine1. See // specs/audit-data/mlb-grade-degradation.md. const engine1 = require('../../src/services/intelligence/engine1'); const { __internals } = require('../../src/services/intelligence/analyzeViaEngine1'); const { projectionFor, edgePctFor, insufficientDataResult } = __internals; const gradeAdapter = require('../../src/utils/gradeAdapter'); const { fourLetterGrade, legacyConfidence } = gradeAdapter.__internals; const BANDS = require('../../src/services/python/data/grade_thresholds.json').grade_scale; // The canonical threshold table: displayed 0-100 confidence -> 11-step letter. function thresholdTable(conf0to100) { const p = conf0to100 / 100; for (const [letter, band] of Object.entries(BANDS)) { if (p >= band.low && p <= band.high) return letter; } return null; } describe('FIX #1 — projection <= 0 REFUSES (a grade is never emitted with no projection)', () => { test('a zero l5_avg is not a projection -> null (falls through to nothing)', () => { expect(projectionFor({ l5_avg: 0 }, { stat_type: 'home_runs' })).toBeNull(); }); test('a negative reference is not a projection -> null', () => { expect(projectionFor({ l5_avg: -1.2 }, { stat_type: 'hits' })).toBeNull(); }); test('a zero PRIMARY reference falls through to the next POSITIVE one', () => { // l5 degenerate 0, but l20 is real -> use l20 (player still has a projection) expect(projectionFor({ l5_avg: 0, l20_avg: 1.2 }, { stat_type: 'total_bases' })).toBe(1.2); }); test('all references zero/absent -> null -> the read refuses', () => { expect(projectionFor({ l5_avg: 0, l20_avg: 0 }, { stat_type: 'hits' })).toBeNull(); expect(projectionFor({}, { stat_type: 'hits' })).toBeNull(); }); test('a real positive projection is preserved', () => { expect(projectionFor({ l5_avg: 1.4 }, { stat_type: 'total_bases' })).toBe(1.4); }); test('the refusal shape carries grade=null, projection=null, insufficient_data', () => { const r = insufficientDataResult({ player: 'X', stat_type: 'home_runs', line: 0.5 }, []); expect(r.grade).toBeNull(); expect(r.projection).toBeNull(); expect(r.insufficient_data).toBe(true); }); }); describe('FIX #2 — edge_pct is (model - line) / line, never the projection=0 degeneracy', () => { test('a proj=0 under no longer yields a fabricated +100% (ref nulled upstream)', () => { // projectionFor now nulls a 0 reference, so edgePctFor sees ref=null -> 0 expect(edgePctFor({ l5_avg: 0 }, { line: 0.5, direction: 'under', stat_type: 'home_runs' })).toBe(0); }); test('a real over edge = (proj - line) / line * 100', () => { // proj 0.6 vs line 0.5 over -> (0.6-0.5)/0.5 = 20% expect(edgePctFor({ l5_avg: 0.6 }, { line: 0.5, direction: 'over', stat_type: 'home_runs' })).toBe(20); }); test('a real under edge flips sign', () => { // proj 6 vs line 6.5 under -> (6.5-6)/6.5 ~ 7.7% expect(edgePctFor({ l5_avg: 6 }, { line: 6.5, direction: 'under', stat_type: 'strikeouts' })).toBeCloseTo(7.7, 1); }); test('a passed ref is used verbatim (edge and persisted projection cannot diverge)', () => { expect(edgePctFor({ l5_avg: 999 }, { line: 0.5, direction: 'over' }, 0.6)).toBe(20); }); test('edges over a plausible slate are NOT quantized to a degenerate cluster', () => { // varied real projections + lines -> a spread of values, none the fake 100 const slate = [ { f: { l5_avg: 1.4 }, line: 1.5, dir: 'under' }, { f: { l5_avg: 6.9 }, line: 6.5, dir: 'over' }, { f: { l5_avg: 2.1 }, line: 2.5, dir: 'under' }, { f: { l5_avg: 0.7 }, line: 0.5, dir: 'over' }, ]; const edges = slate.map((s) => edgePctFor(s.f, { line: s.line, direction: s.dir })); expect(edges).not.toContain(100); expect(new Set(edges).size).toBeGreaterThan(1); // not one repeated value }); }); describe('FIX #3 — letter == threshold_table(displayed_confidence) for EVERY grade', () => { test('each 11-step grade’s confidence resolves back to the same letter', () => { for (const g of engine1.GRADE_SCALE) { const displayedConf = legacyConfidence(engine1.GRADE_TO_CONFIDENCE[g]); // 0-100 const resolved = thresholdTable(displayedConf); // 11-step expect(resolved).toBe(g); // the midpoint lands squarely back in the grade's own band } }); test('the 4-letter collapse also agrees (what the leaderboard shows)', () => { for (const g of engine1.GRADE_SCALE) { const displayedConf = legacyConfidence(engine1.GRADE_TO_CONFIDENCE[g]); const resolvedLetter = fourLetterGrade(thresholdTable(displayedConf)); expect(resolvedLetter).toBe(fourLetterGrade(g)); } }); test('confidence values stay 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]); }); });