Skip to content

Add Policy.two_given_names, the compound given name as a rule kind (3.0, 4g) - #954

Merged
derek73 merged 4 commits into
masterfrom
feat/v3-4g-compound-given-name
Oct 30, 2026
Merged

derek73 merged 4 commits into
masterfrom
feat/v3-4g-compound-given-name

Conversation

@derek73

@derek73 derek73 commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

3.0 sub-phase 4g of #754: the compound given name as a rule kind (#704). It adds Policy.two_given_names, rules/O7 (two given names), the mirror of rules/O6 (two family names). It is off by default and no default parse moves.

The rule

Under the switch, the given name takes the middle unit beside it:

Input Default With the switch
Juan Carlos Pérez given Juan, middle Carlos given Juan Carlos
Ana María José Pérez given Ana, middle María José given Ana María, middle José
María del Carmen de la Vega given María, middle del Carmen given María del Carmen

How it reads:

  • Orders. The unit after the given name is taken, under the default order and after a family comma. Under FAMILY_FIRST_GIVEN_LAST it is the unit before the given name.
  • What a unit is. A unit is counted as O6 counts a name word, so a particle chain left in the middle moves whole.
  • Initials. An initial is taken (the reverse of O6), since an initial writes a given name.
  • What it declines:
    • a given name that already holds two words: a hyphen, or a bound pair (P5);
    • a unit that is not one word, or that has no letter;
    • a given name with no letter in it;
    • a word an ending rule reads as a patronymic (R9), and a Turkic marker's father;
    • a name whose script set its order (W4), as O6 declines one.
  • Precedence. O6 reads first and O3 (middle_as_family) reads last. So with both switches on, María José García López reads given María José, family García López, while Gabriel García Márquez keeps given Gabriel.
    • Why O6 first: in ORCID records from Spain and Spanish-speaking Latin America, a name of three words is given 1 + family 2 in 73–93% of records, and a name of four words is 2 + 2 in 96–99.9%.
  • Reports. It reports nothing, as O6 reports nothing.

It is a rule kind as 4f set one up:

  • a Policy field with ADDED_IN 3.0.0, appended after two_family_names in Policy and PolicyPatch;
  • a flag row in RULE_KEYS, with its default false refused;
  • a row in _locale._RULE_VALUES;
  • the mechanisms.md entry DECLARED-SPAN, which it shares with O6.

Design choices for review

  1. The name two_given_names, after two_family_names, whose shape it mirrors.
  2. The given name takes one unit, not the whole given part. That keeps CLDR's given2 as middle. The alternative, every given-part word in given, would leave middle empty under the switch.
  3. The precedence O6 → O7 → O3 (above).
  4. Out of scope: a particle chain that runs to the end of the name. The RFC's pt example, Maria do Carmo Silva, still reads family do Carmo Silva with the switch on. It is the same string shape as João da Silva Santos, whose family it is.
    • Measured in ORCID: 5,506 given boxes hold a particle inside, and 5,625 family boxes open with one.
    • The given boxes' objects are mostly Marian titles (Carmen, Jesús, los Ángeles, Pilar), and several of them also open family boxes.
    • So telling the two apart needs vocabulary. This is left Open in decisions/O7 (options below).

Measured with the rule on (2026-10-30)

PSC (psc-individuals, whose form has a middle-name box), the share of records where every box agrees, scored by local_agreement.score with each Policy:

Filers Records default O7 O6 O6+O7
Spanish 6,089 31.8% 15.7% 80.2% 55.0%
Portuguese 5,333 61.2% 28.5% 56.2% 35.9%
Brazilian 1,920 53.6% 20.9% 55.8% 43.1%
Mexican 402 25.9% 10.2% 80.1% 37.6%
Colombian 377 29.2% 12.7% 84.4% 28.6%
Argentine 320 81.9% 28.4% 45.6% 34.7%
  • PSC, local_agreement.py --base: O7 against the default: 550,064 records regress and 1,801 progress. With O6 on in both trees: 22,297 regress and 173 progress. The filers split a compound given name across the form's two boxes.
  • ORCID (local_agreement.py orcid-researchers --base): "matches the base", both O7 against the default and O6+O7 against O6. Its given box holds the middle names too, so it cannot see this rule.
  • CLDR (given agreement, given-first renderings):
    • all 880: 636 by default, 568 under O7, 746 under O6+O7;
    • es: 6 → 8 of 8 under O6+O7;
    • pt: 7 of 8 under O6+O7, 5 under O7 alone.
  • TSE (snapshot.py --local tse-donors --base, on the final code): 689,057 of 854,531 entries read differently under O7, and 270,169 with O6 on in both trees. The corpus states no fields, so these count how many names the rule reaches, not whether it reads them right.

The recipes are in decisions/O7.

