Files
vyndr/tests/unit/profileIntelUsage.test.js
T
builtbykev 91b03c4044 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>
2026-06-19 02:42:13 -04:00

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('—');
});
});