Grade-board sort: signed signal, takeable-gated p_win, missing sorts LAST
Display ORDERING only. No grade, ledger row, lock_line, scoring, or edge_pct
scale/display change. Push scoring untouched.
Two defects removed from selectTopGrades (wrong at ANY scale, independent of
edge_pct's separate retirement):
1. edge: Math.abs(numOr(g.edge, -Infinity)) — abs() on an already-
direction-signed value ranked the model's strongest DISAGREEMENTS level
with its strongest agreements (177 public ledger rows carry a negative
edge; positive = the model AGREES with the graded side).
2. Math.abs(-Infinity) === Infinity, so a row with NO edge sorted FIRST —
absent data presented as the top pick (the Number(null) class).
New key: grade -> confidence -> takeable-gated p_win (nulls LAST) -> SIGNED
edge (nulls LAST) -> input order. Scales are never mixed in one comparator.
Takeable band = web valueState.isTakeable, asserted byte-equal to the hero's
config/valueEngine.isTakeable (-160..+200) incl. strict-null.
Alt-line ladder (analyzeViaEngine1:506) no longer sorts by edge_pct: ordered
highest-p_win-first derived analytically at zero added compute — P(stat >= k)
is monotone non-increasing in k, so p_win-desc is line-ASC for an over and
line-DESC for an under. base stays marked; no consumer depends on
alt_lines[0]; deskShowcaseService.rungsOf already re-sorted by line.
THREE PREMISE BREAKS found report-first, before code:
- /api/props/top-graded 404s in prod (absent from src/) so the dashboard
board renders receipts/empty — the edge sort orders nothing there today.
The prior order's "97.3% of rows tie" was a LEDGER measurement wrongly
extrapolated to that board. Fix is correct-in-itself and lands when the
feed is restored.
- p_win cannot be a client-side key for all tiers: snapshotGating strips it
for unentitled tiers ("shipping p_win is shipping the model price").
Verified live: prod /api/snapshot carries p_win on 0/8 MLB, 0/25 WNBA.
- Ladder rungs carry no per-rung price, so the takeable gate is inapplicable.
Verified on real data, both sports, both paths: unentitled — WNBA (n=25)
ordering CHANGED, MLB (n=8) unchanged, signed edge non-increasing in every
(grade,confidence) tie group (20 pairs, 0 violations); entitled — 40 real
ledger rows with p_win+locked_odds, p_win-descending, untakeable chalk NOT
promoted (Trea Turner .757 @-275 does not beat Rhyne Howard .745 @-120)
(36 pairs, 0 violations).
Hero consistency, stated honestly: same signal + same gate, different
precedence BY CONTRACT (board = grade-tier-first "top GRADES"; hero =
p_win-first "top read"). Identical within the leading tier (verified); across
tiers the board may lead with an A the hero doesn't pick. Not a contradiction.
Floor: 310 suites / 3864 tests green, web build exit 0. Dashboard + Desk
visuals are auth/feed-gated -> tagged for the Chrome audit, no visual faked.
Held: edge_pct rescale/display retirement (Order B); building the missing
/api/props/top-graded selector; exposing p_win to unentitled tiers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
@@ -454,10 +454,62 @@ const numOr = (v, fallback) => {
|
||||
return Number.isFinite(n) ? n : fallback;
|
||||
};
|
||||
|
||||
/** Strict numeric read — `Number(null) === 0` is the recurring fabrication bug. */
|
||||
const strictNum = (v) => (v == null || v === '' ? null : (Number.isFinite(Number(v)) ? Number(v) : null));
|
||||
|
||||
/**
|
||||
* The takeable-gated champion probability for one grade row, or null.
|
||||
*
|
||||
* Gate = `valueState.isTakeable` (−160..+200), which mirrors
|
||||
* `src/config/valueEngine.isTakeable` — the IDENTICAL band the hero ranks on
|
||||
* (`heroPropService` HERO RULE v3), so the two ranking surfaces agree. The gate
|
||||
* is mandatory: raw p_win crowns −300 chalk, which is not the product.
|
||||
*
|
||||
* p_win is ABSENT BY DESIGN for unentitled tiers — `utils/snapshotGating`
|
||||
* strips it on the way out because "shipping p_win is shipping the model price
|
||||
* in a different base" (Session 67). Those callers legitimately get null here
|
||||
* and fall through to the signed edge.
|
||||
*/
|
||||
function takeablePWin(g) {
|
||||
const { isTakeable } = require('./valueState');
|
||||
const p = strictNum(g && g.p_win);
|
||||
if (p == null) return null;
|
||||
const price = strictNum(g && g.book_odds) ?? strictNum(g && g.gradedAt && g.gradedAt.odds);
|
||||
if (price == null || !isTakeable(price)) return null;
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Descending comparator that always sorts a null signal LAST (never first). */
|
||||
function descNullsLast(a, b) {
|
||||
if (a == null && b == null) return 0;
|
||||
if (a == null) return 1;
|
||||
if (b == null) return -1;
|
||||
return b - a;
|
||||
}
|
||||
|
||||
/**
|
||||
* #13 — rank tonight's grades by TIER, then the VARYING signal so a leaderboard
|
||||
* of near-identical rows stops being noise: confidence desc, then |edge| desc,
|
||||
* stable by input order. Drops gradeless rows. Returns at most `limit`.
|
||||
* of near-identical rows stops being noise. Drops gradeless rows. Returns at
|
||||
* most `limit`.
|
||||
*
|
||||
* SORT FIX (2026-07-29, `specs/grade-board-sort.md`). The old key was
|
||||
* `edge: Math.abs(numOr(g.edge, -Infinity))`, carrying two defects that were
|
||||
* wrong at ANY scale — independent of edge_pct's separate retirement:
|
||||
*
|
||||
* 1. abs() ON AN ALREADY-SIGNED VALUE. `edge` is signed BY DIRECTION upstream
|
||||
* (`edgePctFor`: over ? proj−line : line−proj), so POSITIVE = the model
|
||||
* AGREES with the graded side. Ranking |edge| put the model's strongest
|
||||
* DISAGREEMENTS level with its strongest agreements — the board promoted
|
||||
* reads the model contradicts (177 public ledger rows carry a negative edge).
|
||||
* 2. `Math.abs(-Infinity) === Infinity`, so a row with NO edge sorted FIRST.
|
||||
* Absent data presented as the top pick — the `Number(null)` class again.
|
||||
*
|
||||
* Now: takeable-gated champion p_win first (so this board and the hero agree),
|
||||
* then the SIGNED edge, each descending with MISSING SORTING LAST. Rows are
|
||||
* never dropped for a missing signal — they rank at the bottom.
|
||||
*
|
||||
* SCALES ARE NEVER MIXED: p_win (0..1) is compared only against p_win, edge (%)
|
||||
* only against edge. Comparing 0.62 against 62 is not a comparison.
|
||||
*/
|
||||
function selectTopGrades(grades, limit = 10) {
|
||||
const arr = (Array.isArray(grades) ? grades : []).filter((g) => g && g.grade);
|
||||
@@ -466,9 +518,15 @@ function selectTopGrades(grades, limit = 10) {
|
||||
idx,
|
||||
rank: gradeRankOf(g.grade),
|
||||
conf: numOr(g.confidence, -1),
|
||||
edge: Math.abs(numOr(g.edge, -Infinity)),
|
||||
pWin: takeablePWin(g),
|
||||
// SIGNED — no abs(). null (absent) sorts last, not first.
|
||||
edge: strictNum(g.edge),
|
||||
}));
|
||||
scored.sort((a, b) => a.rank - b.rank || b.conf - a.conf || b.edge - a.edge || a.idx - b.idx);
|
||||
scored.sort((a, b) => a.rank - b.rank
|
||||
|| b.conf - a.conf
|
||||
|| descNullsLast(a.pWin, b.pWin)
|
||||
|| descNullsLast(a.edge, b.edge)
|
||||
|| a.idx - b.idx);
|
||||
return scored.slice(0, Math.max(0, limit)).map((s) => s.g);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user