Session 43: Data pipeline + audit fixes + depth chart foundation (2045 tests)

P0 fixes + wiring real data into the S42 Player Intelligence architecture.

- P0 dropdown z-index: the nav's backdrop-filter stacking context let the
  Ticker/HeartbeatBar paint over the avatar/More dropdowns and eat clicks.
  nav now position:relative zIndex:2; menus zIndex:100. Avatar Settings ->
  /settings.
- Real MLB stats: mlbStatsAdapter.searchPlayer + getPlayerStats (name->id->
  season+gamelog). playerIntelService.resolvePlayerStats normalizes into the
  archetype classifier; getPlayerIntel returns found:true + real season +
  archetype classified from real stats. NBA via nbaStatsClient (degrades).
- Game cards: slateAdapter.groupPropsByPlayer (playerStrips, name once) +
  mapPitchers (MLB probables), folded into mapScheduleToGameCards. Legacy
  GameCard line grid renders BookChip (brand colors) not grey text.
- Grade card intel: analyzeViaEngine1.buildIntelFields computes stat-context +
  form/usage/matchup/rest from the existing feature vector (zero extra I/O);
  gradeAdapter lights up the card sections. Archetype deferred (needs season
  line at grade time).
- Depth chart foundation: depthChartService (getLineup/getDepthChart/
  getCascadeProjection) + /api/stats/lineup|depth|cascade, graceful + injectable.
- Mobile: player hero name overflow-wrap + 24px on <=640px (was clipping).

Backend 2011 -> 2045 tests (+34), 163 suites. Web build clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-06-18 15:38:06 -04:00
parent 8bc79f3c38
commit 80683e71b4
19 changed files with 940 additions and 18 deletions
+62 -1
View File
@@ -129,10 +129,71 @@ async function getBatterVsPitcher(batterId, pitcherId, group = 'hitting') {
return splits.length > 0 ? (splits[0].stat || null) : null;
}
const normName = (n) => String(n == null ? '' : n).toLowerCase().replace(/[^a-z0-9]/g, '');
/**
* Resolve a player name → MLB person record (Session 43). Pulls the season
* player list (cached 24h — heavy but rarely changes) and matches by
* normalized full name. Returns { id, fullName, team, teamId, position } or
* null. Needed because every other adapter method keys on playerId.
*/
async function searchPlayer(name, season = DEFAULT_SEASON) {
const target = normName(name);
if (!target) return null;
const url = `${BASE}/sports/1/players?season=${season}`;
const data = await fetchWithCache(url, `mlbstats:players:${season}`, 24 * 3600);
const people = (data && Array.isArray(data.people)) ? data.people : [];
const hit = people.find((p) => normName(p.fullName) === target)
|| people.find((p) => normName(p.fullName).includes(target) && target.length >= 6);
if (!hit) return null;
return {
id: hit.id,
fullName: hit.fullName ?? name,
team: hit.currentTeam?.name ?? null,
teamId: hit.currentTeam?.id ?? null,
position: hit.primaryPosition?.abbreviation ?? null,
};
}
/**
* Name-keyed convenience: resolve the player, then fetch the season stat
* object for the right group (pitching for pitchers, hitting otherwise) plus a
* recent game log. Returns { found, id, name, team, position, group, season,
* last10 } — `season` is the raw MLB stat object, mapped by the caller. Returns
* { found: false } on any miss/failure (never throws).
*/
async function getPlayerStats(name, season = DEFAULT_SEASON) {
try {
const person = await searchPlayer(name, season);
if (!person) return { found: false };
const group = person.position === 'P' ? 'pitching' : 'hitting';
const [seasonStat, log] = await Promise.all([
getSeasonAverages(person.id, season, group),
getPlayerGameLog(person.id, season, group),
]);
if (!seasonStat) return { found: false, id: person.id, name: person.fullName, team: person.team, position: person.position, group };
return {
found: true,
id: person.id,
name: person.fullName,
team: person.team,
position: person.position,
group,
season: seasonStat,
last10: (log || []).slice(-10),
};
} catch (err) {
console.warn('[mlbStats] getPlayerStats failed:', name, err.message);
return { found: false };
}
}
module.exports = {
getScheduleWithPitchers,
getPlayerGameLog,
getSeasonAverages,
getBatterVsPitcher,
__internals: { BASE, TTL, extractSplits, ymd, DEFAULT_SEASON },
searchPlayer,
getPlayerStats,
__internals: { BASE, TTL, extractSplits, ymd, DEFAULT_SEASON, normName },
};