91b03c4044
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>
47 lines
1.9 KiB
JavaScript
47 lines
1.9 KiB
JavaScript
// 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('—');
|
|
});
|
|
});
|