47ada9013c
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>
62 lines
2.4 KiB
JavaScript
62 lines
2.4 KiB
JavaScript
/**
|
|
* Player headshot URL construction — PURE core (Wave 2A).
|
|
*
|
|
* CommonJS on purpose: importable by the `.ts` resolver (`playerHeadshot.ts`,
|
|
* allowJs) AND requireable by the plain-JS Jest suite — same doctrine as
|
|
* `vyndrTokens.js` / `playerName.js`. Keep the URL logic HERE so it stays
|
|
* genuinely unit-testable (jest can't transform the `.ts`).
|
|
*
|
|
* Each league hosts its own CDN. Fallback chain inside the resolver:
|
|
* 1. `cachedPhotoUrl` — a stored URL (soccer, where API-Football returns the
|
|
* photo). We DO NOT construct soccer URLs (no central CDN).
|
|
* 2. League CDN with `playerId` — official source.
|
|
* 3. ESPN CDN with `espnId` — NBA/WNBA (and dormant NFL/NHL) headshots.
|
|
* 4. `/images/player-silhouette.svg` — the sentinel the component swaps for a
|
|
* team-colored MONOGRAM (never a gray blob, never a broken image).
|
|
*/
|
|
|
|
const PLAYER_SILHOUETTE = '/images/player-silhouette.svg';
|
|
|
|
const ESPN_SPORT_PATH = {
|
|
nba: 'nba',
|
|
wnba: 'wnba',
|
|
mlb: 'mlb',
|
|
// Wave 2A — dormant leagues (not in ACTIVE_SPORTS + off-season). Cheap
|
|
// correctness: an ESPN athlete id ingested later resolves for free.
|
|
nfl: 'nfl',
|
|
nhl: 'nhl',
|
|
// ESPN soccer headshots are inconsistent across leagues — explicitly omit so
|
|
// soccer falls through to the silhouette unless a cached photo is provided.
|
|
};
|
|
|
|
function getHeadshotUrl(input) {
|
|
input = input || {};
|
|
const sport = String(input.sport || '').toLowerCase();
|
|
const playerId = input.playerId != null ? String(input.playerId) : '';
|
|
const espnId = input.espnId != null ? String(input.espnId) : '';
|
|
const cached = input.cachedPhotoUrl ? String(input.cachedPhotoUrl) : '';
|
|
|
|
if (cached) return cached;
|
|
|
|
if (playerId) {
|
|
switch (sport) {
|
|
case 'nba':
|
|
return `https://cdn.nba.com/headshots/nba/latest/260x190/${playerId}.png`;
|
|
case 'wnba':
|
|
return `https://cdn.wnba.com/headshots/wnba/latest/260x190/${playerId}.png`;
|
|
case 'mlb':
|
|
return `https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:67:current.png/w_213,q_auto:best/v1/people/${playerId}/headshot/67/current`;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (espnId && ESPN_SPORT_PATH[sport]) {
|
|
return `https://a.espncdn.com/combiner/i?img=/i/headshots/${ESPN_SPORT_PATH[sport]}/players/full/${espnId}.png&w=130&h=95`;
|
|
}
|
|
|
|
return PLAYER_SILHOUETTE;
|
|
}
|
|
|
|
module.exports = { PLAYER_SILHOUETTE, ESPN_SPORT_PATH, getHeadshotUrl };
|