Files
vyndr/tests/unit/streakLens.test.js
T
builtbykev 2c9cbca7bd Session B (night2): streaks/hot-list engine — producer + form heat + THE LENS
RESURRECT verdict: the Session-23 engine existed, was pure, tested, and
mounted — it starved because every producer was external and unarmed
(tank01-prefetch via n8n, offline Python flow). The snapshot pipeline is
now the producer: each run merges slate players' real game logs (already
fetched for archetypes — zero extra calls) into rosterlogs:{sport}.

- computeFormHeat: hot hitters (7d AVG), hot sluggers (SLG), hot shooters
  (FG%) — correct sum/sum rate math, season baseline else prior stretch,
  min-sample refusals, never extrapolated.
- streakLens: no raw streak renders alone — built-vs opponents, tonight's
  matchup + opposing SP w/ ERA, step-up/step-down difficulty, one-line
  read. Absent context = say less, never invent.
- /api/streaks/:sport: heat merged into the feed, lens applied from cached
  schedule + pitchers (time-bounded, can never hang the route), snapshot
  grade letters joined.
- ACCEPTANCE (live statsapi, real 2026 logs): 32 rows found — Turang
  12-gm on-base, Reynolds 8-gm on-base, Pratt 7-gm on-base + 3-gm
  multi-hit, Meidroth 5-gm on-base, Cortes 5-gm on-base.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 00:39:18 -04:00

127 lines
5.3 KiB
JavaScript

// Session 60 (night2/B) — the VYNDR Lens + form heat. THE RULE: no raw
// streak renders alone; every row carries built-vs, tonight's matchup,
// difficulty, and a one-line read — all real or absent, never invented.
const { buildLens, applyLens, tonightFor, opposingPitcherFor, __internals } = require('../../src/services/streakLens');
const { computeFormHeat } = require('../../src/services/streaksService');
const SCHEDULE = [
{
homeTeam: { name: 'Detroit Tigers', abbreviation: 'DET' },
awayTeam: { name: 'Tampa Bay Rays', abbreviation: 'TB' },
gameTime: '2026-07-11T23:10:00Z',
},
];
const PITCHERS = [
{
home: { team: 'Detroit Tigers', pitcher: 'Tarik Skubal', era: 2.41 },
away: { team: 'Tampa Bay Rays', pitcher: 'Ryan Pepiot', era: 3.9 },
},
];
describe('streakLens — tonight resolution', () => {
test('finds the opponent + home/away for a team by mascot token', () => {
const t = tonightFor('Tampa Bay Rays', SCHEDULE);
expect(t.opponent).toBe('Detroit Tigers');
expect(t.isHome).toBe(false);
});
test('resolves the OPPOSING probable SP (not the player-team SP)', () => {
const sp = opposingPitcherFor('Tampa Bay Rays', PITCHERS);
expect(sp.pitcher).toBe('Tarik Skubal');
expect(sp.era).toBe(2.41);
});
});
describe('streakLens — the one-line read', () => {
const ROW = {
sport: 'mlb', player: 'Brandon Lowe', team: 'Tampa Bay Rays',
type: 'hit_streak', category: 'hits', currentStreak: 7,
description: '7-game hit streak', opponents: ['Oakland Athletics', 'Los Angeles Angels'],
};
test('composes streak + built-vs + tonight + difficulty', () => {
const lens = buildLens(ROW, { scheduleGames: SCHEDULE, pitcherGames: PITCHERS });
expect(lens.builtVs).toEqual(['Oakland Athletics', 'Los Angeles Angels']);
expect(lens.matchup).toBe('@ Detroit Tigers · SP Tarik Skubal (2.41 ERA)');
expect(lens.difficulty).toBe('step up'); // 2.41 ERA ≤ 3.4
expect(lens.read).toContain('7-game hit streak');
expect(lens.read).toContain('built vs Oakland Athletics');
expect(lens.read).toContain('a step up');
});
test('says LESS when context is missing — never invents', () => {
const lens = buildLens({ ...ROW, opponents: [] }, {});
expect(lens.builtVs).toBeNull();
expect(lens.matchup).toBeNull();
expect(lens.difficulty).toBeNull();
expect(lens.read).toBe('7-game hit streak');
});
test('pitcher streaks do not get an opposing-SP difficulty read', () => {
const lens = buildLens(
{ ...ROW, type: 'k_streak', description: '3-game 7+ K streak' },
{ scheduleGames: SCHEDULE, pitcherGames: PITCHERS },
);
expect(lens.matchup).toBe('@ Detroit Tigers'); // no SP line for pitchers
expect(lens.difficulty).toBeNull();
});
test('ERA difficulty bands', () => {
expect(__internals.eraDifficulty(2.9)).toBe('step up');
expect(__internals.eraDifficulty(4.0)).toBe('neutral');
expect(__internals.eraDifficulty(5.2)).toBe('step down');
expect(__internals.eraDifficulty(null)).toBeNull();
});
test('applyLens attaches a lens to every row', () => {
const rows = applyLens([ROW, { ...ROW, player: 'X' }], { scheduleGames: SCHEDULE, pitcherGames: PITCHERS });
expect(rows).toHaveLength(2);
expect(rows.every((r) => r.lens && r.lens.read)).toBe(true);
});
});
describe('computeFormHeat — hot hitters / sluggers (correct rate math)', () => {
const day = (n) => new Date(Date.now() - n * 86_400_000).toISOString().slice(0, 10);
// 7-day window: 12-for-24 (.500); season .270 → hot hitter.
const hotGames = [
{ date: day(1), opponent: 'Boston Red Sox', hits: 3, atBats: 4, totalBases: 7 },
{ date: day(2), opponent: 'Boston Red Sox', hits: 2, atBats: 4, totalBases: 5 },
{ date: day(3), opponent: 'New York Yankees', hits: 2, atBats: 4, totalBases: 2 },
{ date: day(4), opponent: 'New York Yankees', hits: 2, atBats: 4, totalBases: 6 },
{ date: day(5), opponent: 'New York Yankees', hits: 1, atBats: 4, totalBases: 1 },
{ date: day(6), opponent: 'Baltimore Orioles', hits: 2, atBats: 4, totalBases: 4 },
];
test('flags a hot hitter vs his SEASON average with the honest line', () => {
const rows = computeFormHeat(
[{ name: 'Hot Guy', team: 'Tampa Bay Rays', games: hotGames, seasonRaw: { avg: '0.270', slg: '0.420' } }],
'mlb',
);
const hitter = rows.find((r) => r.type === 'hot_hitter');
expect(hitter).toBeTruthy();
expect(hitter.rate).toBe(0.5); // 12/24, Σ/Σ not mean-of-rates
expect(hitter.description).toContain('.500 over the last 7 days');
expect(hitter.description).toContain('season .270');
const slugger = rows.find((r) => r.type === 'hot_slugger');
expect(slugger).toBeTruthy(); // 25 TB / 24 AB ≈ 1.042 SLG vs .420
expect(slugger.opponents).toContain('Boston Red Sox');
});
test('small samples are refused (minAtt), not extrapolated', () => {
const rows = computeFormHeat(
[{ name: 'Two AB Guy', games: [{ date: day(1), hits: 2, atBats: 2 }], seasonRaw: { avg: '0.250' } }],
'mlb',
);
expect(rows).toEqual([]);
});
test('no season baseline + no prior games → no claim', () => {
const rows = computeFormHeat(
[{ name: 'No Baseline', games: hotGames, seasonRaw: null }],
'mlb',
);
expect(rows).toEqual([]); // all games in-window, nothing to trend against
});
});