Checks

  • uv run pytest: 33,167 passed. uv run --python 3.15 pytest passes too; namedivider is absent there, so that run also covers the path without the ja extra.
  • -n0 -m serial: 42 passed on the second run. The first run failed two wall-clock scaling rows, a different pair each time, at load average 30–70. Master's tree failed two such rows under the same load.
  • mypy, ruff, build.py --check, sphinx-build -b doctest, python -m doctest README.rst: all pass.
  • Snapshot: it gains the example texts of the new rule and case-table rows. No reading moves.
  • Pickles: a 2.8.0-wheel Policy(two_family_names=True) and Parser(policy=...) load in the tree with two_given_names=False, equal to fresh ones. The Policy pickle is committed as tests/v2/data/policy_v28.pickle, with a test.

Open questions

  • Which particle phrases complete a given name (do Carmo, de Fátima, del Pilar before a family), and where that knowledge lives. Options:
    • (a) a property on the word's reading in es/pt vocabulary, read by this rule kind;
    • (b) a list the rule kind holds in the core, as the patronymic endings are;
    • (c) leave the shape unread.
  • Whether es and pt turn the switch on. That is 3g's decision. PSC says no, CLDR's es says yes, and ORCID cannot tell.
  • Deriving DEVIATES from the rules (4f's item 6) stays open. This rule has no surface in the name for a pack to declare.

🤖 Generated with Claude Code

@derek73 derek73 self-assigned this Oct 30, 2026
derek73 added a commit that referenced this pull request Oct 30, 2026
…ements recipes

The design-docs review of #954 found the Because arguing for every
given-part word, the FAMILY_FIRST_GIVEN_LAST bullet silent on a family
comma (where the part reads given-first and the unit after the given
name is taken; now a rule example and a case row), DECLARED-SPAN's
contract missing the family-comma arm, and no recompute recipe for the
ORCID and PSC figures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
derek73 added a commit that referenced this pull request Oct 30, 2026
The second review of #954 found the switch moving a patronymic
particle's run into the given name while the patronymic view still
claimed it ("Ahmad bin Ali bin Hassan" gave given "Ahmad bin Ali"):
the guard now reads classify's vocab:patronymic tag, R9's vocabulary
arm. It adds the untested Turkic arm's row, removes the two edge tests
no input reaches (none fired over the committed corpora's 6,428 texts
under every order, patronymic rule and O6/O3 setting), states the
script-order agreement O6 states, and the nickname exception to
DECLARED-SPAN's rendering claim. The PSC table is re-taken through
local_agreement.score, the recipe now naming it, and TSE on the final
code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
derek73 added a commit that referenced this pull request Oct 30, 2026
The third review of #954 found the guard reading only the unit taken,
so a given name that is itself a patronymic particle joined the word
after it ("Ahmad, bin Ali" gave given "bin Ali"); the guard now reads
the given side too, with rows for the four inputs, each failing on
e945790. Relabels three O7 rows against the 1.4.0 wheel: the binti
row fix(#604), the script-order row fix(#271), the family-first Turkic
row feat(#704).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
derek73 and others added 4 commits October 30, 2026 12:15
….0, 4g)

rules/O7, the mirror of rules/O6: under the switch the given name takes
the middle unit beside it, so "Juan Carlos Pérez" gives given "Juan
Carlos". Off by default and a [rules] key (two_given_names = true), no
shipped pack turning it on; it reads after two_family_names and before
middle_as_family, and a Policy pickled by 2.8 loads with it off
(ADDED_IN 3.0.0). No default parse moves: the snapshot gains the rule's
example texts only.

Docs: rules/O7, decisions/O7 and the vocabulary-model 4g entry,
mechanisms.md#DECLARED-SPAN, the RFC's 4g row, customize.rst's
"Names with a compound given name", the vocab README's rules table
and a release-log bullet.

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

The design-docs review of #954 found the Because arguing for every
given-part word, the FAMILY_FIRST_GIVEN_LAST bullet silent on a family
comma (where the part reads given-first and the unit after the given
name is taken; now a rule example and a case row), DECLARED-SPAN's
contract missing the family-comma arm, and no recompute recipe for the
ORCID and PSC figures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The second review of #954 found the switch moving a patronymic
particle's run into the given name while the patronymic view still
claimed it ("Ahmad bin Ali bin Hassan" gave given "Ahmad bin Ali"):
the guard now reads classify's vocab:patronymic tag, R9's vocabulary
arm. It adds the untested Turkic arm's row, removes the two edge tests
no input reaches (none fired over the committed corpora's 6,428 texts
under every order, patronymic rule and O6/O3 setting), states the
script-order agreement O6 states, and the nickname exception to
DECLARED-SPAN's rendering claim. The PSC table is re-taken through
local_agreement.score, the recipe now naming it, and TSE on the final
code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The third review of #954 found the guard reading only the unit taken,
so a given name that is itself a patronymic particle joined the word
after it ("Ahmad, bin Ali" gave given "bin Ali"); the guard now reads
the given side too, with rows for the four inputs, each failing on
e945790. Relabels three O7 rows against the 1.4.0 wheel: the binti
row fix(#604), the script-order row fix(#271), the family-first Turkic
row feat(#704).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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