7efb04e280ceaa9712cb418cb06060dc09f55a61
1 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f7cc19772b |
Canonical MLB participant: prove the human, then dedupe
VYNDR had three definitions of "same player": raw p.player (dedupe), utils/normalize.normalizeName (features, NO nicknames), utils/playerName.nameKey (retention, WITH nicknames). Retention was the first layer to notice they disagreed, and all it could do was discard the loser — 28 collisions. The fix is not a better spelling. The event-scoped roster ALREADY carries the StatsAPI personId; buildPlayerTeamIndex was fetching and discarding it. SCOPE IS LOAD-BEARING, and this is the finding that shaped the design. Measured on the real 2026-08-28 league rosters (1,583 rows, 30 teams): a bare name key is NOT globally unique — `max muncy` (ATH 691777 / LAD 571970), `jose fermin` (665877/820862) and `luis garcia` (472610/671277) each resolve to TWO different humans. Team-scoped: 0 ambiguous. Event-scoped across 33 real events including the verified doubleheader date: 0 ambiguous. So resolution is scoped to the two teams actually playing, and FAILS CLOSED — no event, no game, no candidate, or more than one candidate yields null and the prop keeps prior behaviour. FEATURE IDENTITY PARITY, measured on both real cohorts before writing the patch (504 participants): RAW_SUCCESS/CANONICAL_SUCCESS/SAME id 365 RAW_SUCCESS/CANONICAL_SUCCESS/DIFFERENT id 0 <- required 0 RAW_SUCCESS/CANONICAL_FAIL 0 <- required 0 CANONICAL_AMBIGUOUS 0 <- required 0 RAW_FAIL/CANONICAL_SUCCESS 7 <- repair, not regression The seven improvements are exactly the alias class: Michael->Mickey Gasper, AJ->A.J. Ewing, JT->J.T. Realmuto, Mike->Michael Busch, Richard->Richie Palacios. This is why raw spelling could not be trusted: player_id_map holds `mickey gasper` and NOT `michael gasper`, so under the raw name the lookup outcome depended on which alias happened to survive dedupe. Provider arrival order was deciding model input availability. ALL SEVEN COLLISION GROUPS PROVEN SAME_PLAYER_ALIAS against the authoritative roster for the exact event date — one personId each (681715, 699625, 676356, 695491, 673357, 681508, 671739), zero false normalizations, zero unresolved — and all seven collapse to one participant under the new key, so the 14 colliding propositions merge upstream instead of being discarded downstream. FALSE-MERGE SIMULATION on both cohorts: cross-event 0, different-stat 0, different-line 0, false merges 0. Note honestly: the retained rows cannot exhibit the merges themselves, because retention already discarded the losers — so the merge half is proven directly on the alias groups, the safety half on the cohorts. RETENTION IS UNTOUCHED — schema, player_key, conflict identity and the collision counter are all unchanged. collision_count must reach 0 by upstream repair, never by making the counter lenient. A teeth proof injects raw spelling into the retention identity and the suite rejects it. Source provenance preserved: p.player is never overwritten; the participant rides beside it as mlb_person_id + canonical_player_name. Twelve teeth against a green baseline of 117 — dedupe back to raw name (3), feature lookup back to the alias (2), resolver stops blocking ambiguity (1), lookup degraded to raw (1), event scope dropped (1), event/line/stat dropped from the key (2/2/2), provenance destroyed (1), raw spelling in retention identity (2), collision_count accepted nonzero (2), and a guard proving the frozen game-date repair cannot be reverted (10). Restored byte-identically. Teeth #4 (canonical resolving to a DIFFERENT feature id) is covered by the real-data parity measurement rather than an injected branch, because it is a data comparison, not a code path — stated plainly rather than claimed as a test. No hardcoded player names in production code — a test greps for all seven. 4 files, 86 insertions. retentionService, gameBinder, oddsService, acquisitionTrace, probabilityEstimator, bookRoles, playerName, normalize and snapshotScheduler: UNCHANGED. Admission rules, MODEL_BOOK filters and model formulas: 0 changed lines. The game-date repair is intact. 391 suites / 5,339 tests pass. web tsc exit 0. Lineage stays OFF. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CQJeAG8vcDoL5zkiaJyVb8 |