← All findingssource · memory/project_grade_v2_system.md
Shipped 2026-07-08 (grading-service PRs #5/#6/#7 + nightlypicks.com #2), replacing conviction-letter grades after the audit proved the old ladder sorted JUICE not accuracy (all grades hit 50-54%; A-grades laid at −156 avg vs C at −119; LLM confidence added ZERO predictive signal beyond market price — Brier got worse with it).
Model (internal/grademodel, go:embed grade_model_v1.json): p_hit = sigmoid(logit(p_in) + 0.0381), where p_in = median proportional de-vig prob across ≥2 books at the bet line → capture-book two-sided → one-sided implied with a fixed 0.145 logit haircut. Identity calibration, slope pinned 1.0 — richer models (formula/prop/side features, isotonic) all LOST out-of-time and are documented as failed variants in the JSON. Fit on WNBA 5/23-7/6 (8,913 decided); out-of-time (6/21-7/6, n=7,933) band ladder MONOTONE: A+(≥.65) 67.1% / A 61.9% / B 55.7% / C 55.2% / PASS 48.2% (unique picks 71.6→46.3). Live backfilled d30 ladder matches (A+ 65.2 → PASS 50.8). WNBA-fit: refit a before headlining NBA grades.
Key concepts (user-chosen design): letter = likely to hit (A+ still ≈ −7% ROI — market prices likelihood; NEVER imply "bet the A+s"); value_flag = separate +EV marker (two-sided-source only + p_hit > break-even + 0.02; ~4-5% of picks; +14-30% unique-pick ROI validated). Hard rule baked in: never judge a price by the offering book's own quote (adverse selection: 1/12 hit).
Odds floor (user rule, refined twice): served picks (GET /picks/nightly) drop decimal < 1.70 (≈ −143); env RECOMMEND_MIN_DECIMAL_ODDS; deliberately NO upper cap ("open to 2+ — might be a good play there"); odds=0 unpriced rows pass. Serving-only — bet_clv captures everything. Parlay legs are a separate pool: PARLAY_MAX_LEG_JUICE=143 env (user to set) matches the band.
Related: project_wnba_audit_2026_07, project_wnba_week_counterfactual, feedback_model_edge_calibration (this system operationalizes it).
CALIBRATION MEASURED 2026-08-25 — p_hit IS the market, and value_flag is noise
4,759 settled rows / 2,924 unique bets, 05-28..08-24, 8 formulas.
STRUCTURAL, and it explains everything: p_hit = sigmoid(logit(p_in) + 0.0381), slope pinned at 1.0, calibration = identity, where p_in IS the
de-vig market probability. No projection, no scout feature. grade_v2 cannot
disagree with the market — it is a repricing OF the market. "grade_v2 ≈
market" is true BY CONSTRUCTION, not coincidence. A refit cannot buy selection
power it has no features to find.
p_hit is UNDER-confident, not over: mean 0.5324 vs actual 0.5588 (+2.6pp), positive gap in 11 of 14 bins. Brier 0.2448 vs 0.2465 constant = skill +0.0018. AUC 0.555. It barely ranks.
⚠️ THE PREMISE I HAD WRONG — the board is NOT losing. "value_flag:false on 18/18" is a LABELLING ARTIFACT of an intercept ~0.11 logits too low. Realized: +1.70% ROI on 2,924 unique bets, 55.9% hit; +3.42% on 4,759 rows. 7 of the last 8 slates positive. Voids are 89.5% in May / 3.0% in July — the settled sample is effectively a clean July-August forward test.
⚠️ value_flag does NOT discriminate — do NOT turn on NIGHTLY_REQUIRE_VALUE. TRUE n=151 hit 50.3% ROI +3.28%; FALSE n=2,773 hit 56.2% ROI +1.61%. TRUE hits worse (z=−1.40). Date-clustered bootstrap on ROI: median +1.64%, 95% CI [−18.0%, +21.7%], P(true better)=0.565 — a coin flip. The floor would cut the board ~95% for no measurable gain. WHY: it is a LONGSHOT SELECTOR. Median odds TRUE +103, FALSE −121. It selects price shape, not skill. ⚠️ And value=TRUE is the ONLY subgroup where p_hit is OVER-confident (−2.6pp vs +2.9pp elsewhere) — the de-vig phantom-edge signature. ⚠️ The model card's own claim (n=356, hit .624, ROI +37.4%) did not replicate: forward hit .503, ROI +3.3%, and decays +13.6% (Jul) → −2.8% (Aug).
grade_v2 bands are monotone in HIT and ANTI-monotone in ROI: A+ .713 hit / +1.58% ROI … PASS .517 hit / +3.78% ROI (row-level PASS +7.26%). The letters work exactly as documented — they sort PRICE. Best-graded = most juice.