'use strict'; // Hero rule v3 (hero-ranking-fix, 2026-07-29): rank by the CHAMPION's p_win among // TAKEABLE-priced A/B reads. No ev_pct (NULL on served grades). No backfill — nothing // qualifies → honest empty state. const { pickHeroProp } = require('../../src/services/heroPropService'); function cacheFrom(map) { return async (key) => (key in map ? map[key] : null); } const grade = (o) => ({ player_name: o.player, stat_type: o.stat || 'hits', line: o.line ?? 1.5, projection: o.proj ?? 2.0, direction: o.dir || 'over', grade: o.grade, book: o.book || 'dk', p_win: o.pwin, ev_pct: o.ev ?? null, book_odds: o.odds ?? -120, value: o.value ?? null, fair_odds: o.fair ?? null, model_odds: o.model ?? null, gradedAt: { line: o.line ?? 1.5, odds: o.odds ?? -120, timestamp: o.ts || '2026-07-17T19:00:00Z' }, }); describe('pickHeroProp — v3 (champion p_win among takeable A/B)', () => { test('picks the HIGHEST p_win among takeable A/B reads', async () => { const cacheGet = cacheFrom({ 'snapshot:mlb:latest': { grades: [ grade({ player: 'LowP', grade: 'A', pwin: 0.55, odds: -120 }), grade({ player: 'HighP', grade: 'B', pwin: 0.71, odds: 110 }), ] } }); const hero = await pickHeroProp({ cacheGet, sports: ['mlb'] }); expect(hero.available).toBe(true); expect(hero.player).toBe('HighP'); expect(hero.p_win).toBeUndefined(); // p_win is RANKING-ONLY, never exposed on the hero }); test('un-takeable chalk is EXCLUDED even with the highest p_win (raw p_win crowns chalk)', async () => { const cacheGet = cacheFrom({ 'snapshot:mlb:latest': { grades: [ grade({ player: 'Chalk', grade: 'A', pwin: 0.90, odds: -300 }), // outside -160..+200 grade({ player: 'Takeable', grade: 'B', pwin: 0.60, odds: -130 }), ] } }); const hero = await pickHeroProp({ cacheGet, sports: ['mlb'] }); expect(hero.player).toBe('Takeable'); }); test('C/D/F grades ineligible even with high p_win', async () => { const cacheGet = cacheFrom({ 'snapshot:mlb:latest': { grades: [ grade({ player: 'HighP_C', grade: 'C', pwin: 0.80, odds: -110 }), grade({ player: 'RealA', grade: 'A', pwin: 0.60, odds: -110 }), ] } }); const hero = await pickHeroProp({ cacheGet, sports: ['mlb'] }); expect(hero.player).toBe('RealA'); }); test('Number(null)===0 GUARD: a null-p_win prop never ranks (no arbitrary hero)', async () => { // The v2 bug: Number(null)===0 → every null-EV prop tied at 0 → the first one won. const cacheGet = cacheFrom({ 'snapshot:mlb:latest': { grades: [ grade({ player: 'NullP', grade: 'A', pwin: null, odds: -120 }), // must NOT win grade({ player: 'RealP', grade: 'A', pwin: 0.58, odds: -120 }), ] } }); const hero = await pickHeroProp({ cacheGet, sports: ['mlb'] }); expect(hero.player).toBe('RealP'); // and when EVERY prop has null p_win → honest empty state, not an arbitrary pick const allNull = cacheFrom({ 'snapshot:mlb:latest': { grades: [grade({ player: 'X', grade: 'A', pwin: null, odds: -120 })] } }); expect((await pickHeroProp({ cacheGet: allNull, sports: ['mlb'] })).available).toBe(false); }); test('exposes book/fair/model odds CONTEXT on the hero (display, not ranking)', async () => { const cacheGet = cacheFrom({ 'snapshot:mlb:latest': { grades: [ grade({ player: 'Trip', grade: 'A', pwin: 0.64, odds: -145, fair: -132, model: -110 }), ] } }); const hero = await pickHeroProp({ cacheGet, sports: ['mlb'] }); expect(hero.book_odds).toBe(-145); expect(hero.fair_odds).toBe(-132); expect(hero.model_odds).toBe(-110); }); test('no takeable A/B p_win read → HONEST EMPTY STATE, never a backfilled recent read', async () => { const cacheGet = cacheFrom({ 'snapshot:mlb:latest': { grades: [ grade({ player: 'OnlyChalk', grade: 'A', pwin: 0.9, odds: -800 }), // un-takeable ] } }); const hero = await pickHeroProp({ cacheGet, sports: ['mlb'] }); expect(hero.available).toBe(false); expect(hero.reason).toBe('no_qualifying_read'); expect(hero.is_recent).toBeUndefined(); // NO recent-read backfill }); test('nothing cached → { available:false }', async () => { const hero = await pickHeroProp({ cacheGet: cacheFrom({}), sports: ['mlb'] }); expect(hero.available).toBe(false); }); });