Skip to content

Collapse one person's two author records into one row - #66

Open
ssavutu wants to merge 1 commit into
mainfrom
fix/author-pool-merge
Open

ssavutu wants to merge 1 commit into
mainfrom
fix/author-pool-merge

Conversation

@ssavutu

@ssavutu ssavutu commented Sep 11, 2026

Copy link
Copy Markdown
Member

Why this is worth merging even though we are not reseeding again

Being straight about the value: this fixes a bug that only fires during a full
WP-export reseed, and the plan is that there will never be another one. So the
practical payoff today is zero. It is here because the diagnosis was done
anyway, the ETL is still the thing anyone would reach for if an import ever
does happen again, and leaving a known root cause undocumented in code is worse
than a small tested patch. Reviewers should feel free to weigh it accordingly.

The bug

Authors are sanitized in two independent pools — WP users (auth_*) and
Co-Authors Plus guest authors (gauth_*). The MinHash similarity dedupe and the
logs/auth_conflicts.json decision cache both run within a pool only, so
combineAndReindexAuthors is the single place a person represented on both
sides gets collapsed.

It matched on exact display_name equality. The two sides routinely
disagree, because a WP user's display name is title-cased off the login while
the guest record is hand-typed:

WP user guest author
Erik Heyman-meltzer Erik Heyman-Meltzer
Devan Suber DevanSuber

Each mismatch emits a second authors row for the same person.
Utility.canonicalizeAuthorLogins then finds two identical logins and breaks
the tie with dedupe_slug, which appends the row id — so an author slug ending
in its own id (erik-heyman-meltzer-870) is the fingerprint of exactly this
miss, not of anything done in the CMS.

The fix

  • Match on Utility.cleanDocument(name, "similarity"), the normalization the
    within-pool dedupe already trusts.
  • Merge the matched guest record instead of discarding it:
    • blank fields are filled from the guest record (usually where a real
      first/last name lives);
    • a name field that differs only by case or punctuation takes the guest's
      spelling, since that one was typed by a person;
    • anything genuinely different is left alone — a guest record must never be
      able to rename somebody;
    • email is fill-only and never overwritten. cms_users.author_id is linked
      by email at first login, so the emailed row is the one tied to a person's
      CMS account.

Effect on the Aug 2026 export

Replayed against the cached auth_output.json / gauth_output.json:

pools: 559 wp users + 435 guest authors
main:  875 rows
fix:   866 rows  (9 newly collapsed)
logins still disambiguated by id: 0
remaining same-person dupes: {}

The nine: Erik Heyman-Meltzer (the one a writer actually reported), plus
Devan Suber, Ben Wolbransky, Jacky Tsang, Colin Peterson, David Hagelgans,
Zachary Snyder, Atticus Deeny and Sally Ehlers — all eight of those the
missing-space variant.

Tests

tests/test_author_pool_merge.py, 8 cases. Four of them fail on main and
pass here; the other four pin behaviour this must not break (fresh ids, no id
collisions, no renaming, email survival). Full suite green — every module under
tests/ run individually.

Note on live data

Delta has already been repaired by hand for Erik (author 870 merged into 571).
The other eight duplicate rows exist on Delta but carry zero article links,
so they are cosmetic clutter in the author picker rather than split bylines.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HCehtwqGJGNYhmMVdwAAgF

The author pools are sanitized separately -- WP users and Co-Authors Plus
guest authors -- so combineAndReindexAuthors is the only place someone who
appears on both sides gets collapsed. It matched on exact display_name
equality, and the two sides routinely disagree on spacing or a capital: a WP
user's display name is title-cased off the login ("Erik Heyman-meltzer",
"DevanSuber") while the guest record is typed by hand. Every disagreement
emitted a second authors row for the same person, and
canonicalizeAuthorLogins then broke the colliding logins apart by appending
the row id -- which is why a slug like erik-heyman-meltzer-870 exists at all.

Match on the normalized name the within-pool dedupe already uses, and fold
the guest record's fields in rather than dropping it: fill anything blank,
and prefer the guest's spelling only where the two differ by case or
punctuation, so the hand-typed "Heyman-Meltzer" wins over the one derived
from a login. A genuinely different value is left alone -- a guest record
must not be able to rename somebody -- and the WP user's email always
survives, since cms_users links an account to an author row by email.

On the Aug 2026 export this collapses 875 rows to 866 and leaves no login
disambiguated by id.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCehtwqGJGNYhmMVdwAAgF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant