Session 48: Name normalization at every layer + usage field (2156 tests)
Trace-first: the normalizer functions were correct (S47) but raw names still
flowed through paths that skipped them. Fixed each leaking path.
- 2a (source chokepoint): snapshotService.runSnapshot normalizes each grade's
player to the de-dotted display AND dedupes to one grade per nameKey|stat
(highest confidence) before writing grades:{sport} + snapshot:latest. Every
consumer (GameCard, Explore, leaders, profile) now gets clean merged names.
- 2b: buildPlayerStripsFromProps dedupes a player's props by stat (graded >
awaiting) → one row per stat (kills "Ks 5.5 AND Ks 3.5" variant dupes).
- 2c: scan tonightsPlayers grid groups by nameKey, displays normalized name.
- 3: profile VYNDR INTELLIGENCE "+0%"/"—" was buildIntel's defaults (separate
from the grade card's buildIntelFields, which already works). resolvePlayerStats
now attaches real usage (AB/G) + rest (B2B/Xd); buildIntel renders them; REST
default is now "—".
Backend 2149 -> 2156 tests (+7), 181 suites. Web build clean (exit 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
// Session 48 — Phase 3: player profile VYNDR INTELLIGENCE shows real usage
|
||||
// (AB/G) + rest, not "—"/"+0%".
|
||||
|
||||
const svc = require('../../src/services/playerIntelService');
|
||||
|
||||
const judgeAdapter = {
|
||||
async getPlayerStats() {
|
||||
return {
|
||||
found: true, group: 'hitting', team: 'New York Yankees',
|
||||
season: { homeRuns: 34, atBats: 330, gamesPlayed: 92, avg: '.288', ops: '1.012', rbi: 87 },
|
||||
last10: [
|
||||
{ date: '2026-06-14', stat: { totalBases: 2 } },
|
||||
{ date: '2026-06-16', stat: { totalBases: 4 } }, // gap 2 → 1 day off
|
||||
{ date: '2026-06-18', stat: { totalBases: 3 } },
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
describe('resolvePlayerStats attaches usage + rest for MLB', () => {
|
||||
it('computes AB/G usage and rest from the game log', async () => {
|
||||
const r = await svc.resolvePlayerStats('Aaron Judge', 'mlb', { mlbAdapter: judgeAdapter });
|
||||
expect(r.classifierInput.usage).toMatch(/AB\/G$/);
|
||||
expect(r.classifierInput.usage).toBe(`${Math.round((330 / 92) * 10) / 10} AB/G`);
|
||||
expect(r.classifierInput.rest).toBe('1d rest');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPlayerIntel surfaces real usage + rest (no "—"/"+0%")', () => {
|
||||
it('USAGE shows AB/G, REST shows a real value', async () => {
|
||||
const r = await svc.getPlayerIntel('Aaron Judge', 'mlb', {
|
||||
cacheGet: async () => null,
|
||||
resolveStats: (name, sport) => svc.resolvePlayerStats(name, sport, { mlbAdapter: judgeAdapter }),
|
||||
});
|
||||
const usage = r.intel.find((m) => m.label === 'USAGE');
|
||||
const rest = r.intel.find((m) => m.label === 'REST');
|
||||
expect(usage.value).toMatch(/AB\/G$/);
|
||||
expect(rest.value).toBe('1d rest');
|
||||
expect(rest.value).not.toBe('+0%');
|
||||
});
|
||||
|
||||
it('REST falls back to "—" not "+0%" when no data', async () => {
|
||||
const r = await svc.getPlayerIntel('Nobody', 'nba', { cacheGet: async () => null, resolveStats: async () => ({ found: false }) });
|
||||
expect(r.intel.find((m) => m.label === 'REST').value).toBe('—');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user