Merge duplicate players by name, and make the maintenance pass actually heal duplicates (#266) - #270
Merged
Merged
Conversation
Automatic player merging only grouped rows sharing a fide_id, so two
records for one person with no FIDE ID between them stayed split
forever. That also hid duplicate games: dedup_games pairs on white_id
AND black_id, so every copy hanging off the second record survived
deduplication — the reported symptom, the same game listed twice with
no maintenance step able to clear it.
dedup_players now clusters on two keys, union-find over both so the
relation is transitive (A—fide—B—name—C collapses to one survivor):
- same fide_id, as before
- same name_normalized, which is the key get_or_create_player already
identifies people by. One normalised name has always meant one
person at import; two rows holding one are an artefact — usually a
rename, since players normalise / players import rewrite a row to
its FIDE-canonical spelling, which can land on a name another row
already holds. So this restores an invariant rather than guessing.
The one refusal: a name held by two different FIDE IDs is left entirely
alone, including any FIDE-less row under it. Those are namesakes FIDE
itself distinguishes, and a merge cannot be undone.
Survivor selection now prefers a row carrying the cluster's FIDE ID —
the merge only rewrites games, so a FIDE-less survivor would drop the ID
off the database — then name_score, then the lowest (oldest) id, which
is what actually decides inside a name group where spellings score
alike. Game counts are refreshed at the end: survivors own their losers'
games, and a merge run on its own from the Maintenance page has to leave
the numbers right without waiting for a later dedup_games.
The Maintenance page's tabs and button names are reorganised in the same
pass: "Deduplication" was games deduplication and "Merge duplicate
players" was the automatic one while "Merge players" on another tab was
the manual one. They are now "Remove duplicate games", "Merge duplicate
players — automatic" and "Merge two players — manual", the tabs hold
what their name says, and each opens with the order its steps belong in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hx8hR7hu2hrMGhZr3HxRko
…#266) Three follow-ups to the name-based player merge, two of which are what actually makes the pipeline self-healing. players dedup --dry-run lists every planned merge — which record is kept, which are folded in, and whether a FIDE ID or the name alone linked them — and writes nothing. A merge cannot be undone, so the Maintenance card offers Preview beside the real run rather than hiding it behind a checkbox, and the report calls out the name-only links: the only ones that could be genuine namesakes. A merge now clears `deduped` on the kept player's games. dedup_games pairs on white_id AND black_id, so every verdict reached while the records were split is stale — and the pipeline's dedup_games is incremental, so without this the duplicate copies a merge had just exposed were never re-examined and survived forever. This was the real reason the previous commit only appeared to fix the reported symptom: verified with a full CLI dedup, which is not what the pipeline runs. The manual merge endpoint gets the same treatment, since merging by hand is exactly how a user exposes copies dedup could not previously see. The maintenance pass now runs normalise BEFORE dedup_players. Renaming a row to its FIDE-canonical spelling is itself a way to create a duplicate — the new name can be one another row already holds — so merging has to come after the renames, or every pass ends by creating duplicates that only the next one cleans up. Verified end to end through the job API: a split pair with both copies already vetted collapses to one player and one game in a single pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hx8hR7hu2hrMGhZr3HxRko
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #266 — the same game listed twice in a player's game list, with no maintenance step able to clear it.
The chain
Player identity is keyed on the normalised name (lowercase, comma→space, whitespace collapsed) —
get_or_create_playerlooks a name up in a cache of every existingname_normalizedbefore it ever considers a FIDE ID. Re-importing the same PGN, with or without FIDE headers, is therefore already a no-op; that part was never broken.What broke was a rename.
players normalise/players importrewrite a record to its FIDE-canonical spelling and recomputename_normalized, and the new value can be one another record already holds. Two records, one person, indistinguishable on screen. From there:fide_id, so a pair where one side had no FIDE ID stayed split forever;dedup_gamespairs onwhite_idANDblack_id, so every copy hanging off the second record was invisible to game deduplication too.That is the reported symptom: duplicate games that no button could remove.
What this does
Merge by normalised name as well as by FIDE ID. Union-find over both keys, so the relation is transitive (
A —fide— B —name— Ccollapses to one survivor). This is the key import already identifies people by, so it restores an invariant rather than guessing.The one refusal: a name held by two different FIDE IDs is left entirely alone, including any FIDE-less row under it. Those are namesakes FIDE distinguishes, and a merge cannot be undone.
A merge re-opens the kept player's games for deduplication. Every dedup verdict reached while the records were split is stale, and the pipeline's
dedup_gamesis incremental — without this the copies a merge had just exposed were never re-examined. This is the fix that actually makes the pass self-healing; the merge alone only looked like it worked, because I first verified with a full CLI dedup rather than the incremental one the pipeline runs. The manual merge endpoint gets it too.normalisenow runs beforededup_players. A rename can itself create a duplicate, so merging has to come after the renames — otherwise every pass ends by creating duplicates only the next one cleans up.Survivors keep the FIDE ID (the merge only rewrites
games, so a FIDE-less survivor would drop it off the database), and game counts are refreshed, whichdedup_playersnever did on its own.Preview
chess-db players dedup --dry-run, and a Preview button beside the real one on the Maintenance card — merges cannot be undone, so the safe action is offered alongside rather than hidden behind a checkbox. It lists every planned merge and counts the ones linked by name alone, the only ones that could be genuine namesakes:In practice those should be near-zero: the only thing that creates a same-name split is a rename, and renames act on records that carry a FIDE ID.
Maintenance page
Same pass, since the names were actively misleading: "Deduplication" was games deduplication, "Merge duplicate players" was the automatic one, and "Merge players" — on a different tab — was the manual one.
Tabs now hold what their name says, in pipeline order, and each opens with one line on the order its steps belong in — including that duplicate games are matched through their player records, which is the dependency the old grouping hid.
Verification
Prepare databaserun:Still not covered
Duplicates spelled differently with no FIDE ID on either side (
Dr. Vahram,AbadjianvsAbadjian, Vahram). Those normalise differently and still need the manual merge; fuzzy matching would be a much larger, much riskier change.🤖 Generated with Claude Code
https://claude.ai/code/session_01Hx8hR7hu2hrMGhZr3HxRko