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

13 KiB
Raw Blame History

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.