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:
@@ -298,6 +298,13 @@ async function runSnapshot(sport, opts = {}) {
|
||||
// (statsapi/ESPN), never guessed. Feeds the ledger team/opponent columns
|
||||
// and the slate join guard (a prop only attaches to its own game).
|
||||
const teamByPlayer = {};
|
||||
// Wave 2A — the REAL athlete id from the SAME stats resolve, keyed by player.
|
||||
// MLB → MLBAM id (mlbstatic headshot CDN); NBA/WNBA → ESPN athlete id
|
||||
// (a.espncdn headshot CDN). Stored on the enriched grade so it flows free to
|
||||
// grades:{sport} → slate strips → PlayerAvatar. Zero new I/O. Absent → the
|
||||
// component falls to a team-colored monogram (never a fabricated face).
|
||||
const playerIdByPlayer = {};
|
||||
const espnIdByPlayer = {};
|
||||
// Wave 1 (trust bug) — the prop's game participants become the resolve's
|
||||
// teamHint: it disambiguates namesake collisions (two "James Wood") and, when
|
||||
// the resolved player's real team isn't in the prop's game, the resolver drops
|
||||
@@ -326,6 +333,9 @@ async function runSnapshot(sport, opts = {}) {
|
||||
const c = deps.classify(sp, stats.classifierInput || {});
|
||||
archByPlayer[player] = c.primary ? c.primary.name : null;
|
||||
if (stats.team) teamByPlayer[player] = stats.team;
|
||||
// Wave 2A — capture the resolved athlete id (headshot thread).
|
||||
if (stats.playerId != null) playerIdByPlayer[player] = stats.playerId;
|
||||
if (stats.espnId != null) espnIdByPlayer[player] = stats.espnId;
|
||||
if (Array.isArray(stats.rawLog) && stats.rawLog.length > 0) {
|
||||
logEntries.push({
|
||||
name: normalizeName(player).display || player,
|
||||
@@ -341,12 +351,19 @@ async function runSnapshot(sport, opts = {}) {
|
||||
});
|
||||
await mergeRosterLogs(sp, logEntries, deps);
|
||||
|
||||
const enriched = graded.map((g) => ({
|
||||
...g,
|
||||
gradedAt: gradedAtFor(g, oddsByKey, ts),
|
||||
archetype: archByPlayer[g.player || g.player_name] || null,
|
||||
team: teamByPlayer[g.player || g.player_name] || g.team || null,
|
||||
}));
|
||||
const enriched = graded.map((g) => {
|
||||
const pn = g.player || g.player_name;
|
||||
return {
|
||||
...g,
|
||||
gradedAt: gradedAtFor(g, oddsByKey, ts),
|
||||
archetype: archByPlayer[pn] || null,
|
||||
team: teamByPlayer[pn] || g.team || null,
|
||||
// Wave 2A — real headshot id (MLBAM for MLB, ESPN for NBA/WNBA), threaded
|
||||
// from the stats resolve above. Absent → PlayerAvatar renders a monogram.
|
||||
playerId: playerIdByPlayer[pn] ?? g.playerId ?? null,
|
||||
espnId: espnIdByPlayer[pn] ?? g.espnId ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
// Line deltas vs the previous snapshot's locked lines.
|
||||
const prev = await deps.cacheGet(`snapshot:${sp}:latest`);
|
||||
|
||||
Reference in New Issue
Block a user