Files
vyndr/BACKEND_HANDOFF.md
T
builtbykev 7969a4971a Session 44: Make it visible — VYNDR archetype names, grade intel, schedule fix, landing page (2061 tests)
Frontend + wiring only. Wires existing backend into the pages users see.

- VYNDR Original archetype rename (41) across archetypeService.js + lib/
  archetypes.js + ArchetypeBadge, each keeping legacyName (resolves stale data).
  Judge -> BOMBER. Old POWER PULL slot -> WHIFF strikeout-artist pitcher.
- BACKEND_HANDOFF.md: canonical frontend<->backend data contract.
- Grade card intel: scan/page.tsx now forwards the engine's intel fields
  (season_avg/form/usage/matchup_grade/archetype/...) into mapScanToGradeResult
  -> STAT CONTEXT + VYNDR INTELLIGENCE sections populate. The chain already
  preserved them (tierGating + /api/scan spread); the page was dropping them.
- Schedule freshness: slateAdapter.isRelevantGame drops completed games >24h
  old; Slate.filteredGames applies it. (TTL already 60s.)
- Landing: Features.tsx rewritten to user-facing copy (no Point-biserial/Zone
  14/ABS/Phi-coefficient).
- Depth chart Next proxies added (/api/stats/lineup|depth|cascade) - were 404.
- GameCard swap DEFERRED (Kev): legacy on-demand card stays as a bridge until
  the snapshot pipeline populates the grades cache; vyndr/GameCard swaps in then.

Backend 2045 -> 2061 tests (+16), 167 suites. Web build clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 20:15:38 -04:00

5.9 KiB
Raw Blame History

VYNDR — Backend / Frontend Data Contract (BACKEND_HANDOFF.md)

Canonical frontend↔backend data contract for the Player Intelligence System. Every future session references this. Authored Session 44 from the shipped implementation (Sessions 4244). When an endpoint or component shape changes, update this file in the same commit.

Naming: archetypes are VYNDR Originals (TORCH, BOMBER, ALPHA, …). The old descriptive labels live on each archetype as legacyName and are NEVER shown.


1. Archetypes (src/services/archetypeService.js)

classify(sport, stats) → { sport, primary, secondary|null, blend }

