Repository navigation
Add a GND corpus: real names with their parts stated, scored like CLDR - #688
Merged
Merged
Conversation
CLDR's person-name data gave the harness its first ground truth, but its names are fictional. The German National Library's authority file (GND, CC0) stores every person's preferred name in parts -- surname, forename, prefix, counting, or a personalName for someone entered under the whole name with no surname -- so it can be scored role by role the same way. build_gnd_corpus.py samples deceased, Wikipedia-linked people (public figures only) from lobid by shape and area: no-surname names from Iceland, Malaysia/Indonesia, the Arab world, Iran, Ethiopia and Europe; regnal names; particles by area, plus elided (d') and Arabic (al-) ones; Spanish and Latin American compound surnames; CJK and Hungarian index forms; and a plain control slice. 801 renderings of 528 names: each preferred name as cataloged, plus a given-first form composed from its fields outside family-first areas, flagged composed. Raw responses are cached outside the tree, like tools/name_usage.py's. The fields are written in CLDR's vocabulary, so cldr_agreement.py compares them by its existing rules as a second source, with its own record (gnd_agreement.json) and the same ratchet test. 559 of the 801 renderings agree in every role. The 242 that do not are a worklist: Icelandic and Malay names given a family, Spanish compound surnames read as middle plus family, regnal numbers read as a family name (`Sancho I.`), elided and Arabic particles not split off. The snapshot gains 797 names and no existing reading moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #688 +/- ##
=======================================
Coverage 99.03% 99.03%
=======================================
Files 46 46
Lines 4541 4541
=======================================
Hits 4497 4497
Misses 44 44 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
A GND surname with no prefix was written as surname-core, which tells the comparison the name has no particle. GND does not split a particle inside a surname (Vélez de Guevara), so 19 renderings recorded the parser's `de` as a disagreement no fix could clear. Such a surname is now stated whole, and only the family is compared: the GND record drops 12 renderings and 71 particle/base entries, and gains none. The build now fails before writing instead of shrinking the corpus: a stratum below its floor, a query that matches nothing, or pages that do not add up to the total. A cached response is validated on read and named when damaged, and written atomically. The regnal branch checks its preferred name, and a record carrying a field the script does not know is refused. --record prints, per source, how many changes of each kind it accepts, and an empty fields file is an error. Tests: every rejection guard isolated, the glued separators parametrized, pool() and build() on a stubbed search, the cache, the committed files' invariants, and the re-record test redirected through sources() so a later source cannot be written into the tree. Six mutants of the new guards are each killed. Docs: the license quote made verbatim, the README's _CORPUS_NAMES sentence rewritten (the release-figure rule is AGENTS.md's to state, and this PR leaves it alone), CLDR-only wording generalized, an "adding a source" lesson in the README, and AGENTS.md's validation paragraph now names the GND record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The second review of the fix found its rule ("what GND leaves unsplit is
not stated") broken one shape over. With a prefix before a core of
several words, the core can still hold an unstated particle:
`Zuylen van Nijevelt, Julius van` recorded the parser's second `van` as
a particle disagreement no fix could clear. A family is now stated whole
unless a prefix stands before a one-word core. The GND record drops 8
renderings and 18 entries, and gains none.
Also from that review: the README's blind-spot cell no longer says no
title reaches a preferred name (two do, inside a personalName), the
builder docstring lists `surname` in the field vocabulary, and the
refusal table's comment no longer claims every row isolates its guard.
The README's "adding a source" lesson names this second miss.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Derek's call on #688's open question: the names are real, so they count, composed renderings included; the AGENTS.md rule stays CLDR's alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 tasks done
derek73
added a commit
that referenced
this pull request
Oct 17, 2026
…igned (#689) * Add a Wikidata corpus: second surnames and patronymics, token-aligned Wikidata's P1950 (second family name in a Spanish name) and P5056 (patronym or matronym) are the only open labels for the two fields 3.0 is weighing. build_wikidata_corpus.py samples public figures (deceased, or 10+ sitelinks) one country and one property per WDQS query, in MD5 order of the QID, and keeps a written name (P1559 or the own-language label) only where every word aligns with exactly one stated part. cldr_agreement.py gains the source, and PENDING_ROLES: surname2 is scored inside family and patronymic inside given_names until ParsedName has those attributes, then on its own. Derek chose that, the given_names placement (a patronymic is not a family name) and connectives joining the part after them. The CLDR, GND and locale records do not move; the snapshot gains 668 names, none moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Say the pending fields stay in their merged roles; eight citizenships, not six Review: the merge in expected_roles never ends, and the comments said it would. It is kept on purpose (whether family drops surname2 is 3.0's design), so the comments now say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hold the committed Wikidata fields to occurring in, and covering, their text The GND review (#688, 1de7ee0) found the comparison reads a missing field as an empty role. The alignment makes that sound here by claiming every word; these checks hold the committed file to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fail the Wikidata build loudly; pin the alignment's decisions Review of #689: - strata get floors, and build() raises before writing when one falls short (Malaysia's floor is 8: its pool held 17 on 2026-10-16); - responses are shape-checked before caching (an API error arrives as HTTP 200), cached atomically, and a damaged cache file is named; - a query matching nothing and an item the API leaves out or reports missing both raise; - the pending-role gate skips only PENDING_ROLES, now a map to the role each is scored inside, which the summary prints; - Malay Bin/Binti capitalized are connectives; _REJECT, which pinned nothing, is gone; - tests for every decision a mutation showed unpinned, the build, the cache, and the committed strata; doc wording fixed, and the README no longer stretches the GND release-figure attribution to Wikidata. The rebuilt corpus is byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Refuse a person whose name-part item is deleted, rather than stopping the build Second review of #689: the missing-item raise covered part items too, so one deleted item in any pool would block every rebuild with advice (--refresh) that cannot fix it. A missing pooled person still raises. Also: the summary asks got_roles itself whether a pending field is answered, Malaysia's floor leaves 3 names of slack, the README's Malaysian count says 'matched the query', and the lesson is recorded in the README's Adding a source paragraph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Simplify the Wikidata corpus code: derived merge, align helpers, shared stub /simplify over #689: - expected_roles derives the pending merge from PENDING_ROLES instead of spelling surname2 and patronymic three times; - align() splits span search and the widest-span reduction into helpers, and says why the connective pass runs right to left; - the missing-person check moves to build(), so entities() has one contract and no flag; - the urlopen stub both build tests copied is shared in _differential_fixtures; the field-occurs check is one test over every stated-field source; the coverage check uses the public same(). Skipped: reverting the pending-role gate to 'role in got' (the last review asked for the loud failure it gives), case-folding the Malay connectives (would admit B. and Bt.), a per-language feminizer hook and a shared shape constant across tools (one language; the build-helpers follow-up). Corpus and every record byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
CLDR's person-name data (#654) gave the harness its first ground truth, but its names are fictional. This adds a second labeled source of real people: the German National Library's authority file (GND), read through lobid-gnd.
GND stores each person's preferred name in parts:
surname,forename, andprefix(the particle);counting, a regnal number;personalName, for someone entered under the whole name with no surname (Icelanders, Malays, rulers, saints).A corpus survey on 2026-10-16 ranked GND first among open sources, for three reasons:
personalName/surnamesplit answers whether a person has a family name at all.What's in it
build_gnd_corpus.pysamples only people who are deceased and have a Wikipedia article, which keeps the corpus to public figures.Gogh, Vincent van).Vincent van Gogh), flaggedcomposed: true.$NAMEPARSER_TOOLS_CACHE/gnd), astools/name_usage.pydoes. Each cache file is validated on read and written atomically. Only the derived rows are committed, withgnd_LICENSEas the credit.cldr_agreement.pynow takes more than one source (sources()).gnd_fields.jsonlis written in CLDR's field vocabulary, so the existing comparison rules apply unchanged.gnd_agreement.json), held by the same ratchet test, now parametrized per source.--recordprints, per source, how many changes of each kind it accepts.prefixstands before a one-word surname.Vélez de Guevara,Zuylen van Nijevelt), so a missing split means "not stated", not "no particle".d'Arenberg) is stated whole in its composed line.snapshot.jsonlgains 797 names, and no existing reading moves.Agreement, measured 2026-10-16
579 of the 801 renderings agree in every role. Per stratum:
What the record says (the worklist)
Kristján Eldjárnreads familyEldjárn. This is the "familymay be empty" question 3.0 is weighing.Josep Domènech Estapà. This bears onsurname2.Sancho I.reads familyI., whileJohann II.reads it as a suffix.Arenberg, Auguste Marie Raymond d'andǦaʿfarī, Ibrāhīm al-.What it is blind to (also in the docstring and the README row)
personalName, such asImam Bonjol.personalName, so no stratum samples those areas.Review rounds
/pr-review-toolkit:review-prran the code, test, comment and silent-failure reviewers. The fixes are in 45dc2a8:deinside a GND surname as a disagreement no fix could clear. Such surnames are now stated whole.A second review of that fix commit found the same unstated-particle gap one shape over, inside a several-word core after a prefix (
Zuylen van Nijevelt, Julius van). That is fixed in 1de7ee0, and its revert is killed by the new test. The README's new "Adding a source" note records both misses for the next source.Release figures (decided)
A release figure counts GND's moved names in, its composed renderings included, because the names are real (Derek, 2026-10-16). AGENTS.md's rule that counts CLDR's fictional renderings out is unchanged. The decision is recorded in tools/differential/README.md.
Test plan
uv run pytest: 18,079 passeduv run mypy,uv run ruff checksources(), so it cannot write into the tree, and it checks that every record was rewritten.🤖 Generated with Claude Code