ef4ac60b81
DELTA MEASURED on live prod grades (live ordering unchanged): MLB 7/8 props move (87.5%), mean 2.5 places, TOP READ CHANGES (corey seager hits 1.5 under -> jake burger hits 0.5 over). WNBA 25/25 move, mean 4.1, max 12. This is a large re-ordering, not a tweak. Caveat recorded rather than buried: MLB had only 8 graded props at measurement time. The percentages are real; the sample is one small slate. Re-run before the flip -- it is one call. PER-SPORT DOCTRINE ENFORCED IN CODE. WNBA moves the most and must NOT adopt this: its p_win is anti-predictive, so ranking that board by p_win would sort it by a signal measured to point the WRONG WAY -- worse than the incumbent, not better. A comment would not have stopped a future flip from going global, so FORECAST_RANKED_SPORTS = Set(['mlb']) gates the forecast_rank stamp, with tests asserting no sport inherits MLB's result. A sport joins only by passing its own holdout. EDGE IS NOW DIAGNOSTIC-ONLY IN DISPLAY. MobileEdgeBoard.EdgeCell rendered green (--g-a) for positive edge and red (--miss) for negative. Two things were wrong: green/red IS a quality claim on a quantity that does not predict, and ROW-GRAMMAR reserves red for settled-negative ONLY -- a negative diagnostic is not a settled loss. Now neutral mono with a diagnostic tooltip; header reads "MKT GAP · DIAGNOSTIC". The number is still shown -- no display went blank. DeskShowcase neutralised likewise. PINNACLE LOGGED, NOT ENSHRINED. Per the order, "market-not-sharp" is PENDING-RECOVERY rather than a confirmed permanent limitation. The single question for PropLine is in BLOCKERS.md with its evidence, and MASTER-PLAN now carries the pending status instead of the permanent claim. Live sorts remain byte-identical: selectTopGrades, flattenToEdgeBoard and topGradedService all still call the incumbent. Gates: 4,041 tests / 323 suites green; next build exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
154 lines
6.3 KiB
Markdown
154 lines
6.3 KiB
Markdown
# 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.**
|
||
WNBA's `p_win` is **anti-predictive** on its own data — it abstains. Ranking that
|
||
board by `p_win` would sort it by a signal measured to point the *wrong way*:
|
||
worse than the incumbent, not better.
|
||
|
||
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.
|