Session 15: Intelligence hardening — park factors, weather, Tank01 prefetch, pace factors, signal audit, founder pricing fix (1405 tests)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
// MLB context helpers (Session 15) — pitcher handedness + lineup PA.
|
||||
|
||||
const { platoonAdvantage, projectedPA, __internals } = require('../../src/services/intelligence/mlbContext');
|
||||
|
||||
describe('platoonAdvantage', () => {
|
||||
test('LHP vs RHB → batter has advantage', () => {
|
||||
expect(platoonAdvantage('L', 'R')).toBe(true);
|
||||
});
|
||||
test('RHP vs LHB → batter has advantage', () => {
|
||||
expect(platoonAdvantage('R', 'L')).toBe(true);
|
||||
});
|
||||
test('LHP vs LHB → pitcher has advantage', () => {
|
||||
expect(platoonAdvantage('L', 'L')).toBe(false);
|
||||
});
|
||||
test('RHP vs RHB → pitcher has advantage', () => {
|
||||
expect(platoonAdvantage('R', 'R')).toBe(false);
|
||||
});
|
||||
test('switch hitter ALWAYS has the edge (vs LHP)', () => {
|
||||
expect(platoonAdvantage('L', 'S')).toBe(true);
|
||||
});
|
||||
test('switch hitter ALWAYS has the edge (vs RHP)', () => {
|
||||
expect(platoonAdvantage('R', 'S')).toBe(true);
|
||||
});
|
||||
test('null pitcher → null', () => {
|
||||
expect(platoonAdvantage(null, 'R')).toBeNull();
|
||||
});
|
||||
test('null batter → null', () => {
|
||||
expect(platoonAdvantage('L', null)).toBeNull();
|
||||
});
|
||||
test('invalid input → null (does not throw)', () => {
|
||||
expect(platoonAdvantage('Z', 'R')).toBeNull();
|
||||
expect(platoonAdvantage('L', 'X')).toBeNull();
|
||||
});
|
||||
test('case-insensitive', () => {
|
||||
expect(platoonAdvantage('l', 'r')).toBe(true);
|
||||
expect(platoonAdvantage('Right', 'Left')).toBe(true);
|
||||
});
|
||||
test('whitespace tolerant', () => {
|
||||
expect(platoonAdvantage(' L ', ' R ')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('projectedPA', () => {
|
||||
test('leadoff (1) sees the most PAs (~4.7)', () => {
|
||||
expect(projectedPA(1)).toBeGreaterThan(4.6);
|
||||
expect(projectedPA(1)).toBeLessThan(4.8);
|
||||
});
|
||||
test('9-hole sees the fewest PAs (~3.7)', () => {
|
||||
expect(projectedPA(9)).toBeGreaterThan(3.6);
|
||||
expect(projectedPA(9)).toBeLessThan(3.8);
|
||||
});
|
||||
test('PA decreases monotonically by slot', () => {
|
||||
let prev = Infinity;
|
||||
for (let i = 1; i <= 9; i += 1) {
|
||||
const pa = projectedPA(i);
|
||||
expect(pa).toBeLessThan(prev);
|
||||
prev = pa;
|
||||
}
|
||||
});
|
||||
test('numeric string input is accepted', () => {
|
||||
expect(projectedPA('3')).toBeCloseTo(4.43, 2);
|
||||
});
|
||||
test('out-of-range returns null', () => {
|
||||
expect(projectedPA(0)).toBeNull();
|
||||
expect(projectedPA(10)).toBeNull();
|
||||
expect(projectedPA(-1)).toBeNull();
|
||||
});
|
||||
test('null / undefined / non-numeric → null', () => {
|
||||
expect(projectedPA(null)).toBeNull();
|
||||
expect(projectedPA(undefined)).toBeNull();
|
||||
expect(projectedPA('NaN')).toBeNull();
|
||||
});
|
||||
test('non-integer (e.g. 5.5) → null', () => {
|
||||
expect(projectedPA(5.5)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeHand', () => {
|
||||
test('accepts L, R, S in upper/lower case', () => {
|
||||
expect(__internals.normalizeHand('l')).toBe('L');
|
||||
expect(__internals.normalizeHand('R')).toBe('R');
|
||||
expect(__internals.normalizeHand('Switch')).toBe('S');
|
||||
});
|
||||
test('rejects unknown letters', () => {
|
||||
expect(__internals.normalizeHand('X')).toBeNull();
|
||||
expect(__internals.normalizeHand('')).toBeNull();
|
||||
expect(__internals.normalizeHand(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user