37 — Identity Reconciliation Fix (draft → game link rate)
37 — Identity Reconciliation Fix (draft → game link rate)
Date: 2026-05-30
Scripts: scripts/reconcile_identities_v2.py (merge engine) and
scripts/build_reconcile_worklist.py (worklist builder)
Outputs: data/processed/draft_picks_reconciled.csv,
identity_merges.csv, canonical_id_remap.csv, identity_ambiguous.csv,
reconcile_worklist.csv
TL;DR
The low draft→game link rate was a reconciliation bug, not reality. The
original pipeline (scripts/reconcile_identities.py) gave every source its own
canonical_id and only bridged across sources via discord stems and exact real
names. For NSBA1 (nickname-only games) and the abbreviated-name NSBA3 game box,
the same human ended up with two different canonical_ids — one carrying the
draft real name, one carrying the in-game nickname — and they never linked.
After adding five new bridges and merging 43 canonical_id pairs, the link rates improved:
| Season | OLD (linked / picks) | NEW | Notes |
|---|---|---|---|
| nsba1_2022 | 14/72 (19%) | 31/72 (43%) | nickname-only; hardest case |
| nsba2_2023 | 60/60 (100%) | 60/60 (100%) | already clean (discord tags) |
| nsba3_2025 | 55/84 (65%) | 65/84 (77%) | real names + discord; mostly fixed |
(The "12/72 = 16%" David quoted counts game-box records only; counting win_shares or player_season_master gives 14/72 as the pre-fix baseline.)
The remaining NSBA1 gap is not mostly a bug: the unlinked NSBA1 picks largely played under an unrelated in-game nickname (no algorithmic bridge exists) or did not play. Those, plus a handful of genuine ambiguities, are in the worklist for David to confirm by hand.
Root cause (per season)
- NSBA1 (nickname-only): game box uses in-game nicknames (
arolakiv,cryo,VulcanForge,anonymouscapybara…). Draft + combine use real names. The combine workbook tab names are sometimes the nickname (Rosetta stone) but for ~25 entrants the tab is just the player's first/real name, so no nickname↔realname bridge exists in any data we hold. The NSBA1 discord export does not exist (only NSBA4 is indata/raw/discord/). - NSBA2: game
player_rawcarries the fullname#dddddiscord tag → already linked at 100%. No work needed. - NSBA3: game box uses real names, but abbreviated (
Ishaan A,Anurag S) or bare first names (evan,daniel,Ryan). Draft/combine use full real names + discord. The original first-name-only fuzzy matcher refused to merge anything ambiguous, leaving 29 picks unlinked.
A second, subtler bug surfaced for NSBA3: several bare-first-name game ids are
shared by 2+ humans (one evan id spans teams limitless and no like blue;
one daniel spans okc and the other team; one Ryan spans lebron and
usmic). These are NOT safe to merge — they need a split, which only David
can do. They are flagged as ambiguous_collision in the worklist.
Bridges added (and merges made)
| Bridge | Reason code | # merges | What it does |
|---|---|---|---|
| A | nsba3_overall |
16 | NSBA3 combine Overall sheet pairs real name ↔ discord tag; links game/draft/scibowl ids that share either |
| B | nsba1_rosetta |
10 | NSBA1 combine tab handle == in-game nickname ↔ real name in the sheet's Name cell (e.g. VulcanForge→Isaac Zhu, anonymouscapybara→Alan Kappler, tiberius→Nathan Zhang, thunderbolt190→Harish Premkumar) |
| C | team_fuzzy |
7 | NSBA1 team-scoped fuzzy: draft team ↔ game team derived by name voting, then drafted real name ↔ game nickname within the pair (e.g. Aditya Sivakumar→AdiyaSivahuma, Andrey Nikitin→ANdrey, Viraj Negandhi→Viraj) |
| D | first_last_initial |
5 | game First L ↔ a single full-name First Last… (e.g. Daniel S→Daniel Sun, Anurag S→Anurag Sodhi) |
| E | team_name_nsba23 |
5 | NSBA3 team bridge (draft_team_bridge.csv) restricts the candidate pool to ONE draft team, resolving first-name records only when unambiguous and single-team (e.g. Sanjay Oruganti, Michael Wei, Ishaan Kabra, Sean Fei→Sean Yu, Kevin Qiu) |
Plus shared-discord-tag (discord_tag) and exact multiword real-name
(exact_realname) bridges that the original engine already had (re-applied here
for completeness; they produced 0 new cross-id merges beyond what existed).
Total: 43 canonical_id pairs merged → 646 ids collapse to 603.
Full list in data/processed/identity_merges.csv.
Conservatism guards (no false merges)
- A bare-first-name game id that appears on >1 team is treated as multiple
humans and is never auto-merged (→
ambiguous_collisionworklist). - NSBA3 team-name merges require exactly one team-local draft candidate.
- NSBA1 team-fuzzy uses greedy 1-to-1 assignment with a score floor so two drafted players can't both grab the same game record.
- Multi-candidate first+last-initial cases (two
Daniels, twoIshaans) go to the worklist rather than guessing.
How to apply downstream
canonical_id_remap.csv (old_id → new_id) is the single source of truth. Any
downstream table keyed by canonical_id should be remapped through it. The
draft join should use draft_picks_reconciled.csv (adds canonical_id
re-resolved + linked_game). The merges have NOT been folded back into
canonical_players.csv to avoid silently rewriting every dependent artifact;
remap explicitly when you rebuild.
Worklist for David — data/processed/reconcile_worklist.csv
52 rows, sorted by importance (early draft picks & high win-share players first). Three kinds:
1. ambiguous_collision (4) — DO THESE FIRST (data-quality risk)
One game canonical_id is shared by multiple drafted humans; confirm the split.
| game id | name | teams | combined WS | split into |
|---|---|---|---|---|
| P0228 | evan | limitless / no like blue | 5.59 | Evan Zhang (limitless) + Evan L (no like blue) |
| P0166 | Connor | chonk | 2.13 | Connor Chang? (vs Connor Zhao) |
| P0499 | Ryan | lebron / usmic | 0.68 | Ryan Eto (lebron) + Ryan Karim (usmic) |
| P0179 | daniel | okc / the other team | 0.47 | Daniel Y (okc) + Daniel Wang (the other team) |
2. draft_unlinked (38) — early picks with no game record
Each has best-guess game candidate(s) + a confidence. Highest-value:
| season | pick | drafted player | best guess | conf |
|---|---|---|---|---|
| nsba3 | 3 | Sanjay Suresh | sanjay [r-salam] (the other Sanjay is Oruganti) | medium |
| nsba3 | 10 | Connor Chang | Connor [chonk] WS=1.3 | medium |
| nsba3 | 24 | Evan L | evan [no like blue] WS=1.5 | high |
| nsba3 | 17 | Ryan Eto | Ryan [lebron] WS=0.7 | medium |
| nsba3 | 25 | Daniel Wang | daniel [the other team] | medium |
| nsba3 | 57 | Daniel Y | daniel [okc] WS=0.2 | high |
| nsba1 | 6 | Bald | rbald02 [con1596] | medium |
| nsba1 | 18 | Justin Chen | (Stitch)^2 / just / jucijuce | low |
| nsba1 | 23 | Daniel Mousavi | dc1mousavi [nearest airport] | medium |
| nsba1 | 55 | Elyas Nuh | Elyas [devansh] | medium |
| nsba1 | 54 | Allison Eto | Allusion [Bellevue]* (*flagged — Allusion may = Nihal) | medium |
Many other NSBA1 early picks (Gid, Ben, Yucc, Jonathan Huang, Caleb, Srijan Oduru, Atharva, Saheb Gulati, William Huang…) have no candidate — they either played under an unrelated nickname or did not play. Confirm/deny each.
3. game_high_ws_unlinked (10) — high-WS NSBA1 nicknames with no identity
These scored a lot but we can't name them. Tell us who they are:
arolakiv (WS 5.10), reverse (2.69), TechnologicalError (1.47),
somath (1.38), 4435 (1.31), Fez_Keyreb (1.28), abcisosm5 (1.26),
cryo (1.22), Fumarole (1.10), Fishface (1.00).
Confirmation format
Open reconcile_worklist.csv, add a verdict column with y/n (and the
chosen candidate where there are several). For ambiguous_collision rows, note
which game record belongs to which person so we can split the id. Re-run
reconcile_identities_v2.py after wiring confirmed pairs into a small override
table and the link rate climbs further automatically.