Files
vyndr/specs/full-output-grade-mapping.md
T
builtbykev 708f0fde5c report: full-output grade mapping spec + collapse cost measured
Report-only. Nothing built, reconnected, or promoted.

Premise corrected again: the three-layer engine is BUILT but NOT WIRED and NOT
DEPLOYED (0 python refs in every grade-path file, 0 python in Dockerfile; there
is no engine1Adapter). So no posterior/CI/similarity prior exists to inventory
or diff. Measured against the collapse that actually exists instead.

THREE collapses, not one: (A) estimateProbability's components discarded at
analyzeViaEngine1:521-524; (B) THE SEVERE ONE - p_win never reaches the grade
at all (engine1.js has zero probability references), so the probability is
excluded from grading rather than collapsed into it; (C) grade_thresholds.json
(probability->grade) read backwards to manufacture confidence.
Market-efficiency scaling is never computed - a gap, not a collapse.

MEASURED on 354 settled rows carrying the served letter and the locked pre-game
p_win (forward, not lookahead). Grade->outcome point-biserial r: champion letter
0.0050 (p~0.93, null) vs probability letter 0.1313 (p~0.013). Per sport: MLB
champ 0.0686 n.s. vs prob 0.2356 (p~0.0004); WNBA champ -0.0986 vs prob -0.1258
- BOTH INVERSE. The served letter is inverted between its only two populated
tiers (B 52.4% n=168 vs C 56.9% n=174).

Verdict: costly on MLB, and un-collapsing does NOT help WNBA -> the challenger
must be MLB-FIRST. Five falsifiable mapping rules specced, incl. R2
(uncertainty grades down) stated explicitly and droppable if it fails.

Hard requirement on the next order: persist per-row n, SE and pre-adjustment p,
or R2/R4 can never be adjudicated (not stored today).

Re-adjudication list flagged incl. proj-v1.1's NOT PROVEN verdict (judged
against the collapsed champion, so not final) and ROI-by-grade (with B/C
inverted, the MLB-C +4.57% segment is likely an artifact of a meaningless
letter).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-30 23:47:11 -04:00

