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
This commit is contained in:
@@ -86,11 +86,13 @@ const clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);
|
||||
const toLogOdds = (p) => Math.log(p / (1 - p));
|
||||
const fromLogOdds = (l) => 1 / (1 + Math.exp(-l));
|
||||
|
||||
function num(v) {
|
||||
if (v == null || v === '') return null;
|
||||
const n = typeof v === 'number' ? v : Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
// MIGRATED to the shared guard (2026-08-02). `Number(null) === 0` has produced
|
||||
// at least SIX separate defects in this codebase, including one in a module
|
||||
// written the same week its author documented the trap — per-module vigilance
|
||||
// has demonstrably failed. Semantics are byte-identical to the local copy this
|
||||
// replaces, so no output changes; the point is that there is now ONE rule.
|
||||
const { knownNumber } = require('../utils/known');
|
||||
const num = knownNumber;
|
||||
|
||||
/**
|
||||
* adjust({ pWin, direction, statType, classification }) — PURE.
|
||||
|
||||
@@ -40,11 +40,13 @@ function median(xs) {
|
||||
}
|
||||
|
||||
/** Strict numeric: null/''/NaN are ABSENT, never 0. (`Number(null) === 0`.) */
|
||||
function num(v) {
|
||||
if (v == null || v === '') return null;
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
// MIGRATED to the shared guard (2026-08-02). `Number(null) === 0` has produced
|
||||
// at least SIX separate defects in this codebase, including one in a module
|
||||
// written the same week its author documented the trap — per-module vigilance
|
||||
// has demonstrably failed. Semantics are byte-identical to the local copy this
|
||||
// replaces, so no output changes; the point is that there is now ONE rule.
|
||||
const { knownNumber } = require('../utils/known');
|
||||
const num = knownNumber;
|
||||
|
||||
/**
|
||||
* Per-book de-vigged fair probability for one side, at one line.
|
||||
|
||||
@@ -71,11 +71,13 @@ const clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);
|
||||
const toLogOdds = (p) => Math.log(p / (1 - p));
|
||||
const fromLogOdds = (l) => 1 / (1 + Math.exp(-l));
|
||||
|
||||
function num(v) {
|
||||
if (v == null || v === '') return null;
|
||||
const n = typeof v === 'number' ? v : Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
// MIGRATED to the shared guard (2026-08-02). `Number(null) === 0` has produced
|
||||
// at least SIX separate defects in this codebase, including one in a module
|
||||
// written the same week its author documented the trap — per-module vigilance
|
||||
// has demonstrably failed. Semantics are byte-identical to the local copy this
|
||||
// replaces, so no output changes; the point is that there is now ONE rule.
|
||||
const { knownNumber } = require('../utils/known');
|
||||
const num = knownNumber;
|
||||
|
||||
/** percentileOf(sortedAsc, x) — fraction of the reference below x, 0..1. */
|
||||
function percentileOf(sortedAsc, x) {
|
||||
|
||||
@@ -60,11 +60,13 @@ const STAT_RATE = Object.freeze({
|
||||
/** Stats where a HIGHER rate means a LOWER prop outcome. */
|
||||
const INVERTED = Object.freeze(new Set(['strikeouts']));
|
||||
|
||||
const num = (v) => {
|
||||
if (v == null || v === '') return null;
|
||||
const n = typeof v === 'number' ? v : Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
};
|
||||
// MIGRATED to the shared guard (2026-08-02). `Number(null) === 0` has produced
|
||||
// at least SIX separate defects in this codebase, including one in a module
|
||||
// written the same week its author documented the trap — per-module vigilance
|
||||
// has demonstrably failed. Semantics are byte-identical to the local copy this
|
||||
// replaces, so no output changes; the point is that there is now ONE rule.
|
||||
const { knownNumber } = require('../utils/known');
|
||||
const num = knownNumber;
|
||||
const clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
* actual generative structure, not by a family that happens to fit its name.
|
||||
*/
|
||||
|
||||
const { knownRate } = require('../../utils/known');
|
||||
|
||||
const TB_CAP = 20; // bases per game; beyond this is not a real outcome
|
||||
const N_CAP = 8; // per-component events per game
|
||||
|
||||
@@ -66,13 +68,12 @@ function tbPmf(rates = {}) {
|
||||
{ w: 3, rate: rates.triples },
|
||||
{ w: 4, rate: rates.home_runs },
|
||||
];
|
||||
// STRICT: `Number(null) === 0`, so a null rate would pass a naive finite check
|
||||
// and be treated as a real, measured zero — the difference between "this
|
||||
// player never triples" and "we do not know his triple rate". Absent stays
|
||||
// absent; only a genuine number counts.
|
||||
const isRate = (v) => v != null && v !== '' && typeof v !== 'boolean'
|
||||
&& Number.isFinite(Number(v)) && Number(v) >= 0;
|
||||
const usable = components.filter((c) => isRate(c.rate));
|
||||
// MIGRATED to the shared guard (2026-08-02). `Number(null) === 0` would read a
|
||||
// null rate as a real, measured zero — the difference between "this player
|
||||
// never triples" and "we do not know his triple rate". `knownRate` is the one
|
||||
// rule; this module previously carried its own copy, which is exactly the
|
||||
// pattern that has failed six times.
|
||||
const usable = components.filter((c) => knownRate(c.rate) !== null);
|
||||
if (usable.length === 0) return null;
|
||||
|
||||
let pmf = new Array(TB_CAP + 1).fill(0);
|
||||
@@ -117,10 +118,9 @@ function pAtLeast(pmf, k) {
|
||||
|
||||
/** Expected total bases implied by the component rates. */
|
||||
function tbMean(rates = {}) {
|
||||
// Same strictness as tbPmf: an absent component contributes nothing, and is
|
||||
// not silently read as a measured zero.
|
||||
const n = (v) => ((v != null && v !== '' && typeof v !== 'boolean'
|
||||
&& Number.isFinite(Number(v)) && Number(v) >= 0) ? Number(v) : 0);
|
||||
// Same shared rule: an absent component contributes nothing and is not
|
||||
// silently read as a measured zero.
|
||||
const n = (v) => knownRate(v) ?? 0;
|
||||
return n(rates.singles) + 2 * n(rates.doubles) + 3 * n(rates.triples) + 4 * n(rates.home_runs);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,11 +48,13 @@ const STAT_FIELD = Object.freeze({
|
||||
});
|
||||
|
||||
const isNum = (v) => typeof v === 'number' && Number.isFinite(v);
|
||||
const num = (v) => {
|
||||
if (v == null || v === '') return null;
|
||||
const n = typeof v === 'number' ? v : Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
};
|
||||
// MIGRATED to the shared guard (2026-08-02). `Number(null) === 0` has produced
|
||||
// at least SIX separate defects in this codebase, including one in a module
|
||||
// written the same week its author documented the trap — per-module vigilance
|
||||
// has demonstrably failed. Semantics are byte-identical to the local copy this
|
||||
// replaces, so no output changes; the point is that there is now ONE rule.
|
||||
const { knownNumber } = require('../utils/known');
|
||||
const num = knownNumber;
|
||||
const clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);
|
||||
const abbrOf = (team) => {
|
||||
if (!team) return null;
|
||||
|
||||
@@ -34,11 +34,13 @@ const statcast = require('./adapters/statcastAdapter');
|
||||
const MIN_PA = Number(process.env.STATCAST_MIN_PA) || 50;
|
||||
const MIN_IP = Number(process.env.STATCAST_MIN_IP) || 10;
|
||||
|
||||
const num = (v) => {
|
||||
if (v == null || v === '') return null;
|
||||
const n = typeof v === 'number' ? v : Number(v);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
};
|
||||
// MIGRATED to the shared guard (2026-08-02). `Number(null) === 0` has produced
|
||||
// at least SIX separate defects in this codebase, including one in a module
|
||||
// written the same week its author documented the trap — per-module vigilance
|
||||
// has demonstrably failed. Semantics are byte-identical to the local copy this
|
||||
// replaces, so no output changes; the point is that there is now ONE rule.
|
||||
const { knownNumber } = require('../utils/known');
|
||||
const num = knownNumber;
|
||||
|
||||
/**
|
||||
* roleDetail(usage) — TRUE role from real usage, not the IP proxy (which drifts
|
||||
|
||||
Reference in New Issue
Block a user