Skip to content

Commit 2af8da8

Browse files
derek73claude
andauthored
Ask one predicate per question for split credentials, numerals and paired initials (#752) (#884)
* Ask one predicate per question for split credentials, numerals and paired initials (#752) Neutral. `_vocab.split_pair` replaces the three copies of the Ph./D. test (group's merge, `_comma._pieces`, `is_wholly_suffix`), each asking `SPLIT_PAIR_OPENS` C-level first so an ordinary word pays no frame. `_vocab.reads_as_generation` replaces `is_trailing_numeral_suffix` and takes the vocabulary path as `listed`, which C12's evidence passes. `_vocab.is_cased_paired_initials` lifts #872's inline script condition out of classify; C5 keeps the bare `is_paired_initials`. The regex roster named `_group._PH` and `_group._D` after both had moved to `_vocab`; a new test refuses an entry naming no pattern. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Record the shared predicates in the design docs (#752) mechanisms.md's ONE-PREDICATE-PER-QUESTION names the three predicates, decisions/vocabulary-model records the change, its neutrality measurement and what the later #752 changes widen, and the decisions entries and AGENTS.md naming the old numeral predicate point at its new name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Say where the numeral is still recognized apart from its predicate (#752) The design-docs review found the catalog calling `reads_as_generation` the numeral's one recognizer while S8's run start, C1's listing form and the peel's vocabulary path still recognize a numeral on their own; that `listed` carries C12's whole suffix-word tag, not a numeral test; that the peel's `is_suffix_piece` vetoes `i` and `v`, so passing `listed` at its fork would move `John J. V`; that the split-credential widening never reaches `is_cased_paired_initials` for `A. B.`; and two notes still naming the retired `PH`/`D`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * State which words pay the split test's frame (#752) The code review counted one frame per Ph word with a word after it, whether a D. follows or not; the entry had said per standing pair. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name C9's walk among the sites recognizing a numeral apart (#752) The second review found the list of numeral sites missing C9's family-comma walk and the one-letter numeral behind a suffix word there, and the comment above reads_as_generation still calling it the numeral's one recognizer. The list is now stated as found by search. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disambiguate phase 2a's Standing pointer beside this entry (#752) Merging master put two 2026-10-25 #752 log entries in decisions/vocabulary-model, so the Standing line pointing at 2a's names it by its opening words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tighten the comments the PR review found loose (#752) The peel's veto is on the initial tag, not the shape; the numeral predicate's summary says what listed makes it answer; the split pre-test holds only while the first-word test is _PH; group alone declines a head pair; the site list lives in decisions/ only; import order and a cases.py note naming _vocab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep C12's suffix-word test beside its call, and pin the periodless split (#752) `reads_as_generation` no longer takes C12's suffix-word tag as `listed`: C12 asks whether the word ending the part is any suffix word or reads as a generation, and `Jr.` and `Esq.` are no numerals, so the tag test stands beside the call again, as on master, and costs master's frames. The listed numerals belong in the predicate, but reading them from the lexicon now would move `John J. V`, so that half waits for the change that has every numeral rule ask it; the decisions entry says so. The test review found no row holding a split credential without its periods, so making either period required passed the suite: four parity rows (`Ph D`, `Ph. D`, `Smith, John Ph D`, `John Smith, PH D`), a periodless pair in `test_is_wholly_suffix`, and unit tests that `SPLIT_PAIR_OPENS` is a necessary condition of `split_pair` and that `is_cased_paired_initials` refuses Thai `พ.บ.`. Re-measured on 2dd0414: the grid, the committed snapshot, every agreement record, PSC and the ctgov, FEC and tse local snapshots match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Say which listed numerals the predicate is missing, and what adding them moves (#752) The review of the fix commits found that the shape test already takes ii to iv; only the one-letter i and v would be new, and they move 375 readings at the peel's fork and 144 reports at the numeral-before-a- credential report, not the peel alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 2dd0414 commit 2af8da8

22 files changed

Lines changed: 199 additions & 69 deletions

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎docs/design/decisions/C12-suffix-word-ending-the-name-before-the-comma.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Open: #774 — a roman numeral S10 reads is lost into the family before dotted p
1616

1717
- 2026-10-18 (Derek), [#656](https://github.com/derek73/python-nameparser/issues/656) (its declined `mbchb` superseded by #797, decisions/suffix-acronym-collisions) — A SUFFIX WORD ENDING A WHOLE NAME BEFORE THE COMMA IS EVIDENCE THAT THE COMMA IS A SUFFIX COMMA, AND THE PART AFTER IT DECIDES ONLY WHETHER IT HOLDS A NAME. Until this rule only the part after the comma decided (C1), so an unlisted credential there that no shape rule admitted reversed the whole name into the listing form: `John Smith II, AD` read given 'AD', family 'John Smith'. Derek's ground, in the #720/#656 session: a generation is part of the name, more so than a credential, and is commonly written before the comma that starts the credentials (`John J Ingram III, MD`), and `II` counts as such a word though it is also initial-shaped. The evidence is the suffix WORD (`vocab:suffix-word`: every member of `suffix_words`, 170 on 2026-10-18 by `len(Lexicon.default().suffix_words)` — the generations, `esq`, `ret`, `vet`, `2`, and the honorifics, `rinpoche`, जी, 先生, 씨 among them), not an acronym, because a listing with a long surname puts an acronym there as readily (`von bergen wessels MD, pennie`, a corpus.jsonl row, keeps its given name).
1818
DECLINED: the generation alone deciding. CLDR's own renderings hold the counter-case, a two-word surname with a generation before a listing comma: `Excm. Sr. Lloret Palol II, Josep Antoni Carles Joan` and `Excma. Dra. Van Dalen Hoogeveen II, Maria da Graça César Martín`, both keep the listing form and their given names before and after this rule (their title and family fields disagree with CLDR for other reasons, the title run left in the family). So the part after the comma still decides, by a narrower question than C1's: a word in it written as a name (`_vocab.written_as_a_name`, unlisted, no period) or an initial keeps the listing form. That needs the name's case contrast, which is C6's test (`_comma._name_contrast`) reused rather than restated; without it a given name written in capitals is indistinguishable from `AD`, so a one-case name and a caseless script decline (the Arabic, Gujarati, Kannada and Malayalam CLDR renderings of this shape stay as they read).
19-
THE NUMERALS, found by the sweep before review. The evidence is reached by vocabulary AND by shape: `ii`–`iv`, `v` and `i` carry the suffix-word tag, but `VI` to `X` are numerals by shape alone, so a tag-only test read `John Smith III, AD` and left `John Smith VI, AD` in the listing form, #610's class. The numeral path asks `_vocab.is_trailing_numeral_suffix`, the trailing fork's own predicate, behind a C-level `ROMAN.match` filter in `decide` so the listing form pays no frame. Both paths then count the words IN FRONT of the generation as C3 does (`_whole_name(seg0[:-1])`): `Smith VI` is one word and a numeral, as `Smith III` is, where counting the whole part had made the shape-only numeral its own name word. And a one-letter numeral is evidence only where S10 reads it as the generation, a name word to spare in front of it (`name_word_count`, a title being none): `Smith V, AD` and `St. Berg V, AD` read the V as a name word and keep the listing form.
19+
THE NUMERALS, found by the sweep before review. The evidence is reached by vocabulary AND by shape: `ii`–`iv`, `v` and `i` carry the suffix-word tag, but `VI` to `X` are numerals by shape alone, so a tag-only test read `John Smith III, AD` and left `John Smith VI, AD` in the listing form, #610's class. The numeral path asks `_vocab.is_trailing_numeral_suffix` (`_vocab.reads_as_generation` since #752), the trailing fork's own predicate, behind a C-level `ROMAN.match` filter in `decide` so the listing form pays no frame. Both paths then count the words IN FRONT of the generation as C3 does (`_whole_name(seg0[:-1])`): `Smith VI` is one word and a numeral, as `Smith III` is, where counting the whole part had made the shape-only numeral its own name word. And a one-letter numeral is evidence only where S10 reads it as the generation, a name word to spare in front of it (`name_word_count`, a title being none): `Smith V, AD` and `St. Berg V, AD` read the V as a name word and keep the listing form.
2020
THE HONORIFICS, found by the design-docs review: the tag reaches every suffix word, so `Tenzin Gyatso Rinpoche, AD` reads suffix 'Rinpoche, AD' where the parent read given 'AD'. Kept as stated rather than narrowed to generations: an honorific written after the name stands in the same place a generation does, and no vocabulary set separates the generations from the rest. Dotted paired initials were the review's other find: `Smith Jones Jr., J.A.` read suffix 'Jr., J.A.', since the veto asked only the `initial` tag; `is_paired_initials` joins it, as C5 keeps such a pair the given name.
2121
DECLINED: adding `mbchb` to SUFFIX_ACRONYMS, approved in the session before the rule was measured. `MBChB` ends in a capital, so it is not written as a name and the rule reads `van den Berg jnr., MBChB PhD` without it; a vocabulary member would also reach `Smith, MBChB`, which nothing here measured.
2222
Measured 2026-10-18 on this change against its parent: five corpus names move, every one a CLDR rendering of the shape and each to the agreeing reading on family, given, suffix and title where CLDR states them (`Sig. Fabio Massimo Carlo Alberto Martini II, AD` in both orders, `prof. dr. Ada Cornelia César Martín von Brühl Jr, univ. bacc`, `Prof. dr. Cornelia Margarita Sofia Aletta van den Berg jnr., MBChB PhD`, `Skeggjason Hr. Geir Jón Magnús Brynjar yngri, þingmaður`); the last still disagrees on its given names, CLDR's `Geir Jón Magnús Brynjar` against `Hr. Geir Jón Magnús Brynjar`, a title inside a surname-first name, which is not this rule. No other corpus name moves, read off `snapshot.py`'s report. The issue's comment counted 25 disagreements of the shape on 2026-10-13; the `Jr, MP` renderings among them read correctly on the parent already, MP being listed.

‎docs/design/decisions/C9-one-character-suffix-word-inside-a-comma-part.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Open: [#819](https://github.com/derek73/python-nameparser/issues/819) (whether r
1616
- 2026-08-25 #430 / #432 (its accepted `Smith, John PSM I I.` reading superseded by #602, decisions/S8) — one boundary, fixed as one: inside a comma segment a ONE-CHARACTER suffix word — the only kind a reader could take for an initial — is read by WHAT STANDS BEFORE IT. Behind another suffix it describes that suffix and continues the run, period included — `Smith, PSM I` and `Smith, PSM I.` are suffix 'PSM I'/'PSM I.' where they read given 'PSM' plus suffix 'I', an initial in that slot being no shape anyone writes. Behind a name word the period is decisive, marking an abbreviation and so name material — `Smith, John V.` is middle 'V.' where it read suffix 'V.', which is what `Smith, John B.` always did; the only thing that made V differ is that V is also suffix vocabulary. The bare form is the boundary and does not move: `Smith, John V` stays suffix 'V' (v1 parity, #144), which is why the test is the PERIOD and not the numeral. SCOPE, stated as the code reads rather than as the intent was argued: the class is one-character SUFFIX VOCABULARY, not "a roman numeral". It sweeps in `2` (`Smith, John 2.` is middle '2.'), misses `X` and `IX` (roman, but not vocabulary — `Smith, PSM X` is unchanged), and never touched `II`/`III`/`IV` at all, which are two characters and so were never initial-shaped and never vetoed. Anyone adding a one-character suffix in a script that has initials extends it. And the strict knob is consulted: under `lenient_comma_suffixes=False` the initial veto stands and the run ends at the numeral, which the first draft silently overrode by reading no policy at all.
1717
#430's cause was rules/S2's initial veto in `is_suffix_piece`, which keeps a numeral out of a suffix reading — right after a name, wrong inside a credential run — so the segment did not look like a run at all and fell to the given-and-suffix walk. Two wrong answers came out of that one cause, and a fix verified on `Smith, PSM I` alone would have left the other: where the run's first word is TITLES vocabulary too (`Smith, MD I`, and `Smith, Jr. I` through the period-abbreviation inference) the leading-title peel took it instead, giving title 'MD' plus given 'I'.
1818
THREE readers, one reading, which is the part worth copying. The gate that says a segment is a credential run, assign's router that assigns its roles, and group's one-entry join that renders it must agree PIECE FOR PIECE, and #429 shipped the inverse of its own fix by deriving that agreement twice. `segment_suffix_reading` returns the per-piece verdicts and all three read it (mechanisms.md#ONE-PREDICATE-PER-QUESTION); the intermediate state where only the gate and the router had been converted rendered `Smith, PSM I` as 'PSM, I', the render splitting a run whose roles were already right — the same tell as #429, one predicate along. TWO readers since 2026-09-06: #436/#437 took the render join out of group, and `segment_suffix_reading`'s remaining readers are assign's gate and assign's router, one call site between them. The predicate is not moved — one call site is no reason to relocate what two sites will contest again — and what is worth carrying away is unchanged, since the reason the three had to agree is the reason the two still do.
19-
Declined, and the v1 suite caught it: asking `is_trailing_numeral_suffix` for #432's half. It answers the period question — `_ROMAN` is anchored and admits no period — but it also refuses a numeral standing behind an initial-shaped piece, which is a NO-COMMA rule and the opposite of this path's parity: `Chang, Andy C I` is first 'Andy', middle 'C', suffix 'I', and asking that predicate here made the numeral a middle. decisions.md's #401/#421 entry records the same fork declining to transfer to this walk under LENIENT. The half that applies is the period alone.
19+
Declined, and the v1 suite caught it: asking `is_trailing_numeral_suffix` (`_vocab.reads_as_generation` since #752) for #432's half. It answers the period question — `_ROMAN` is anchored and admits no period — but it also refuses a numeral standing behind an initial-shaped piece, which is a NO-COMMA rule and the opposite of this path's parity: `Chang, Andy C I` is first 'Andy', middle 'C', suffix 'I', and asking that predicate here made the numeral a middle. decisions.md's #401/#421 entry records the same fork declining to transfer to this walk under LENIENT. The half that applies is the period alone.
2020
BLAST RADIUS, over the 1069 corpus names against 56fb505c (PR #443): seven carry a one-character numeral behind a word inside a comma segment, every one bare, and none moves; the only movers are the three names this change added as examples, so the case table is the whole coverage. `Berg, abdul V` cannot reach the walk at all, the bound-given join making `abdul V` one piece, which is why the LENIENT mirror #401/#421 declined does not collide here.
2121
Two things the /simplify round settled by measurement rather than argument. The walk asks `is_suffix_piece(pieces[m - 1])` — a VOCABULARY test — where the reading carries the verdict it just recorded, and unifying them looks obviously right until you run it: the walk assigns `PSM` to GIVEN in `Smith, PSM I.`, so a reading-style flag says "behind a name" and sends the numeral to middle, losing the strict parity restored two commits earlier. The two sites ask different questions on purpose, and the vocabulary answer is the load-bearing one. The cost is a reading the walk cannot reach: `Smith, John PSM I I.` moves from middle 'I' + suffix 'PSM, I.' to middle 'I I.' + suffix 'PSM', two consecutive one-character numerals behind a credential — accepted, both readings being defensible for a shape nobody writes, and recorded here rather than chased at the price of two correct pins.
2222
The walk is also strict-blind, pre-existing and unchanged: it calls is_suffix_lenient unconditionally, so `Smith, John V` reads suffix 'V' under `lenient_comma_suffixes=False` as under the default. The reading consults the knob and the walk does not, which is now conspicuous where it was merely unexamined. Not this change's to fix.

0 commit comments

Comments
 (0)