225 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC — FULL-OUTPUT GRADE MAPPING + COLLAPSE-COST MEASUREMENT
Report-only, 2026-07-30. **Nothing built, reconnected, promoted, or changed.**
Track-B order 1 of 3. The challenger BUILD is the next order, gated on this.
---
## REVIEW ZERO — the premise needs one correction before anything else
**The order's premise says the three-layer engine is "BUILT and WIRED."** Re-verified
independently this order: **it is BUILT but NOT WIRED and NOT DEPLOYED.** Every grade-path
file (`gradeSlateService`, `analyzeViaEngine1`, `engine1`, `featureCache`,
`probabilityEstimator`) contains **zero** references to the Python service, and `Dockerfile`
contains **zero** python/pip/requirements lines. There is also no `engine1Adapter` — the real
files are `utils/gradeAdapter.js` and `intelligence/analyzeViaEngine1.js`.
**Consequence for this order:** there is no Layer-1 similarity prior, no Layer-2 posterior,
and no confidence interval being produced per prop today. **Phase 1 cannot inventory a live
three-layer output, and Phase 3 cannot diff against one.** So Phase 3 was executed against
the collapse that *actually exists* — which turns out to be more severe than the premise
describes, and measurable right now.
### 0.1 The collapse points — THREE, not one (VERIFIED)
**Collapse A — the estimator's components are discarded.**
`probabilityEstimator.estimateProbability` returns
`{ p_over, p_under, components: { base, recency, weighted, opp_adjustment, home_adjustment,
consistency_adjustment, cv } }`. At `analyzeViaEngine1.js:521-524` only the scalar survives:
const est = estimateProbability({ gameLogs: meta.gameLogs, line: prop.line, ... });
const pWin = dir === 'under' ? (1 - est.p_over) : est.p_over;
if (pWin != null) legacy.p_win = Math.round(pWin * 1000) / 1000;
`est.components` is never attached to anything. Discarded: the empirical base rate, the
recency rate, each adjustment's magnitude, and `cv` (the only uncertainty proxy produced).
**Collapse B — THE SEVERE ONE: `p_win` never reaches the grade at all.**
The letter comes from `engine1.gradeProp` (`idx = NEUTRAL_INDEX(3); idx += f.delta`), and
`engine1.js` contains **zero** references to `p_win` or any probability. So the probability is
not "collapsed into the grade" — **it is computed, stored on the payload, and excluded from
grading entirely.**
**Collapse C — `grade_thresholds.json` is read BACKWARDS.** The table maps
PROBABILITY → GRADE (`A+ 0.85-1.00`). The live path picks a letter from the factor index and
then reads that letter's band MIDPOINT to manufacture `confidence`.
**NOT produced anywhere today (so not "discarded" — absent):** a posterior distribution, a
confidence interval, a similarity prior, an instance count. `cv` is a coefficient of variation
of the STAT, not a CI on the probability.
### 0.2 Market-efficiency-per-sport — SPECCED-BUT-ABSENT, not flattened (VERIFIED)
It is not computed-then-flattened; it is **never computed**. One global 11-band scale with no
sport dimension. The spec's MLB 0.55 / NBA-stars 0.80 exists nowhere in code. So it is a
second *gap*, not a second collapse.
---
## PHASE 1 — INVENTORY
| Component | Produced today? | Reaches the grade? |
|---|---|---|
| Layer-1 similarity prior | **NO** (engine not deployed) | — |
| Instance count / min-instance fallback | **NO** | — |
| Layer-2 Bayesian posterior | **NO** | — |
| Posterior confidence interval | **NO** | — |
| Archetype context | YES (snapshot classify) | **NO** — display + challenger only |
| `p_over` point estimate | YES | **NO** (Collapse B) |
| Estimator components / `cv` | YES, then dropped | **NO** (Collapse A) |
| Factor deltas (l5/l20, opp_rank, rest, usage) | YES | **YES — these ARE the grade** |
**Min-instance fallback (spec: 15): CANNOT DETERMINE / does not fire.** The similarity engine
never runs. The code's only abstention rule is `ABSTENTION_RULES.similar_games_below = 3`
(`bayesian.py:46`); the only `15` in the file is NBA `min_minutes_per_game`. When data is thin
today the live path does not fall back to a prior — `analyzeViaEngine1` **REFUSES** the read
(`insufficient_data: true`, grade null) if `projectionFor` finds no reference.
---
## PHASE 2 — THE DISTRIBUTION → GRADE MAPPING (design, on paper)
Five explicit rules. **Each is stated so it can be falsified on the forward instrument. None
of them is assumed to be an improvement.**
**R1 — BASE: the posterior sets the letter.**
`grade = band(p_posterior)` using `grade_thresholds.json` read FORWARD (the direction it was
written for). *Testable:* within each band, realized hit rate should fall inside the band.
**R2 — UNCERTAINTY GRADES DOWN (the explicit rule, not smuggled).**
Compute the posterior's SE; shrink toward 0.5 by the CI half-width before banding:
halfWidth = z * SE(p) // z = 1.96
p_adj = 0.5 + (p - 0.5) * (1 - k * min(1, halfWidth / W0))
grade = band(p_adj) // defaults k = 1.0, W0 = 0.15
So two props at the same `P(>=line)` grade DIFFERENTLY when their uncertainty differs — the
wider one grades lower. *Testable, and falsifiable:* rows that R2 moves DOWN should hit closer
to their NEW band than their OLD band. If they hit closer to the old band, **R2 is wrong and
must be dropped** — high uncertainty would then be noise, not a reason to downgrade.
**R3 — ARCHETYPE ADJUSTS THE PROJECTION, NEVER THE LETTER.**
Archetype modifies the projection *before* the probability is computed; it must never be a
post-hoc letter bump. *Testable:* archetype-adjusted projections should lower MAE against
actuals vs unadjusted. If MAE does not improve, R3 is dropped.
**R4 — MARKET-EFFICIENCY SCALES THE THRESHOLD, NOT THE PROBABILITY.**
threshold_sport(letter) = 0.5 + (threshold_base(letter) - 0.5) * E_sport
Higher `E_sport` (more efficient market) demands more probability for the same letter.
*Testable, and this is the definition of the goal:* after scaling, **the realized hit rate for
a given letter should be EQUAL across sports.** That is what "an A means the same thing
everywhere" cashes out to, and it is measurable. `E_sport` is FIT from each sport's own
accrued record — never hand-set.
**R5 — ABSTENTION, not a default C.** Below the min-instance threshold, refuse (grade null),
matching today's honest refusal behaviour. The threshold is a declared parameter (code says 3;
the spec's 15 is unreconciled) and must be set explicitly, not inherited by accident.
---
## PHASE 3 — THE COLLAPSE COST, MEASURED
**Method.** 354 SETTLED public ledger rows carrying both the served champion letter and the
stored `p_win`. `p_win` was computed pre-game and locked, so this is a forward test, **not
lookahead**. The champion letter is compared against the probability letter obtained by
reading `grade_thresholds.json` FORWARD (R1 only — R2/R3/R4 are not measurable from stored
data, see the gap note below).
### Discrimination — grade vs realized outcome (point-biserial r)
| population | n | **champion letter** | **probability letter** | raw `p_win` |
|---|---|---|---|---|
| **ALL** | 354 | **0.0050** (t≈0.09, p≈0.93 — null) | **0.1313** (t≈2.49, **p≈0.013**) | 0.1349 |
| **MLB** | 224 | 0.0686 (n.s.) | **0.2356** (t≈3.61, **p≈0.0004**) | — |
| **WNBA** | 130 | **0.0986** | **0.1258** | — |
### Hit rate by letter
| champion | n | hit% | | probability | n | hit% |
|---|---|---|---|---|---|---|
| A | 2 | 50.0 | | A+ | 24 | 62.5 |
| B | 168 | **52.4** | | A | 29 | 65.5 |
| C | 174 | **56.9** | | A- | 25 | 44.0 |
| D | 5 | 20.0 | | B+ | 30 | **70.0** |
| F | 5 | 20.0 | | B / B- | 54 / 48 | 53.7 / 56.3 |
| | | | | C+ / C / C- | 43 / 50 / 15 | 53.5 / 50.0 / 53.3 |
| | | | | D / F | 26 / 10 | **30.8** / 40.0 |
**Flat-stake ROI:** probability-top (A-family, n=78) **1.42%** vs probability-bottom
(C-/D/F, n=51) **26.62%** — a **25-point spread**. The champion's top tier is n=2 (unusable).
Letters actually used: champion **3-4**; probability **10-11** of 11.
### 3.6 — VERDICT: **COSTLY on MLB, and un-collapsing does NOT help WNBA**
- **The champion's served letter is uninformative.** r = 0.005 overall, and it is **INVERTED
between its only two populated tiers — B hits 52.4% while C hits 56.9%.** A user reading B
as better than C is reading noise.
- **MLB: the collapse is costly and the cost is significant.** The discarded probability
carries real signal (r = 0.236, p ≈ 0.0004) where the served letter carries effectively none
(0.069, n.s.). **This is the strongest single finding of the model line to date.**
- **WNBA: un-collapsing makes it WORSE, not better** (prob r = **0.126**, i.e. inverse). The
pooled r = 0.131 is MLB's signal diluted by WNBA's inversion. This independently corroborates
the 2026-07-26 calibration diagnosis ("champion does not discriminate on WNBA"; Brier worse
than always-0.5).
- **Therefore the full-output challenger must be MLB-FIRST.** Shipping it for WNBA on the
strength of a pooled number would ship an anti-predictive grade. WNBA needs a different fix.
- Banding costs almost nothing vs the raw probability (0.1313 vs 0.1349) — the 11-band scale is
not the lossy part.
### GAP — R2/R3/R4 are NOT measured here (CANNOT DETERMINE)
`SE(p)` needs the per-row instance count `n`, which **is not stored on the ledger**. So the
CI-width rule, archetype-on-projection, and efficiency scaling are **specified but unmeasured**.
**The challenger build must emit and persist `n`, `SE`, and the pre-adjustment `p`** or R2/R4
can never be adjudicated. That is a hard requirement on the next order.
### 3.7 — Consumers that assume the grade is a POINT
Nothing consumes a distribution, so a distribution-based grade is safe **provided it still
emits a letter**. What DOES change is the letter DISTRIBUTION, and these depend on it:
`tierGating` (Desk gates), `AccuracyBadge` / `ModelRecord` / `TierRecord` byGrade buckets,
`outcomeService.gradeBucket` + `TIERS`, `heroPropService.isAB` and `deskShowcaseService.isAB`
(A/B filters — the hero pool would grow), `gradeRanking.GRADE_RANK`, `selectTopGrades`,
`grade_thresholds.json:capper_minimum_grade = 'A-'`, and the newsletter/media templates.
**🔴 The A-grade question, stated precisely.** Probability grading would emit **53 A-family
rows where the champion emitted 2**. CLAUDE.md's permanent founder ruling forbids *rescaling
thresholds to mint A's*. **This is not that** — it is grading on a different and measurably more
informative basis (r 0.236 vs 0.069 on MLB), which is new information, and the A's earn it
(prob-A 65.5%, A+ 62.5%, B+ 70.0% vs D 30.8%). **But one honest caveat: A- hits 44.0%, breaking
monotonicity at the top.** The A-RATED marketing hold should stay until the challenger's own
forward record shows a monotone top tier — the ruling's spirit (never sell a relabelled B as an
A) is satisfied by evidence, not by the mapping alone.
---
## PHASE 4 — RE-ADJUDICATION LIST (collapsed-output verdicts, flagged not re-run)
Every one of these was measured on the collapsed output and must be re-measured on the full
output before being trusted forward:
1. **The champion's p_win→CLV edge** (partial r = 0.375, p ≈ 0.003, n = 62 takeable MLB overs)
— measured on the collapsed p_win. Direction is likely preserved (it already used p_win, not
the letter), but the magnitude is a collapsed-output number.
2. **The over-side skew audit** ("SURVIVES BASELINE", +7.14pt marginal) — same basis.
3. **proj-v1.1's "NOT PROVEN"** — it was judged against the COLLAPSED champion. A full-output
champion is a different, stronger benchmark, so **proj-v1.1's death is not final** — it must
be re-run against the new champion, and could fare better or worse.
4. **The takeable-floor derivation (C1)** — ROI-by-price buckets were computed on rows graded by
the collapsed engine; the population selection itself is collapsed-output-conditioned.
5. **The calibration diagnosis (2026-07-26)** — Brier/reliability/resolution on collapsed p_win.
Its WNBA finding is *corroborated* by this order and looks robust; its MLB numbers need re-running.
6. **ROI-by-grade (MLB-C +4.57%, MLB-B 1.02%, WNBA 5%)** — these are grade buckets from a
letter now measured at r ≈ 0.005. **Given B and C are inverted, "MLB-C is the profitable
segment" is very likely an artifact of a meaningless letter, not a real segment.**
7. **`confidence` on every historical row** — a band-midpoint of a letter with no predictive
content; it should not be used as a weight in any future analysis.
---
## TAGS
VERIFIED: the three collapse points; the engine is not wired/deployed; discrimination numbers
(n=354, per sport); consumer list. CANNOT DETERMINE: R2/R3/R4 effects (per-row `n` not stored);
the spec's min-15 instance rule (absent from code). BLOCKED: none.
## HELD
No grade change, no challenger build, no promotion, no reconnection, no retro-grading.