/* ============================================================ Session 72 — TIER-1 MAPPINGS LIVE + the TIER-2 nomination harness. ============================================================ */ const ch = require('../../src/services/challengerProjection'); const axes = require('../../src/services/archetypeAxes'); const harness = require('../../src/services/mappingHarness'); // ── 1. NO SILENT NO-OPS ────────────────────────────────────────────────── describe('every mapping key is a CANONICAL emitted axis', () => { it('batter mappings reference only real batter axes', () => { const bad = []; for (const [stat, m] of Object.entries(ch.BATTER_MAP)) { for (const k of Object.keys(m)) if (!(k in axes.BATTER_AXES)) bad.push(`${stat}.${k}`); } // A key that does not exist would look active and never fire. expect(bad).toEqual([]); }); it('pitcher mappings reference only real pitcher axes', () => { const bad = []; for (const [stat, m] of Object.entries(ch.PITCHER_MAP)) { for (const k of Object.keys(m)) if (!(k in axes.PITCHER_AXES)) bad.push(`${stat}.${k}`); } expect(bad).toEqual([]); }); }); // ── 2. TIER 1 FIRES, WITH THE RIGHT SIGN ───────────────────────────────── const PITCHER = (o) => axes.classifyPlayer({ role: 'pitcher', role_detail: 'starter', sample_ip: 80, k_pct: 22, bb_pct: 8, chase_pct: 30, gb_pct: 42, fb_pct: 26, barrel_pct: 8, ...o, }); const BATTER = (o) => axes.classifyPlayer({ role: 'batter', sample_pa: 300, k_pct: 22, bb_pct: 8, chase_pct: 30, barrel_pct: 7, avg_launch_angle: 14, sweet_spot_pct: 33, ...o, }); const move = (cls, stat, dir = 'over', p = 0.5) => ch.adjust({ pWin: p, direction: dir, statType: stat, classification: cls }).delta; describe('TIER 1 — tautological mappings, live and directional', () => { it('WHIFF / PUNCHOUT arm → strikeouts UP', () => { expect(move(PITCHER({ k_pct: 31 }), 'strikeouts')).toBeGreaterThan(0); }); it('SINKER / SEAM arm → home runs allowed DOWN', () => { expect(move(PITCHER({ gb_pct: 55 }), 'home_runs_allowed')).toBeLessThan(0); }); it('FLY BALL / ELEVATOR arm → home runs allowed UP', () => { expect(move(PITCHER({ fb_pct: 36 }), 'home_runs_allowed')).toBeGreaterThan(0); }); it('SURGEON ARM / PINPOINT → walks allowed DOWN', () => { expect(move(PITCHER({ bb_pct: 5 }), 'walks_allowed')).toBeLessThan(0); }); it('SLUGGER / BOMBER → total bases UP and home runs UP', () => { const slugger = BATTER({ barrel_pct: 14 }); expect(move(slugger, 'total_bases')).toBeGreaterThan(0); expect(move(slugger, 'home_runs')).toBeGreaterThan(0); }); it('TECHNICIAN / SURGEON → hits UP and strikeouts DOWN', () => { const contact = BATTER({ k_pct: 12 }); expect(move(contact, 'hits')).toBeGreaterThan(0); expect(move(contact, 'strikeouts')).toBeLessThan(0); }); it('GRINDER / SNIPER → walks UP', () => { expect(move(BATTER({ chase_pct: 21 }), 'walks')).toBeGreaterThan(0); }); it('an average player is untouched on every Tier-1 stat', () => { for (const s of ['strikeouts', 'home_runs_allowed', 'walks_allowed']) { expect(move(PITCHER({}), s)).toBe(0); } for (const s of ['total_bases', 'home_runs', 'hits', 'walks']) { expect(move(BATTER({}), s)).toBe(0); } }); it('every Tier-1 mapping mirrors exactly on the UNDER side', () => { const p = PITCHER({ k_pct: 31 }); expect(move(p, 'strikeouts', 'under', 0.5)).toBeCloseTo(-move(p, 'strikeouts', 'over', 0.5), 3); }); }); describe('SPEED mappings are honestly absent, not faked', () => { it('stolen_bases has no axis to key on — Layer 2 shelved speed', () => { // SB is a statsapi field that never reached the aggregate store, so a // BURNER axis does not exist. The mapping is empty rather than invented. expect(ch.BATTER_MAP.stolen_bases).toEqual({}); expect(move(BATTER({}), 'stolen_bases')).toBe(0); }); }); // ── 3. THE TIER-2 HARNESS ──────────────────────────────────────────────── /** Rows where the archetype carries REAL residual signal the projection missed. */ function rows({ n = 300, effect = 0.10, withRate = 0.4, stat = 'hits_allowed', flipInTest = false }) { const out = []; for (let i = 0; i < n; i++) { const day = 1 + Math.floor(i / 10); // ~30 dates const isTest = day > 18; const has = i % Math.round(1 / withRate) === 0; const eff = has ? (flipInTest && isTest ? -effect : effect) : 0; // p_win fixed at .5; hit rate shifted by the effect for archetype-carriers. // The draw uses a modulus COPRIME with the carrier selector — otherwise the // two alias and the fixture manufactures an effect the generator never put // there (which is exactly what happened first time, and the harness caught). const y = ((i * 37) % 101) / 101 < 0.5 + eff ? 'hit' : 'miss'; out.push({ stat, p_win: 0.5, outcome: y, game_date: `2026-06-${String(day).padStart(2, '0')}`, archetype_vector: has ? { vector: { ground_ball: { tier: 'hi', label: 'SINKER' } } } : { vector: {} }, }); } return out; } const CAND = { axis: 'ground_ball', stat: 'hits_allowed', sign: -1, role: 'pitcher' }; describe('Tier-2 harness — marginal, out-of-sample, direction-validated', () => { it('INSUFFICIENT below the row floor — never a nomination on thin data', () => { const r = harness.evaluateMapping(rows({ n: 50 }), CAND); expect(r.verdict).toBe('INSUFFICIENT'); expect(r.reason).toMatch(/need 200 settled rows/); }); it('REJECTS a redundant mapping — the projection already knows it', () => { // Zero residual difference between carriers and non-carriers. const r = harness.evaluateMapping(rows({ effect: 0 }), CAND); expect(r.verdict).toBe('REJECT'); expect(r.reason).toMatch(/redundant/); }); it('REJECTS an overfit mapping — effect in fit, gone/flipped out-of-sample', () => { const r = harness.evaluateMapping(rows({ effect: 0.12, flipInTest: true }), { ...CAND, sign: +1 }); expect(r.verdict).toBe('REJECT'); }); it('NEVER silently flips a contradicted sign', () => { const r = harness.evaluateMapping(rows({ effect: 0.10 }), { ...CAND, sign: -1 }); if (r.verdict === 'REJECT') expect(r.reason).toMatch(/contradicts the candidate sign/); expect(r.verdict).not.toBe('NOMINATE'); // the data says +, the candidate says − }); it('NOMINATES a mapping with real out-of-sample residual signal', () => { const r = harness.evaluateMapping(rows({ effect: 0.10 }), { ...CAND, sign: +1 }); expect(r.verdict).toBe('NOMINATE'); expect(Math.abs(r.test_effect)).toBeGreaterThanOrEqual(harness.MIN_EFFECT); expect(r.direction_holds).toBe(true); }); it('splits by DATE, never randomly — same-game rows would leak', () => { const s = harness.splitByDate(rows({ n: 300 })); const fitDates = new Set(s.fit.map((r) => r.game_date)); const testDates = new Set(s.test.map((r) => r.game_date)); for (const d of testDates) expect(fitDates.has(d)).toBe(false); }); it('reads the archetype RETAINED at grade time, never a re-classification', () => { const src = require('fs').readFileSync(require.resolve('../../src/services/mappingHarness'), 'utf8'); expect(src).toMatch(/archetype_vector/); expect(src).toMatch(/look-ahead leakage/); }); }); describe('lifecycle — nothing sits dark', () => { it('nominated → live → claimed, and only the CLAIM is withheld', () => { expect(harness.LIFECYCLE).toEqual(['nominated', 'live', 'claimed']); }); });