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:
@@ -0,0 +1,149 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* STRUCTURAL GUARDS (2026-08-02) — two recurring failure families, locked.
|
||||
*
|
||||
* Both are the same principle: never silently convert something ABSENT or THIN
|
||||
* into a false DEFINITE value.
|
||||
*
|
||||
* GUARD 1 unknown ≠ zero (`Number(null) === 0`, 6× recurring)
|
||||
* GUARD 2 takeability = book identity, never price shape
|
||||
*
|
||||
* The fixtures are REAL rows from prod (2026-08-02 re-stamp), not invented.
|
||||
*/
|
||||
|
||||
const { knownNumber, knownRate, isKnown, firstKnown } = require('../../src/utils/known');
|
||||
const { isTakeableMarket, isWithinPriceBand, assessQuote } = require('../../src/config/takeability');
|
||||
|
||||
// ─────────────────────────────── GUARD 1 ───────────────────────────────
|
||||
describe('GUARD 1 — unknown is not zero', () => {
|
||||
it('every absent form is UNKNOWN, never 0', () => {
|
||||
for (const v of [null, undefined, '', NaN, 'abc', {}, []]) {
|
||||
expect(knownNumber(v)).toBeNull();
|
||||
expect(isKnown(v)).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('a REAL zero survives — 0 rest days is a fact, not an absence', () => {
|
||||
expect(knownNumber(0)).toBe(0);
|
||||
expect(isKnown(0)).toBe(true);
|
||||
expect(knownRate(0)).toBe(0);
|
||||
});
|
||||
|
||||
it('Infinity is not a measurement', () => {
|
||||
expect(knownNumber(Infinity)).toBeNull();
|
||||
expect(knownNumber(-Infinity)).toBeNull();
|
||||
});
|
||||
|
||||
it('knownRate additionally rejects booleans and negatives', () => {
|
||||
expect(knownRate(true)).toBeNull(); // Number(true) === 1
|
||||
expect(knownRate(false)).toBeNull(); // Number(false) === 0 — the trap
|
||||
expect(knownRate(-0.5)).toBeNull();
|
||||
expect(knownNumber(-0.5)).toBe(-0.5); // a signed gap is legitimately negative
|
||||
});
|
||||
|
||||
it('firstKnown prefers a present 0 over a later fallback (|| gets this wrong)', () => {
|
||||
expect(firstKnown(0, 5)).toBe(0);
|
||||
expect(firstKnown(null, 5)).toBe(5);
|
||||
expect(firstKnown(null, undefined, '')).toBeNull();
|
||||
});
|
||||
|
||||
it('REAL FIXTURE — a null triple rate must not read as "never triples"', () => {
|
||||
const tb = require('../../src/services/projection/compoundTotalBases');
|
||||
// Same components, one with an UNKNOWN triple rate vs a MEASURED zero.
|
||||
const unknown = tb.tbPmf({ singles: 0.6, doubles: 0.2, triples: null, home_runs: 0.15 });
|
||||
const zero = tb.tbPmf({ singles: 0.6, doubles: 0.2, triples: 0, home_runs: 0.15 });
|
||||
// Both are computable, and an unknown component contributes nothing —
|
||||
// but it must do so by being SKIPPED, not by being asserted as zero.
|
||||
expect(unknown).not.toBeNull();
|
||||
expect(zero).not.toBeNull();
|
||||
// The guard that matters: no component usable at all → null, not a flat curve.
|
||||
expect(tb.tbPmf({ singles: null, doubles: null, triples: null, home_runs: null })).toBeNull();
|
||||
expect(tb.tbPmf({ singles: false })).toBeNull();
|
||||
});
|
||||
|
||||
it('the migrated sites all delegate to the ONE rule', () => {
|
||||
// Semantics must be identical across every migrated module, or the
|
||||
// migration reintroduced drift.
|
||||
const mods = [
|
||||
require('../../src/utils/gradeRanking').strictNum,
|
||||
require('../../src/services/consensusRuler').__internals.num,
|
||||
];
|
||||
for (const fn of mods) {
|
||||
expect(fn(null)).toBeNull();
|
||||
expect(fn('')).toBeNull();
|
||||
expect(fn(0)).toBe(0);
|
||||
expect(fn('2.5')).toBe(2.5);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────── GUARD 2 ───────────────────────────────
|
||||
describe('GUARD 2 — takeability is BOOK IDENTITY, never price shape', () => {
|
||||
it('REAL FIXTURE — one-sided takeable markets are TAKEABLE', () => {
|
||||
// Verified in prod: betrivers and hardrockbet quote one side only on real
|
||||
// props (5 such rows surfaced in the 2026-08-02 re-stamp). One-sidedness is
|
||||
// normal baseball market structure, not a defect.
|
||||
expect(isTakeableMarket('betrivers')).toBe(true);
|
||||
expect(isTakeableMarket('hardrockbet')).toBe(true);
|
||||
// A one-sided quote: the other side is genuinely absent, and takeability
|
||||
// does not depend on it at all.
|
||||
expect(assessQuote({ book: 'betrivers', odds: 6600 }).takeable).toBe(true);
|
||||
expect(assessQuote({ book: 'hardrockbet', odds: 400 }).takeable).toBe(true);
|
||||
});
|
||||
|
||||
it('REAL FIXTURE — an extreme price is still TAKEABLE (steep juice is a price)', () => {
|
||||
// Ohtani hits-over at -266 and Schwarber hits-over at -209 are real,
|
||||
// placeable DraftKings bets.
|
||||
for (const odds of [-266, -209, -300, -1400, 4900]) {
|
||||
expect(assessQuote({ book: 'draftkings', odds }).takeable).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('REAL FIXTURE — a DFS book at an ordinary -119 is NOT takeable', () => {
|
||||
// The ordinary-looking price is the fake one: PrizePicks is not a
|
||||
// sportsbook. Book identity decides, price shape never does.
|
||||
for (const book of ['prizepicks', 'underdog', 'sleeper', 'dabble']) {
|
||||
expect(isTakeableMarket(book)).toBe(false);
|
||||
expect(assessQuote({ book, odds: -119 }).takeable).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('exchanges and offshore are not takeable however normal the price looks', () => {
|
||||
for (const book of ['kalshi', 'novig', 'smarkets', 'bovada', 'onexbet', 'pinnacle']) {
|
||||
expect(isTakeableMarket(book)).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('an ABSENT book is not takeable — we cannot assert a market we cannot name', () => {
|
||||
for (const b of [null, undefined, '', 'not_a_book']) expect(isTakeableMarket(b)).toBe(false);
|
||||
});
|
||||
|
||||
it('THE DISTINCTION: takeable and price-band are independent axes', () => {
|
||||
// A -300 DraftKings prop is a REAL bet we would not promote. Both true.
|
||||
const steep = assessQuote({ book: 'draftkings', odds: -300 });
|
||||
expect(steep.takeable).toBe(true);
|
||||
expect(steep.within_price_band).toBe(false);
|
||||
|
||||
const normal = assessQuote({ book: 'draftkings', odds: -110 });
|
||||
expect(normal.takeable).toBe(true);
|
||||
expect(normal.within_price_band).toBe(true);
|
||||
|
||||
// ...and a DFS book inside the band is still not takeable.
|
||||
const dfs = assessQuote({ book: 'prizepicks', odds: -119 });
|
||||
expect(dfs.takeable).toBe(false);
|
||||
expect(dfs.within_price_band).toBe(true);
|
||||
});
|
||||
|
||||
it('an UNKNOWN price is not an out-of-band price', () => {
|
||||
expect(assessQuote({ book: 'draftkings', odds: null }).within_price_band).toBeNull();
|
||||
expect(assessQuote({ book: 'draftkings' }).takeable).toBe(true); // book still decides
|
||||
});
|
||||
|
||||
it('no drift: the canonical rule IS bookRoles.isTakeableBook', () => {
|
||||
const { isTakeableBook } = require('../../src/config/bookRoles');
|
||||
for (const b of ['draftkings', 'prizepicks', 'pinnacle', 'betrivers', null]) {
|
||||
expect(isTakeableMarket(b)).toBe(isTakeableBook(b));
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user