Wave 2A: real player headshots — sport-agnostic id threaded from ingestion
Threads a REAL athlete id from the snapshot's per-player stats resolve (zero
new I/O) → enriched grade → grades:{sport} → slate strip → PlayerAvatar. Real
photo where an id resolves; team-colored monogram (never a gray silhouette,
never a broken image) where it can't. Ids are never fabricated.
Ingestion (Addition 1):
- espnStatsAdapter.getSeasonAverages now RETURNS the resolved ESPN athlete id
(was discarded) as espnId; non-numeric uid degrades to null.
- playerIntelService surfaces MLBAM playerId (MLB) / ESPN espnId (NBA/WNBA).
- snapshotService captures both per player and stores them on the enriched
grade beside archetype/team (null when unresolved → monogram path).
Thread → component:
- slateAdapter.buildPlayerStripsFromProps carries playerId/espnId onto each
strip; StatStrip → PlayerAvatar (accepts both ids; getHeadshotUrl routes by
sport: MLB→mlbstatic, NBA/WNBA→a.espncdn).
- Silhouette surfaces rewired to PlayerAvatar (branded monogram on null):
scan search dropdown (guarded MLBAM p.id) + tonight chips, SearchModal,
HotListPanel, GradeResultCard header. Scan grade card feeds the picked
MLBAM id through gradeAdapter.
- playerHeadshot pure URL logic extracted to CommonJS playerHeadshotUrl.js
(unit-testable; the .ts re-exports it). nfl/nhl added to ESPN_SPORT_PATH.
Tests: tests/unit/headshotThread.test.js (per-league URL + id thread + monogram
null path) + extended snapshotService/espnStatsAdapter suites. Full suite
241 suites / 2915 green; next build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -115,6 +115,34 @@ describe('runSnapshot (fully injected)', () => {
|
||||
expect(cache.store['grades:mlb'].grades).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('Wave 2A — threads the resolved athlete id (playerId/espnId) onto the enriched grade', async () => {
|
||||
const cache = memCache();
|
||||
const d = deps(cache);
|
||||
// Judge resolves an MLBAM id; Betts resolves an ESPN id (cross-sport shape).
|
||||
d.resolveStats = async (player) => (player === 'Aaron Judge'
|
||||
? { found: true, classifierInput: { hr: 34, avg: 0.28, ops: 0.95, k_rate: 28 }, playerId: 592450 }
|
||||
: { found: true, classifierInput: {}, espnId: 4433403 });
|
||||
await svc.runSnapshot('mlb', d);
|
||||
const snap = cache.store['snapshot:mlb:latest'];
|
||||
const judge = snap.grades.find((g) => g.player === 'Aaron Judge');
|
||||
const betts = snap.grades.find((g) => g.player === 'Mookie Betts');
|
||||
expect(judge.playerId).toBe(592450);
|
||||
expect(betts.espnId).toBe(4433403);
|
||||
// grades:{sport} inherits the same ids (GameCard/Explore read from it).
|
||||
const g = cache.store['grades:mlb'].grades.find((x) => x.player === 'Aaron Judge');
|
||||
expect(g.playerId).toBe(592450);
|
||||
});
|
||||
|
||||
it('Wave 2A — no resolved id → enriched grade carries null ids (monogram path)', async () => {
|
||||
const cache = memCache();
|
||||
const d = deps(cache);
|
||||
d.resolveStats = async () => ({ found: false }); // nothing resolves
|
||||
await svc.runSnapshot('mlb', d);
|
||||
const snap = cache.store['snapshot:mlb:latest'];
|
||||
expect(snap.grades[0].playerId).toBeNull();
|
||||
expect(snap.grades[0].espnId).toBeNull();
|
||||
});
|
||||
|
||||
it('rotates latest → previous and computes deltas on the second run', async () => {
|
||||
const cache = memCache();
|
||||
let line = 1.5;
|
||||
|
||||
Reference in New Issue
Block a user