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:
Kev
2026-06-18 11:12:24 -04:00
parent 32069863dc
commit 8bc79f3c38
33 changed files with 2655 additions and 22 deletions
+38 -1
View File
@@ -86,7 +86,44 @@ function mapScanToGradeResult(input = {}) {
altLadder: includeAlt && Array.isArray(input.alt_lines)
? input.alt_lines.map((a) => ({ line: a.line, grade: a.grade }))
: [],
// Session 42 — Player Intelligence additions. All OPTIONAL + self-hiding:
// they only render once the engine supplies them (Session 43 data pipeline).
...buildIntelFields(input),
};
}
module.exports = { mapScanToGradeResult, statLabel, computeEdge, isPhosphorConfirmed, toSignals };
/**
* Build the optional archetype / stat-context / vyndr-intelligence fields for
* the grade card from whatever the engine provided. Returns {} when nothing is
* present so the card sections stay hidden (no empty boxes).
*/
function buildIntelFields(input) {
const out = {};
if (Array.isArray(input.archetype_blend) && input.archetype_blend.length) {
out.archetypeBlend = input.archetype_blend;
} else if (input.archetype) {
out.archetypeBlend = [{ archetype: String(input.archetype), weight: 1 }];
}
if (input.prop_dna && (input.prop_dna.reliable || input.prop_dna.volatile)) {
out.propDNA = {
reliable: input.prop_dna.reliable || [],
volatile: input.prop_dna.volatile || [],
};
}
const sc = {};
if (input.season_avg != null) sc.season = String(input.season_avg);
if (input.last10_avg != null) sc.last10 = String(input.last10_avg);
if (input.vs_opp_avg != null) sc.vsOpp = String(input.vs_opp_avg);
if (Object.keys(sc).length) out.statContext = sc;
const vi = {};
if (input.form != null) vi.form = input.form;
if (input.usage != null) vi.usage = String(input.usage);
if (input.matchup_grade != null) vi.matchup = String(input.matchup_grade);
if (input.rest != null) vi.rest = String(input.rest);
if (Object.keys(vi).length) out.vyndrIntel = vi;
return out;
}
module.exports = { mapScanToGradeResult, statLabel, computeEdge, isPhosphorConfirmed, toSignals, buildIntelFields };