Skip to content

Add Policy.capitals_as_family, reading the words in capitals as the family (#659) - #1363

Open
derek73 wants to merge 7 commits into
masterfrom
feat/issue-659-family-in-capitals
Open

derek73 wants to merge 7 commits into
masterfrom
feat/issue-659-family-in-capitals

Conversation

@derek73

@derek73 derek73 commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

The first of two PRs implementing #659's decision, as amended after the measurement gate. The amendment is the issue's comment of 2026-11-12 (#659 (comment)); the "Specification" section below is that wording as the reviews settled it. The second PR turns the switch on from Parser.suggest and follows this one.

What it does

A new opt-in switch, Policy.capitals_as_family, off by default (rules/O9). It reads the words a name writes in capitals as its family name, wherever they stand:

Input default (1.4.0, 2.0.0, 2.8.0, 3.0.1 and this tree) capitals_as_family=True
DUPONT Marie given DUPONT, family Marie given Marie, family DUPONT
Ayla BURSALI AKSOY middle BURSALI, family AKSOY family BURSALI AKSOY
Jeffrey LAM Kin-fung middle LAM, family Kin-fung given Jeffrey, middle Kin-fung, family LAM
Hugo LAC suffix LAC, reports given-or-family family LAC, no report
DOYEN Hugo title DOYEN, family Hugo given Hugo, family DOYEN

The Jeffrey LAM Kin-fung row is #1181's answer under the switch: the other words keep their written order.

Specification (the decision as amended 2026-11-12)

  • A capitals word has two or more letters in capitals, after any lowercase prefix glued in front: O'BRIEN, ALLIOT-MARIE, McDONALD and d'ESTAING count. A word holding a full stop, slash or digit does not (DR., N/A), nor does anything in brackets or quotes, initials joined by a hyphen (J-P), or a word whose capitals are one letter (PhD).
  • One run:
    • runs joined only by particles or conjunctions are one run (Aude BROSSARD de VILLEMONT), a listed one-letter conjunction written bare included (Ana LOPEZ Y GARCIA; Y. is an initial);
    • a run neither opens nor ends in a conjunction (Mary SMITH AND Jones → family SMITH);
    • a lowercase particle written right before the run joins it (Jean de LA FONTAINE);
    • a run of particles alone is no run, and neither is a lone roman numeral (II to X); the name then reads by position (Karim AL Haddadi, John Smith III).
  • Two runs: where a name word divides two runs, the last is the family (PETER Okello MUWANGA).
  • Which names it reads: a name is in mixed case only where a word no run took, other than a title, a suffix, or a particle or conjunction written in lowercase, holds a lowercase letter. Dr. ANITA ROY, MARIA de SOUZA LIMA, JOHN McCAIN and JAN Van DER BERG are unaffected; MORRISON Van is read.
  • The other words: in written order, the given name the first unit holding a word that may be one. A lowercase particle or conjunction outside the run is a joining word, so a lowercase particle stays with the word after it (de Marie DUPONT → given de Marie) and a unit of nothing else is a middle name (JEAN y Perez → given Perez); a particle written with a capital may be the given name (De Marie DUPONT, Van Mary DUPONT).
  • Peel: a listed credential or numeral ending the run comes off where a capitals word that is no particle is left (Marie GRAJALES III, Jean DUPONT MBA), but never leaves only particles (Anna DE CARO), and only at the end of the name.
  • Commas: the switch reads only the part before the first comma; a family comma names the family (Dupont, MARIE).
  • Construction: setting it with unlisted_caps_suffixes=CapsSuffixes.EVERYWHERE raises ValueError naming both values and offering Policy(capitals_as_family=True, unlisted_caps_suffixes=CapsSuffixes.AFTER_COMMA). A test executes the offered code. It raises from Policy(...), from patched() on either side, from dataclasses.replace and from parser_for with a pack.
  • Accepted costs, stated in rules/O9 with their counts:
    • joined initials in the middle of a name read as the family (Jan GM Brouwer → family GM; 4,246 ORCID records);
    • a credential after the surname with no comma reads as the family (John Smith MD → family MD; 757 ORCID records, none from a source that writes families in capitals).
  • Left open: a bare DR in capitals at the head reads as the family (DR Anna Berg; 204 ORCID given boxes). It is recorded as rules/O9's last Accepted bullet and decisions/O9's Open: line.

Where it runs

_post_rules._family_in_capitals:

  • Position: after H1, M4, P1, O1, O2 and P6, so none of their moves undoes it. Before O6, which stands down where it acted. O7 and O3 then read the words it leaves. O8 needs no stand-down, since the capitals family is never a single initial.
  • What it reads: the tokens before the first comma whose role is a name, a title or a suffix. A word S6 read as a credential, or H1/H9 read as a title, is in reach; nicknames and maiden clauses are not.
  • Cost: with the switch off it is one attribute test. Frame counts for John Smith, Smith, John and John Q. Smith are 125, 135 and 176 on this branch and on the merge base.

_vocab.is_capitals_word is the word test, shared with PR B. A new internal tag, capitals-family, which reaches no published token, makes Token.reading give the family words' source as shape, the source the type already names for "a word in capitals".

Ambiguity reports (decided by nameparser/_pipeline/AGENTS.md), where the rule acts:

  • dropped, since the capitals settled the fork, as S6 already says of a credential its capitals settle: particle-or-given and given-or-family reports (the capitals said where the family is), and any suffix-or-name or title-or-name report on a word of the run (DEAN Hugo, Hugo JUNIOR);
  • kept, under every order: a title or suffix fork on any other word (Dean Mary DUPONT keeps title-or-name).

The rule emits nothing of its own, since the caller declared the convention, as O6 and O8 do.

capitalized() leaves a name in mixed case as written, so DUPONT Marie renders Marie DUPONT and keeps its capitals. capitalized(force=True) gives Marie Dupont: the family is named by its field by then.

Measurements

The gate (before code; scripts and outputs in the 3.1 session notes, 659-gate/; dated snapshots in decisions/O9)

The capped orcid-researchers/raw.jsonl the decision read was no longer cached. I read the full ORCID 2025-10 dump instead (24,990,909 records), with a 20,000-a-country cap to approximate the corpus. Not measured: France's register and LegCo (not cached), and the parliament datasets fetched after 2026-11-08 (Korea, Indonesia, Ethiopia, Israel).

ORCID: which box holds the capitals (records where one box is wholly capitals words and the other holds none):

family box given box share
capped, 4+ letters 44,952 4,882 90.2%
capped, 2–3 letters 5,336 787 87.1%
whole dump, 4+ letters 327,396 40,175 89.1%
whole dump, 2–3 letters 69,550 15,522 81.8%

The coordinator's figures were 90.1% and 87.3%. Where the family box is in capitals, the prototype read both fields right in 396,793 of 396,946 records, composed either way.

Parliaments, capitals taken as the family, the shipped switch (parliaments_shipped.py; cache refreshed 2026-11-12, 23 datasets)

default switch on
tr_parliament 549 / 591 591 / 591
az_parliament 0 / 124 124 / 124
kz_mazhilis 152 / 152 152 / 152

The refreshed cache adds kr_assembly, which the gate could not measure. Of its 297 names in Latin script, 293 are written wholly in capitals (PARK SUNGHOON) and 4 in mixed case with no word in capitals, so the switch reads none of them.

Switch off

  • Committed snapshot: changes only by the rule's 51 example names, new to the rules corpus.
  • CLDR agreement: cldr_agreement.py matches every record.
  • Local corpora: snapshot.py --local <source> --base <checkout of 670b3498> "matches the base" for 15 fetched corpora: ctgov-officials, cz-candidates, dof-signatures, fec-individuals, govuk-people, jrc-names, nepal-candidates, orcid-researchers, patentsview-inventors, psc-individuals, tse-donors, tw-donors, uit-vinames, ukec-individuals, vn-assembly.
  • opensanctions-parliaments:
    • the tool refused the first cache (predating six datasets), so its 16,976 names were parsed on both trees and their readings hashed: identical;
    • on the cache refreshed 2026-11-12, snapshot.py --local opensanctions-parliaments --base <checkout of 2b39f23d> matches the base.

Switch on (sweep_on.py, all three orders)

The grid: 26 heads × 8 wrappers (title, suffix, suffix comma, nickname, maiden clause, salutation) plus 7 comma shapes.

  • Lower-case and Title-case twins of every text read the same with the switch on and off.
  • NFD reads as NFC.
  • No name holding a capital lost its family.
  • Every shipped pack as the base, with the switch on, reads the family of Marie DUPONT, DUPONT Marie Ann, Ayla BURSALI AKSOY and Jeffrey LAM Kin-fung.
  • Han, Hangul, kana and Arabic names do not move.

Also checked:

  • Views: initials() gives M. D., surname2 is empty, and Reading is name as family, shape.
  • Patronymics: under east-slavic and turkic the patronymic view is kept (ИВАНОВ Иван Иванович → patronymic Иванович).
  • Other paths: Parser.revise and pickling round-trip.
  • Matching: Parser.matching().check("DUPONT Jean", "Jean Dupont") is consistent under the switch (family folded) and conflicts without it.

Tests

  • cases.py: two default rows (parity; 1.4.0, 2.0.0, 2.8.0 and 3.0.1 read them so, run 2026-11-12) and eleven feat(#659) rows, one per comma shape and order. Each feat row fails on the merge base, which has no such field.
  • rules/O9's examples: 70 lines, run by test_rules_doc.py, two of them construction subjects (raises=).
  • Guards: test_post_rules.py::_CAPITALS_GUARDS has 40 rows, at least one per condition of the rule, each with its recorded negative control (the reading, or the exception, with the condition deleted, measured on dd5cb4d). All 37 mutations, one per condition, fail at least one row. Conditions removed because nothing reaches them:
    • the run-extension's lower bound (the merge across particles already takes those words);
    • O8's stand-down (the capitals family is never one initial);
    • the peel's reversal, the tag's capitals-only test, full-stop handling of a word that holds none and the ambiguous suffix halves (subsets), each found by the test analysis and checked.
  • Other tests: is_capitals_word gets 30 spelling rows; _EXPECTED_READINGS gets three rows; the 3.0.1 pickle fixtures load with the default; refused-skew rows for both types; the CLI flag; the field rosters (test_policy, test_parser, test_suggest's base sweep, test_cli); and a _POLICY_SHAPES scaling row (the peel loop was made linear first).

Decisions for the maintainer

  • Name and type: capitals_as_family: bool, beside initials_as_family and middle_as_family. Alternatives considered: family_in_capitals, caps_family.
  • Rule id O9, beside O8 among the positional switches.
  • Readings the amendment did not spell out, settled in the reviews:
    • a lowercase conjunction, like a lowercase particle, neither makes a name mixed case nor breaks a run (MARIA y LOPEZ GARCIA is read as a name in capitals);
    • a suffix's lowercase does not make a name mixed (MARY SMITH Jr. is unaffected);
    • a particle or conjunction written with a capital outside a run is a name word (MORRISON Van is read; De Marie DUPONT gives given De);
    • a listed one-letter conjunction written bare between two capitals words joins them (Artur MAS I GAVARRO → family MAS I GAVARRO); written elsewhere it is the initial T4 and P8 read (Mary Y SMITH).
  • Accepted, in rules/O9:
    • a particle in Title case right before the run stays out of it (Mary Van DUPONT → middle Van);
    • a conjunction in capitals trimmed off the front of a run keeps its field (THE ROCK Mary → title THE).
  • Unlisted post-nominal without a comma: an unlisted one after a listed one is the last run, so it becomes the family (Hon CHAN Kin-por GBS JP → family GBS). With LegCo's commas it reads right. This is an Accepted bullet.
  • No [rules] key: no pack sets the switch, per the decision, so tools/vocab/build.py's RULE_KEYS does not offer it.

Docs

  • docs/policy.rst: a table row and a section, Family names written in capitals.
  • The release log, one bullet.
  • rules/O9 (new); S6, S9, H1 and H9 each gain a Precedence bullet naming O9; decisions/O9 (new); both indexes.
  • Root AGENTS.md: the pickling ADDED_IN roster.
  • nameparser/AGENTS.md: the spelling sweep's new class, A word written in capitals, with what the gate found.

docs/usage.rst's list of CLI flags does not name --capitals-as-family yet. That file was outside this PR's scope while the docs rewrite ran, so it is listed for the coordinator.

Reviews

Each review ran as a subagent in its own detached worktree at the pushed head, with a read-only base, briefed with the diff and the issue only. I reproduced every finding below with a command before fixing it. Fix commits: e04bd73 (round 1), 446d992 (the review of e04bd73) and dd5cb4d (the review of 446d992).

  • design-docs-reviewer:

    • a one-letter capitals conjunction broke a run against the rule's own text (Ana LOPEZ Y GARCIA);
    • H3 and S8 were changed by the switch but not amended;
    • a Standing line pointed at an entry that did not say it;
    • counts were undated;
    • the mixed-case Except omitted conjunctions;
    • the Signal tag was wrong.

    All fixed.

  • code-reviewer:

    • a particle in Title case was passed over by the mixed-case test (MORRISON Van);
    • a leading particle was parted from its word (de Mesnil JEAN Paul);
    • the stage's Reads: header did not name the field;
    • the peel could leave a joining word ending the family.

    All fixed. Recorded rather than changed: Marie II DUPONT (family II DUPONT), and O6 standing down under the ES pack (already a Precedence bullet).

  • silent-failure-hunter:

    • reports were dropped for words the capitals did not decide (Dean Mary DUPONT);
    • a never-given particle could become the given name (de Mesnil Jean DUPONT);
    • the error offered one remedy.

    All fixed. Recorded: the CLI prints the Python call rather than flag names, and a hand-built pickle state can hold the forbidden pair (the shared guard does not re-validate, by convention).

  • comment-analyzer:

    • a joining word became the given name (JEAN y Perez);
    • three wordings of the mixed-case test disagreed;
    • "reports nothing" hid the dropped reports;
    • the release bullet had extra examples;
    • the example names were inconsistent.

    All fixed.

  • type-design-analyzer: keep the bool; the constraint is enforced on every path. Recommended adding the constraint to CapsSuffixes.EVERYWHERE's docstring, splitting the field docstring into paragraphs, and a test that a patch holding both values raises only when applied. All done.

  • pr-test-analyzer: 47 mutations, 20 survived. Rows were added for each survivor with an input that moves; two recorded controls were relabelled; the dead conditions were removed (above).

  • Review of e04bd73 (code-reviewer): the widened mixed-case test let a run swallow its own evidence (JAN Van DER BERG, 3,030 grid rows lost the given name); reports survived on words the capitals had moved; dotted Y. joined runs; De Marie DUPONT lost its order. All fixed in 446d992.

  • Review of 446d992 (code-reviewer, 84,000 texts × 15 configurations): every move traced to the stated hunks, and four more findings, all fixed in dd5cb4d:

    • a kept report depended on the declared order (Dean Mary DUPONT lost title-or-name under FAMILY_FIRST);
    • the ATTACHED_TAG clearing, which I had judged dead from a probe in lowercase, mattered for a capitals patronymic particle (Siti AMINAH BINTI under FAMILY_FIRST);
    • mixed case was read before the peel (DUPONT Van MBA);
    • the peel took a joining word listed as a suffix (Joan MAS i MBA).
  • Review of dd5cb4d (mine, on the same grid against its parent): every move traces to those four changes:

    • title and suffix forks restored under the family-first orders;
    • the patronymic and second-surname views no longer depend on the order;
    • names whose peel hands back a Title-case particle are now read;
    • the peel takes only words in capitals.

Checks

Run on dd5cb4d (master 943318d merged in):

  • uv run pytest: 46,404 passed (the ja extra installed). Without the extra, 46,288 passed on 60d250c.
  • uv run pytest -n0 -m serial: 36 passed; frame counts for John Smith, Smith, John and John Q. Smith still 125, 135 and 176.
  • Python 3.15 in its own environment: 46,288 passed on 60d250c.
  • Codecov's patch check failed on 60d250c (98.42% of the diff). The three uncovered lines are now covered: a run made only of a capitals conjunction, a combining mark with no composed form, and a digit in a glued prefix.
  • uv run mypy, uv run ruff check, sphinx-build -b doctest docs, sphinx-build -W -b html docs, python -m doctest README.rst and tools/vocab/build.py --check: clean.

Refs #659
Refs #1181

🤖 Generated with Claude Code

derek73 and others added 2 commits November 12, 2026 11:45
…amily (#659)

An opt-in switch, off by default (rules/O9): in a name in mixed case,
the run of words written in capitals is the family name wherever it
stands, outranking a credential or title reading, and the other words
keep their written order. Runs join across particles and
conjunctions, a lowercase particle right before the run joins it, a
run of particles alone or a lone roman numeral is none, the last of
two runs is the family, a listed credential or numeral peels off the
end of the run where a capitals word is left, and only the part
before the first comma is read. Setting it with
unlisted_caps_suffixes=CapsSuffixes.EVERYWHERE raises at construction.

The decision as amended after the measurement gate is in
decisions/O9, with the gate's measurements as dated snapshots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@derek73 derek73 added this to the 3.1 milestone Nov 12, 2026
@derek73 derek73 added enhancement name-order Name order, family-first and two family names (rules/O) labels Nov 12, 2026
@derek73 derek73 self-assigned this Nov 12, 2026
@derek73 derek73 added case-repair capitalized() case repair (rules/R) suffixes Generational suffixes and credentials (rules/S) titles Titles and honorifics (rules/H) labels Nov 12, 2026
derek73 and others added 4 commits November 12, 2026 12:18
…, particles (#659)

The code, silent-failure, design-docs, comment and type reviews of
PR #1363 found, each reproduced on its head:

- a report on a word the capitals did not decide was dropped
  (`Dean Mary DUPONT` lost title-or-name); now only a report on a word
  of the run, or a given-or-family report, is dropped;
- the first word outside the run became the given name whatever it
  was (`JEAN y Perez` given `y`, `de Marie DUPONT` given `de`); now
  the first unit holding a word that may be a given name is, and a
  lowercase particle stays with the word after it;
- a one-letter conjunction in capitals broke a run (`Ana LOPEZ Y
  GARCIA`), and a run could end in one (`Mary SMITH AND Jones`);
- a particle in Title case was passed over by the mixed-case test
  (`MORRISON Van`);
- the peel could leave a joining word ending the family.

Also: the stage's Reads: header, the field docstring in paragraphs,
CapsSuffixes.EVERYWHERE naming the constraint, the error offering
both remedies, H3 and S8 Precedence bullets, dated counts, the
decisions entries for each finding and the parliament re-take on the
refreshed cache (kr_assembly reads none), and a guard row with its
recorded control for every new condition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… words (#659)

A review of e04bd73 against its parent found its widened checks
overreaching, each reproduced:

- a particle in Title case counted as a name word while a run still
  joined it, so a name in capitals had no given name (`JAN Van DER
  BERG`); mixed case is now read over the words no run took;
- a report outside the run survived where the capitals had moved its
  word or ruled out its reading (`Mary DUPONT Van` under FAMILY_FIRST,
  `Van Mary DUPONT`); particle-or-given and given-or-family reports
  are dropped wherever the rule acts, and any report on a word the
  layout moved, while a title or suffix fork on a word left in place
  stands;
- a one-letter conjunction read by its vocabulary matched a dotted
  initial (`Ana LOPEZ Y. GARCIA`); only a bare one joins;
- a never-given particle in Title case lost its written place (`De
  Marie DUPONT`); only lowercase joining words are passed over for
  the given name.

The test analysis of the first head added guard rows, each with its
recorded control, for the shapes its mutants survived, and found dead
conditions, now removed: the peel's reversal, the capitals-only tag,
full-stop handling for a word that holds none, the ambiguous suffix
halves (subsets) and the clearing of P6's attachment mark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…659)

A review of the second fix commit against its parent, over 84,000
texts under fifteen configurations, found four more, each reproduced:

- a report on a word the layout moved was dropped, measured against
  the positional reading, so `Dean Mary DUPONT` kept title-or-name
  given-first and lost it under FAMILY_FIRST; a title or suffix fork
  on a word outside the run now stands under every order;
- clearing only P6's fold mark left a patronymic particle in capitals
  attached (`Siti AMINAH BINTI` under FAMILY_FIRST read patronymic
  `AMINAH BINTI`); both marks are cleared again;
- mixed case was read before the peel, so a joining word it hands
  back gave no evidence (`DUPONT Van MBA`); it is read after;
- the peel took a joining word listed as a suffix (`Joan MAS i MBA`
  read suffix `i MBA`); it takes only words in capitals, and the
  joining word it leaves is laid out with the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#659)

The row's unit of particles (Ann DUPONT de LA MBA) is superlinear with
the switch off too, 4.58x for 4x locally, and read 7.0x on CI's 3.13
runner; a conjunction join reaches the same run join and peel at 4.3x
with the switch on and off.

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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

case-repair capitalized() case repair (rules/R) enhancement name-order Name order, family-first and two family names (rules/O) suffixes Generational suffixes and credentials (rules/S) titles Titles and honorifics (rules/H)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant