diff --git a/docs/design/decisions.md b/docs/design/decisions.md index 94a77697..d9a585b1 100644 --- a/docs/design/decisions.md +++ b/docs/design/decisions.md @@ -405,6 +405,10 @@ Declined (ambiguity kinds for script-resolved names, 2026-07-27): FOUND BY THE PR REVIEW'S P5 FINDING, and that is the part worth keeping. The reserve fix under #P5 was correct on its own terms and left `Sir abdul rahman Prof.` reading family `abdul rahman`: the join fired for the right reason and the field was still wrong. What the review then asked was which run each site reads, and the answer was that they read DIFFERENT runs — group's licence has always read only the pieces ahead of the bound word (`range(fk)`), while H1 read every title token — so the 2026-08-22 #369 invariant under #P5, that H1 and P5 cannot read one run two ways, had been broken by the composite keying without any site's own tests noticing. Both now read the leading run, through the same predicate, and `nameparser/_pipeline/_group.py` records that beside the licence. MEASURED 2026-09-09, over every distinct name in `tools/differential/corpus*.jsonl`, parsed on the fix's parent (`e489dc1`) and on the fix and diffed across the seven fields and the ambiguity kinds: over the corpus AS THE PARENT HAD IT, not one name moves; over the corpus as this commit leaves it, exactly TWO do and they are the two rules.md examples this entry adds (`Sir John Prof.`, `Dr. Smith Sir.`). The composite shape needs a title run at BOTH ends and no corpus name had one before those two, which is why a defect this plain sat behind four green gates. `Dr. King Sir.` is untouched and stays #H4's: its leading run is `dr king`, whose last word `king` IS a given-name title, so the word the peel left standing reads given `Sir.` with `title-or-name` reported. The end-to-end invariant is `tests/v2/test_parser.py::test_the_p5_licence_and_h1_read_a_title_run_the_same_way`, whose trailing-title half now requires `f"{title} abdul rahman Prof."` and `f"{title} John Prof."` to give the same given and family as the spellings without the trailing title; restoring the whole-`titles` key fails six of its eleven rows, which is the mutation that proves it discriminates. It carries the nickname spellings as well, and those are the only rows that separate the two splits above. Frame delta zero on both entry points: the run is built inside H1's guard, after the role counts have short-circuited, and the reference name `Dr. Juan0000 de la Vega III` has a family and never enters it. +- 2026-09-10 [#519](https://github.com/derek73/python-nameparser/issues/519) — `prince` and `princess` join the given-name titles; `lord` and `lady` do not. This RESOLVES the VOCABULARY SCOPE paragraph of the 2026-09-08 #489 bullet above, which is left as written: it named the four as not given-name titles and filed the question, and two of the four have now moved. The criterion is rules.md H Background's — membership follows how the title addresses — and `prince` and `princess` address by the given name (`Prince Harry`, `Princess Anne`), with no surname reading of the word behind it. The clause is for the two words and not for a royal class: TITLES holds `emperor`, `tsar`, `sultan`, `pharaoh`, `archduke`, `maharani` and their pairs, none of them weighed here, and `Emperor Akihito` still reads family `Akihito`. `lord` and `lady` split the same way as each other, by the bearer's rank, which the text does not carry: the given-name form is a courtesy style for children of the senior ranks alone — younger sons of dukes and marquesses (`Lord Peter`), daughters of dukes, marquesses and earls (`Lady Diana`) — and every peer and every wife takes the title or surname (`Lord Byron`, `Lady Thatcher`). [Debrett's "The trouble with titles"](https://debretts.com/the-trouble-with-titles/) and [Bratman's peerage primer](https://www.dbratman.net/nobility.html), both read 2026-09-10, agree on that boundary, Debrett's naming `Lady Jane Debrett` for a baronet's wife as the typical error. The issue gave `lord` the surname reading outright, hedged with a "mostly"; the hedge is this class. The set has no way to say "sometimes" — the `venerable` reasoning under #indic-honorifics — so Derek's call was to leave both out, decided rather than deferred, and the issue's proposed frequency test on `lady` was not run. Both are pinned as negative controls (`Lord Byron`, `Lady Gaga`) beside `His Excellency Lord Duncan`. + Both read sites move, through the one predicate: H1's fold reads `Prince Harry` given `Harry` and `Her Royal Highness Princess Anne` given `Anne` (the #489 bundle's negative control, flipped), and P5's licence reads `Prince abdul Rahman` given `abdul Rahman` with an empty family, as `Sir abdul Rahman` does. What membership does NOT decide is whether the word is a title at all: `Prince Fielder` reads given `Fielder` now and read family `Fielder` before, wrong both ways, and that is [#348](https://github.com/derek73/python-nameparser/issues/348)'s collision. + Measured 2026-09-10 over every name in `tools/differential/corpus*.jsonl` (1308 rows), parsed on this tree and on the same tree with a Lexicon whose `given_name_titles` lacks the two words, diffed across the seven fields and the ambiguity kinds: exactly TWO names move, `Prince Charles` and `Prince Fielder`, both radar-tier in corpus_issues.jsonl, both `{family, given}`. THREE after the docs commit, which puts `Her Royal Highness Princess Anne` in corpus_rules.jsonl as an H1 example. `Prince of Wales Jr` does not move — the joined run addresses by `wales`. Recompute by parsing the corpus glob twice with the two Lexicons and diffing. TITLES membership is unchanged, TITLES being the union. + ### H2 — the leading-abbreviation title - 2026-06-30 (leading-period-title design; v2 core, PR #288) — the shape test is v1 parity (period_abbreviation): two-plus letters then a period, bare initials exempt. Its site is the head of the part CARRYING THE GIVEN NAME — the whole name, or the post-comma part under a family comma — not "the head of the name"; that scope correction is PR #315 (2026-08-01, docs-only), verified against 1.4.0 from PyPI, so the parity claim is real and the narrower description never was. The extraction litmus (2026-08-15): the spec drafted this rule as diff --git a/docs/design/rules.md b/docs/design/rules.md index ca8bc389..b21b21b7 100644 --- a/docs/design/rules.md +++ b/docs/design/rules.md @@ -65,6 +65,7 @@ H1. Rationale: a title normally addresses by surname, so a title "Sir John Prof." → given="John" "Dr. Smith Sir." → family="Smith" "His Excellency Lord Duncan" → family="Duncan" + "Her Royal Highness Princess Anne" → given="Anne" "Sir John" → given="John" · boundary Accepted: a given-name title plus one name word leaves the family empty — the input names no family, and inventing one @@ -78,8 +79,12 @@ H1. Rationale: a title normally addresses by surname, so a title not change what the run addresses by, the last word being the one asked — `His Excellency Lord Duncan` reads family `Duncan` because `lord` is not a given-name title, not because the run is - long, and `Her Royal Highness Princess Anne` reads family `Anne` - for the same reason. + long. `lord` and `lady` stay out of that list by decision + (#519): each addresses by given name only as a courtesy style for + children of the senior ranks (`Lord Peter`, `Lady Diana`) and by + title or surname for every peer and every wife (`Lord Byron`, + `Lady Thatcher`), and the text does not say which the bearer is, + which the list cannot express; `prince` and `princess` are in it. history: decisions.md#H1 · interacts: H3, H5, P2, P3, P5, M2, S1, S2, N1, N3 · implemented: nameparser/_pipeline/_post_rules.py H2. Rationale: before a name, an abbreviation is almost always a diff --git a/docs/release_log.rst b/docs/release_log.rst index 0cfe93ab..0477cdd3 100644 --- a/docs/release_log.rst +++ b/docs/release_log.rst @@ -18,7 +18,7 @@ Release Log - **Mark ba as an acronym that is also an ordinary name, so a bare trailing Ba keeps the family name.** ``HumanName("Anna Ba")`` gives last ``Ba`` and reports a suffix-or-name ambiguity, where 2.0.0 through 2.2.0 gave suffix ``Ba`` and no last name. The SPACED full-name form keeps the credential reading: ``John Smith BA`` still gives suffix ``BA``, now flagged, and the dotted ``John Smith B.A.`` is an unflagged suffix, the periods settling it. The COMMA forms move, and this is the marking's real cost: ``Smith, BA`` gives first ``BA``, and ``John Smith, BA`` gives first ``BA``, last ``John Smith``, where 2.0.0 through 2.2.0 gave suffix ``BA`` for both -- what ``Smith, Ed`` costs, which S2 already accepted for the other ambiguous acronyms. A bracketed or quoted ``John Smith (BA)`` falls through to nickname parsing, as the 2.0 note for ``ma``/``do`` below recorded for that pair. Write ``B.A.`` to keep the credential reading. BA is a common credential and Ba a real surname in Vietnamese and Senegalese Fula, which is the ``ma``/``Ma`` shape exactly. No corpus name moves (#342) - - **Fix a title run addressing by its first title rather than its last.** ``HumanName("Her Majesty Queen Elizabeth")`` gives first ``Elizabeth`` with an empty last name, where every release since 1.4.0 gave last ``Elizabeth``. Several titles written together are one form of address and the one that does the addressing is the last, so the run is now matched whole or by its last word: ``Reverend Mother Teresa``, ``Dr. Sir John`` and ``Mr Sir John`` move the same way, and ``Sir Sheikh abdul rahman`` gives first ``abdul rahman``. What does NOT move is a run whose last word addresses by surname: ``His Excellency Lord Duncan`` still gives last ``Duncan`` and ``Her Royal Highness Princess Anne`` last ``Anne``, ``lord`` and ``princess`` not being given-name titles -- a vocabulary question with its own argument, filed separately (#519). A caller's multi-word entry still matches as a phrase. Two names in the differential corpora read differently for this rule. See the ``H1`` entry of ``docs/design/decisions.md`` (closes #489) + - **Fix a title run addressing by its first title rather than its last.** ``HumanName("Her Majesty Queen Elizabeth")`` gives first ``Elizabeth`` with an empty last name, where every release since 1.4.0 gave last ``Elizabeth``. Several titles written together are one form of address and the one that does the addressing is the last, so the run is now matched whole or by its last word: ``Reverend Mother Teresa``, ``Dr. Sir John`` and ``Mr Sir John`` move the same way, and ``Sir Sheikh abdul rahman`` gives first ``abdul rahman``. What does NOT move is a run whose last word addresses by surname: ``His Excellency Lord Duncan`` still gives last ``Duncan``, ``lord`` not being a given-name title. A caller's multi-word entry still matches as a phrase. Two names in the differential corpora read differently for this rule. See the ``H1`` entry of ``docs/design/decisions.md`` (closes #489) - **Fix the leading title peel taking a name word and leaving a post-nominal to be the name.** ``HumanName("Dr King Jr")`` gives title ``Dr``, last ``King``, suffix ``Jr``, where every release since 1.4.0 gave title ``Dr King``, last ``Jr`` and no suffix at all; ``Dr. King MD`` moves the same way, and both now read as the comma spelling ``King, Dr Jr`` always has. A title addresses somebody, so the run leaves a name word standing and a post-nominal is not one. A name that is nothing but titles or nothing but post-nominals is untouched, the word given back having to be a name candidate: ``Marquess of Bath``, ``MD DDS`` and ``Jr. Ph. D.`` are unchanged, and so is a title written as one joined unit -- ``Prince of Wales Jr`` keeps title ``Prince of Wales`` rather than losing the title to make a name. Where the run's whole content is the word given back there is no title left, so ``Dr Jr`` gives first ``Dr``, suffix ``Jr`` and reports a title-or-name ambiguity. Three names in the differential corpora read differently for this rule. See the ``H3`` entry of ``docs/design/decisions.md`` @@ -34,6 +34,8 @@ Release Log - **Add the renunciate titles to the given-name title list, so a renunciate's one name is a given name.** ``HumanName("Swami Vivekananda")`` gives first ``Vivekananda`` with an empty last name, where every release since 1.4.0 gave last ``Vivekananda``; ``Guru Nanak``, ``Baba Ramdev`` and ``Lama Zopa`` move the same way, and so do the Devanagari and Bengali spellings added below. Two name words behind the title are unchanged -- ``Swami Vivekananda Saraswati`` keeps last ``Saraswati`` -- and a surname-retaining title is untouched: ``Rabbi Cohen`` still gives last ``Cohen``. ``venerable`` is deliberately not in the list, the traditions using it splitting on whether the family name survives. See the ``indic-honorifics`` entry of ``docs/design/decisions.md`` (closes #346) + - **Add prince and princess to the given-name title list, so a royal's one name is a given name.** ``HumanName("Prince Harry")`` gives first ``Harry`` with an empty last name, where every release since 1.4.0 gave last ``Harry``; ``Princess Anne``, ``Her Royal Highness Princess Anne`` and ``Prince Charles`` move the same way, and ``Prince abdul Rahman`` gives first ``abdul Rahman`` as ``Sir abdul Rahman`` does. Two name words behind the title are unchanged -- ``Prince Harry Windsor`` keeps last ``Windsor`` -- and ``Prince of Wales Jr`` is unchanged, the joined title addressing by its last word. ``lord`` and ``lady`` are deliberately NOT in the list: ``Lord Byron`` still gives last ``Byron``, and ``Lady Gaga`` last ``Gaga``, because each addresses by given name only as a courtesy style for children of the senior ranks (``Lord Peter``, ``Lady Diana``) and by title or surname for every peer and every wife (``Lord Byron``, ``Lady Thatcher``), which the list cannot express. The given-name collision is untouched: ``Prince Fielder`` now gives first ``Fielder`` (#348). See the ``H1`` entry of ``docs/design/decisions.md`` (closes #519) + - **Add trailing honorifics as post-nominal vocabulary:** Latin ``rinpoche``, Devanagari ``जी``, ``साहब``, ``साहिब``, ``साहेब``, ``महाराज``, and Bengali ``সাহেব``, ``বাবু``, ``মহারাজ``. ``HumanName("Lama Zopa Rinpoche")`` reads title ``Lama``, first ``Zopa``, suffix ``Rinpoche``; ``नरेन्द्र मोदी जी`` reads last ``मोदी``, suffix ``जी``. They are recognized SPACED only and are deliberately absent from ``Lexicon.honorific_tails``: Banerjee, Mukherjee and Chatterjee end in the ``जी`` substring (``बनर्जी``, ``मुखर्जी``, ``चटर्जी``), so a glued peel would cut a real family name in two, and ``गांधीजी`` staying unpeeled is the accepted cost. Bengali ``বাবু`` is trailing where Devanagari ``बाबू`` is a leading title (#344, #343) - **Add Devanagari honorifics (#344):** ``डॉक्टर``, ``डा``, ``प्रो``, ``प्रोफेसर``, ``प्राध्यापक``, ``प्रा``, ``पंडित``, ``पं``, ``सरदार``, ``सुश्री``, ``श्रीयुत``, ``श्रीमान``, ``सौ``, ``बाबू``, ``महात्मा``, ``न्यायमूर्ति``, ``मौलाना``, ``जनाब`` and ``महाराजा`` as titles, beside the ``श्री``/``श्रीमती``/``डॉ`` that shipped in 2.1.0, and ``स्वामी``, ``गुरु``, ``बाबा``, ``संत`` as given-name titles. ``डॉक्टर शर्मा`` reads title ``डॉक्टर``, last ``शर्मा``; ``स्वामी विवेकानंद`` reads first ``विवेकानंद`` with no last name. Dotted spellings (``प्रो.``, ``पं.``) match the same entries. Excluded under the collision rule: ``कुमारी`` (Kumari is a given and a family name), ``बेगम``, ``शेख``, ``आचार्य``, ``राजा``/``रानी`` and ``ठाकुर``, all borne as ordinary names (closes #344) diff --git a/nameparser/config/titles.py b/nameparser/config/titles.py index 7f9856f6..ce2b3349 100644 --- a/nameparser/config/titles.py +++ b/nameparser/config/titles.py @@ -28,6 +28,20 @@ 'master', 'mother', 'pope', + # #519: prince and princess address by the GIVEN name ("Prince + # Harry", "Princess Anne"), which is what membership here means + # (rules.md#H Background); both were in the TITLES-only block + # until 2026-09-10. 'lord' and 'lady' stay there, decided rather + # than deferred: both split by the bearer's rank, which the text + # does not carry -- the given-name form is a courtesy style for + # children of the senior ranks alone ("Lord Peter", "Lady Diana"), + # and every peer and every wife takes the title or surname ("Lord + # Byron", "Lady Thatcher") -- and this set has no way to say + # "sometimes" (the 'venerable' reasoning above). Membership decides only which field the one word + # behind the title takes: "Prince Fielder" is #348's collision + # either way. + 'prince', + 'princess', 'queen', 'sir', 'sister', @@ -584,8 +598,6 @@ 'primate', 'prime', 'prin', - 'prince', - 'princess', 'principal', 'printer', 'printmaker', diff --git a/tests/v2/cases.py b/tests/v2/cases.py index 1a0a656e..1729a04a 100644 --- a/tests/v2/cases.py +++ b/tests/v2/cases.py @@ -2478,13 +2478,58 @@ def _check_cjk_shape_purity(self) -> None: classification="parity", notes="negative control: `lord` is not a given-name title, so " "the run's last word does not address by given name"), - Case("title_run_princess_is_vocabulary_scope", + # -- #519: prince and princess join the given-name titles. The + # criterion is rules.md#H Background's: a title that precedes and + # addresses by the GIVEN name belongs. "Prince Harry", "Princess + # Anne" address by given name and no surname reading of the word + # behind them exists. lord and lady stay out, decided: both split + # by the bearer's rank, given name for children of the senior + # ranks (Lord Peter, Lady Diana) and title or surname for every + # peer and every wife (Lord Byron, Lady Thatcher), and the set + # has no way to say "sometimes" -- the reason `venerable` stayed + # out. + Case("title_run_princess_addresses_by_given_name", "Her Royal Highness Princess Anne", - {"title": "Her Royal Highness Princess", "family": "Anne"}, - classification="parity", - notes="negative control: `princess` is not a given-name title " - "either -- a vocabulary question with its own frequency " - "argument (Prince Harry, Lady Gaga), deliberately out"), + {"title": "Her Royal Highness Princess", "given": "Anne"}, + classification="fix(#519)", + notes="was the #489 bundle's negative control for the " + "vocabulary question it filed as #519; `princess` now " + "IS a given-name title, so the run's last word " + "addresses by given name and the family is empty"), + Case("prince_and_one_name_word_is_a_given_name", "Prince Harry", + {"title": "Prince", "given": "Harry"}, + classification="fix(#519)", + notes="the name the release note advertises: 1.4.0 through " + "2.2.0 read family 'Harry'. H1's fold is pinned by the " + "Sir John rows; this pins the membership"), + Case("prince_licences_the_bound_given_join", "Prince abdul Rahman", + {"title": "Prince", "given": "abdul Rahman"}, + classification="fix(#519)", + notes="the second site that reads the set, rules.md#P5's " + "licence: behind a given-name title there is no family " + "to spare, so the bound word joins forward as it does " + "behind Sir. Read family 'Rahman' before #519"), + Case("prince_as_a_given_name_is_still_the_collision", "Prince Fielder", + {"title": "Prince", "given": "Fielder"}, + classification="fix(#519)", + notes="wrong either way -- Prince is his given name (#348). " + "Membership only decides which field the one word " + "behind the title takes; it does not decide whether " + "the word IS a title, and that is #348's question, " + "not this one's"), + Case("lord_stays_a_surname_title", "Lord Byron", + {"title": "Lord", "family": "Byron"}, + notes="negative control for #519: `lord` addresses a duke's " + "or marquess's younger son by given name (Lord Peter) " + "and every peer by title (Lord Byron); the set cannot " + "say 'sometimes', so it stays out and H1 families the " + "one word"), + Case("lady_stays_a_surname_title", "Lady Gaga", + {"title": "Lady", "family": "Gaga"}, + notes="negative control for #519: `lady` addresses by given " + "name for a peer's daughter (Lady Diana) and by " + "surname for a wife (Lady Thatcher); the set cannot " + "say 'sometimes', so it stays out as `lord` does"), Case("title_and_two_name_words_is_not_h1s", "Sir John Smith", {"title": "Sir", "given": "John", "family": "Smith"}, classification="parity", diff --git a/tests/v2/test_ledger_guards.py b/tests/v2/test_ledger_guards.py index ff84bcb6..ccff26f9 100644 --- a/tests/v2/test_ledger_guards.py +++ b/tests/v2/test_ledger_guards.py @@ -919,6 +919,17 @@ def test_case_shape_ids_exist_in_the_inventory() -> None: "fix(#346) a renunciate title and one name word leave the name a given name": ("Swami Vivekananda Saraswati", "Guru Gobind Singh", "Rabbi Cohen", "Swami", "Mr Guru Nanak"), + # #519's rule is a literal alternation, so a widening reaching + # only names the corpora lack would leave _CORPUS_CLAIMS unmoved. + # These probes are the wall: two name words behind the title (H1 + # does not fold), the joined run that addresses by its last word + # `wales`, the word standing last as an ordinary surname, a + # surname-addressing title that stayed out, the bare title, and + # 'Mr Prince Charles', which the anchored regex rejects but an + # alternation that lost its left anchor would claim. + "fix(#519) prince and princess join the given-name titles": + ("Prince Harry Windsor", "Prince of Wales Jr", "Robert Prince", + "Lord Byron", "Prince", "Mr Prince Charles"), # #449's six rules. The five CJK ones are literal-anchored on one # corpus name each, so _CORPUS_CLAIMS cannot move under a widening # that reaches only names the corpora lack; these probes are the @@ -970,8 +981,11 @@ def test_case_shape_ids_exist_in_the_inventory() -> None: # The run rule's boundary is the vocabulary the SHAPE needs: a # one-word run addresses nobody by given name, and a run whose # last word is title vocabulary that is NOT given-name vocabulary - # keeps its family name -- which is the question filed as #519 and - # deliberately not answered here. + # keeps its family name. 'Her Royal Highness Princess Anne' was + # that shape when this wall was built and stopped being it on + # 2026-09-10, when #519 made `princess` given-name vocabulary: it + # now MOVES, under fix(#519)'s rule and not this one, and it stays + # here because #489's regex must still not reach it. "fix(#489) a title run addresses by its last title": ("Sir John Smith", "His Excellency Lord Duncan", "Her Royal Highness Princess Anne", "Dr. Smith"), @@ -1741,6 +1755,14 @@ class _LatinCopy(NamedTuple): # move. One set, identical in all four ledgers. frozenset({"Baba Ramdev", "Guru Nanak", "Lama Zopa", "Swami Vivekananda"}), + # #519's movers, one corpus name per alternative -- a list of + # names, not a copy of any wordlist. The rule's subject is a + # SHAPE the vocabulary participates in (a royal title plus + # exactly one name word), and a member copying GIVEN_NAME_TITLES + # would reach 'Prince Harry Windsor', which does not move. One + # set, identical in all four ledgers. + frozenset({"Her Royal Highness Princess Anne", "Prince Charles", + "Prince Fielder"}), # #342's movers, one corpus name per alternative -- a list of # names, not a copy of any wordlist, so there is no vocabulary # for it to drift from. The rule's subject is what two REMOVED @@ -2374,6 +2396,12 @@ def _claim(rule: dict) -> _Claim: # before it reached the gate. "fix(#346) a renunciate title and one name word leave the name a given name": _Claim(4, ('family', 'given'), "a3399ee7b21e", None), + # #519's alternation. Three corpus names, `family` and `given` + # together: the fold moves both roles at once, so a widening + # taking one alone would change the roles here before it + # reached the gate. + "fix(#519) prince and princess join the given-name titles": + _Claim(3, ('family', 'given'), "ca37331f9803", None), # #342's alternation. Five corpus names and four roles: the # comma forms move `given` where the bare forms move `middle`, # so a widening taking one shape alone would change the roles @@ -2746,6 +2774,12 @@ def _claim(rule: dict) -> _Claim: # before it reached the gate. "fix(#346) a renunciate title and one name word leave the name a given name": _Claim(4, ('family', 'given'), "a3399ee7b21e", None), + # #519's alternation. Three corpus names, `family` and `given` + # together: the fold moves both roles at once, so a widening + # taking one alone would change the roles here before it + # reached the gate. + "fix(#519) prince and princess join the given-name titles": + _Claim(3, ('family', 'given'), "ca37331f9803", None), # #342's alternation. Five corpus names and four roles: the # comma forms move `given` where the bare forms move `middle`, # so a widening taking one shape alone would change the roles @@ -3014,6 +3048,12 @@ def _claim(rule: dict) -> _Claim: # before it reached the gate. "fix(#346) a renunciate title and one name word leave the name a given name": _Claim(4, ('family', 'given'), "a3399ee7b21e", None), + # #519's alternation. Three corpus names, `family` and `given` + # together: the fold moves both roles at once, so a widening + # taking one alone would change the roles here before it + # reached the gate. + "fix(#519) prince and princess join the given-name titles": + _Claim(3, ('family', 'given'), "ca37331f9803", None), # #342's alternation. Five corpus names and four roles: the # comma forms move `given` where the bare forms move `middle`, # so a widening taking one shape alone would change the roles @@ -3134,6 +3174,12 @@ def _claim(rule: dict) -> _Claim: # before it reached the gate. "fix(#346) a renunciate title and one name word leave the name a given name": _Claim(4, ('family', 'given'), "a3399ee7b21e", None), + # #519's alternation. Three corpus names, `family` and `given` + # together: the fold moves both roles at once, so a widening + # taking one alone would change the roles here before it + # reached the gate. + "fix(#519) prince and princess join the given-name titles": + _Claim(3, ('family', 'given'), "ca37331f9803", None), # #342's alternation. Five corpus names and four roles: the # comma forms move `given` where the bare forms move `middle`, # so a widening taking one shape alone would change the roles diff --git a/tools/differential/corpus_rules.jsonl b/tools/differential/corpus_rules.jsonl index 623332f9..44d6bb2a 100644 --- a/tools/differential/corpus_rules.jsonl +++ b/tools/differential/corpus_rules.jsonl @@ -55,6 +55,7 @@ "Hassan Mohamad Ali" "Hassan, Mohamad Ahmad Ali" "Her Majesty Queen Elizabeth" +"Her Royal Highness Princess Anne" "His Excellency Lord Duncan" "II Van Johnson" "J. Smith" diff --git a/tools/differential/expected_since_1.4.0.toml b/tools/differential/expected_since_1.4.0.toml index 715776f1..3c71e711 100644 --- a/tools/differential/expected_since_1.4.0.toml +++ b/tools/differential/expected_since_1.4.0.toml @@ -113,6 +113,33 @@ issue = "fix(#346) a renunciate title and one name word leave the name a given n name_regex = "^(?:Baba Ramdev|Guru Nanak|Lama Zopa|Swami Vivekananda)$" fields = ["family", "given"] +# #519: prince and princess moved from the TITLES-only block into +# GIVEN_NAME_TITLES on 2026-09-10. rules.md#H1's fold now reaches +# them, so the one name word behind the title is the GIVEN name and +# the family is empty, where every baseline read it as the family. +# Both roles move together and `fields` declares both. +# +# Three corpus names: 'Prince Charles' and 'Prince Fielder' from the +# radar-tier corpus_issues.jsonl, and the contract-tier rules.md +# example 'Her Royal Highness Princess Anne' (corpus_rules.jsonl), all +# moving the same way. 'Prince Fielder' moves too and is wrong either +# way -- Prince is his given name, which is #348's collision and not +# this rule's subject. The boundary is pinned by _MUST_NOT_MATCH +# probes: two name words behind the title ('Prince Harry Windsor'), +# the chained run 'Prince of Wales Jr' (addresses by `wales`), the +# trailing 'Robert Prince', 'Lord Byron' (`lord` stays out) and the +# bare 'Prince'. +# +# An anchored alternation of the NAMES rather than a shape, under +# the ledger discipline expected_since_1.4.0.toml's header states: +# keep each entry's `name_regex` as tight as the diff allows. It +# copies no wordlist, which _NOT_A_VOCABULARY_COPY records; +# _CORPUS_CLAIMS pins the reach with its digest. +[[change]] +issue = "fix(#519) prince and princess join the given-name titles" +name_regex = "^(?:Her Royal Highness Princess Anne|Prince Charles|Prince Fielder)$" +fields = ["family", "given"] + [[change]] issue = "fix(#271/#272/#298) native-script CJK: family-first order, hangul segmentation, the kana license and the dots" # '毛 泽东', '김민준': script_orders flips first/last for a name written diff --git a/tools/differential/expected_since_2.0.0.toml b/tools/differential/expected_since_2.0.0.toml index 4c6e0d50..809ab1f6 100644 --- a/tools/differential/expected_since_2.0.0.toml +++ b/tools/differential/expected_since_2.0.0.toml @@ -215,6 +215,33 @@ issue = "fix(#346) a renunciate title and one name word leave the name a given n name_regex = "^(?:Baba Ramdev|Guru Nanak|Lama Zopa|Swami Vivekananda)$" fields = ["family", "given"] +# #519: prince and princess moved from the TITLES-only block into +# GIVEN_NAME_TITLES on 2026-09-10. rules.md#H1's fold now reaches +# them, so the one name word behind the title is the GIVEN name and +# the family is empty, where every baseline read it as the family. +# Both roles move together and `fields` declares both. +# +# Three corpus names: 'Prince Charles' and 'Prince Fielder' from the +# radar-tier corpus_issues.jsonl, and the contract-tier rules.md +# example 'Her Royal Highness Princess Anne' (corpus_rules.jsonl), all +# moving the same way. 'Prince Fielder' moves too and is wrong either +# way -- Prince is his given name, which is #348's collision and not +# this rule's subject. The boundary is pinned by _MUST_NOT_MATCH +# probes: two name words behind the title ('Prince Harry Windsor'), +# the chained run 'Prince of Wales Jr' (addresses by `wales`), the +# trailing 'Robert Prince', 'Lord Byron' (`lord` stays out) and the +# bare 'Prince'. +# +# An anchored alternation of the NAMES rather than a shape, under +# the ledger discipline expected_since_1.4.0.toml's header states: +# keep each entry's `name_regex` as tight as the diff allows. It +# copies no wordlist, which _NOT_A_VOCABULARY_COPY records; +# _CORPUS_CLAIMS pins the reach with its digest. +[[change]] +issue = "fix(#519) prince and princess join the given-name titles" +name_regex = "^(?:Her Royal Highness Princess Anne|Prince Charles|Prince Fielder)$" +fields = ["family", "given"] + [[change]] issue = "fix(#342) rai and cha left the credential acronyms, so a trailing Rai or CHA is a name word" # 'rai' and 'cha' left SUFFIX_ACRONYMS on 2026-09-07 diff --git a/tools/differential/expected_since_2.1.0.toml b/tools/differential/expected_since_2.1.0.toml index 6b4231a5..5d40d66d 100644 --- a/tools/differential/expected_since_2.1.0.toml +++ b/tools/differential/expected_since_2.1.0.toml @@ -271,6 +271,33 @@ issue = "fix(#346) a renunciate title and one name word leave the name a given n name_regex = "^(?:Baba Ramdev|Guru Nanak|Lama Zopa|Swami Vivekananda)$" fields = ["family", "given"] +# #519: prince and princess moved from the TITLES-only block into +# GIVEN_NAME_TITLES on 2026-09-10. rules.md#H1's fold now reaches +# them, so the one name word behind the title is the GIVEN name and +# the family is empty, where every baseline read it as the family. +# Both roles move together and `fields` declares both. +# +# Three corpus names: 'Prince Charles' and 'Prince Fielder' from the +# radar-tier corpus_issues.jsonl, and the contract-tier rules.md +# example 'Her Royal Highness Princess Anne' (corpus_rules.jsonl), all +# moving the same way. 'Prince Fielder' moves too and is wrong either +# way -- Prince is his given name, which is #348's collision and not +# this rule's subject. The boundary is pinned by _MUST_NOT_MATCH +# probes: two name words behind the title ('Prince Harry Windsor'), +# the chained run 'Prince of Wales Jr' (addresses by `wales`), the +# trailing 'Robert Prince', 'Lord Byron' (`lord` stays out) and the +# bare 'Prince'. +# +# An anchored alternation of the NAMES rather than a shape, under +# the ledger discipline expected_since_1.4.0.toml's header states: +# keep each entry's `name_regex` as tight as the diff allows. It +# copies no wordlist, which _NOT_A_VOCABULARY_COPY records; +# _CORPUS_CLAIMS pins the reach with its digest. +[[change]] +issue = "fix(#519) prince and princess join the given-name titles" +name_regex = "^(?:Her Royal Highness Princess Anne|Prince Charles|Prince Fielder)$" +fields = ["family", "given"] + [[change]] issue = "fix(#342) rai and cha left the credential acronyms, so a trailing Rai or CHA is a name word" # 'rai' and 'cha' left SUFFIX_ACRONYMS on 2026-09-07 diff --git a/tools/differential/expected_since_2.2.0.toml b/tools/differential/expected_since_2.2.0.toml index 96073432..c3ae5386 100644 --- a/tools/differential/expected_since_2.2.0.toml +++ b/tools/differential/expected_since_2.2.0.toml @@ -260,6 +260,33 @@ issue = "fix(#346) a renunciate title and one name word leave the name a given n name_regex = "^(?:Baba Ramdev|Guru Nanak|Lama Zopa|Swami Vivekananda)$" fields = ["family", "given"] +# #519: prince and princess moved from the TITLES-only block into +# GIVEN_NAME_TITLES on 2026-09-10. rules.md#H1's fold now reaches +# them, so the one name word behind the title is the GIVEN name and +# the family is empty, where every baseline read it as the family. +# Both roles move together and `fields` declares both. +# +# Three corpus names: 'Prince Charles' and 'Prince Fielder' from the +# radar-tier corpus_issues.jsonl, and the contract-tier rules.md +# example 'Her Royal Highness Princess Anne' (corpus_rules.jsonl), all +# moving the same way. 'Prince Fielder' moves too and is wrong either +# way -- Prince is his given name, which is #348's collision and not +# this rule's subject. The boundary is pinned by _MUST_NOT_MATCH +# probes: two name words behind the title ('Prince Harry Windsor'), +# the chained run 'Prince of Wales Jr' (addresses by `wales`), the +# trailing 'Robert Prince', 'Lord Byron' (`lord` stays out) and the +# bare 'Prince'. +# +# An anchored alternation of the NAMES rather than a shape, under +# the ledger discipline expected_since_1.4.0.toml's header states: +# keep each entry's `name_regex` as tight as the diff allows. It +# copies no wordlist, which _NOT_A_VOCABULARY_COPY records; +# _CORPUS_CLAIMS pins the reach with its digest. +[[change]] +issue = "fix(#519) prince and princess join the given-name titles" +name_regex = "^(?:Her Royal Highness Princess Anne|Prince Charles|Prince Fielder)$" +fields = ["family", "given"] + [[change]] issue = "fix(#342) rai and cha left the credential acronyms, so a trailing Rai or CHA is a name word" # 'rai' and 'cha' left SUFFIX_ACRONYMS on 2026-09-07