Session 46: Grade card intel + name normalization + pitchers (2122 tests)

Three focused P1 fixes on the Session-45 snapshot model.

- Grade card intel ROOT CAUSE: gameLogService is NBA/WNBA-only (offline Python),
  so MLB props never got l5_avg/l20_avg and buildIntelFields returned {}. Wired
  MLB game logs into featureCache.gameLogFeatures via mlbStatsAdapter.getPlayerStats
  (pure mlbGameLogFeatures + MLB stat_type->field map). buildIntelFields gained
  playerStats/projection fallbacks for partial intel.
- Player name normalization: src/utils/playerName.js (+ web/src/lib copy):
  normalizeName -> {display,key}. Strips periods, de-dots suffix, accent-folds
  the key. Applied in snapshotService grouping, slateAdapter grade index +
  player-strip merge (variants collapse, longest name shown), and
  playerIntelService. "A.J. Ewing"/"AJ Ewing" + "Jazz Chisholm"/"Jr." now merge.
- MLB starting pitchers: new GET /api/schedule/:sport/pitchers (probablePitchers
  service wrapping mlbStatsAdapter.getScheduleWithPitchers + best-effort ERA).
  Slate fetches it, builds a team->pitcher map (full name + mascot match),
  attaches pitchers to MLB GameCardData. + Next proxy.

Backend 2100 -> 2122 tests (+22), 176 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 23:56:26 -04:00
parent f8b120c0aa
commit c8fc9f577e
20 changed files with 608 additions and 32 deletions
+23
View File
@@ -450,6 +450,29 @@ snapshot, locked to the line, and read from cache.
- **Env:** PROPLINE_API_KEY_1/2/3, VYNDR_INTERNAL_KEY, SNAPSHOT_CRON=1,
SNAPSHOT_HOURS_UTC (optional), TICKER_MANUAL (JSON array).
## Grade Intel + Name Norm + Pitchers (Session 46 — non-obvious)
- **Grade-card intel root cause:** `buildIntelFields` reads l5_avg/l20_avg/
opp_rank_stat/rest_days from the feature vector. `gameLogService.getGameLogs`
is NBA/WNBA-ONLY (offline Python service) → MLB props had NO recent/season
averages → intel always empty. FIX: `featureCache.gameLogFeatures` has an MLB
branch using `mlbStatsAdapter.getPlayerStats` + the pure `mlbGameLogFeatures`
(MLB stat_type→game-log field via `MLB_LOG_FIELD`). If you add an MLB stat
type, add it to `MLB_LOG_FIELD` or its intel won't compute. `buildIntelFields`
also takes `{ playerStats, projection }` fallbacks — don't remove the resilience.
- **Player name normalization:** `src/utils/playerName.js` is the source of truth
(frontend copy at `web/src/lib/playerName.js` — keep them identical; a test
cross-checks). `normalizeName(raw)→{display,key}`: display strips periods +
de-dots suffix (keeps accents); key accent-folds + suffix-strips for comparison.
Used in snapshotService grouping, slateAdapter grade-index + player-strip
merge, and playerIntelService. `sanitizePlayerName` now returns the de-dotted
display ("A.J. Ewing"→"AJ Ewing") — tests that asserted the dotted form were
updated.
- **MLB pitchers:** the ESPN `/api/schedule` has NO probable pitchers. They come
from `GET /api/schedule/:sport/pitchers` (MLB) → `probablePitchers` service →
`mlbStatsAdapter.getScheduleWithPitchers`. The Slate matches them to games by
team (full name OR mascot via `slateAdapter.buildPitcherMap`/`pitchersForGameTeams`).
ERA is best-effort (season stats per pitcher id, cached).
## Active Skills
- vyndr-voice (all user-facing output)
- prop-analysis (grading methodology)