78db55d499
- Name normalization completed: NICKNAMES table (Matt↔Matthew, Mike↔Michael...)
resolved in nameKey, parenthetical team-tag strip "(STL)", verified accent-fold
(Iván/Ivan, José/Jose). Slate strip now DISPLAYS the normalized de-dotted name
("AJ Ewing" not "A.J. Ewing") via buildPlayerStripsFromProps.
- Complete MLB VYNDR INTELLIGENCE: mlbGameLogFeatures derives rest_days (days off
between latest games; 0=B2B) + ab_per_game (usage). buildIntelFields renders
usage as "X AB/G", rest as B2B/Xd, matchup from bvp_advantage fallback.
- Ticker SCAN dedup: pushTickerItems keeps one SCAN per sport (sport field or
text-prefix parse for legacy); MOVE/GRADE preserved; cap 50.
- BOMBER threshold prorated for mid-season (hr>=15 strong / >=10 mod) so June
sluggers classify BOMBER not FLEX/DRIVER.
Backend 2122 -> 2149 tests (+27), 179 suites. Web build clean (exit 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
54 lines
2.3 KiB
JavaScript
54 lines
2.3 KiB
JavaScript
// Session 47 — Phase 2: complete VYNDR INTELLIGENCE for MLB (rest + usage).
|
|
|
|
const { __internals: fc } = require('../../src/services/intelligence/featureCache');
|
|
const { __internals: eng } = require('../../src/services/intelligence/analyzeViaEngine1');
|
|
|
|
const judge = (dates) => ({
|
|
found: true, group: 'hitting',
|
|
season: { totalBases: 180, atBats: 330, gamesPlayed: 92 },
|
|
last10: [
|
|
{ date: dates[0], stat: { totalBases: 2 } },
|
|
{ date: dates[1], stat: { totalBases: 4 } },
|
|
{ date: dates[2], stat: { totalBases: 3 } },
|
|
],
|
|
});
|
|
|
|
describe('mlbGameLogFeatures — rest + usage', () => {
|
|
it('computes rest_days from the two most recent game dates', () => {
|
|
const f = fc.mlbGameLogFeatures(judge(['2026-06-15', '2026-06-16', '2026-06-18']), 'total_bases');
|
|
expect(f.rest_days).toBe(1); // 06-16 → 06-18 (gap 2 = 1 day off)
|
|
});
|
|
it('marks back-to-back as 0', () => {
|
|
const f = fc.mlbGameLogFeatures(judge(['2026-06-15', '2026-06-17', '2026-06-18']), 'total_bases');
|
|
expect(f.rest_days).toBe(0);
|
|
});
|
|
it('computes ab_per_game (MLB usage equivalent)', () => {
|
|
const f = fc.mlbGameLogFeatures(judge(['2026-06-15', '2026-06-16', '2026-06-18']), 'total_bases');
|
|
expect(f.ab_per_game).toBeCloseTo(330 / 92, 2);
|
|
});
|
|
});
|
|
|
|
describe('buildIntelFields — all four MLB fields', () => {
|
|
it('renders usage as AB/G and rest from MLB features', () => {
|
|
const f = fc.mlbGameLogFeatures(judge(['2026-06-15', '2026-06-16', '2026-06-18']), 'total_bases');
|
|
const intel = eng.buildIntelFields(f);
|
|
expect(intel.usage).toMatch(/AB\/G$/);
|
|
expect(intel.rest).toBe('1d rest');
|
|
expect(intel.season_avg).toBeDefined();
|
|
});
|
|
it('renders B2B for zero rest', () => {
|
|
expect(eng.buildIntelFields({ rest_days: 0 }).rest).toBe('B2B');
|
|
});
|
|
it('produces all four fields for a complete feature set', () => {
|
|
const intel = eng.buildIntelFields({ l20_avg: 1.9, l10_avg: 2.1, l5_avg: 2.4, ab_per_game: 3.6, opp_rank_stat: 0.7, rest_days: 1 });
|
|
expect(intel.season_avg).toBeDefined();
|
|
expect(intel.form).toBeDefined();
|
|
expect(intel.usage).toBe('3.6 AB/G');
|
|
expect(intel.matchup_grade).toBe('A');
|
|
expect(intel.rest).toBe('1d rest');
|
|
});
|
|
it('derives matchup from batter-vs-pitcher edge when no opp rank', () => {
|
|
expect(eng.buildIntelFields({ bvp_advantage: 0.1 }).matchup_grade).toBe('A');
|
|
});
|
|
});
|