primary | secondary : {
  name, legacyName, tag, sport, color (#hex), glyph (key),
  description, propDNA: { reliable: string[], volatile: string[] }, education
}
blend : [{ archetype: <NAME>, weight: 0..1 }]   // normalized, top 4, sums ~1

Roster (41): see ARCHETYPES registry. Colors are unique within a sport, reused across sports. Frontend visual map (color/glyph/desc) is mirrored in web/src/lib/archetypes.js — keep colors identical (a test enforces it).

  • NBA (15): TORCH, CONDUCTOR, FORTRESS, ARTILLERY, SURGE, DUAL THREAT, CONNECTOR, FASTBREAK, PAINT BOSS, LOCKDOWN, SWITCHBOARD, ARCHITECT, PISTON, SENTINEL, IGNITER
  • WNBA-unique (5): DISTRIBUTOR, SHIELD, RANGE, SPARK, ANCHOR (plus reused NBA)
  • MLB (15): BOMBER, BRUSH, DRIVER, ALPHA, WHIFF, GHOST, HYBRID, FLEX, WORKHORSE, CATALYST, MIRROR, HAMMER, SINKER, BRIDGE, SWITCH
  • Soccer (6): FINISHER, MAESTRO, TOWER, MOTOR, BLADE, WALL

Classifier input shapes:

  • NBA/WNBA: { ppg, rpg, apg, bpg, spg, threes, usg, fg3a, pos, bench }
  • MLB hitter: { avg, hr, rbi, sb, ops, runs, k_rate, doubles }
  • MLB pitcher: { era, k9, whip, ip_per_start, saves, role: 'SP'|'RP'|'CL' }

2. Stats API (src/routes/stats.js, mounted at /api/stats)

All player-intelligence endpoints are public, rate-limited 60/min, and ALWAYS return 200 with a valid (possibly empty) shape — the UI must never hard-fail. Browser must hit the Next proxy under web/src/app/api/stats/..., never Express directly.

GET /api/stats/player/:name?sport=nba

{
  player, sport, team, found: boolean,
  archetype: { primary, secondary, blend },     // §1
  propDNA: { reliable, volatile },
  education: string,
  season:  [{ k, v, lg? }],                      // display rows (mono)
  last10:  [{ d, opp, res?, stat }],
  splits:  [{ k, a, b }],
  gradeHistory: [{ grade, prop, hit?, miss? }],
  activeProps:  [{ stat, line, side: 'O'|'U', grade, confidence }],
  intel: [{ label, kind: 'form'|'grade'|'plain', value, score?, color }],
  injury: { label, note, cascade } | null
}

found is true when real season stats OR tonight's graded props exist. MLB stats come from mlbStatsAdapter.getPlayerStats(name); NBA/WNBA from nbaStatsClient (degrades to found:false when the Python service is offline).

GET /api/stats/leaders?sport=mlb&stat=hits&limit=10

{ sport, stat|null, leaders: [{ player, team, stat, line, side, grade, confidence }] }

Source: the grades:{sport} cache (tonight's graded slate, by confidence desc).

GET /api/stats/game/:id?sport=nba

ESPN game summary (injuries, leaders, ESPN Bet odds, box score) or {error}.

GET /api/stats/lineup/:team?sport=mlb (Session 43)

{ sport, team, lineup: [{ player, position, battingOrder, projectedMinutes }] } MLB returns the probable starting pitcher from the schedule.

GET /api/stats/depth/:team?sport=nba

{ sport, team, positions: [{ position, starter, backup, thirdString }] }

GET /api/stats/cascade/:player?sport=nba&team=SA

{ sport, player, cascade: [{ player, stat, delta: '+x%', reason }] } Usage redistribution weighted by archetype (SURGE benefits most).


3. Grade Result Card (web/src/components/vyndr/GradeResultCard.tsx)

GradeResultData (built by web/src/lib/gradeAdapter.js mapScanToGradeResult): core fields (player, team, sport, stat, line, side, grade, confidence, edge, projection, signals[], killConditions[], books[], altLadder[]) plus optional Player-Intelligence fields that self-hide when absent:

archetypeBlend?: [{ archetype, weight }]
propDNA?:        { reliable: string[], volatile: string[] }
statContext?:    { season?, last10?, vsOpp? }
vyndrIntel?:     { form?, usage?, matchup?, rest? }

The engine (analyzeViaEngine1) attaches snake_case fields (season_avg/last10_avg/form/usage/matchup_grade/rest, and when a season line is available, archetype/archetype_blend/prop_dna); the adapter maps them in. Archetype at grade time requires a multi-stat season line — until the snapshot pipeline supplies it, the archetype strip stays hidden.


4. Game Card (web/src/components/vyndr/GameCard.tsx)

GameCardData core (id, sport, away/home, time, venue, lines[], …) plus:

playerStrips?: [{ player, team, archetype?: {primary, secondary?}, stats: [{label,value}], props: [{stat,line,side,grade}] }]
pitchers?:     { away: {name, era, archetype?}, home: {name, era, archetype?} }

Built by slateAdapter.mapScheduleToGameCards (groupPropsByPlayer + mapPitchers). The card prefers playerStrips (name once, horizontal) over legacy per-prop rows. Book chips use web/src/lib/books.js brand colors.


5. Stat Strip (web/src/components/vyndr/StatStrip.tsx)

compact (game cards) | expanded (profile hero / grade result). HARD RULE: the player name appears ONCE; stats flow horizontally in JetBrains Mono. Props render inline with GradeBadge. onPlayerClick/player/:name?sport= (lib/playerHref.js).


6. Freshness & caching

  • Schedule cache TTL ≤ 30 min (scheduleService). Frontend filters completed games older than 24h out of the slate (slateAdapter.isRelevantGame).
  • Player props: PropLine primary (3-key rotation), The-Odds-API backup.
  • grades:{sport} cache (TTL 2h) is written by gradeSlateService on a fresh odds fetch; it feeds /leaders + the player card's activeProps.