Session 51: Complete Team Hub (2234 tests)
Research-depth team view: /team/[abbr] with roster, archetypes, stats, props.
- Team API: mlbStatsAdapter.getTeams/resolveTeam/getTeamRoster (statsapi, abbr→id
+ active roster, cached). teamService.getTeamHub assembles roster → per-player
season stats (bounded concurrency) + archetype (snapshot grade or classify) +
tonight's graded props from grades:{sport}; whole hub cached 15min. MLB real;
NBA/WNBA graceful snapshot roster. GET /api/team/:abbr (404 unknown) + proxy.
- Team Hub page: server page.tsx (generateMetadata) + TeamHub client — header,
sort (archetype/graded/A-Z), archetype filter chips, roster rows (archetype +
player link + position + stats + graded props + parlay "+"), "No active props"
greyed state, loading/error.
- Game cards: team abbreviations are now TeamLinks → /team/:abbr?sport= (green
hover, stops propagation). Team Hub has "← Back to Slate".
Backend 2215 -> 2234 tests (+19), 190 suites. Web build clean (exit 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -188,6 +188,42 @@ async function getPlayerStats(name, season = DEFAULT_SEASON) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All MLB teams (Session 51) → [{ id, abbr, name }]. Cached 24h. Used to map a
|
||||
* UI abbreviation ("NYY") to the statsapi team id.
|
||||
*/
|
||||
async function getTeams(season = DEFAULT_SEASON) {
|
||||
const url = `${BASE}/teams?sportId=1&season=${season}`;
|
||||
const data = await fetchWithCache(url, `mlbstats:teams:${season}`, 24 * 3600);
|
||||
const teams = (data && Array.isArray(data.teams)) ? data.teams : [];
|
||||
return teams.map((t) => ({ id: t.id, abbr: t.abbreviation || null, name: t.name || null }));
|
||||
}
|
||||
|
||||
/** Resolve a team abbreviation → { id, abbr, name } or null. */
|
||||
async function resolveTeam(abbr, season = DEFAULT_SEASON) {
|
||||
const a = String(abbr || '').toUpperCase();
|
||||
if (!a) return null;
|
||||
const teams = await getTeams(season);
|
||||
return teams.find((t) => String(t.abbr).toUpperCase() === a) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Active roster for a team id (Session 51) → [{ id, name, position, jersey }].
|
||||
* Cached 6h. [] on failure.
|
||||
*/
|
||||
async function getTeamRoster(teamId, season = DEFAULT_SEASON) {
|
||||
if (!teamId) return [];
|
||||
const url = `${BASE}/teams/${teamId}/roster?rosterType=active&season=${season}`;
|
||||
const data = await fetchWithCache(url, `mlbstats:roster:${teamId}:${season}`, 6 * 3600);
|
||||
const roster = (data && Array.isArray(data.roster)) ? data.roster : [];
|
||||
return roster.map((r) => ({
|
||||
id: r.person?.id ?? null,
|
||||
name: r.person?.fullName ?? null,
|
||||
position: r.position?.abbreviation ?? null,
|
||||
jersey: r.jerseyNumber ?? null,
|
||||
})).filter((p) => p.id && p.name);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getScheduleWithPitchers,
|
||||
getPlayerGameLog,
|
||||
@@ -195,5 +231,8 @@ module.exports = {
|
||||
getBatterVsPitcher,
|
||||
searchPlayer,
|
||||
getPlayerStats,
|
||||
getTeams,
|
||||
resolveTeam,
|
||||
getTeamRoster,
|
||||
__internals: { BASE, TTL, extractSplits, ymd, DEFAULT_SEASON, normName },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user