From d02dc41e4636f51270a83e3b18683262c7523505 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Tue, 1 Sep 2026 21:53:32 -0700 Subject: [PATCH] =?UTF-8?q?test:=20triage=20the=20v1=20xfails=20=E2=80=94?= =?UTF-8?q?=20four=20pinned=20as=20decided,=20four=20annotated=20with=20th?= =?UTF-8?q?eir=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eight v1-era @pytest.mark.xfail tests survived the bank reconciliation, each an aspiration nobody had judged since. xfail_strict makes them live claims that 2.0 does not do the thing, but a bare marker never says whether the thing is wanted. Triaged 2026-09-01: | test | disposition | |-------------------------------------------------|---------------| | conjunctions::..._address_with_a_first_name_title | xfail -> #489 | | conjunctions::test_two_initials_conflict_... | xfail -> #490 | | titles::test_chained_title_first_name_title_... | xfail -> #490 | | capitalization::..._already_capitalized_III_... | xfail -> #492 | | suffixes::..._explicit_suffix_comma_... | pinned | | suffixes::test_king | pinned | | titles::test_title_multiple_titles_with_apostro.. | pinned | | titles::test_ben_as_conjunction | pinned | Grounds for the four not-fixes, each test rewritten to pin the measured parse with the rejected aspiration recorded beside it: - "Maier, Amy I, Jr." reads middle 'I', suffix 'Jr.'. A middle initial I is far more common than an ordinal I borne without a Sr./Jr.-style companion. decisions.md#vocabulary-collisions' commonality reasoning, applied to a shape rather than a word. - "Dr King Jr" reads title 'Dr King', family 'Jr'. 'king' stays in TITLES for the addressing forms; the comma format "King, Dr Jr" is the road to the surname reading and is now pinned alongside. - "Ahmad ben Husain" reads middle 'ben'. Decided in v0.2.5; 'ben' collides with the given name Ben in the position a particle claim would act on. - The all-titles input takes its last title-word as the family. A name parser, not a title parser; accepted convention. The silent guess is #491. Renames where the old name now stated a falsehood: the roman-numeral test lost _known_limitation, test_ben_as_conjunction became test_ben_is_not_a_ particle. The capitalization test keeps _KNOWN_FAILURE — still true, and redundant rather than misleading beside the annotated reason. Pinning current behavior exposed two places where rules.md predicts something else. Both are named here rather than left for a future reader to rediscover as a regression; neither changes parser behavior. C1 is AMENDED. Its one-character clause said a bare single letter behind a name word is the generation, full stop — which predicts the very suffix "I, Jr." this triage rejects. The discriminator the parser has always applied and C1 omitted is the further comma: "Maier, Amy I" reads suffix 'I' while "Maier, Amy I, Jr." reads middle 'I', and "Smith, John V" against "Smith, John V, Jr." is the same pair on a name C1 already exemplifies. All four measured. The rule was incomplete, not the behavior — a rules.md defect, not a parser one, the call this file already made for 'Donald mc'. The qualifier is normative prose with no example line of its own; C1's new Accepted block records why and names the bank pair as its witness, so the rules corpus is unchanged (1120 names, gate parity at 2.2.0). S2 gets a DESCRIPTIVE note, not an amendment. S2 plus its Accepted clause predicts "Dr King Jr" -> suffix 'Jr', family ''; it reads family 'Jr', suffix '' because 'king' is title vocabulary and H1 then claims the one remaining word. "Dr Smith Jr" isolates the cause, reading family 'Smith', suffix 'Jr' as stated. Only the vocabulary half is Derek's decision; the leftover reading is recorded as today's, not endorsed, and a change toward S2's prediction is an improvement that updates the pin rather than a regression — the test says so. S2 now also declares interacts: H1, H2, C1, which H1 already declared back. 'ben' additionally lands in the particle Excluded block, where a wordlist sweep will meet it; a keep-out living only in a triage entry is one the next patronymic-particle sweep never reads. #485 is superseded: it proposed prefixing corpus labels with xfail: so radar diffs on these names read as known-bad parses improving, but after the triage the v1 marker no longer tracks the disposition — "Dr King Jr" is a pin, so a diff on it means a decided reading moved. Left open for the controller to close at merge. Recorded at docs/design/decisions.md#v1-xfail-triage with the recompute. Co-Authored-By: Claude Fable 5 --- docs/design/decisions.md | 19 ++++++++++++ docs/design/rules.md | 44 ++++++++++++++++++++++++--- tests/test_capitalization.py | 2 +- tests/test_conjunctions.py | 4 +-- tests/test_suffixes.py | 59 ++++++++++++++++++++++++++++++------ tests/test_titles.py | 33 +++++++++++++++----- 6 files changed, 137 insertions(+), 24 deletions(-) diff --git a/docs/design/decisions.md b/docs/design/decisions.md index 4e928bfc..ea625bb6 100644 --- a/docs/design/decisions.md +++ b/docs/design/decisions.md @@ -252,6 +252,24 @@ The mechanism shipped twice before anyone wrote down its criterion. Sizes as of Open: [#348](https://github.com/derek73/python-nameparser/issues/348) applying C-i to the 711 title entries, then titles_ambiguous plus a TITLE_OR_GIVEN kind. Blocked on data, not on judgement — the census needs a given-name frequency corpus this repo does not have, which is why the criterion is recorded here and the census is not attempted. +### v1-xfail-triage — the eight inherited aspirations, four retired and four kept + +The reconciled v1-style banks (`tests/test_*.py`) carried eight `@pytest.mark.xfail` tests inherited from v1, each an aspiration nobody had judged since. `xfail_strict = true`, so all eight were live claims that 2.0 still does not do the thing — but a bare marker says only that, never whether the thing is WANTED, and that is the gap this triage closes. After it, every surviving marker cites an issue and every retired one is a deliberate pin of current behavior. The parses quoted below are this entry's evidence and its own recompute: + + uv run python -c "from nameparser import HumanName; [print(repr(s), repr(HumanName(s))) for s in ('Maier, Amy I, Jr.', 'Dr King Jr', 'King, Dr Jr', 'Ahmad ben Husain', \"The Right Hon. the President of the Queen's Bench Division\")]" + +- 2026-09-01 (Derek's triage) — NOT FIXED, four. The aspiration is rejected, the marker is gone, and the test now pins what the parser does with the grounds recorded beside it so nobody re-derives the rejected proposal: + - `Maier, Amy I, Jr.` — v1 wanted suffix "I, Jr."; it reads given Amy, middle I, family Maier, suffix "Jr.". A middle initial `I` is far more common than an ordinal I borne without a Sr./Jr.-style companion, so where an explicit suffix comma has already named the suffix, the trailing I stays a middle initial. That is #vocabulary-collisions' commonality reasoning applied to a SHAPE rather than to a word — the criterion is written per-word, and the extension is named here rather than smuggled in. **This amends rules.md#C1 in the same diff.** C1's one-character clause said a bare single letter behind a name word is the generation, full stop, which predicts exactly the suffix "I, Jr." being rejected here. The discriminator the parser has always applied and C1 omitted is the FURTHER COMMA: `Maier, Amy I` reads suffix 'I' while `Maier, Amy I, Jr.` reads middle 'I', and `Smith, John V` against `Smith, John V, Jr.` is the same pair on a name C1 already exemplifies — all four measured. So the rule was incomplete, not the behavior: a rules.md defect rather than a parser one, which is the call this file already made for `'Donald mc'` in the fields-only arc entry (2026-08-27, #451). The qualifier is normative prose carrying no example line of its own; C1's Accepted block records why and names the bank pair as its executable witness. + - `Dr King Jr` — v1 wanted title 'Dr', family 'King', suffix 'Jr'; it reads title 'Dr King', family 'Jr'. `king` stays in the titles vocabulary: it is there for the addressing forms ("King Charles"), and taking it out to serve the surname reading trades a common use for a rarer one, which is the direction #vocabulary-collisions cuts. TITLES has no ambiguous subset and no AmbiguityKind, so — as with MAIDEN_MARKERS and `roz` — the only two expressions of the criterion available here are ship and do not ship; #348 is the open work that would give this set a third answer. The comma format is the road to the surname reading, and is now pinned alongside rather than left as prose — `King, Dr Jr` reads title 'Dr', family 'King', suffix 'Jr'. The test cites [#27](https://github.com/derek73/python-nameparser/issues/27), which is closed; this is the decision it never got. + Two halves, and only one is decided. DECIDED: `king` stays in TITLES. RECORDED, not endorsed: what becomes of the leftover `Jr`. rules.md#S2 predicts suffix 'Jr' with an empty family — its Accepted clause consumes an unambiguous suffix even when nothing is left to be the family (`Smith Jr.` → family "") — but once the title chain has taken `Dr King`, H1 claims the one remaining word and it reads family 'Jr', suffix ''. `Dr Smith Jr` isolates the cause: family 'Smith', suffix 'Jr', exactly as S2 states. S2 now carries a descriptive note saying so. A future change moving `Dr King Jr` toward S2's prediction is an IMPROVEMENT and updates the pin; it is not a regression, and the test says as much so nobody reads the pin as an endorsement. + - `Ahmad ben Husain` — v1 wanted family "ben Husain"; it reads given Ahmad, middle ben, family Husain. Already decided in v0.2.5, when `ben` came out of the prefixes, and for the reason that still holds: `ben` collides with the given name Ben, in the position the particle claim would act on — `Ahmad Ben Husain` reads middle 'Ben' today, which is exactly the token a forward-joining particle claim would take. That is C-i's position test, and it keeps `ben` out. Recorded a second time as a standing keep-out in this file's Excluded block for the particle set, because that is where a wordlist sweep meets it: a keep-out that lives only in a triage entry is one the next Arabic/Hebrew patronymic-particle sweep never reads. Worth naming as a failure mode of its own — the marker was an aspiration that outlived its own resolution, and nothing in a bare xfail says which of the eight were like that. + - `The Right Hon. the President of the Queen's Bench Division` — v1 wanted the whole string as one title; it reads title "The Right Hon. the President of the Queen's Bench", family 'Division'. This is a name parser, not a title parser: handed an input that is all titles it assumes the last title-word is the name. Accepted as convention rather than defended as correct — what is actually wrong is that the guess is silent, which is [#491](https://github.com/derek73/python-nameparser/issues/491), not this reading. +- 2026-09-01 — FIX CANDIDATES, four. The marker stays and now carries its issue, so `pytest -rx` names the work instead of listing anonymous aspirations: + - [#489](https://github.com/derek73/python-nameparser/issues/489) — `Her Majesty Queen Elizabeth` should address by given name (`tests/test_conjunctions.py::test_conjunction_in_an_address_with_a_first_name_title`). + - [#490](https://github.com/derek73/python-nameparser/issues/490) — `E.T. Smith` (`tests/test_conjunctions.py::test_two_initials_conflict_with_conjunction`) and `U.S. District Judge Marc Thomas Treadwell` (`tests/test_titles.py::test_chained_title_first_name_title_is_initials`). One issue for two tests deliberately: each test's own comment names the other's shape as what blocks a fix — dotted initials against dotted title and credential vocabulary — so they are one question, and fixing either alone is what has failed before. + - [#492](https://github.com/derek73/python-nameparser/issues/492) — `capitalize()` leaves `juan garcia III` lowercase (`tests/test_capitalization.py::test_capitalization_exception_for_already_capitalized_III_KNOWN_FAILURE`). The `_KNOWN_FAILURE` suffix is kept: it is still true, and beside the annotated reason it reads as redundant rather than misleading. +- [#485](https://github.com/derek73/python-nameparser/issues/485) is superseded. It proposed prefixing corpus labels with `xfail:` so a radar diff on a name like `Dr King Jr` reads as a known-bad parse improving rather than a regression. The premise was that a v1 xfail marker is a usable triage signal; after this triage it is not, because the marker no longer tracks the disposition — `Dr King Jr` is now a PIN, so a radar diff on it means a decided reading moved, which is precisely the signal the prefix would have suppressed. The four that remain carry issue numbers, which is the same information in a place that cannot go stale against the pinned historical ref. + ### suffix-field-composition — three kinds of thing in one field - 2026-08-16 (suffix keystone; #326) — measured composition of suffix_words (40 entries): 11 generational (i, ii, iii, iv, v, jr, jnr, sr, snr, junior, 2), 5 neither (dr, esq, esquire, ret, vet), and 24 POSTNOMINAL HONORIFICS — 20 CJK (さん, さま, くん, ちゃん, 様, 殿, 氏, 先生, 博士, 教授, 女士, 小姐, 씨, 양, 군, 님, 박사, 박사님, 교수님, 선생님) and 4 Hebrew (ז"ל, ז״ל, שליט"א, שליט״א). The honorifics are the LARGEST group. @@ -389,6 +407,7 @@ Excluded (the never-given / ambiguous particle line, nameparser/config/particles - Only 9 of the ambiguous members were ever individually justified; the rest sit there by the conservative default (ambiguous unless argued never-given). - mc, ste — measured misparses ("Mc Donald" → given "Mc"), tracked in #360; st is inert at the head because TITLES claims it first; mac must stay ambiguous because Mac is a real given name. +- ben — OUT of the set entirely, removed in v0.2.5 and re-affirmed 2026-09-01 (#v1-xfail-triage). Ben is a common given name standing exactly where a forward-joining particle claim would act: "Ahmad Ben Husain" reads middle 'Ben' today, and admitting `ben` would take that token into the family. This is the C-i position test, and the entry is here because the pressure to re-add is real and recurring — "Ahmad ben Husain" wants family "ben Husain", the v1 suite carried that aspiration as an xfail for years, and a sweep of Arabic and Hebrew patronymic particles is exactly how it comes back. A caller who needs the patronymic reading adds it to their own Lexicon. - Encoding rationale (#293, predating #360's membership questions): the data layer stores the NEVER-GIVEN set and derives the ambiguous one, because that is safe-by-default for new particles — a one-place addition — and the v1 shim translates by one-directional complement. And the constants are FROZEN specifically to kill the cached-Lexicon.default()-vs-fresh- Constants desync that runtime module-constant mutation caused. - Load-bearing dependency: TITLES ∩ ambiguous — {do, freiherr, st} until #296's audit, {freiherr, st} since — is what keeps the particle-or-given ambiguity emitter reachable at all; moving them all would leave the shipped vocabulary unable to reach it, which is why test_the_chained_emitter_is_reachable_by_construction (tests/v2/test_parser.py) pins reachability by construction rather than by shipped vocabulary — an empty intersection fails no test and does not mean the emitter is unreachable. diff --git a/docs/design/rules.md b/docs/design/rules.md index 1a515558..f6cc6523 100644 --- a/docs/design/rules.md +++ b/docs/design/rules.md @@ -572,7 +572,22 @@ S2. Rationale: generational suffixes and credentials are recognized "Jack Wei Ma" → suffix="Ma" "Jack Wei Ma" → ambiguities=("suffix-or-name",) "Smith Jr." → family="" - implemented: nameparser/_pipeline/_classify.py, nameparser/_pipeline/_group.py, nameparser/_pipeline/_pieces.py, nameparser/_pipeline/_vocab.py + Note, DESCRIPTIVE and not promised: where a title chain consumes + every word but one, the word left over is claimed by H1's + one-word reading before this rule's trailing-suffix reading + reaches it — as the family ordinarily, as the given name behind + a given-name title (`Sir Jr` reads given `Jr`). `Dr King + Jr` reads title `Dr King`, family `Jr`, empty suffix — not the + suffix `Jr` with an empty family the Accepted clause above + predicts — because `king` is title vocabulary and H1 then takes + the one remaining word. The contrast that isolates the cause is + `Dr Smith Jr`, which reads family `Smith`, suffix `Jr` as + stated. Only the vocabulary half is decided + (decisions.md#v1-xfail-triage: `king` stays a title, for the + addressing forms); the leftover reading is recorded as today's + rather than endorsed, and a change moving it toward this rule's + prediction is an improvement, to be argued here. + interacts: H1, H2, C1 · implemented: nameparser/_pipeline/_classify.py, nameparser/_pipeline/_group.py, nameparser/_pipeline/_pieces.py, nameparser/_pipeline/_vocab.py S3. Rationale: credentials are often written run together with periods; the chunks between the periods are what carry the @@ -836,9 +851,17 @@ C1. Rationale: a credential run after the comma means the name is in run, written with a period or without, an initial being no shape anyone writes there; behind a name word it is the generation only when written bare, a period marking it the abbreviation of - a name and so a middle initial. Longer suffix words are not in - question either way, and the strict knob above still vetoes the - initial-shaped ones, so the run ends at them there. + a name and so a middle initial. Both branches then ask that the + generation slot still be open: where a further comma has already + named the suffix, a single letter ending the GIVEN part has no + generation left to be, and stays a middle initial — with a + period or without, and whether a name word or another suffix + word stands before it. Only the given part is touched; a part + after the comma that is nothing but suffix words is the + credential run, and a letter in it continues that run up to the + further comma. Longer suffix words are not in question either + way, and the strict knob above still vetoes the initial-shaped + ones, so the run ends at them there. "Smith, John" → family="Smith" "سلمان، محمد" → family="سلمان" "田中、太郎" → family="" @@ -885,6 +908,19 @@ C1. Rationale: a credential run after the comma means the name is in form alone, and that limitation is kept as parity: "Smith, RN - CRNA" reads given "RN" under the policy as without it. "John Smith, LEED AP" → family="Smith" deviates: #291 (today: family="John Smith") + Accepted: the further-comma qualifier carries no example line of + its own. It discriminates PAIRS and spans both branches, so + exemplifying it means a with-comma partner for each — every one + of them a name entering the rules corpus for behavior that has + not moved since v1. Its executable witness is instead the pair + already standing in the v1-style bank, + tests/test_suffixes.py's + test_roman_numeral_i_after_single_initial_lastname_comma_format + and test_roman_numeral_i_with_explicit_suffix_comma_stays_a_middle_initial. + Read the examples above with the qualifier in hand: `Smith, John + V` reads the suffix and `Smith, John PhD I.` continues the run, + while adding a suffix comma after either turns that same letter + into the middle initial. history: decisions.md#C1 · interacts: H2, P6, W3 · implemented: nameparser/_pipeline/_segment.py, nameparser/_pipeline/_assign.py, nameparser/_pipeline/_group.py C2. Rationale: text beyond the recognized comma parts should be diff --git a/tests/test_capitalization.py b/tests/test_capitalization.py index 63b456ff..9cad8aaf 100644 --- a/tests/test_capitalization.py +++ b/tests/test_capitalization.py @@ -17,7 +17,7 @@ def test_capitalization_exception_for_III(self) -> None: # FIXME: this test does not pass due to a known issue # http://code.google.com/p/python-nameparser/issues/detail?id=22 - @pytest.mark.xfail + @pytest.mark.xfail(reason="#492") def test_capitalization_exception_for_already_capitalized_III_KNOWN_FAILURE(self) -> None: hn = HumanName('juan garcia III') hn.capitalize() diff --git a/tests/test_conjunctions.py b/tests/test_conjunctions.py index 8c7c0b18..8133d622 100644 --- a/tests/test_conjunctions.py +++ b/tests/test_conjunctions.py @@ -87,7 +87,7 @@ def test_lowercase_middle_initial_comma_lastname_and_suffix_conflict_with_conjun self.m(hn.last, "Smith", hn) self.m(hn.suffix, "III, Jr", hn) - @pytest.mark.xfail + @pytest.mark.xfail(reason="#490") def test_two_initials_conflict_with_conjunction(self) -> None: # Supporting this seems to screw up titles with periods in them like M.B.A. hn = HumanName('E.T. Smith') @@ -213,7 +213,7 @@ def test_conjunction_in_an_address_with_a_title(self) -> None: self.m(hn.title, "His Excellency Lord", hn) self.m(hn.last, "Duncan", hn) - @pytest.mark.xfail + @pytest.mark.xfail(reason="#489") def test_conjunction_in_an_address_with_a_first_name_title(self) -> None: hn = HumanName("Her Majesty Queen Elizabeth") self.m(hn.title, "Her Majesty Queen", hn) diff --git a/tests/test_suffixes.py b/tests/test_suffixes.py index 35661547..ad9d3f00 100644 --- a/tests/test_suffixes.py +++ b/tests/test_suffixes.py @@ -1,5 +1,3 @@ -import pytest - from nameparser import HumanName from tests.base import HumanNameTestBase @@ -141,9 +139,36 @@ def test_potential_suffix_that_is_also_last_name_with_suffix_comma(self) -> None self.m(hn.suffix, "III, Jr", hn) # https://github.com/derek73/python-nameparser/issues/27 - @pytest.mark.xfail def test_king(self) -> None: + # v1 aspired to read "Dr King Jr" as title 'Dr', family 'King', + # suffix 'Jr', and shipped this as an xfail. + # Two halves, and this pin means different things about each. + # DECIDED 2026-09-01 (v1-xfail triage, decisions.md#v1-xfail-triage): + # 'king' stays in the titles vocabulary, because removing it breaks + # the addressing forms it is there for ("King Charles"). + # decisions.md#vocabulary-collisions cuts toward keeping it, so the + # title chain takes 'Dr King'. The comma format is the road to the + # surname reading, pinned below. + # RECORDED, NOT ENDORSED: what becomes of the leftover 'Jr'. + # rules.md#S2 has a trailing suffix-vocabulary word read as a suffix, + # and its Accepted clause consumes one even when that leaves no family + # at all, so S2 predicts suffix 'Jr', family ''. Once the title + # chain has eaten two words, H1 claims the one that remains and it + # reads family 'Jr' instead. Compare 'Dr Smith Jr', which reads + # family 'Smith', suffix 'Jr' exactly as S2 states. A change moving + # this toward S2's prediction is an IMPROVEMENT that updates this + # pin, not a regression. hn = HumanName("Dr King Jr") + self.m(hn.title, "Dr King", hn) + self.m(hn.first, "", hn) + self.m(hn.middle, "", hn) + self.m(hn.last, "Jr", hn) + self.m(hn.suffix, "", hn) + + def test_king_as_a_family_name_via_the_comma_format(self) -> None: + # The workaround test_king's decision rests on: writing the family + # first defeats the title chain and reads 'King' as the family. + hn = HumanName("King, Dr Jr") self.m(hn.title, "Dr", hn) self.m(hn.last, "King", hn) self.m(hn.suffix, "Jr", hn) @@ -200,14 +225,28 @@ def test_roman_numeral_i_after_single_initial_lastname_comma_format(self) -> Non self.m(hn.last, "Chang", hn) self.m(hn.suffix, "I", hn) - @pytest.mark.xfail - def test_roman_numeral_i_with_explicit_suffix_comma_known_limitation(self) -> None: - # When an explicit suffix comma is present (len(parts)==3), the trailing 'I' - # is conservatively left in middle to avoid misclassifying true initials. - # This is a known limitation of the lastname-comma lenient-suffix - # guard in parse_full_name (issue #144). + def test_roman_numeral_i_with_explicit_suffix_comma_stays_a_middle_initial(self) -> None: + # v1 aspired to read the trailing 'I' as an ordinal joining the explicit + # suffix ("I, Jr."), and shipped this as an xfail. + # NOT FIXED, decided 2026-09-01 (v1-xfail triage, + # decisions.md#v1-xfail-triage): a middle initial 'I' is far more common + # than an ordinal I borne without a Sr./Jr.-style companion, so when an + # explicit suffix comma has already named the suffix, the trailing 'I' + # stays a middle initial. The commonality reasoning is the + # vocabulary-collision criterion (decisions.md#vocabulary-collisions) + # applied to a shape rather than to a word. + # Contrast test_roman_numeral_i_after_single_initial_lastname_comma_format + # above, where no explicit suffix comma is present and the 'I' does go to + # suffix. + # rules.md#C1 gained a qualifier with this triage: where a further + # comma has already named the suffix, the trailing single letter has + # no generation left to be. C1 carries no example line for it (see the + # Accepted note there), so this pair of tests is its witness. hn = HumanName("Maier, Amy I, Jr.") - self.m(hn.suffix, "I, Jr.", hn) + self.m(hn.first, "Amy", hn) + self.m(hn.middle, "I", hn) + self.m(hn.last, "Maier", hn) + self.m(hn.suffix, "Jr.", hn) def test_suffix_delimiter_default_on_constants(self) -> None: from nameparser.config import CONSTANTS diff --git a/tests/test_titles.py b/tests/test_titles.py index 19e68396..11b32123 100644 --- a/tests/test_titles.py +++ b/tests/test_titles.py @@ -61,7 +61,7 @@ def test_title_is_title(self) -> None: # chain never starts (an interim 2.0 build that stripped interior # periods made this pass, but that normalization wrongly turned # 'J.R.' into the title 'jr'; v1 parity won) - @pytest.mark.xfail + @pytest.mark.xfail(reason="#490") def test_chained_title_first_name_title_is_initials(self) -> None: hn = HumanName("U.S. District Judge Marc Thomas Treadwell") self.m(hn.title, "U.S. District Judge", hn) @@ -97,10 +97,20 @@ def test_chained_hyphenated_title_with_comma_suffix(self) -> None: self.m(hn.last, "Davis", hn) self.m(hn.suffix, "III", hn) - @pytest.mark.xfail def test_title_multiple_titles_with_apostrophe_s(self) -> None: + # v1 aspired to read the whole string as one title, and shipped this + # as an xfail. + # NOT FIXED, decided 2026-09-01 (v1-xfail triage, + # decisions.md#v1-xfail-triage): this is a name parser, not a title + # parser. Handed an input that is all titles, it assumes the last + # title-word is the name, so 'Division' becomes the family. Accepted + # convention rather than a defect; #491 tracks reporting the guess. hn = HumanName("The Right Hon. the President of the Queen's Bench Division") - self.m(hn.title, "The Right Hon. the President of the Queen's Bench Division", hn) + self.m(hn.title, "The Right Hon. the President of the Queen's Bench", hn) + self.m(hn.first, "", hn) + self.m(hn.middle, "", hn) + self.m(hn.last, "Division", hn) + self.m(hn.suffix, "", hn) def test_title_starts_with_conjunction(self) -> None: hn = HumanName("The Rt Hon John Jones") @@ -187,12 +197,21 @@ def test_possible_conflict_with_suffix_that_could_be_initial(self) -> None: self.m(hn.suffix, "V, Jr.", hn) # 'ben' was removed from the particle set (then PREFIXES) in v0.2.5 - # this test could re-enable this test if we decide to support 'ben' as a prefix - @pytest.mark.xfail - def test_ben_as_conjunction(self) -> None: + def test_ben_is_not_a_particle(self) -> None: + # v1 aspired to read 'ben' as a particle joining the family + # ("ben Husain"), and shipped this as an xfail. + # NOT FIXED, decided 2026-09-01 (v1-xfail triage, + # decisions.md#v1-xfail-triage): the question was already settled in + # v0.2.5, when 'ben' was removed from the prefixes. 'ben' collides with + # the given name Ben, which is the same criterion + # (decisions.md#vocabulary-collisions) that keeps it out today. + # test_ben_as_first_name below is the collision it protects. hn = HumanName("Ahmad ben Husain") + self.m(hn.title, "", hn) self.m(hn.first, "Ahmad", hn) - self.m(hn.last, "ben Husain", hn) + self.m(hn.middle, "ben", hn) + self.m(hn.last, "Husain", hn) + self.m(hn.suffix, "", hn) def test_ben_as_first_name(self) -> None: hn = HumanName("Ben Johnson")