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:
+142
-2
@@ -110,6 +110,146 @@
|
||||
> hero is Kelsey Mitchell (WNBA A), the max-p_win read ACROSS sports, and untakeable chalk
|
||||
> (Yainer Diaz −200, Altuve C) is excluded → new code confirmed serving. Visual is auth-gated
|
||||
> (landing hero public, dashboard not) — data fingerprint used, `cf-cache-status: DYNAMIC`.
|
||||
> ## ✅ PUSH-SCORING PREMISE VERIFY 2026-07-29 (report-only): **RULING HOLDS — row closed**
|
||||
> Tested whether the standing "push scoring is correct — do not touch" ruling still rests on a
|
||||
> true premise, or whether whole-number lines had entered the feed since it was made (which
|
||||
> would make 0 pushes a real mis-scoring bug the ruling was shielding). **The premise HOLDS.**
|
||||
> 0.1 basis confirmed verbatim (`VYNDR-CANONICAL-STATE.md:69`): "*no `push`* (pushes structurally
|
||||
> impossible: half-number lines)". **Phase 1 — the feed is STILL 100% half-numbers, on 4
|
||||
> independent populations, per sport AND per market (no blend): `closing_captures` **117,970
|
||||
> priced market lines → 0 whole** (5 books incl. `sharp`/pinnacle, 16 sport/stat groups, Jul 20–29
|
||||
> continuous); `model_snapshots` 6,050 → 0 (incl. grader refusals, so not survivorship);
|
||||
> `ledger_entries` 1,141 public → 0 (11 markets: MLB hits/doubles/TB/SB/ER/HR/outs, WNBA pts/reb/
|
||||
> ast/3s — every min AND max ends in `.5`); `lock_lines` 173 → 0 (TODAY's lock, freshest feed).
|
||||
> **Phase 2** — push branch is `outcomeService.js:151` `if (a === l) return 'push'` **after**
|
||||
> `Number()` + `Number.isFinite` guards on both operands → sound numeric compare, NOT the
|
||||
> `Number(null)===0` string-vs-number class. It is the **single scoring chokepoint** (`ledgerService.js:31`
|
||||
> imports `settleResult`; no parallel scorer derives hit/miss anywhere in `src/`), it is **unit-tested
|
||||
> live** (`outcomeService.test.js:38` `settleResult('over',2,2)==='push'`, `nbaSettlement.test.js:104`),
|
||||
> and both `ledger_entries.outcome` + `outcomes.result` CHECK constraints **include `'push'`** → a real
|
||||
> push would score, write, and persist. **Phase 2.6 / the decisive number: 0 exact ties in 992 settled
|
||||
> rows, 0 fractional actuals, and the SMALLEST actual-vs-line gap across all 992 rows is 0.5** — the
|
||||
> arithmetic minimum between an integer result and a half-number line. **VERDICT: RULING HOLDS.**
|
||||
> Expected push rate is **exactly 0 (P=0), not "low" — 0/992 is FORCED, not chance**, so the
|
||||
> "implausible" flag is retired: it mistook an arithmetic impossibility for a suspicious absence.
|
||||
> The open-items row is CLOSED honestly. Nothing was modified (no scoring, settlement, re-settle,
|
||||
> or backfill). NOTE the stale n: the flag said 470 settled; it is now **1,097 settled** (593 hit /
|
||||
> 399 miss / 105 void / 44 unsettled-today). **No latent bug either** — the branch is correct and
|
||||
> covered, so if a whole-number market ever DOES enter (NFL/NHL are code-wired but out of season;
|
||||
> whole-number K props exist at some books), it scores as a push automatically. Residual risk is
|
||||
> a monitoring gap, not a scoring gap: nothing ALERTS on the first whole-number line.
|
||||
|
||||
> ## 🔬 EDGE_PCT SCALE DIAGNOSIS 2026-07-29 (report-only): **not a units bug — a mislabelled metric that DRIVES A LIVE SORT**
|
||||
> **0.1 What it is.** `analyzeViaEngine1.js:265-270 edgePctFor()`:
|
||||
> `signed = over ? (projection − line) : (line − projection)`; `Math.round((signed/line)*1000)/10`
|
||||
> → **edge_pct = ((projection − line) / line) × 100**, signed by direction, 1dp. `projection` =
|
||||
> `projectionFor` = `l5_avg ?? l20_avg ?? {stat}_per_90 ?? xg_per_90` (`:252-257`). **It is
|
||||
> INDEPENDENT of `p_win`** — so it is NOT tainted by the overconfidence that damns `ev_pct`. But it
|
||||
> takes **NO price input at all**, so it cannot express edge in the betting sense. **Verdict:
|
||||
> arithmetically correct, MISLABELLED. Honest as "% the model's projection differs from the line";
|
||||
> a lie at ANY scale as "EDGE"** — which is exactly how every surface labels it. **TWO independent
|
||||
> implementations exist** — backend `edgePctFor` AND `web/src/lib/gradeAdapter.js:25-31 computeEdge`
|
||||
> (same formula); the grade card renders the WEB one, so a backend-only fix would not reach it.
|
||||
> **0.2 The cap.** `SANE_EDGE_MAX = 40`, `deskShowcaseService.js:31`, comment: *"beyond this the
|
||||
> (model-line)/line value isn't a market edge"*. Mirrored `slateAdapter.js:613 EDGE_BOARD_SANE_MAX=40`
|
||||
> + `MobileEdgeBoard.tsx:45`. It is a self-declared plausibility bound from an earlier order, **not a
|
||||
> derived statistical bound**.
|
||||
> **0.3 Mechanism = SMALL-DENOMINATOR EXPLOSION** (not units, not inverted, not missing ×100 — the
|
||||
> ×100 is present and correct). `line` is the denominator and **562 of 655 MLB rows (86%) sit at line
|
||||
> 0.5**, where every 0.1 of projection is ±20 points. Max 620 = projection ≈3.6 on a 0.5 line. PROVEN
|
||||
> per-line: MLB 0.5 → 73.0% over cap, max 620 · MLB 1.5 → 43.8%, max 153 · WNBA 12.5 → 6.7% · **WNBA
|
||||
> 26.5 → max 1.9**. Monotone decay with line size.
|
||||
> **PHASE 1 — matrix figures RE-VERIFIED, partly stale.** Blended over cap-40 = **502/1141 = 44.0%**
|
||||
> (matrix said 51.5% — direction right, number stale). **Worst value 620 = EXACT match.** Per sport:
|
||||
> **MLB n=655** — 13.6% >100, 65.8% >50, median **60** (already 1.5× the cap), p95 180, p99 238,
|
||||
> min −86.7, max 620. **WNBA n=486 — 0% >100**, median 12, p95 49, max 77.8. **Shape: structurally
|
||||
> broken for MLB, essentially SANE for WNBA.** Not a mild calibration — and note what that means:
|
||||
> the scale is a FUNCTION OF LINE SIZE, so the metric is incomparable across markets **by
|
||||
> construction**. No rescaling fixes that; only changing what the metric IS would.
|
||||
> **PHASE 2 — the ~13-surface count is NOT confirmed. Only 3 surfaces RENDER it:**
|
||||
> (a) **`GradeResultCard.tsx:182,216,325`** — LIVE (3 importers), **auth-gated `/scan`** →
|
||||
> **TAGGED FOR THE CHROME AUDIT, no visual faked** — renders **"+620% edge" RAW and GREEN**, no cap;
|
||||
> (b) **`DeskShowcase.tsx:40`** — LIVE, **PUBLIC** `/pricing` — **already honest**, shows "—"
|
||||
> (service nulls >40); (c) **`SoccerGradeResult.tsx:229`** — uncapped, on the ORPHAN `/soccer`
|
||||
> (0 nav links, public by URL). **Dead:** `MobileEdgeBoard` (0 importers, pulled in the honesty pass),
|
||||
> `DemoScan` (0 importers). **TYPE-ONLY, never rendered:** `PropRow.tsx:45`, `GradeCard.tsx:32`,
|
||||
> `ledger/page.tsx:40`. **API-only:** `contentTemplateService.js:164` emits an uncapped "+620% edge"
|
||||
> string on public `/api/content` — **no page fetches it** (verified).
|
||||
> **🔴 THE SORT ANSWER — YES, IT DRIVES TWO LIVE SORTS.** (1) `slateAdapter.selectTopGrades:469-471`
|
||||
> sorts `grade → confidence → |edge| desc`, consumed by `dashboard/page.tsx:419` for the dashboard
|
||||
> **TOP GRADES top-10**. **MEASURED: 97.3% of rows (1110/1141) sit in a (date,sport,grade,confidence)
|
||||
> tie group of ≥2 (biggest 56)** → the |edge| key is operative for essentially the whole slate, so it
|
||||
> is the **de facto ordering** of that leaderboard. (2) `analyzeViaEngine1.js:506` sorts the Desk
|
||||
> alt-line ladder by `edge_pct` desc. **TWO SCALE-INDEPENDENT DEFECTS FOUND INSIDE THAT SORT:**
|
||||
> `edge: Math.abs(numOr(g.edge, -Infinity))` — (i) **abs()** on an already-direction-signed value ranks
|
||||
> the model's strongest DISAGREEMENTS equal to its strongest agreements (**177 ledger rows carry a
|
||||
> negative edge**: 58 B / 118 C / 1 F, most negative −86.7); (ii) `Math.abs(-Infinity) = Infinity`, so
|
||||
> a **MISSING edge sorts FIRST** — the `Number(null)` fabrication class again, in a new costume.
|
||||
> **PHASE 4 — the matrix's "nothing renders `ev_pct`" is WRONG.** `PriceTriplet.tsx:60,67,76` renders
|
||||
> `${pct(ev)} EV` and is LIVE + wired (scan → `gradeAdapter:143` → PriceTriplet). It shows nothing only
|
||||
> because `ev_pct` is NULL on served grades, so `valueState.js:121` correctly falls to **NO_MODEL
|
||||
> honest-absent**. So: **the right metric already has a live, honest render site starved of data** —
|
||||
> and the card's "EDGE" row sits exactly where a price-aware number belongs.
|
||||
> **PHASE 3 — THE FORK (reported, NOT chosen).** **FIX is not honest**: no rescaling turns a
|
||||
> price-free projection gap into an edge — you would be renaming, not fixing; it silently re-ranks the
|
||||
> dashboard top-10 (the hero-class bug just fixed); and it must land in TWO implementations.
|
||||
> **HIDE is cheap**: only 3 render sites, every one already has a null branch (so no layout breaks),
|
||||
> and **DeskShowcase already proves the honest "—" pattern in-product**. **RECOMMENDED: HIDE the
|
||||
> number, and re-point the sort at `p_win`** (the hero order established p_win is on 100% of recent
|
||||
> ledger rows and is the one signal that survived an adversarial audit) rather than repair a key that
|
||||
> is a 0.5-line artifact. Coherent end state: **p_win ranks · ev_pct displays · edge_pct retires.**
|
||||
> The abs()/null-first sort defects are worth their own small order regardless of the fork.
|
||||
|
||||
> ## 🔧 GRADE-BOARD SORT FIXED 2026-07-29 (spec `specs/grade-board-sort.md`): signed signal, missing sorts LAST
|
||||
> Display ORDERING only — no grade, ledger, lock_line, scoring, or edge_pct scale/display change.
|
||||
> **THREE PREMISE BREAKS found REPORT-FIRST, before code:** (1) **`/api/props/top-graded` returns 404
|
||||
> in prod** — it does not exist in `src/` (only 3 axios *callers*), so the Next proxy catches →
|
||||
> `{props:[]}` → the dashboard board renders `proofMode`/empty and **the edge sort orders nothing on
|
||||
> that surface today**. CORRECTION to my prior order: its "97.3% of rows tie → the edge key decides the
|
||||
> board" was a LEDGER measurement I extrapolated to this board — wrong; the board has no rows. The fix
|
||||
> is still correct-in-itself and lands the moment the feed is restored. (2) **p_win CANNOT be a
|
||||
> client-side sort key for all tiers** — `utils/snapshotGating.stripModelPrice` (Session 67) strips
|
||||
> `p_win`/`ev_pct`/`model_odds`/`value`/`takeable` for unentitled tiers because *"shipping p_win is
|
||||
> shipping the model price in a different base"*, and `selectTopGrades` runs in the BROWSER. Ranking
|
||||
> there by p_win for everyone would REVERSE that gate. **VERIFIED LIVE: prod `/api/snapshot` returns
|
||||
> p_win on 0/8 MLB and 0/25 WNBA grades** (stripped, as designed). (3) **The ladder cannot take the
|
||||
> takeable gate** — rungs carry NO per-rung price (books price each line differently; we don't fetch
|
||||
> them) and `isTakeable` is a property of price alone.
|
||||
> **WHAT SHIPPED.** `selectTopGrades`: `grade → confidence → takeable-gated p_win (nulls LAST) →
|
||||
> SIGNED edge (nulls LAST) → input order`. `Math.abs()` GONE — `edge` is signed by direction upstream
|
||||
> so positive = the model AGREES; `|edge|` had been ranking the model's strongest DISAGREEMENTS level
|
||||
> with its agreements (177 public ledger rows carry a negative edge). `Math.abs(-Infinity)=Infinity`
|
||||
> GONE — a missing signal sorted FIRST (absent data as the top pick, the `Number(null)` class); it now
|
||||
> sorts LAST and rows are never dropped. **Scales are never mixed** (p_win 0..1 vs edge % — 0.62 vs 62
|
||||
> is not a comparison). Takeable band = `web/src/lib/valueState.isTakeable`, asserted by test to be
|
||||
> byte-equal to the hero's `config/valueEngine.isTakeable` (−160..+200) incl. strict-null.
|
||||
> **Alt-line ladder** (`analyzeViaEngine1:506`): was `edge_pct desc` (and `Number(x)||0` collapsed
|
||||
> absent edges to mid-pack); now 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 exactly line-ASC for an
|
||||
> over and line-DESC for an under. `base` stays marked, so order never implies a recommendation; no
|
||||
> consumer depends on `alt_lines[0]` (grepped), and `deskShowcaseService.rungsOf:40` already re-sorted
|
||||
> by line anyway.
|
||||
> **FINGERPRINTED ON REAL DATA (both sports, both paths).** Unentitled path, live prod snapshots:
|
||||
> **WNBA (n=25) ordering CHANGED**, MLB (n=8) unchanged (its top edges were already positive);
|
||||
> **within every (grade,confidence) tie group the signed edge is non-increasing — 20 adjacent pairs,
|
||||
> 0 violations**. Entitled path, 40 real ledger rows carrying p_win+locked_odds: p_win-descending
|
||||
> within tie groups, **untakeable chalk correctly NOT promoted** (Trea Turner p_win .757 @−275 does
|
||||
> not beat Rhyne Howard .745 @−120) — **36 pairs, 0 violations**.
|
||||
> **HERO CONSISTENCY — stated honestly, they are NOT identical and should not be.** Same signal, same
|
||||
> gate, DIFFERENT precedence by contract: the board is `top GRADES` (grade-tier first), the hero is
|
||||
> `top read` (p_win first). On the real rows the board leads with Angel Reese (A, p_win .555) while
|
||||
> the hero picks Brionna Jones (B, p_win .90). **Within the leading tier they agree exactly (verified
|
||||
> true).** That is a difference of question, not a contradiction — do not "fix" it by making the board
|
||||
> ignore grade.
|
||||
> **FLOOR: 310 suites / 3864 tests green, web build exit 0.** New `tests/unit/gradeBoardSort.test.js`
|
||||
> locks: disagreement never outranks agreement, missing sorts LAST and stays present, null/'' edge is
|
||||
> absent-not-zero, takeable p_win beats untakeable higher-p_win chalk, scales never mixed, the two
|
||||
> takeable bands match, grade tier still dominates, and the ladder order for BOTH directions.
|
||||
> **HELD:** edge_pct rescale/display retirement (Order B) · building the missing
|
||||
> `/api/props/top-graded` server selector (that is what would make the board render at all) ·
|
||||
> exposing p_win to unentitled tiers. **Dashboard + Desk visuals are auth/feed-gated → TAGGED FOR
|
||||
> THE CHROME AUDIT, no visual faked.**
|
||||
|
||||
- **Redirect EXISTS + WIRED:** `closingCapture.buildCaptureRows`→`closing_captures` (append-only,
|
||||
provenance: captured_at/book/line_type/both-prices/missed_reason) via `intradayRefreshService:221`
|
||||
+ internal endpoint; `ledgerService.attachClosingProb`→`closing_prob` (de-vigs both raw sides,
|
||||
@@ -243,12 +383,12 @@ exist locally; harmless, the data restores completely.
|
||||
|
||||
| Item | Status | Note |
|
||||
|---|---|---|
|
||||
| **Settlement: 0 pushes / 470 settled** | 🔴 OPEN, unstarted | Implausible — hits/TB land on the number regularly. Exact-number push almost certainly mis-scored as hit or miss. Corrupts every accuracy/ROI number. |
|
||||
| **Settlement: 0 pushes / 470 settled** | ✅ **CLOSED 2026-07-29 — premise re-verified, NOT a bug** | The ruling's basis STILL HOLDS: the feed is **100% half-numbers**. 0 whole-number lines in **117,970 captured market lines** (5 books, 16 markets, `book`+`sharp`, Jul 20–29), 6,050 `model_snapshots` (incl. refusals), 1,141 ledger rows, 173 `lock_lines` (today). All 992 settled actuals are INTEGERS → **smallest actual-vs-line gap across all 992 = 0.5**, the arithmetic minimum. Expected pushes = **exactly 0 (P=0)**, not chance. Push branch sound + tested; CHECK constraints accept `'push'`. See the verify block above. |
|
||||
| **~28 props/day never settle** | 🔴 OPEN, unstarted | Jul 17 MLB 86 graded/57 settled; Jul 18 103/75. Cause undiagnosed. |
|
||||
| **Model-version contamination** | 🟠 PERMANENT, mitigate | `ledger_entries` mixes pre/post-2026-07-19-fix grades with no marker; eras cannot be separated retroactively. **Any backtest/accuracy claim off existing ledger history MUST treat the fix boundary as a hard cutoff.** `model_snapshots` stamps `model_version`+`code_sha` so it can't recur. |
|
||||
| **A-grade unreachable in prod** | 🔴 OPEN | `opp_rank_stat` null; ESPN team endpoint has no defensive metric at all. Marketing hold stands. |
|
||||
| **EV overconfident** | 🟠 OPEN | Needs calibration before it drives any surface. Hero v2 already ranks on it. |
|
||||
| **`edge_pct` broken scale (U-deg pt 2)** | 🔴 OPEN | 51.5% of ledger rows exceed the sane cap; worst 620. 13 frontend surfaces render it; **nothing renders `ev_pct`**; it's the free-tier hook; it's written to the append-only `edge` column every cron. |
|
||||
| **`edge_pct` broken scale (U-deg pt 2)** | 🔴 OPEN — **DIAGNOSED 2026-07-29, fork reported, not chosen** | Re-verified: **44.0% over cap-40 (502/1141)**, not 51.5%; **worst 620 exact**. MLB median 60 / max 620 (86% of MLB rows are 0.5 lines); **WNBA sane** (median 12, 0% >100). Cause = **small-denominator explosion**, NOT units. **Only 3 surfaces RENDER it** (not 13): GradeResultCard (uncapped, auth-gated), DeskShowcase (already honest "—"), SoccerGradeResult (uncapped, orphan). **It DRIVES the dashboard top-10 sort** (operative on 97.3% of rows) + the Desk alt-ladder. `ev_pct` **DOES** have a live render site (PriceTriplet) — starved, not unwired. **Recommendation: HIDE + re-point the sort at p_win.** See the diagnosis block above. |
|
||||
| **CLV broken (C4)** | 🔴 OPEN | `closing_line == locked_line` on ~95% of rows. BEAT CLOSE suppressed. **CLV ledger stays PRIVATE until backtest-proven.** |
|
||||
| **Consistency CV floor** | 🟠 STOPGAP | `CONSISTENCY_MIN_MEAN=4` leaves a ±1.0 dead for MLB low-count stats. Real fix = index-of-dispersion classifier; needs a backtest first. |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user