c8fc9f577e
Three focused P1 fixes on the Session-45 snapshot model.
- Grade card intel ROOT CAUSE: gameLogService is NBA/WNBA-only (offline Python),
so MLB props never got l5_avg/l20_avg and buildIntelFields returned {}. Wired
MLB game logs into featureCache.gameLogFeatures via mlbStatsAdapter.getPlayerStats
(pure mlbGameLogFeatures + MLB stat_type->field map). buildIntelFields gained
playerStats/projection fallbacks for partial intel.
- Player name normalization: src/utils/playerName.js (+ web/src/lib copy):
normalizeName -> {display,key}. Strips periods, de-dots suffix, accent-folds
the key. Applied in snapshotService grouping, slateAdapter grade index +
player-strip merge (variants collapse, longest name shown), and
playerIntelService. "A.J. Ewing"/"AJ Ewing" + "Jazz Chisholm"/"Jr." now merge.
- MLB starting pitchers: new GET /api/schedule/:sport/pitchers (probablePitchers
service wrapping mlbStatsAdapter.getScheduleWithPitchers + best-effort ERA).
Slate fetches it, builds a team->pitcher map (full name + mascot match),
attaches pitchers to MLB GameCardData. + Next proxy.
Backend 2100 -> 2122 tests (+22), 176 suites. Web build clean (exit 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
62 lines
2.3 KiB
JavaScript
62 lines
2.3 KiB
JavaScript
// Session 46 — Phase 2: player name normalization.
|
|
|
|
const be = require('../../src/utils/playerName');
|
|
const fe = require('../../web/src/lib/playerName');
|
|
const slate = require('../../web/src/lib/slateAdapter');
|
|
const intel = require('../../src/services/playerIntelService');
|
|
|
|
describe('normalizeName', () => {
|
|
it('collapses period variants to one key', () => {
|
|
expect(be.nameKey('A.J. Ewing')).toBe(be.nameKey('AJ Ewing'));
|
|
expect(be.normalizeName('A.J. Ewing').display).toBe('AJ Ewing');
|
|
});
|
|
it('collapses suffix variants to one key', () => {
|
|
expect(be.nameKey('Jazz Chisholm Jr.')).toBe(be.nameKey('Jazz Chisholm'));
|
|
expect(be.normalizeName('Jazz Chisholm Jr.').display).toBe('Jazz Chisholm Jr');
|
|
});
|
|
it('keeps the accent in the display but folds it in the key', () => {
|
|
const r = be.normalizeName('Ronald Acuña Jr.');
|
|
expect(r.display).toBe('Ronald Acuña Jr');
|
|
expect(r.key).toBe('ronald acuna');
|
|
expect(be.nameKey('Ronald Acuna')).toBe(r.key);
|
|
});
|
|
it('backend + frontend copies agree', () => {
|
|
for (const n of ['A.J. Ewing', 'Jazz Chisholm Jr.', 'Ronald Acuña Jr.', 'Shohei Ohtani']) {
|
|
expect(fe.nameKey(n)).toBe(be.nameKey(n));
|
|
expect(fe.normalizeName(n).display).toBe(be.normalizeName(n).display);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('sanitizePlayerName normalizes periods/suffix', () => {
|
|
it('strips periods for display', () => {
|
|
expect(intel.sanitizePlayerName('A.J. Ewing')).toBe('AJ Ewing');
|
|
expect(intel.sanitizePlayerName('Jazz%20Chisholm%20Jr.')).toBe('Jazz Chisholm Jr');
|
|
});
|
|
});
|
|
|
|
describe('buildPlayerStripsFromProps merges name variants', () => {
|
|
it('merges "A.J. Ewing" + "AJ Ewing" into one strip', () => {
|
|
const strips = slate.buildPlayerStripsFromProps(
|
|
[
|
|
{ player: 'A.J. Ewing', stat_type: 'hits', line: 1.5 },
|
|
{ player: 'AJ Ewing', stat_type: 'total_bases', line: 1.5 },
|
|
],
|
|
{}, {},
|
|
);
|
|
expect(strips).toHaveLength(1);
|
|
expect(strips[0].props).toHaveLength(2);
|
|
});
|
|
it('displays the longer name variant', () => {
|
|
const strips = slate.buildPlayerStripsFromProps(
|
|
[
|
|
{ player: 'Jazz Chisholm', stat_type: 'hits', line: 1.5 },
|
|
{ player: 'Jazz Chisholm Jr', stat_type: 'runs', line: 0.5 },
|
|
],
|
|
{}, {},
|
|
);
|
|
expect(strips).toHaveLength(1);
|
|
expect(strips[0].player).toBe('Jazz Chisholm Jr');
|
|
});
|
|
});
|