# RANK ON p_win — CHALLENGER DELTA + EDGE RETIREMENT **Date:** 2026-08-01 · **Live ordering byte-identical** · challenger measured on live prod grades · edge retired from decisions and from display quality-signalling. **Gates:** 4,039 tests / 323 suites green · `next build` exit 0 · delta recorded · live sorts untouched. --- ## WHY (the measurement that dictates this) | instrument | corr with outcome, n=200 settled MLB | |---|---:| | **`p_win`** | **+0.26** | | `p_win − fair_prob` (v1 single-book ruler) | −0.010 | | `p_win − fair_prob` (v2 consensus ruler) | −0.022 | **Subtracting the market destroys the signal, under both rulers.** A quantity that does not predict must not rank, gate, or decide. --- ## THE CHALLENGER — `rankByForecast` **Order:** takeable-gated `p_win` → grade tier → confidence → stable input order. **No edge term anywhere** (a test flips edge from −99 to +99 and asserts the order does not move). **`p_win` leads and the letter follows — deliberately.** The grade letter measured **r ≈ 0.005** against outcomes and is **inverted** (B 52.4% < C 56.9%), while `p_win` measures **+0.26**. Leading with the letter would sort the board by the weaker signal and use the stronger one only to break ties. The takeable gate is unchanged and mandatory: raw `p_win` crowns −300 chalk. ### One thing worth stating precisely **Isotonic calibration is a MONOTONE transform, so ranking on raw `p_win` and on calibrated `p_win` produce the SAME ORDER.** Calibration matters when `p_win` is *displayed* or *thresholded* — it cannot change a ranking. The order asked to "rank on calibrated p_win"; for ranking specifically, that is a no-op relative to raw. Recorded in the code so nobody re-derives it. --- ## THE DELTA (live prod grades, nothing flipped) `GET /api/internal/ranking-delta` · `live_ordering_unchanged: true` | | MLB | WNBA | |---|---:|---:| | graded props | 8 | 25 | | `p_win` coverage | 100% | 100% | | **props that move** | **7/8 (87.5%)** | **25/25 (100%)** | | mean \|move\| | 2.5 places | 4.1 places | | max move | 5 | 12 | | top-10 overlap | 80% | 80% | | **top read changes** | **YES** | **YES** | MLB #1: `corey seager | hits 1.5 under` → `jake burger | hits 0.5 over` (Seager falls 1 → 6). **This is a large re-ordering, not a tweak.** Nearly every row moves and the headline read changes. **Caveat, stated because it matters:** MLB's slate had only **8 graded props** at measurement time. The percentages are real but the sample is a single small slate — re-run the endpoint on a full slate before the flip. It is one call. --- ## PER-SPORT DOCTRINE — ENFORCED IN CODE, NOT IN A COMMENT **WNBA moves the most (100% of rows, mean 4.1 places) and must NOT adopt this.** **CORRECTED 2026-08-01:** WNBA does **not** "abstain" and is **not** "anti-predictive". The −0.12 that produced those words was **NBA-template machinery run on WNBA data** — WNBA has never had its own archetypes, variables, conditions or calibration. That is an **unbuilt model's expected failure, not a verdict on the sport.** WNBA is **NOT BUILT YET**, held out until its own model exists; its build is queued after MLB. The live consequence is the same either way — an unbuilt sport must not rank on a signal not shown to hold for it — which is why the guard code is unchanged. A comment would not have stopped a future flip from applying this globally, so: ```js const FORECAST_RANKED_SPORTS = Object.freeze(new Set(['mlb'])); ``` `ranksOnForecast(sport)` gates the `forecast_rank` stamp, and tests assert **no sport inherits MLB's result** — a sport joins only by passing its **own** holdout (honest calibration AND surviving resolution). **The WNBA number above is informational only.** It is in the report to show what the guard is preventing. --- ## WHAT CHANGED, WHAT DID NOT ### Changed now (not rankings, so not gated on the flip) - **`altLineScanner.compareToBookImplied`** no longer returns `value_detected: edge > 0`. **Edge is still computed and returned** — losing the record would be worse than mis-using it — but the verdict is `null` with `value_basis: 'retired:edge_does_not_predict'`. - **`scanAltLines`** no longer filters to `edge > 0` nor calls the survivor `optimal_line`. The whole ladder returns, ranked, labelled `price_gap_diagnostic_unvalidated`. **The module has zero callers** (verified) — unwired like `mlbGrader.js`; left in place and made honest rather than deleted. - **`MobileEdgeBoard.EdgeCell`** no longer renders green for positive edge and red for negative. Two things were wrong with that: green/red **is** a quality claim on a quantity that does not predict, and **ROW-GRAMMAR reserves red for settled-negative only** (miss/dead/stale/faded) — a negative diagnostic is not a settled loss. Now neutral mono with a diagnostic tooltip; the column header reads **`MKT GAP · DIAGNOSTIC`**. **The number is still shown** — no display went blank. - **`DeskShowcase`** edge colour neutralised for the same reason. ### An honest asymmetry I did not paper over **Ranking props against each other must not use edge. Choosing between RUNGS of the same prop is inherently price-relative** — ranking rungs by model probability alone would always pick the lowest line, because P(over 0.5) > P(over 2.5) by construction. So the gap stays the rung key in `scanAltLines`, **explicitly labelled unvalidated**, rather than being replaced by something that would look principled and be degenerate. ### NOT changed (challenger-first) - **`rankGrades`** — the incumbent (grade-first, edge as 4th key) is untouched, and tested as untouched. - **Every live sort** still calls the incumbent. `selectTopGrades`, `flattenToEdgeBoard`, `topGradedService` — all byte-identical. - **`forecast_rank`** is stamped additively on MLB snapshot grades, **before** `stripModelPrice`, so every tier would receive the correct order without the paid values (the `topGradedService` precedent — an ordinal travels where the magnitude cannot). **Nothing sorts by it yet.** - **Edge stays stored** in the ledger, as required. --- ## THE FLIP, WHEN YOU WANT IT One decision, three edits: `selectTopGrades` and `flattenToEdgeBoard` sort by `forecast_rank` when present; `rankGrades` drops its edge key. **Re-run the delta on a full MLB slate first** — 8 props is not a slate. ## PINNACLE — LOGGED, NOT ENSHRINED Per the order: **"market-not-sharp" is PENDING-RECOVERY, not a confirmed permanent limitation.** The question for PropLine is logged in `BLOCKERS.md`: > *Why did Pinnacle MLB player-prop coverage stop on 2026-07-31?* Captures ran > 103,940 over the prior 10 days, then 4,022 → 0 while every other book continued. Until answered, the plan must not record "no sharp anchor exists" as permanent.