Skip to content

Commit 7dbb9bf

Browse files
authored
Merge pull request #493 from derek73/claude/xfail-triage
The v1 xfails get their dispositions: four pinned as decided, four annotated with their issues
2 parents 6c2033c + d02dc41 commit 7dbb9bf

6 files changed

Lines changed: 137 additions & 24 deletions

File tree

docs/design/decisions.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,24 @@ The mechanism shipped twice before anyone wrote down its criterion. Sizes as of
252252

253253
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.
254254

255+
### v1-xfail-triage — the eight inherited aspirations, four retired and four kept
256+
257+
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:
258+
259+
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\")]"
260+
261+
- 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:
262+
- `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.
263+
- `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.
264+
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.
265+
- `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.
266+
- `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.
267+
- 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:
268+
- [#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`).
269+
- [#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.
270+
- [#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.
271+
- [#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.
272+
255273
### suffix-field-composition — three kinds of thing in one field
256274

257275
- 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
389407

390408
- Only 9 of the ambiguous members were ever individually justified; the rest sit there by the conservative default (ambiguous unless argued never-given).
391409
- 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.
410+
- 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.
392411
- 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.
393412
- 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.
394413

docs/design/rules.md

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,22 @@ S2. Rationale: generational suffixes and credentials are recognized
572572
"Jack Wei Ma" → suffix="Ma"
573573
"Jack Wei Ma" → ambiguities=("suffix-or-name",)
574574
"Smith Jr." → family=""
575-
implemented: nameparser/_pipeline/_classify.py, nameparser/_pipeline/_group.py, nameparser/_pipeline/_pieces.py, nameparser/_pipeline/_vocab.py
575+
Note, DESCRIPTIVE and not promised: where a title chain consumes
576+
every word but one, the word left over is claimed by H1's
577+
one-word reading before this rule's trailing-suffix reading
578+
reaches it — as the family ordinarily, as the given name behind
579+
a given-name title (`Sir Jr` reads given `Jr`). `Dr King
580+
Jr` reads title `Dr King`, family `Jr`, empty suffix — not the
581+
suffix `Jr` with an empty family the Accepted clause above
582+
predicts — because `king` is title vocabulary and H1 then takes
583+
the one remaining word. The contrast that isolates the cause is
584+
`Dr Smith Jr`, which reads family `Smith`, suffix `Jr` as
585+
stated. Only the vocabulary half is decided
586+
(decisions.md#v1-xfail-triage: `king` stays a title, for the
587+
addressing forms); the leftover reading is recorded as today's
588+
rather than endorsed, and a change moving it toward this rule's
589+
prediction is an improvement, to be argued here.
590+
interacts: H1, H2, C1 · implemented: nameparser/_pipeline/_classify.py, nameparser/_pipeline/_group.py, nameparser/_pipeline/_pieces.py, nameparser/_pipeline/_vocab.py
576591

577592
S3. Rationale: credentials are often written run together with
578593
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
836851
run, written with a period or without, an initial being no shape
837852
anyone writes there; behind a name word it is the generation
838853
only when written bare, a period marking it the abbreviation of
839-
a name and so a middle initial. Longer suffix words are not in
840-
question either way, and the strict knob above still vetoes the
841-
initial-shaped ones, so the run ends at them there.
854+
a name and so a middle initial. Both branches then ask that the
855+
generation slot still be open: where a further comma has already
856+
named the suffix, a single letter ending the GIVEN part has no
857+
generation left to be, and stays a middle initial — with a
858+
period or without, and whether a name word or another suffix
859+
word stands before it. Only the given part is touched; a part
860+
after the comma that is nothing but suffix words is the
861+
credential run, and a letter in it continues that run up to the
862+
further comma. Longer suffix words are not in question either
863+
way, and the strict knob above still vetoes the initial-shaped
864+
ones, so the run ends at them there.
842865
"Smith, John" → family="Smith"
843866
"سلمان، محمد" → family="سلمان"
844867
"田中、太郎" → family=""
@@ -885,6 +908,19 @@ C1. Rationale: a credential run after the comma means the name is in
885908
form alone, and that limitation is kept as parity: "Smith, RN -
886909
CRNA" reads given "RN" under the policy as without it.
887910
"John Smith, LEED AP" → family="Smith" deviates: #291 (today: family="John Smith")
911+
Accepted: the further-comma qualifier carries no example line of
912+
its own. It discriminates PAIRS and spans both branches, so
913+
exemplifying it means a with-comma partner for each — every one
914+
of them a name entering the rules corpus for behavior that has
915+
not moved since v1. Its executable witness is instead the pair
916+
already standing in the v1-style bank,
917+
tests/test_suffixes.py's
918+
test_roman_numeral_i_after_single_initial_lastname_comma_format
919+
and test_roman_numeral_i_with_explicit_suffix_comma_stays_a_middle_initial.
920+
Read the examples above with the qualifier in hand: `Smith, John
921+
V` reads the suffix and `Smith, John PhD I.` continues the run,
922+
while adding a suffix comma after either turns that same letter
923+
into the middle initial.
888924
history: decisions.md#C1 · interacts: H2, P6, W3 · implemented: nameparser/_pipeline/_segment.py, nameparser/_pipeline/_assign.py, nameparser/_pipeline/_group.py
889925

890926
C2. Rationale: text beyond the recognized comma parts should be

tests/test_capitalization.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def test_capitalization_exception_for_III(self) -> None:
1717

1818
# FIXME: this test does not pass due to a known issue
1919
# http://code.google.com/p/python-nameparser/issues/detail?id=22
20-
@pytest.mark.xfail
20+
@pytest.mark.xfail(reason="#492")
2121
def test_capitalization_exception_for_already_capitalized_III_KNOWN_FAILURE(self) -> None:
2222
hn = HumanName('juan garcia III')
2323
hn.capitalize()

tests/test_conjunctions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def test_lowercase_middle_initial_comma_lastname_and_suffix_conflict_with_conjun
8787
self.m(hn.last, "Smith", hn)
8888
self.m(hn.suffix, "III, Jr", hn)
8989

90-
@pytest.mark.xfail
90+
@pytest.mark.xfail(reason="#490")
9191
def test_two_initials_conflict_with_conjunction(self) -> None:
9292
# Supporting this seems to screw up titles with periods in them like M.B.A.
9393
hn = HumanName('E.T. Smith')
@@ -213,7 +213,7 @@ def test_conjunction_in_an_address_with_a_title(self) -> None:
213213
self.m(hn.title, "His Excellency Lord", hn)
214214
self.m(hn.last, "Duncan", hn)
215215

216-
@pytest.mark.xfail
216+
@pytest.mark.xfail(reason="#489")
217217
def test_conjunction_in_an_address_with_a_first_name_title(self) -> None:
218218
hn = HumanName("Her Majesty Queen Elizabeth")
219219
self.m(hn.title, "Her Majesty Queen", hn)

0 commit comments

Comments
 (0)