Hero ranking fix: rank by champion p_win among takeable, honest empty state

Review Zero found the hero's ACTUAL behavior was worse than "unknown": it ranks
on ev_pct (heroPropService v2), but ev_pct is NULL on served grades and
Number(null)===0 made Number.isFinite(Number(null)) TRUE — so every prop tied at
EV 0 and the "top read" was really the FIRST takeable A/B prop in cache order
(arbitrary, dressed as ranked).

v3: rank by the CHAMPION's p_win (the only signal with a promising, not proven,
edge — its takeable-MLB-over CLV survived the skew audit) among A/B, TAKEABLE-
priced reads (isTakeable band -160..+200, same as the proof/audit). Strict
null guard kills the Number(null)=0 bug. Takeable filter is mandatory (raw p_win
crowns -300 chalk). NO backfill: nothing qualifies → honest empty state
(available:false, reason:'no_qualifying_read'), never a weak recent read.

p_win is RANKING-ONLY, server-side — toHero never exposes it and the route strips
it. Framing unchanged in substance (model number vs book number, grade,
timestamp) — no proven-edge / +EV / best-bet claim, no CLV/ROI/edge number.
Display-only: reads snapshot caches, writes to nothing (no grade/ledger/lock_lines).
Full suite 3852 green, web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
This commit is contained in:
Kev
2026-07-29 04:43:21 -04:00
parent e7ec501054
commit 41b86e3874
3 changed files with 72 additions and 57 deletions
+27 -21
View File
@@ -74,35 +74,41 @@ async function pickHeroProp(deps = {}) {
}
}
// HERO RULE v2 (Model Train, step 5): the highest EV among reads that pass the
// TAKEABLE gate, A/B grades only. A huge model-vs-line gap on a -900 line is
// trivia; the hero is the best OPPORTUNITY at a price you'd actually take.
// HERO RULE v3 (hero-ranking-fix, 2026-07-29). Rank by the CHAMPION's p_win among
// TAKEABLE-priced A/B reads — the highest-confidence read at a price you'd take.
//
// - Why not ev_pct (the v2 key): ev_pct is NULL on served grades, and
// `Number(null) === 0` made `Number.isFinite(Number(g.ev_pct))` TRUE, so every
// prop tied at EV 0 and the "top read" was really the FIRST takeable A/B prop in
// cache order — arbitrary, dressed as ranked. The `num()` guard below kills that.
// - p_win is the only signal with a *promising* (not proven) edge (the champion's
// takeable-MLB-over CLV survived the skew audit). It is used for RANKING ONLY,
// server-side; it is never exposed (toHero omits it; the route strips it too).
// - TAKEABLE filter is MANDATORY: raw p_win crowns -300 chalk, which is not the
// product. Floor = the project's `isTakeable` band (-160..+200), same definition
// the takeable-edge proof/audit used.
// - NO BACKFILL: nothing qualifies → honest empty state, never a weak recent read.
const { isTakeable } = require('../config/valueEngine');
let hero = null, heroEv = -Infinity;
// Strict null: `Number(null) === 0` is the exact fabrication this hero fell to.
const num = (v) => (v == null || v === '' ? null : (Number.isFinite(Number(v)) ? Number(v) : null));
let hero = null; let heroP = -Infinity;
for (const { g, sport } of all) {
if (!isAB(g.grade) || !candidate(g)) continue;
if (!Number.isFinite(Number(g.ev_pct))) continue; // need a real EV to rank
if (!isTakeable(g.book_odds)) continue; // promoted surface → takeable only
if (Number(g.ev_pct) > heroEv) { heroEv = Number(g.ev_pct); hero = { g, sport }; }
const p = num(g.p_win);
if (p == null) continue; // no champion p_win → cannot rank
const price = num(g.book_odds) ?? num(g.gradedAt && g.gradedAt.odds);
if (price == null || !isTakeable(price)) continue; // takeable price only — excludes chalk
if (p > heroP) { heroP = p; hero = { g, sport }; }
}
if (hero) {
const gap = candidate(hero.g) ? Math.abs(Number(hero.g.projection) - Number(hero.g.line)) : null;
const gap = Math.abs(Number(hero.g.projection) - Number(hero.g.line));
return toHero(hero.g, hero.sport, gap, false);
}
// Empty slate → the MOST RECENT real graded read (any grade), by timestamp.
let recent = null, recentTs = '';
for (const { g, sport } of all) {
const ts = (g.gradedAt && g.gradedAt.timestamp) || '';
if (ts && ts > recentTs) { recentTs = ts; recent = { g, sport }; }
}
if (recent) {
const gp = candidate(recent.g) ? Math.abs(Number(recent.g.projection) - Number(recent.g.line)) : null;
return toHero(recent.g, recent.sport, gp, true);
}
// Truly nothing cached — the card hides. Never a fabricated fallback.
return { available: false };
// HONEST EMPTY STATE — no takeable high-p_win read qualifies (or the slate is empty).
// Deliberately NO recent-read backfill: a weak read dressed as today's hero is exactly
// the dishonesty this order removes. The card self-hides.
return { available: false, reason: 'no_qualifying_read' };
}
module.exports = { pickHeroProp, __internals: { isAB, candidate, DEFAULT_SPORTS } };