2c9cbca7bd
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>
127 lines
5.3 KiB
JavaScript
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
|
|
});
|
|
});
|