Cleaning Names Is Most of Building a Corpus
The first time we tried to merge two data sources into a single corpus, we lost about eleven percent of our basketball entries without noticing. Not dropped — silently unmatched. A player listed as "D. Okafor" in one feed and "Dami Okafor" in the other became two separate records, each half-populated, neither flagged. The system did not complain. It just quietly treated one player as two, assigned each a thin sample, and let them both fail the minimum-games gate. We were proud of how clean the corpus looked. That should have been the warning.
Name resolution sounds like infrastructure. It is not. It is the corpus. Everything the shop does downstream — the gates, the line-reading, the shrinkage, the grading — depends on the assumption that a player's history has been correctly assembled into one coherent record. If that assembly is wrong, the confidence intervals are wrong, the sample sizes are wrong, and the calibration is measuring something that does not correspond to a real person's performance. You can have meticulous gate logic and still be scoring ghosts.
We spent the better part of one offseason auditing the matching layer. What we found was not one systemic flaw but a taxonomy of small ones, each with its own failure mode, each invisible until you went looking. This is a piece about that taxonomy, and about what we got wrong while fixing it.
Practical guides for building smarter test frameworks, pipelines, and automation strategies.
The Ways a Name Breaks Before You Ever Score It
Name mismatches in a sports corpus arrive in at least four distinct forms, and they do not all cause the same kind of damage.
The first is the initial-versus-full-name split — the D. Okafor problem above. One source abbreviates, another does not. These are usually recoverable: the team and position context narrows the candidate pool, and a fuzzy match catches most of them. We used a normalized string comparison with a team-season constraint and resolved roughly 80 percent of this class without manual review.
The second is the suffix and prefix problem. A player listed as "Marcus Webb Jr." in one feed and "Marcus Webb" in another is not always the same person — sometimes there is a Marcus Webb Sr. who played a decade earlier and whose records are still in the system. A naive match merges them. We found three instances of this in our baseball corpus. In one case, the father's home-run rate was being averaged into the son's slugging profile. The son's corpus entry was, technically, correct about someone. Just not about him. We wrote about a version of this problem when we built an entry that was technically correct and useless — the data was accurate, the subject was wrong.
The third is the transliteration variant. Players whose names originate in non-Latin scripts arrive in feeds with different romanization choices depending on who did the transliteration. We had a hockey player whose name appeared in six distinct spellings across four sources. None of them were wrong, exactly. None of them matched.
The fourth, and the one we handled worst, is the mid-career legal name change. A player who changes their name — legally, for personal reasons, not a nickname — will appear under two names in the historical record with no link between them unless someone builds one manually. Our system had no mechanism for this at all. The pre-change records and the post-change records sat in separate corpus entries, each with an artificially short history, each more likely to fail a sample-size gate than they should have been.
What We Built to Catch the Mismatches in This Corpus
We built a three-pass matching pipeline. The first pass was deterministic: exact match on a normalized form of the name — lowercase, stripped of punctuation, suffixes removed. This caught the easy cases and ran fast enough to be applied at ingestion time.
The second pass was probabilistic: a token-based similarity score combined with a team-season filter. Any candidate pair that scored above a threshold but below certainty was written to a review queue rather than automatically merged. The threshold was set conservatively — we preferred false negatives (missing a match) to false positives (merging two different people). A false negative means a player has a thinner corpus than they deserve. A false positive means someone else's history is contaminating their record. The second error is worse.
The third pass was manual, and it was the only one that caught the legal name changes. Remi, who maintains the corpus day-to-day, built a lookup table by hand — cross-referencing public records sources, not feeds — for every player in our system who had flagged as a possible duplicate or a suspiciously short career. It took him the better part of six weeks.
"The problem with the second pass is that 'conservative threshold' is doing a lot of work," Remi told me when we reviewed the results. "Every time I adjusted it, I found a new class of thing it was wrong about. At some point you just have to accept that the manual queue is not a failure mode — it is the product."
He was right, and I had resisted that conclusion for longer than I should have because the manual queue felt like admitting the automation had failed. It had not failed. It had correctly identified what it could not decide.
What the Name Audit Actually Cost Us in Confidence
When we reprocessed the corpus after the audit, 340 entries changed materially. Of those, 218 gained sample — previously split records merged into a single, longer history. 94 lost sample — previously merged records that turned out to be two different people, now correctly separated. 28 were flagged as unresolvable with current data and quarantined.
The 94 that lost sample were the uncomfortable ones. Several of them had been rated as gems — they had survived all the gates and received a confidence score — based on a history that was partly someone else's. When we corrected the records, the genuine sample was too thin to rate. The gems were withdrawn. We do not quietly delete; the grades were updated with a note explaining the correction. That is the policy. It is not a comfortable policy.
We also found that our calibration figures for the previous two seasons were slightly inflated — not because our predictions had been better than they were, but because some of the "correct" grades had been graded against the right outcome for the wrong player. The corpus entry said one thing, the actual player did another, and by coincidence they had aligned often enough to look like accuracy. This is the kind of thing that makes you distrust a result you are pleased about, and we have written about that instinct directly — the good-looking number that turns out to be measuring noise.
The deeper cost was epistemic. We had been running a corpus built on the wrong version of a stat for longer than we knew, and the name-matching problem compounded that. Two sources of silent error, neither of them dramatic on its own, adding together in a way that made the system look functional. The corpus had not collapsed. It had just been quietly wrong about specific players for specific reasons, and we had not looked closely enough to notice.
What the Corpus Matching Layer Looks Like Now
We kept the three-pass structure. The deterministic pass and the probabilistic pass run at ingestion; anything that does not resolve cleanly goes to the manual queue the same day. The queue is not allowed to age — unresolved items older than 48 hours trigger a flag, because a corpus entry that is frozen while a player is active is almost as bad as a missing one.
We added a new gate: any entry whose sample size increased by more than 40 percent in a single reprocessing run is held for manual review before it is used in scoring. A sudden large gain in sample almost always means a merge happened, and merges can be wrong. The 40 percent figure is not principled — it is the number that would have caught the worst of our errors in the audit. We expect to revise it.
The legal name change table is now a maintained artifact rather than a one-time project. Remi updates it when a change is reported anywhere in the feeds, and it is versioned so we can see exactly when a link between two records was established and why. This matters for grading: a rating issued before the link was established was based on different information than one issued after, and the two should not be evaluated the same way.
What we did not fix is the transliteration problem. We have a partial solution — a hand-built equivalence list for the names that have caused problems so far — but it is reactive rather than systematic. New players from the same language backgrounds will still arrive with variant spellings, and we will still catch them only after the fact. The gates are where most candidates should die, and right now the transliteration gap means some candidates survive the gates on a false identity. We know this. We have not solved it.
The thing I keep returning to is that name cleaning is not a solved problem that sits upstream of the real work — it is continuous, it is never finished, and every time we think we have it contained, we find a new class of error we had not imagined. The corpus is only as coherent as the identities inside it. Whether that means the corpus is always slightly wrong, or whether it means the work is just genuinely this granular, I am not sure those are different things.
Note: PlayerGem is a fictional analytics shop and these accounts are invented. Nothing here is a pick, a recommendation, or betting or investment advice, and the players, teams and competitions described do not exist.