Session 42: Player Intelligence System — archetypes, stat strips, player profile, enhanced cards (2011 tests)
Built from the Claude Design "VYNDR Player Intelligence" bundle (10 sections). - Archetypes: src/services/archetypeService.js — 41 archetypes (15 NBA / 5 WNBA-unique / 15 MLB / 6 soccer), classify -> primary+secondary+blend. Frontend visual map web/src/lib/archetypes.js (colors verified == backend). ArchetypeBadge (full/ghost/tint + glyphs) + ArchetypeBlend (DNA bar). - StatStrip (compact/expanded): player name once, horizontal mono stats, inline GradeBadge props, onPlayerClick -> profile. - Stats API: extended src/routes/stats.js with /player/:name, /leaders, /game/:id (rate-limited). Aggregation in playerIntelService.js (sanitizes name param; grades cache; graceful on cold cache). Next proxies added. - Player Profile /player/[name]: all 9 design sections, graceful empty states. - Enhanced GameCard (MLB pitchers + player-grouped StatStrips) + GradeResultCard (archetype strip + stat context + VYNDR intelligence, optional/self-hiding via gradeAdapter.buildIntelFields). Player-name links wired everywhere. - Settings page replaces the S41 redirect (account/subscription/notifications/ display/responsible-play/danger-zone with DELETE-gated delete). LINKS to the real /settings/security MFA page — does not replace it. + BookChip. - Bonus: Stats Explorer /explore (real /api/stats/leaders leaderboard); added Explore + Settings to Nav MORE. Deferred (need data pipelines, Session 43): Team Hub, Offseason Intel, Slate redesign, Stats Explorer sub-panels. Backend 1940 -> 2011 tests (+71), 157 suites. Web build clean (exit 0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
const express = require('express');
|
||||
const { getSupabaseServiceClient } = require('../utils/supabase');
|
||||
const { getStatFilters } = require('../config/statFilters');
|
||||
const { createRateLimit } = require('../middleware/rateLimit');
|
||||
const { getPlayerIntel, getLeaders } = require('../services/playerIntelService');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const MISSION_HEADER = { 'X-VYNDR-Mission': 'Kill bad satisfieds before they satisfieds you' };
|
||||
|
||||
// Player-intelligence endpoints are public + cache-backed (Session 42). Cap
|
||||
// per-IP like the other public cached routers (60/min).
|
||||
const intelLimit = createRateLimit({ windowMs: 60_000, max: 60 });
|
||||
|
||||
// GET /filters/:sport — stat-filter categories for the StatFilterPills UI
|
||||
// (Session 23). Lets the frontend stay data-driven without re-declaring
|
||||
// the category list. NO auth / NO DB — pure config.
|
||||
@@ -117,4 +123,46 @@ router.get('/live', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /player/:name?sport=nba — full player intelligence payload (Session 42):
|
||||
// standard stats + archetype DNA + VYNDR intelligence + tonight's graded props.
|
||||
// Name is sanitized inside the service. Always 200 with a valid shape (degrades
|
||||
// gracefully on cold caches) — the profile page must never hard-fail.
|
||||
router.get('/player/:name', intelLimit, async (req, res) => {
|
||||
try {
|
||||
const sport = String(req.query.sport || 'nba').toLowerCase();
|
||||
const data = await getPlayerIntel(req.params.name, sport);
|
||||
res.set(MISSION_HEADER).json(data);
|
||||
} catch (err) {
|
||||
console.error('[stats/player]', err.message);
|
||||
res.status(503).set(MISSION_HEADER).json({ error: 'Service temporarily unavailable' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /leaders?sport=mlb&stat=hits&limit=10 — tonight's stat leaders (top
|
||||
// graded props by confidence) for the Terminal / Stats Explorer.
|
||||
router.get('/leaders', intelLimit, async (req, res) => {
|
||||
try {
|
||||
const sport = String(req.query.sport || 'nba').toLowerCase();
|
||||
const leaders = await getLeaders(sport, { stat: req.query.stat, limit: req.query.limit });
|
||||
res.set(MISSION_HEADER).json({ sport, stat: req.query.stat || null, leaders });
|
||||
} catch (err) {
|
||||
console.error('[stats/leaders]', err.message);
|
||||
res.status(503).set(MISSION_HEADER).json({ error: 'Service temporarily unavailable' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /game/:id?sport=nba — game-level enrichment (pitchers, injuries, leaders,
|
||||
// team records) via the ESPN summary (Session 30). Best-effort.
|
||||
router.get('/game/:id', intelLimit, async (req, res) => {
|
||||
try {
|
||||
const sport = String(req.query.sport || 'nba').toLowerCase();
|
||||
const { getGameSummary } = require('../services/scheduleService');
|
||||
const summary = await getGameSummary(sport, req.params.id);
|
||||
res.set(MISSION_HEADER).json(summary || { error: 'not_found' });
|
||||
} catch (err) {
|
||||
console.error('[stats/game]', err.message);
|
||||
res.status(503).set(MISSION_HEADER).json({ error: 'Service temporarily unavailable' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user