Session 48: Name normalization at every layer + usage field (2156 tests)

Trace-first: the normalizer functions were correct (S47) but raw names still
flowed through paths that skipped them. Fixed each leaking path.

- 2a (source chokepoint): snapshotService.runSnapshot normalizes each grade's
  player to the de-dotted display AND dedupes to one grade per nameKey|stat
  (highest confidence) before writing grades:{sport} + snapshot:latest. Every
  consumer (GameCard, Explore, leaders, profile) now gets clean merged names.
- 2b: buildPlayerStripsFromProps dedupes a player's props by stat (graded >
  awaiting) → one row per stat (kills "Ks 5.5 AND Ks 3.5" variant dupes).
- 2c: scan tonightsPlayers grid groups by nameKey, displays normalized name.
- 3: profile VYNDR INTELLIGENCE "+0%"/"—" was buildIntel's defaults (separate
  from the grade card's buildIntelFields, which already works). resolvePlayerStats
  now attaches real usage (AB/G) + rest (B2B/Xd); buildIntel renders them; REST
  default is now "—".

Backend 2149 -> 2156 tests (+7), 181 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-19 02:42:13 -04:00
parent 78db55d499
commit 91b03c4044
10 changed files with 259 additions and 15 deletions
+19
View File
@@ -493,6 +493,25 @@ snapshot, locked to the line, and read from cache.
moderate). If you re-tune archetype thresholds, remember season totals are
partial mid-season — don't use full-season cutoffs.
## Normalization Chokepoints (Session 48 — non-obvious)
- **The snapshot is the normalization SOURCE.** `snapshotService.runSnapshot`
normalizes every grade's player to `normalizeName().display` AND dedupes to one
grade per `nameKey|stat_type` (highest confidence) before writing
`grades:{sport}` + `snapshot:{sport}:latest`. So GameCard overlay, Explore,
leaders, and profile activeProps ALL inherit clean, merged names — don't add
per-consumer normalization, fix it here.
- **Three paths that needed it (all fixed):** snapshot grades (above);
`buildPlayerStripsFromProps` dedupes a player's props by stat (graded > awaiting);
scan `tonightsPlayers` groups by `nameKey`. If a NEW surface lists players,
group by `nameKey` + display `normalizeName().display`.
- **TWO intel renderers — don't confuse them:** the GRADE CARD (scan) uses
`analyzeViaEngine1.buildIntelFields(features)`; the PLAYER PROFILE uses
`playerIntelService.buildIntel(stats)`. The "+0%"/"—" bug was the PROFILE's
defaults — fixed by `resolvePlayerStats` attaching real `usage` (AB/G) + `rest`
and `buildIntel` reading them. The grade card already worked (the full feature
merge carries ab_per_game/rest_days; `FEATURE_NAMES` is meta bookkeeping only,
NOT a whitelist that filters the vector).
## Active Skills
- vyndr-voice (all user-facing output)
- prop-analysis (grading methodology)