8c764c22a4475f090fc86b18ea648ba5009530b3
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8c764c22a4 |
Structural hardening: unknown-is-not-zero + takeability-is-book-identity
Both guards are ADDITIVE. The full suite (4,111 -> 4,126 tests, 331 suites) passes unchanged through the migration, which is the evidence that no currently-correct output moved: served path, champion, reference ruler and the four accruing challengers are byte-identical. GUARD 1 -- src/utils/known.js. Number(null)===0 has produced at least SIX separate defects here, including one in a module written the same week its author documented the trap. Per-module vigilance has demonstrably failed, so the rule lives in one place and SEVEN sites now delegate: platoonSplits, projectionChallenger, challengerProjection, contactChallenger, statcastAggregateService, consensusRuler, gradeRanking -- plus compoundTotalBases moved onto knownRate. Two functions, deliberately: knownNumber (any finite number -- a REAL 0 is a fact and must survive) and knownRate (non-negative, rejects booleans -- for counts/rates where `true` or -1 is broken, not thin). Collapsing them is how the next variant gets in. firstKnown() exists because `a || b` discards a measured 0 and `a ?? b` does not. MY OWN GUARD HAD THE BUG IT EXISTS TO PREVENT, and its own test caught it: Number([]) === 0, so an empty array coerced to a measured ZERO. Same trap wearing a different type. Both helpers now reject objects outright. GUARD 2 -- src/config/takeability.js. Takeability is BOOK IDENTITY and never price shape. Baseball prop markets are genuinely thin, juiced and one-sided, and all three are NORMAL structure: betrivers and hardrockbet legitimately quote one side only (5 such rows surfaced in yesterday's re-stamp), and a hits-over at -300 is a real placeable bet. A rule that inferred un-takeability from price extremity or one-sidedness would throw those away while still admitting a DFS book at an ordinary -119 -- exactly backwards, because the -119 is the fake one. THE DISTINCTION THAT MUST NOT COLLAPSE, now enforced by test: isTakeableMarket(book) -- CAN it be bet? (identity) isWithinPriceBand(odds) -- SHOULD we promote? (policy band, floor -160) A -300 DraftKings prop is takeable AND out of band; a PrizePicks -119 is in band AND not takeable. Independent axes. FLAGGED, NOT SILENTLY CHANGED: the ledger's `takeable` column is the PRICE-BAND answer, and its name predates this distinction. Four challengers and the ranking gate read it, so renaming or redefining it is its own order -- doing it here would have changed correct current behaviour under cover of a hardening change. Fixtures are REAL prod rows from the 2026-08-02 re-stamp, not invented. Gates: 4,126 tests / 331 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 |
||
|
|
6dd6f59481 |
Layer 3 Step 6: platoon splits, regressed hard
The highest-value adjuster and the thinnest sample in baseball. The regression is not a refinement here, it is the entire feature: applying raw splits would adjust projections on noise, which is worse than not building it. PHASE 0 — both gates clear, and one was already closed. Splits are a statsapi pull, one call per hitter (statSplits with sitCodes vl,vr). The batter-handedness join that Session 69 recorded as pending is in fact DONE: statcast_aggregates carries bats for 604 of 604 batters, 210 left, 327 right, 67 switch. STATE said pending; the data says otherwise, and the note is corrected. Point-in-time holds as long as the split is fetched before first pitch, since a season split queried this afternoon cannot contain tonight — but a historical backtest would use season-final numbers and leak, so clean measurement is forward-accruing. THE SPINE — regressed = (PA x observed + K x prior) / (PA + K), with K = 600 PA and the prior being the hitter's OWN blended rate rather than the league's. The question a platoon adjustment answers is whether he is DIFFERENT against this hand than he normally is, so his own line is the correct null and a hitter with no evidence of a split correctly gets nothing. K is deliberately conservative: platoon skill is famously slow to stabilise, with the half-signal point for right-handed batters near a thousand PA. THE MAKE-OR-BREAK TEST, both halves. A .310 average against left-handed pitching on 30 PA gets 4.8% weight and moves the projection by 0.003 — essentially nothing, which is the correct answer rather than a limitation. The SAME .310 on 400 PA gets 40% weight and moves it by 0.023, eight times as far. A test asserts that ratio stays above five, so if the regression ever breaks the suite says so instead of the projections quietly drifting onto noise. Real data behaves exactly as the mechanism predicts and is worth recording: Josh Bell hits .259 against lefties and .248 against righties, which looks like a platoon split until the sample speaks — 126 PA earns 17% weight and the adjustment lands at 1.005. Aaron Judge, 76 PA against lefties, comes out at 0.999. Neither is material. Most hitters will get nothing from this adjuster, and that is the honest output, not a failure. Honest-absent has five distinct routes, all returning exactly 1.0: no batter handedness, no pitcher handedness, no splits, a stat platoon says nothing about, and a missing side falling back to the prior rather than to zero. INDEPENDENT of the environment. Park and weather compose into one coefficient because they both describe the stadium; platoon describes this hitter against this pitcher's hand, so it rides its own slot with its own label. Entangling them would make both harder to attribute when the instrument scores them. Directional, mirrored on the under, capped at 15%, and inverted for strikeouts where a higher rate means a higher prop rather than a better hitter. Tests 3729 passed / 300 suites, web build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj |