Skip to content

Add a CLDR person-name corpus and an agreement ratchet against its stated fields - #654

Merged
derek73 merged 8 commits into
masterfrom
feat/cldr-corpus
Oct 13, 2026
Merged

derek73 merged 8 commits into
masterfrom
feat/cldr-corpus

Conversation

@derek73

@derek73 derek73 commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

The gate passes at every ledger baseline (1.4.0 through 2.4.0: exit 0, no unexplained diffs, no contests). This branch includes master with #655, the fix for #653, which a CLDR rendering had exposed at 1.4.0.

Gives nameparser its first check against ground truth from outside the project: Unicode CLDR's person-name test data, which states every name's fields separately (title, given, given2, surname or prefix + core, surname2, generation, credentials), in native scripts.

What's added

  • A radar corpus. tools/differential/build_cldr_corpus.py fetches CLDR release-48's personNameTest files and writes:

    • corpus_cldr.jsonl: 1,870 lines from 2,640 renderings in 110 locale files; each name's long, formal rendering in givenFirst, surnameFirst and sorting order, with surname-first forms tagged shape: 4;
    • cldr_fields.jsonl: each rendering's CLDR fields;
    • cldr_LICENSE: the Unicode-3.0 notice.
  • An agreement ratchet. tools/differential/cldr_agreement.py parses every rendering and compares, role by role, with CLDR. The roles are title, given names, family and suffix, plus the particle/base split where CLDR states it. Disagreements are recorded in cldr_agreement.json. tests/v2/test_cldr_agreement.py fails on:

    • a regression;
    • progress not yet recorded;
    • a changed wrong answer;
    • a recorded rendering that no longer exists.

    Re-record with uv run python tools/differential/cldr_agreement.py --record.

  • Agreement today (2026-10-12): 9,025 of 11,880 role verdicts. The record holds 956 disagreeing renderings, which doubles as a measured worklist of names the parser and CLDR read differently.

role agree
title 2389 / 2640
given_names 1724 / 2640
family 1798 / 2640
suffix 2367 / 2640
family_particles 414 / 660
family_base 346 / 660

Decisions along the way

  • Ledger guards don't measure CLDR names. Counted, they moved fourteen 1.4.0-ledger rules' recorded reach at once (the comma rules 461 → 771 names). _UNMEASURED_CORPORA keeps them out of _CORPUS_NAMES; the harness still classifies their diffs on the radar tier.
  • Property tests don't use CLDR names yet. Four Slavic renderings exposed A decomposed (NFD) name loses an abbreviated title: Dr. inž. Janez Novak reads inž. as the given name #652 (decomposed inž. isn't a period abbreviation, so the comma reverses the name). differential_corpus() leaves the file out until that's fixed. Including it costs test_properties.py 34.5 s → 39.6 s (py3.11).
  • fix(#379) ledger regex: its member di case-folded onto the Turkish dotless ı (Yıldırım). Its three ledger copies now use (?ai); on every corpus name, nothing else changes whether it matches.
  • The trailing-Ph. D. [[never]] entry was narrowed to the split pair its why is about, as its own comment asked. It had refused 17 CLDR names: 16 whose only 1.4 diff is the suffix separator (M.D., Ph.D. → M.D. Ph.D.), plus an unsplit Ph.D. title move. Its recorded effect moves from 7 to 3 captures.
  • CLDR names aren't adjudicated in gate contests. At 1.4.0 they raised three UNPINNED CONTESTs (two pairs of initials-per-word rules with equal fields, and the CJK order rule against the delimited-nickname rule). For two of them, an honest pin would have had to name a rule that doesn't describe what moved. compare._UNMEASURED_CORPORA now names the files whose names are classified (by file order) but not adjudicated; a name any other corpus holds still is. A test holds it equal to the guards' copy.
  • Release notes: CLDR's fictional renderings now enter the classified summary wherever a rule classifies them. They add 60 / 203 / 200 / 186 / 12 / 1 radar-unclassified lines at baselines 1.4.0 through 2.4.0 (after A nickname loses its comma: Jane (Janey, Jo) Smith gives nickname Janey Jo (1.4.0: Janey, Jo) #653's fix). The README and AGENTS.md say to count them out of release figures.

Review

  • Executed from a written spec and plan; every deviation is recorded in the commit messages.

  • A whole-branch review found false disagreements: only the ASCII comma was stripped, and unspaced fields were joined in field order. Fixing them moved 57 entries to agreeing and unmasked one false agreement. A review of that fix commit then corrected one explanation (the 17).

  • Negative controls:

    • comparison rules: 4 mutations;
    • ratchet: 3 mutations;
    • the first round of review-driven tests: 2 reverts.

    Each fails at least one test.

  • Full suite on the merged branch: 15,108 passed. Ruff and mypy are clean.

Deferred (minor):

Related: #652; #653 (fixed by #655).

🤖 Generated with Claude Code

