55157b3288
PHASE 1 — CLOSE-CAPTURE RETRY, test-first. The closing capture gets a
retry the snapshot path deliberately does not: a snapshot re-runs at the
next slot, but a MISSED CLOSE IS PERMANENT, and the feed flaked once on a
dry induce. Three hard rules, each driven by a test written before the
logic:
- BOUNDED attempts (default 3) with short backoff so every attempt fits
inside the window. Never infinite.
- HARD LOCK-WALL: inside lockWallMinutes of first pitch (or past it) it
stops and records missed_close. A price captured AT or AFTER lock is
NOT a close; storing one would fabricate the CLV baseline.
- NO BOUND LOCK TIME -> refuse immediately, never burn retries on a prop
whose close cannot be timed.
On exhaustion it records missed_close with NO price — never a stale,
mid-day or post-lock line.
PHASE 3 — MLB opp_rank_stat DERIVED, contract-locked. MLB previously had
no opponent metric at all (ESPN's MLB team endpoint carries none), so
engine1's +/-1.0 opponent factor never fired for the sport carrying most
of our volume. Derived from data we already ingest: statsapi team pitching
splits, all 30 teams in ONE free unauthenticated call.
THE SHARED CONTRACT is documented and TESTED, not assumed: 0-1 scale,
HIGH (>=0.70) = WEAK opponent, LOW (<=0.30) = TOUGH — identical to WNBA's
live semantics. Polarity is the highest-risk part: backwards polarity does
not fail loudly, it silently adjusts every MLB grade the wrong way. A test
asserts MLB polarity EQUALS WNBA polarity using engine1's own thresholds.
PROVEN AGAINST THE LIVE FEED:
Colorado Rockies BAA .286 -> opp_rank 0.983 (weak, fires weak_opponent)
LA Dodgers BAA .215 -> opp_rank 0.017 (tough, fires top_opponent)
POLARITY HOLDS: true
HONEST NULLS, tested: thin league baseline, thin opponent sample, unmapped
stat, unknown opponent, or a missing field all return NULL with a reason —
we are FIXING a silent null, so it is never replaced by a confident guess
off three games. opponentStrengthHealth pages on an empty source AND on
derived-null-for-a-sport-we-expect-to-derive.
Suite 285/3435 green, build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
95 lines
4.0 KiB
JavaScript
95 lines
4.0 KiB
JavaScript
/**
|
||
* Session 64 Phase 3 — opp_rank_stat is ONE column with ONE meaning.
|
||
*
|
||
* The highest-risk item is POLARITY. Backwards polarity does not fail loudly —
|
||
* it silently adjusts every MLB grade in the WRONG direction, which is worse
|
||
* than having no value at all. So the contract is asserted against WNBA's live
|
||
* semantics, not merely "a number appears".
|
||
*/
|
||
const os = require('../../src/services/opponentStrength');
|
||
|
||
// 30 teams; BAA against ranges 0.200 (toughest staff) → 0.290 (weakest).
|
||
const league = Array.from({ length: 30 }, (_, i) => ({
|
||
teamId: i + 1,
|
||
stat: { avg: 0.200 + i * 0.003, slg: 0.350 + i * 0.005, gamesPlayed: 90, homeRuns: 10 + i, strikeOuts: 300 + i },
|
||
}));
|
||
|
||
describe('THE SHARED CONTRACT — scale and polarity match WNBA', () => {
|
||
test('scale is 0–1, never a raw count', () => {
|
||
const r = os.deriveMlbOppRank(league, 15, 'hits');
|
||
expect(r.value).toBeGreaterThanOrEqual(0);
|
||
expect(r.value).toBeLessThanOrEqual(1);
|
||
expect(r.value).not.toBe(league[14].stat.avg); // not the raw stat
|
||
});
|
||
|
||
test('POLARITY: the WEAKEST staff (highest BAA) scores HIGH = weak opponent', () => {
|
||
const weakest = os.deriveMlbOppRank(league, 30, 'hits'); // BAA .287
|
||
expect(weakest.value).toBeGreaterThanOrEqual(0.70);
|
||
});
|
||
|
||
test('POLARITY: the TOUGHEST staff (lowest BAA) scores LOW = tough opponent', () => {
|
||
const toughest = os.deriveMlbOppRank(league, 1, 'hits'); // BAA .200
|
||
expect(toughest.value).toBeLessThanOrEqual(0.30);
|
||
});
|
||
|
||
test('MLB polarity EQUALS WNBA polarity — high fires weak, low fires tough', () => {
|
||
// engine1's live thresholds, applied identically to both sports.
|
||
const WEAK = 0.70;
|
||
const TOUGH = 0.30;
|
||
const weak = os.deriveMlbOppRank(league, 30, 'hits').value;
|
||
const tough = os.deriveMlbOppRank(league, 1, 'hits').value;
|
||
// The same comparison engine1 makes for WNBA must classify MLB the same way.
|
||
expect(weak >= WEAK).toBe(true);
|
||
expect(tough <= TOUGH).toBe(true);
|
||
expect(weak).toBeGreaterThan(tough); // monotone in the right direction
|
||
});
|
||
|
||
test('a mid-pack staff lands mid-scale and fires NEITHER factor', () => {
|
||
const mid = os.deriveMlbOppRank(league, 15, 'hits').value;
|
||
expect(mid).toBeGreaterThan(0.30);
|
||
expect(mid).toBeLessThan(0.70);
|
||
});
|
||
});
|
||
|
||
describe('HONEST NULLS — never a confident value off thin data', () => {
|
||
test('a thin LEAGUE baseline returns null, not a rank', () => {
|
||
const r = os.deriveMlbOppRank(league.slice(0, 5), 1, 'hits');
|
||
expect(r.value).toBeNull();
|
||
expect(r.reason).toBe('league_baseline_too_thin');
|
||
});
|
||
|
||
test("a thin OPPONENT sample returns null", () => {
|
||
const thin = league.map((t, i) => (i === 0 ? { ...t, stat: { ...t.stat, gamesPlayed: 3 } } : t));
|
||
const r = os.deriveMlbOppRank(thin, 1, 'hits');
|
||
expect(r.value).toBeNull();
|
||
expect(r.reason).toBe('opponent_sample_too_thin');
|
||
});
|
||
|
||
test('an unmapped stat returns null rather than a wrong field', () => {
|
||
expect(os.deriveMlbOppRank(league, 1, 'stolen_bases').reason).toBe('stat_not_mapped');
|
||
});
|
||
|
||
test('an unknown opponent returns null', () => {
|
||
expect(os.deriveMlbOppRank(league, 999, 'hits').reason).toBe('opponent_not_found');
|
||
});
|
||
|
||
test('a missing field returns null, never 0', () => {
|
||
const noField = league.map((t) => ({ ...t, stat: { ...t.stat, avg: null } }));
|
||
const r = os.deriveMlbOppRank(noField, 1, 'hits');
|
||
expect(r.value).toBeNull();
|
||
expect(r.value).not.toBe(0);
|
||
});
|
||
});
|
||
|
||
describe('HEALTH — we are fixing a silent null, not rebuilding one', () => {
|
||
test('empty source pages', () => {
|
||
expect(os.opponentStrengthHealth({ sport: 'mlb', teamsLoaded: 0 }).alarm).toBe(true);
|
||
});
|
||
test('all-null despite a loaded source pages', () => {
|
||
expect(os.opponentStrengthHealth({ sport: 'mlb', teamsLoaded: 30, attempted: 25, derived: 0 }).alarm).toBe(true);
|
||
});
|
||
test('healthy derivation is quiet', () => {
|
||
expect(os.opponentStrengthHealth({ sport: 'mlb', teamsLoaded: 30, attempted: 25, derived: 24 }).alarm).toBe(false);
|
||
});
|
||
});
|