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:
@@ -0,0 +1,73 @@
|
||||
# SPEC — Fix the grade-board sort (display ordering only)
|
||||
|
||||
**Status:** built 2026-07-29. Report-first found three premise breaks — see §2.
|
||||
**Scope:** display ORDERING only. No grade, ledger row, lock_line, scoring, model,
|
||||
or edge_pct scale/display change. Push scoring untouched.
|
||||
|
||||
## 1. The two defects (wrong at ANY scale, independent of edge_pct's retirement)
|
||||
|
||||
`web/src/lib/slateAdapter.js selectTopGrades` ranked on
|
||||
`edge: Math.abs(numOr(g.edge, -Infinity))`:
|
||||
|
||||
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**. Taking `|edge|` ranked the model's strongest
|
||||
DISAGREEMENTS equal to its strongest agreements. 177 public ledger rows carry a
|
||||
negative edge (58 B / 118 C / 1 F, worst −86.7).
|
||||
2. **`Math.abs(-Infinity) === Infinity`** → a row with NO edge sorted **FIRST**.
|
||||
Absent data presented as the top pick — the `Number(null)` fabrication class.
|
||||
|
||||
`src/services/intelligence/analyzeViaEngine1.js:506` sorted the Desk alt-line
|
||||
ladder by `edge_pct` desc — the same price-free 0.5-line artifact.
|
||||
|
||||
## 2. REPORT-FIRST — three premise breaks found before building
|
||||
|
||||
- **B1. Site 1's board is fed by a 404.** `/api/props/top-graded` **does not exist**
|
||||
in `src/` (only three axios *callers* reference it) and returns **404 in prod**.
|
||||
The Next proxy catches → `{props: []}` → `topGrades = []` → `selectTopGrades([])`
|
||||
→ the dashboard renders `proofMode` (yesterday's receipts) or honest empty copy.
|
||||
**The edge sort orders nothing on that surface today.** CORRECTION to the prior
|
||||
order: its "97.3% of rows tie → the edge key decides the board" was a LEDGER
|
||||
population measurement extrapolated to this board; the board has no rows to order.
|
||||
The fix is still correct-in-itself and lands the moment the feed is restored.
|
||||
- **B2. p_win CANNOT be the client-side sort key.** `src/utils/snapshotGating.js`
|
||||
(Session 67) strips `p_win`/`ev_pct`/`model_odds`/`value`/`takeable` for
|
||||
unentitled tiers, with the explicit rationale *"shipping p_win is shipping the
|
||||
price in a different base."* `selectTopGrades` runs in the BROWSER. Ranking there
|
||||
by p_win for all tiers would REVERSE that gate. Implemented instead:
|
||||
**p_win is used WHEN THE ROW CARRIES IT** (entitled tiers / server callers),
|
||||
takeable-gated identically to the hero; unentitled tiers fall through to the
|
||||
signed edge uniformly. Scales are NEVER mixed in one comparator.
|
||||
- **B3. The ladder cannot take the takeable gate, and p_win order there is
|
||||
ANALYTICALLY the line order.** Ladder rungs carry **no per-rung price** (books
|
||||
price each line differently; we do not fetch them), and `isTakeable` is a
|
||||
property of price alone → inapplicable per rung. And `P(stat ≥ k)` is monotone
|
||||
non-increasing in `k`, so **p_win-desc ≡ line-asc for an over, line-desc for an
|
||||
under**. Implemented as the direction-aware line order = exactly p_win-desc, at
|
||||
zero added compute. (`deskShowcaseService.rungsOf:40` already re-sorted rungs by
|
||||
line, discarding the edge order — so only `GradeResultCard` ever showed it.)
|
||||
|
||||
## 3. What ships
|
||||
|
||||
- `selectTopGrades`: `grade → confidence → takeable-gated p_win (nulls last) →
|
||||
SIGNED edge (nulls last) → input order`. No `abs()`. Missing signal ranks LAST,
|
||||
rows are never dropped.
|
||||
- Alt-line ladder: ordered by highest p_win first, computed via the monotonic
|
||||
line equivalence (direction-aware). No `edge_pct` in the sort.
|
||||
- The takeable band is `web/src/lib/valueState.isTakeable` (−160..+200), which
|
||||
mirrors `src/config/valueEngine.isTakeable` — the hero's exact definition.
|
||||
|
||||
## 4. Acceptance criteria
|
||||
|
||||
1. A disagreement (negative-edge) prop does NOT outrank an agreement at equal
|
||||
grade+confidence.
|
||||
2. A missing-signal row sorts LAST and is still PRESENT.
|
||||
3. A takeable p_win row outranks a higher-p_win UNTAKEABLE (chalk) row.
|
||||
4. p_win and edge are never compared against each other.
|
||||
5. Ladder rungs are ordered highest-p_win-first for both over and under.
|
||||
6. No grade/ledger/lock_line/scoring write. Full suite green, web build exit 0.
|
||||
|
||||
## 5. Held (NOT this order)
|
||||
|
||||
edge_pct rescale or display retirement (Order B) · building the missing
|
||||
`/api/props/top-graded` server selector · exposing p_win to unentitled tiers.
|
||||
Reference in New Issue
Block a user