derek73 and others added 5 commits October 12, 2026 21:31
build_cldr_corpus.py fetches CLDR release-48's personNameTest files and
writes corpus_cldr.jsonl (the harness format, radar tier; 1,870 lines
from 2,640 renderings in 110 locale files), cldr_fields.jsonl (each
rendering's CLDR fields, for the agreement ratchet that follows) and
cldr_LICENSE (the Unicode-3.0 notice, outside the JSONL because the
corpus loader refuses non-JSON lines). Enrolled in _CORPUS_TIERS and
_CORPUS_FLOORS in the same commit because both rosters are fail-closed.

Adding it surfaced three things, each decided rather than re-recorded:

- Fourteen 1.4.0-ledger rules' recorded corpus reach grew (the comma
  rules 461 -> 771 names, feat(#269) 6 -> 196). The CLDR names stay
  out of _CORPUS_NAMES, the population the ledger guards measure; the
  harness still classifies their diffs on the radar tier.
- The guard also caught the fix(#379) member 'di' case-folding onto
  the Turkish dotless i in Yildirim. Its three ledger copies now use
  (?ai), which folds ASCII letters only.
- Four Slavic renderings fail the NFC/NFD property test: decomposed
  'inz.' with a combining caron is not a period abbreviation (#652).
  differential_corpus() leaves the file out until that is fixed; it
  would cost test_properties.py 34.5 s -> 39.6 s (py3.11) to include.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cldr_agreement.py decides whether nameparser and CLDR agree on one role
of one rendering: a CLDR field counts only where its value occurs in the
rendering (word by word where it has whitespace, by substring where it
has none), and two values agree when they hold the same multiset of
words (or the same unspaced text). Roles: title, given names (given +
given2 against given + middle), family, suffix, and the particle/base
split where CLDR states it. Each rule's tests carry the wrong answer
its plausible alternative gives; four mutations each fail a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cldr_agreement.py parses every CLDR rendering with the working tree and
records, role by role, where it disagrees with CLDR's stated fields, in
cldr_agreement.json (996 of 2,640 renderings disagree somewhere; 8,982
of 11,880 role verdicts agree, 2026-10-12). tests/v2/test_cldr_agreement.py
fails on a regression, on progress not yet recorded, on a changed wrong
answer, and on a recorded rendering that no longer exists. The record is
also a measured worklist of where the parser and CLDR read a name
differently. Re-record: uv run python tools/differential/cldr_agreement.py --record

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README's corpus table gains corpus_cldr.jsonl, with its source,
tier and blind spot, and a section on the agreement ratchet, its
re-record command, and the two populations that leave the file out
(the ledger guards' _CORPUS_NAMES, and differential_corpus() until
#652). AGENTS.md points rule changes at the agreement report.

The gate at 1.4.0 refused 17 CLDR names ending 'M.D. Ph.D.': the
trailing-'Ph. D.' [[never]] entry's last component, ph\.\s*d\., also
matched the unsplit 'Ph.D.', and their only diff is the suffix
separator ('M.D., Ph.D.' -> 'M.D. Ph.D.'). Narrowed to ph\.\s+d\.,
the split pair its why is about, as the entry's own comment asks; its
recorded effect moves 7 -> 3 captures, the four unsplit names leaving
its reach. One CLDR name still fails at 1.4.0 -- a delimited nickname
loses its comma -- to be fixed in its own PR before this one merges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The comparison stripped only the ASCII comma, so a word glued to an
  Arabic, fullwidth, Myanmar or Ethiopic comma did not occur and CLDR's
  field was expected empty. It now strips nameparser's COMMA_CHARS plus
  those marks (and, defensively, the ideographic comma, which no CLDR 48
  rendering holds).
- An unspaced rendering compares in order, and fields were joined in
  CLDR's field order; they are now joined in the order they occur in the
  text, so a rendering that writes given2 first can agree.
  Re-recorded: 57 recorded disagreements were the comparison's, not the
  parser's, and now agree. One former agreement was false -- the glued
  Arabic comma had hidden CLDR's generation field in a Persian
  rendering -- and is now recorded as the disagreement it is. The record
  holds 956 renderings, from 996.
- A ledger-guard comment called _CORPUS_NAMES the gate's population,
  which stopped being true when corpus_cldr.jsonl was left out of it;
  it now says what follows (a recorded or watched row on a CLDR-only
  name reads as departed), and the recipe beside _carries filters the
  same file, so it prints its prose's '2 8' again.
- The narrowed Ph. D. entry's comment said 17 names ending 'M.D. Ph.D.'
  differ from 1.4. 27 end so in either case: 11 carry a FAMILY_FIRST
  shape and are not compared at 1.4.0, the 16 compared differ only in
  the separator, and the entry's 17th refusal was an unsplit 'Ph.D.'
  name whose diff is a title move.
- The module docstring no longer claims the record never goes stale:
  the ratchet keeps recorded answers current, and text/expected are
  refreshed only by a re-record.
- Release-log counts drawn from the classified summary now include
  CLDR's fictional renderings; the README's CLDR section and AGENTS.md's
  release-log paragraph say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.02%. Comparing base (e5bde5e) to head (2a8a785).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #654   +/-   ##
=======================================
  Coverage   99.02%   99.02%           
=======================================
  Files          46       46           
  Lines        4520     4520           
=======================================
  Hits         4476     4476           
  Misses         44       44           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

derek73 and others added 3 commits October 12, 2026 22:58
The 1.4.0 gate raised three UNPINNED CONTESTs on CLDR renderings -- two
pairs of initials-per-word rules with equal fields, and the CJK order
rule against the delimited-nickname rule -- and a pin would have had
to name, for two of them, a rule that does not describe what moved.
The decision (Derek, 2026-10-12) extends the ledger guards' ruling to
the run: compare._UNMEASURED_CORPORA names the files whose names are
classified but not adjudicated, a name held by any other corpus still
is, and a test holds the set equal to the guards' copy in
_differential_fixtures. The README's CLDR section says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@derek73
derek73 merged commit 04cc081 into master Oct 13, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant