diff --git a/docs/design/decisions/P1-lone-particle-fold.md b/docs/design/decisions/P1-lone-particle-fold.md index 0edf4f76..af8a7011 100644 --- a/docs/design/decisions/P1-lone-particle-fold.md +++ b/docs/design/decisions/P1-lone-particle-fold.md @@ -13,6 +13,7 @@ Examples read under the default order unless a line names another. - A particle chain inside the name is grouped and positioned like any other group, so `Juan de la Vega` under a family-first order reads family 'Juan', given 'de la Vega': what the never-given vocabulary forbids is the bare particle as a given name, not a name part that begins with one — 2026-08-16 #368 REVERSED. - The rule no longer carries the history of its given-position site: a particle where a family-first order puts the given name is not folded (`Mesnil de` family-first → given 'de'), kept as the rule's last Accepted bullet, the decision being decisions/P6's (Elsewhere below) — 2026-10-19 #761 (skeleton migration). - A bare particle has nothing to fold into and is a lone name word read by O5: `de` → given 'de' under the default order, family 'de' under a family-first one — 2026-10-19 (Derek), #761. +- After a family comma the run takes the rest of that part, whatever order is declared, the comma having already fixed the surname (`Smith, de Mesnil Jean` → family 'Smith de Mesnil Jean' under all three orders) — 2026-08-18 #395, family comma. Elsewhere: a word of both the title and the particle vocabulary at the head (`St`, `Freiherr`, bare or period-marked) is rules/P4's exception and not this fold's site, the particle behind it being inside a name P2's chain makes (`St de Mesnil Jean` family-first → title 'St', family 'de Mesnil Jean') — decisions/P2's 2026-10-10 (Derek), #624 entry. The GIVEN position under a family-first order is no fold site (`Mesnil de` family-first → given 'de') — decisions/P6's 2026-08-30 #467 entry. Which particles are never-given is decisions/vocabulary-collisions' criterion, applied by #360 (2026-08-18), which moved `mc` and `ste` into the set. @@ -63,6 +64,8 @@ the 2026-08-16 entries below. The survivor is the degenerate bare - 2026-10-19 #761 (skeleton migration) — the rule no longer carries the history of its second site. A never-given particle standing where a declared order puts the GIVEN name was a P1 site until #467, and dropping it was a correction rather than a narrowing: that slot holds what the caller DECLARED to be the given name, and the never-given vocabulary supplies a reading where position leaves the question open rather than vetoing one position has already given. The argument and its measurements are decisions/P6's 2026-08-30 #467 entry; rules/P1 keeps the reading itself in its last Accepted bullet, scoped to a family-first order (a particle in that given slot is not folded), with `Mesnil de` under FAMILY_FIRST as its example. The default-order reach's "by design" points at the 2026-09-07 #471 entry above, which the rule no longer restates. - 2026-10-19 (Derek), #761 — A BARE PARTICLE IS A LONE NAME WORD. The Accepted bullet said a bare "de" stays the given name, which held only under the default order: under either family-first order it reads family 'de'. Nothing folds a lone particle, so it reads as O5's convention reads any lone name word, and the bullet now says that, with the family-first example. No reading moved. +- 2026-08-18 #395, family comma — AFTER A FAMILY COMMA THE FOLD TAKES THE REST OF THAT PART, WHATEVER ORDER IS DECLARED: the comma has already fixed the surname, so no positional read is left for an order to narrow (`Smith, de Mesnil Jean` → family 'Smith de Mesnil Jean' under all three orders). Decided with #395 (commit 4627604f, whose release note says a family comma is untouched "where the comma has already fixed the surname") and stated in rules/P1 since, but the #395 entry above never recorded it; this entry, written 2026-10-22 for #762, dates it from that commit so the Standing block can point at it. + Declined: - A strict xfail asserting "de Mesnil" → family under FAMILY_FIRST (#359 review) — #359 deliberately left those semantics open, and a strict xfail decides the question by the back door. @@ -75,4 +78,3 @@ Declined: positioned by the declared order — "Cher" reads given under GIVEN_FIRST — so "de la Vega" would read given="de la Vega" unless a further rule forced a particle-headed group into the family. Add that rule and [de Mesnil][Jean] yields the #364 reading anyway, so the deletion buys nothing and costs P4. Decided 2026-08-18 (was Open: [#360](https://github.com/derek73/python-nameparser/issues/360), which particles count as never-given): #360 applied the criterion at decisions/vocabulary-collisions and moved `mc` and `ste` into the set (from the may-be-given half), adding the Spanish/Portuguese articles; later vocabulary work extends the set under the same criterion (#604's Irish particles). - diff --git a/docs/design/decisions/P2-particles-join-forward.md b/docs/design/decisions/P2-particles-join-forward.md index 081ec0c9..3f9c534c 100644 --- a/docs/design/decisions/P2-particles-join-forward.md +++ b/docs/design/decisions/P2-particles-join-forward.md @@ -9,6 +9,7 @@ Examples read under the default order unless a line names another. - Where the name begins has one definition, assign's leading-title run, H2's unlisted abbreviations included, so `Xyz. van Johnson` reads as `Dr. van Johnson` does — 2026-08-22 #424. - The leading position is decided once, as the LAST title in assign's leading-title run, read as written before H3's give-back, that is also a particle, else the first piece past the run: `Freiherr St John Smith MA` → title 'Freiherr St', given 'John', family 'Smith', suffix 'MA' (default order); `Freiherr St van Berg MA` → title 'Freiherr St', family 'van Berg'; `Dr. Mc Mc` → given 'Dr.', suffix 'Mc Mc' (default order) — 2026-10-10 (Derek), #624. - Assign's re-read fallback, where group's joins leave no name word past the titles, stays, since it guards totality — 2026-10-10 (Derek), #624. +- Particle groups are distributed across the middle and family fields rather than combined, the combined double-surname reading living in the surnames view (`Joao da Silva do Amaral de Souza` → middle 'da Silva do Amaral', family 'de Souza', surnames 'da Silva do Amaral de Souza') — 2026-08-16. - Suffix pieces are not skipped when the chain looks for the leading piece, since skipping them reads worse: `Ph.D. Van Johnson` → given 'Ph.D.', family 'Van Johnson'; `Dr. Ph. D. Van Johnson` → family 'Van Johnson' — 2026-10-19 #760. Elsewhere: the stop is S2's trailing run, read once after P3's joins and before the chain is made, counting the chain's own run as the one name word it will make (`John van der Berg V` → family 'van der Berg', suffix 'V'), the re-ask after the chain's merges running only where the run was not read there, and on a main segment the chain stops in front of a trailing title (`John van der Berg Prof.` → title 'Prof.', family 'van der Berg' under the default order) — decisions/S2's 2026-10-07 (Derek), #614 entry and decisions/H5's 2026-10-07 #614 entry, indexed here by this file's 2026-10-07 #614 cross-reference. A particle that is also suffix vocabulary heads the name word behind it before a family comma in any case (`SMITH VD MA, JOHN` → family 'SMITH VD MA') — decisions/P9's 2026-10-02 #573 entry. A bare ambiguous acronym written Title-case in a mixed-case name joins the chain (`John van der Berg Ma` → family 'van der Berg Ma') and one written in capitals ends it with no words to spare (`Freiherr von Berg MA` → family 'von Berg', suffix 'MA') — decisions/S6's 2026-09-14 #289 + #516 and 2026-09-15 (Derek) entries; the absorbed acronym reports `suffix-or-name`, and a word a particle run took (`anh van do`) reports nothing — decisions/S6's 2026-09-18, WHOLE-PR REVIEW ROUND entry; a one-letter numeral behind a title and a chain has no word to spare and joins the chain (`Freiherr von Richthofen V` → family 'von Richthofen V') — decisions/S10's 2026-10-17 (Derek), #718 entry. After a family comma the given part's chain stops where `tail_reading`'s run begins — decisions/H5's 2026-10-10 (Derek), #623 entry. @@ -21,6 +22,7 @@ Open: none in this file. "Sir de Mesnil" grouped [Sir][de Mesnil] — a chain — and reported given="de Mesnil" with no family at all. After, "de" is the leading name piece, and a leading particle chains nothing (rule P4): the family reading comes from P1's fold, which is why P1's fold needed no change (its interacts: points here). Grouped today: [Sir] [de] [Mesnil]. +- 2026-08-16 — GROUP DISTRIBUTION IS LOAD-BEARING, the undated bullet below, dated for #762 on 2026-10-22 so the Standing block can point at it, by the commit that first recorded it (c2381f6b). No commit decided it: the split fields and the combined `surnames` view are v1's (9a3d187c, 2018), held by its Portuguese tests. - Group distribution is load-bearing, not incidental: the v1 Portuguese tests (test_portuguese_dos, test_portuguese_prefixes) require multiple particle groups to land across middle and family, while #132 wanted the combined double-surname reading — the same shape with opposing wants, which is why the combined reading lives in the surnames VIEW and the split in the fields. - 2026-08-22 #424 (its `John van der Berg Ma` and `Freiherr von Berg MA` readings superseded by decisions/S6's 2026-09-14 #289 and 2026-09-15 entries, its `Freiherr von Richthofen V` boundary by decisions/S10's 2026-10-17 #718, its read over the pieces as written and its re-ask by #614, and its leading scan stopping at the first title-particle by #624, below) — the chain stops where assign's trailing peel begins. The chain ran "until a trailing suffix begins" and asked with the suffix-piece test, which vetoes a bare `V` as an initial — the #401 question at a third site — so `John van der Berg V` read family 'van der Berg V' (1.4.0 read it so too: shipped since 1.x), and `John van der Berg Ma` read family 'van der Berg Ma' where 1.4.0 read suffix 'Ma' — a 2.0 regression on S2's other fork. Both now stop where `_trailing_start` says the run begins: the S2 peel (`_peel_trailing` over `_peel_walk`, shared with assign and P5's reserve since #425) read over the pieces as they stand from the first name piece, once per segment, kept as a length from the end that the chain's merges ahead of it do not move (the benchmark's `particles` shape is the guard). The chain takes both forks — its merges leave the acronym at least the three pieces the fork counted, so assign peels it after as the walk did before — where the maiden walk takes only the numeral (the M2 entry says why). The fork reads the piece before the numeral as it stands, so `John van der J. V` keeps family 'van der J. V', as `John J. V` reads the V as a name; and a numeral with a suffix behind it is not last in the walk, so `John van der Berg V Jr` keeps family 'van der Berg V'. No differential corpus name had either shape; the rules examples carry them — the numeral against every baseline, the acronym against 2.0.0 and 2.1.0. Two more sites were asking assign's question with a test of their own, both found by the reviews of this change. The walk's starting point and P4's leading-particle scan asked group's `title()`, which does not see H2's unlisted abbreviations — assign peels those as titles all the same — so `Xyz. van Johnson` chained where `Dr. van Johnson` did not (#367 had keyed the exception on the first piece of the name, by group's test), and `Xyz. van Berg MA` read family 'van Berg MA' on every 2.x tree — the chain had taken the acronym, as it took every acronym; the draft that stopped before it left assign two pieces where the fork had counted three, and MA became the family. Both ask assign's own test now — `_is_leading_title` and the period-abbreviation pattern moved down into group, with `_leading_titles` as the one definition of where the name begins: the chain's walk, the bound join's first name piece (the second docs review found that one still on group's test — `Xyz. abdul John Smith` joined nothing where `Dr. abdul John Smith` read given 'abdul John') and, through `_is_leading_title`, the scan and the PARTICLE_OR_GIVEN report — so `Xyz. van …` reads as `Dr. van …` does. `Esq. van Gogh`, a corpus name, moves with it (1.4.0 read family 'van Gogh' for both, as #367's rule records; its own rule at every baseline, and one for the P4 example). A particle of the unambiguous suffix vocabulary too (vd, mc) is a suffix piece to the peel: where it opens the trailing run the chain stops before it as before any suffix word and the peel takes it — `John Smith Mc V` reads suffix 'Mc, V' where master and 1.4.0 read family 'Mc V', a class the second docs review found unrecorded (2,554 of its 117,306 constructed names, an eighteen-word pool that puts the words in every position) — and where it continues a prefix run, the run takes it as a particle, so `John van Mc` keeps family 'van Mc', every baseline's reading and the one P6 chose for the shape after a comma. Both are Accepted under P2. The fork's count was the last thing the chain asked on its own authority, and the reviews found it wrong behind a title-and-particle word: #367 stops the leading-particle scan at `Freiherr`, `St`, `Do` — the name's own first piece — where assign's title peel steps over them, so the chain takes the name's first word, and the acronym the fork counted with three pieces met assign with two: `Freiherr von Berg MA` read given 'von Berg', family 'MA' (1.4.0's reading, as it happens; master read family 'von Berg MA'). So the chain asks the peel again over the pieces it leaves — `chain(tail)`, then `_trailing_start` over what it built, and a second run without the stop where the verdict changed, the snapshot being one copy per segment with a trailing run — and takes what assign will not peel: family 'von Berg MA'. The numeral cannot flip, a chain group never being initial-shaped, so `Freiherr von Richthofen V` keeps suffix 'V', and its chain, the one name piece left, reads as `Dr. Smith V` reads — where 1.4.0 and master read family 'von Richthofen V'; the code review asked for that boundary to be on record, and P2 carries it. #410 (2026-08-25) then moved the name half of both: a title with one name word behind it names the family whatever stands beside it, so the pair reads family 'von Richthofen' and family 'Smith' today, where each read `given` with no family when this entry was written. The boundary is unchanged — the chain still stops before the numeral, and the two names still read alike; only the field the one name piece lands in moved. This name is the fifth corpus name #410 moves, and the only one already classified, its ledger rule's `fields` spanning the narrower diff. diff --git a/docs/design/decisions/P6-trailing-orphan-particle.md b/docs/design/decisions/P6-trailing-orphan-particle.md index fd7cbace..c71d38d0 100644 --- a/docs/design/decisions/P6-trailing-orphan-particle.md +++ b/docs/design/decisions/P6-trailing-orphan-particle.md @@ -83,7 +83,7 @@ Decided 2026-08-19 (was Open: [#380](https://github.com/derek73/python-nameparse - 2026-10-17 #700 — THE ELIDED PARTICLE AND THE HYPHEN-ATTACHED ARABIC ARTICLE ARE PARTICLE VOCABULARY, NEVER-GIVEN, AND THE RULE IS UNCHANGED. Library catalogs file `Paul d'Ivoi` as `Ivoi, Paul d'` and `Ḥusnī az-Zaʿīm` as `Zaʿīm, Ḥusnī az-`, the particle split off with its mark still on it; both read as a middle name (1.4.0 and 2.4 alike) because neither written form was in `PARTICLES`. P6 asks only the particle tag, so the fix is the vocabulary alone: no code moved. Three decisions, each taken by Derek on the #700 session's options. ELIDED SET: `d'`, `l'`, `dell'`, each also in its U+2019 spelling, and `de’` and `’t` beside the existing `de'` and `'t` (`’t` added in the PR review, a gap the test review found), since nothing folds the typographic apostrophe to the ASCII one; particles.py asserts at import that every elided entry has both spellings (a vocabulary build check since 2026-10-18, decisions/vocabulary-model). ARTICLES: a CLOSED list — `al-`, `ul-`, the article assimilated to each of the fourteen sun letters in ASCII and DMG spellings (`ar-`, `az-`, `aš-`, `ash-`, `aṭ-`, …; lām's being `al-` itself), and the Egyptian `el-` with its assimilations in ASCII spellings only (`es-`, `esh-`, `ed-`, …; no `eš-` or `eṣ-`) — all never-given, where the bare `al` stays ambiguous (Al Gore): written apart with its hyphen, the word is the article and nothing else. RENDERING: the attached family joins with a space like every particle, `d' Ivoi` and `al- Rashid`. Declined: a shape rule (a vowel, consonants, a hyphen) in place of the list. It would need a new predicate and the bare-word collision check the issue named, for a population the list already covers; the corpora attest seven article spellings (`al- az- aš- ar- es- as- aṭ-`), all in it — recompute: every whitespace- or comma-bounded token of `tools/differential/corpus*.jsonl` matching `\w{1,4}-` at a name's end. Not decided here: the GLUED forms (`Paul d'Ivoi`, `Harun al-Rashid`) stay one word with no particle split, out of #700's scope by its own statement. Hyphen-minus only: U+2010 and the non-breaking hyphen are not listed. The Scottish Gaelic `a’` (`Mac a’ Ghobhainn`, in corpus_cldr.jsonl) is mid-name and not an index-form particle, and was not added. Nor are the elisions no corpus holds (`dall'`, `nell'`, `degl'`): the list is closed, so such a spelling reads as a middle name until it is listed. - RENDERING FOLLOWED THE VOCABULARY, found in review: R4's `capitalized()` split a token at its mark and asked the particle test of the stem, so `IVOI, PAUL D'` repaired to `Paul D' Ivoi` — 30 of the 32 first-listed spellings, all but `al-` and `de’`, whose stems are particles themselves. The whole token is now asked the particle arm's question first, and lowercased only where that arm would lowercase it; wherever the arm declines (an all-particle part, the given or suffix role, a recorded casing) the token goes to the per-word walk as before — a first version sent every whole-token particle down the arm's other clauses instead, which moved `smith, john, ed-` from 'ED-' to 'Ed-' and a configured `van-der` from 'Van-Der' to 'Van-der' (the fix commit's own review). The one shipped word outside #700 it reaches is `'t`, whose stem is `t`: `van 't Hoff` repairs to 'van 't Hoff', where 1.4.0 through 2.4.2 wrote 'van 'T Hoff' — R4's reading, and the right Dutch one. A casing configured for a stem (`capitalization_exceptions` `al` → `AL`) no longer reaches the marked spelling, which is now a particle in its own right: `al-` is lowercased. The snapshot cannot see a render view (decisions/R4); measured instead by `capitalized()` and `capitalized(force=True)` against `00dd2759`, over every name in `de6c045d`'s `tools/differential/corpus*.jsonl` as written, lowercased and uppercased (28 names move, every move lowercasing one token ending in `'` or `-`), and over every particle in eight positions under all three orders (adding `'T` → `'t`, which no corpus name held until R4's `van 't hoff` example joined corpus_rules.jsonl). The boundary `Ivoi, d'` was taken out of rules/P6: P6 does not attach there, and the reading it gets instead (family `Ivoi d'`, as `Jong, de` has read since 2.0) is the never-given vocabulary's family-comma reading, which P6's statement does not describe — a P6 example would have pinned another rule's output. It moves from given `d'` (1.4.0 through 2.4.2, the word being no particle there), and so does every new entry standing alone after a comma (`Smith, al-`); it stays pinned as a case row. + RENDERING FOLLOWED THE VOCABULARY, found in review: R4's `capitalized()` split a token at its mark and asked the particle test of the stem, so `IVOI, PAUL D'` repaired to `Paul D' Ivoi` — 30 of the 32 first-listed spellings, all but `al-` and `de’`, whose stems are particles themselves. The whole token is now asked the particle arm's question first, and lowercased only where that arm would lowercase it; wherever the arm declines (an all-particle part, the given or suffix role, a recorded casing) the token goes to the per-word walk as before — a first version sent every whole-token particle down the arm's other clauses instead, which moved `smith, john, ed-` from 'ED-' to 'Ed-' and a configured `van-der` from 'Van-Der' to 'Van-der' (the fix commit's own review). The one shipped word outside #700 it reaches is `'t`, whose stem is `t`: `van 't Hoff` repairs to 'van 't Hoff', where 1.4.0 through 2.4.2 wrote 'van 'T Hoff' — R4's reading, and the right Dutch one. A casing configured for a stem (`capitalization_exceptions` `al` → `AL`) no longer reaches the marked spelling, which is now a particle in its own right: `al-` is lowercased. (Corrected 2026-10-22, #762: the mask still reaches `al-` in the given name and in a family that is `al-` alone, `al-` → 'AL-' and `john smith al-` → 'John Smith AL-'; in the middle name, or in a family with other words before or after it, `al-` is lowercased, `ahmad al- rashid` → 'Ahmad al- Rashid', `smith, al-` → 'Smith al-' and `smith, john al- jones` → 'John al- Jones Smith'; measured on master under `capitalized()` and `capitalized(force=True)`.) The snapshot cannot see a render view (decisions/R4); measured instead by `capitalized()` and `capitalized(force=True)` against `00dd2759`, over every name in `de6c045d`'s `tools/differential/corpus*.jsonl` as written, lowercased and uppercased (28 names move, every move lowercasing one token ending in `'` or `-`), and over every particle in eight positions under all three orders (adding `'T` → `'t`, which no corpus name held until R4's `van 't hoff` example joined corpus_rules.jsonl). The boundary `Ivoi, d'` was taken out of rules/P6: P6 does not attach there, and the reading it gets instead (family `Ivoi d'`, as `Jong, de` has read since 2.0) is the never-given vocabulary's family-comma reading, which P6's statement does not describe — a P6 example would have pinned another rule's output. It moves from given `d'` (1.4.0 through 2.4.2, the word being no particle there), and so does every new entry standing alone after a comma (`Smith, al-`); it stays pinned as a case row. Measured 2026-10-17 against the parent commit `b4a004c3`: 39 corpus names move, every one the same shape — a trailing `d'` or article leaves the middle name and joins the family (38 in corpus_gnd.jsonl, `Orval, Pierre d'` in corpus_conventions.jsonl), and no ambiguity kind changes. Recompute: `uv run python tools/differential/snapshot.py --since b4a004c3`. That is evidence about the corpus as much as the change: these are the comma-bearing names ending in one of the new forms, and GND's index forms are where they occur. - 2026-10-19 — RULE SPLIT: the third exception (a lone word of both the particle and the unambiguous suffix vocabulary inside a run of post-nominals) left rules/P6 for rules/P9, one rule per piece of evidence (docs/design/AGENTS.md), with rules/P2's clause on the same word before a family comma; #573 decided both. The #573 entry moved to decisions/P9 and a cross-reference stands in its place. Where an entry here says P6's third exception or `_inside_a_credential_run` (the #613 entry), that piece is rules/P9's now. - 2026-10-20 #761 (skeleton migration) — three passages of the block-format statement left the rule as argument or history rather than statement. The draft that tested the family-first slot alone and fired on 52 default-order names and 366 FAMILY_FIRST_GIVEN_LAST middles, and the FAMILY_FIRST line for `Nguyen Thi Van` removed rather than updated (#470), are the 2026-08-30 #467 entry above. The third is moved here: the comma site and the no-comma family-first site are checked against each other directly, as a pair, rather than only against their own examples — tests/v2/test_order_correspondence.py generates both writings and asserts they parse alike, plus a negative-control sweep pinning the disagreeing set the precedence over S2 names, so a change that breaks one side of that pair should expect that test, not the rule file, to say so first (decisions/two-input-invariants). diff --git a/docs/design/decisions/S7-company-of-an-unambiguous-credential.md b/docs/design/decisions/S7-company-of-an-unambiguous-credential.md index 3fe3ec89..cfce1a30 100644 --- a/docs/design/decisions/S7-company-of-an-unambiguous-credential.md +++ b/docs/design/decisions/S7-company-of-an-unambiguous-credential.md @@ -5,7 +5,7 @@ Examples name each field as the default order reads it; under the family-first orders the given and family names trade places. - A listed member of the ambiguous class standing behind an unambiguous credential in one run of suffix words, other members between passing it on, reads as the credential at every trailing slot whatever its writing or the count (`John Smith PhD Ed Ma` → suffix 'PhD Ed Ma'; `Smith, PhD MEng` and `Doe, Jane PhD MEng` → suffix 'PhD MEng'); a credential behind the member speaks for nothing (`Wang Ma PhD` keeps family 'Ma') — 2026-09-27 #544, A CREDENTIAL RUN. -- Where no credential run starts (S8's, or C13's in a part a credential opens after a comma), as with one name word before the credential and no comma, what anchors nothing and ends the run is the walk's own leading piece (`PhD Ma` → given 'PhD', family 'Ma'), a connective (`Jane Doe nee Puig i Ma` keeps maiden 'Puig i Ma'), a single-letter roman numeral in any case (`Om Jr V Ma` keeps family 'Ma', where `Om Jr III Ma` reads suffix 'Jr III Ma'), and a word of both the particle and the unambiguous suffix vocabulary (`Jan vd Ma` → family 'vd Ma'; `Smith vd Ma, John` → family 'Smith vd Ma'), whose exclusion is of the word in front only; where either run starts it takes them all (`John Smith PhD V Ma` and `Smith, PhD V Ma` → suffix 'PhD V Ma') — 2026-09-27 #544, A CREDENTIAL RUN. +- Where no credential run starts (S8's, or C13's in a part a credential opens after a comma), as with one name word before the credential and no comma, what anchors nothing and ends the run is the walk's own leading piece (`PhD Ma` → given 'PhD', family 'Ma'), a connective (`Jane Doe nee Puig i Ma` keeps maiden 'Puig i Ma'), a single-letter roman numeral in any case (`Om Jr V Ma` keeps family 'Ma', where `Om Jr III Ma` reads suffix 'Jr III Ma'), and a word of both the particle and the unambiguous suffix vocabulary (`Jan vd Ma` → family 'vd Ma'; `Smith vd Ma, John` → family 'Smith vd Ma'), whose exclusion is of the word in front only; where either run starts it takes the numeral, the particle and a name word (`John Smith PhD V Ma` and `Smith, PhD V Ma` → suffix 'PhD V Ma'), while a connective right behind the credential is joined to it first, so S8's run never starts (`John Smith PhD y Ma` → family 'PhD y Ma', `Smith, John PhD and Ma` → middle 'PhD and Ma'), except in C13's part (`Smith, PhD and Ma` → suffix 'PhD and Ma'), and one further in is the run's (`John Smith PhD Jones and Ma` → suffix 'PhD Jones and Ma') — 2026-09-27 #544, A CREDENTIAL RUN. - A title/suffix dual in the given part's leading title run after a one-word family comma is a title there, anchors nothing and silences the company for the part (`Smith, Ms Ma` → given 'Ma'; `Smith, MD PhD Ma` → title 'MD', given 'PhD', middle 'Ma'); anywhere else a dual anchors as any suffix word (`Jane Doe, MS LAc` → suffix 'MS LAc', at the accepted cost `John Smith, Ms Ma` → suffix 'Ms Ma') — 2026-09-27 #544, A CREDENTIAL RUN. - A member the company decides reports `suffix-or-name` where it was decided (`Smith, PhD Ma`); a member its own capitals made the credential reports nothing new (`John Smith, PhD MA`) — 2026-09-27 #544, A CREDENTIAL RUN. - Accepted: `Om Jr Ma` reads given 'Om', suffix 'Jr Ma' with no family name, and `Smith, MA PhD Ma` reads suffix 'MA PhD Ma' with no given name; the split `Ph. D.` in a main segment (no comma, or before a suffix comma) is outside the walk, so `John Smith Ph. D. MEng` keeps family 'MEng', while the comma and given-slot spellings anchor (`Smith, Ph. D. MEng` → suffix 'Ph. D. MEng') — 2026-09-27 #544, A CREDENTIAL RUN. diff --git a/docs/design/decisions/S8-credentials-trailing-run.md b/docs/design/decisions/S8-credentials-trailing-run.md index a0a412e5..8746fab6 100644 --- a/docs/design/decisions/S8-credentials-trailing-run.md +++ b/docs/design/decisions/S8-credentials-trailing-run.md @@ -19,7 +19,7 @@ Open: [#785](https://github.com/derek73/python-nameparser/issues/785) (whether c ## Log -- 2026-10-04 (Derek), #602 (its Latin restriction and its non-Latin honorific exclusion superseded by #679, below; its lone particle's count superseded in part by #627, below) — A CREDENTIAL AFTER THE NAME CORE STARTS A RUN TO THE END OF ITS PART. An unambiguous Latin suffix word (the Latin restriction retracted by #679, below) of two or more letters standing after two name words with no comma (a lone particle not counting toward the two; qualified by #627, below), or after the given word in the part after a family comma, makes every later word of that part a suffix, except a title word, which reads as a title: `John Smith PhD Jones` → suffix 'PhD Jones', `Eric H. Holder Jr. Attorney General` → suffix 'Jr.', title 'Attorney General'. A name word the run absorbs is reported `suffix-or-name`, a reader hesitating over `Jones` there. Before this the three comma shapes disagreed — middle 'Smith PhD' with no comma, middle 'Jones' and suffix 'PhD' after a family comma, and suffix 'Jr., Jones' after a suffix comma (C2's tail) — and the no-comma reading made a credential a middle name, the one reading nobody intends. The suffix-comma path already read it this way, and the comma spelling of the Holder name always did. +- 2026-10-04 (Derek), #602 (its Latin restriction and its non-Latin honorific exclusion superseded by #679, below; its lone particle's count superseded in part by #627, below; its merged `Ph. D.` kept out of reach superseded after a comma by #603, decisions/C13) — A CREDENTIAL AFTER THE NAME CORE STARTS A RUN TO THE END OF ITS PART. An unambiguous Latin suffix word (the Latin restriction retracted by #679, below) of two or more letters standing after two name words with no comma (a lone particle not counting toward the two; qualified by #627, below), or after the given word in the part after a family comma, makes every later word of that part a suffix, except a title word, which reads as a title: `John Smith PhD Jones` → suffix 'PhD Jones', `Eric H. Holder Jr. Attorney General` → suffix 'Jr.', title 'Attorney General'. A name word the run absorbs is reported `suffix-or-name`, a reader hesitating over `Jones` there. Before this the three comma shapes disagreed — middle 'Smith PhD' with no comma, middle 'Jones' and suffix 'PhD' after a family comma, and suffix 'Jr., Jones' after a suffix comma (C2's tail) — and the no-comma reading made a credential a middle name, the one reading nobody intends. The suffix-comma path already read it this way, and the comma spelling of the Holder name always did. WHAT STARTS THE RUN is one predicate, `_pieces.starts_a_credential_run`, read by the trailing peel (`credential_run`, applied to every finished peel, so assign, P5's reserve, P2's chain stop and the maiden walk see one answer) and by the given-part walk. EXCLUDED, each with its reason: a TITLE WORD — 746 title words are in no suffix set (measured 2026-10-04: `len(L.titles - L.suffix_acronyms - L.suffix_words)` for the default lexicon), many of them surnames (`King`, `Bishop`, `Judge`), so `Mary Jane King Smith` keeps family 'Smith'; a title is read from the end of a name only behind a comma or with a period (H5, Derek). The AMBIGUOUS CLASS, which carries no evidence of which it is. A SINGLE LETTER, in any case: a lowercase `i` or `v` carries no `initial` tag, and found by the v1 off-switch test, `Josep Carod i Rovira` with `i` removed from the conjunctions read suffix 'i Rovira'. A CONNECTIVE. A word that is also PARTICLE or BOUND GIVEN-NAME vocabulary — found by the suite, not foreseen: `abd` is in the credential list (ABD) and heads `Abd Allah`, and `Mohamed Ali Abd Allah` read suffix 'Abd Allah' until it was excluded. The NON-LATIN HONORIFIC words, an honorific followed by a name word being a different question from a credential followed by one (retracted by #679, below). A word that is also a title (`MD`, `Ms`, `Sr.`) does start the run: position decides, and after the name core it is the suffix. DECLINED: lifting the credential out — `John Smith PhD Jones` as given 'John', middle 'Smith', family 'Jones', suffix 'PhD', the family-comma path's old reading. It keeps every name word in a name field, but it reads a credential as something written in the middle of a name, which is the reading the rule rejects. REVERSED BY THIS ENTRY rather than edited: decisions/S5's #578 bullet (`Doe, Jane PhD vd Ma` now reads suffix 'PhD vd Ma', the credential starting the given part's run), and two of the three shapes #544's ACCEPTED LIMITS (decisions/S7) kept out of the company's reach — a title standing between the credential and the member, and a member a particle chain had taken. The case rows `a_title_between_degree_and_member_keeps_it_a_name` and `a_member_the_particle_chain_took_is_out_of_reach` keep their ids and now pin the run. The third, the merged `Ph. D.`, stays out of reach: that piece is outside the walk the run is read over. diff --git a/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md b/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md index f2bca608..c4e3d6c1 100644 --- a/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md +++ b/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md @@ -47,7 +47,7 @@ - **A title run in front of a trailing suffix word** ("Dr King Jr"): a title run leaves one NAME word standing and a post-nominal cannot be it (H3), so `Dr King Jr` reads family `King`, suffix `Jr` exactly as this rule states. - **Written case and the count, at the bare ambiguous acronym** ("Jack MA"): written case outranks the count in a mixed-case name everywhere but the first slot after a comma, where the count decides first (S6, C4); the given part's trailing slot after a family comma is read without the count (S5); and an unambiguous credential in front outranks both (S7). - **An ambiguous acronym written with its periods** ("Jack M.A."): it counts unambiguously, and is not the bare acronym S4 counts words to spare for (S3). -- **A credential's run against its company** ("John Smith PhD Prof. Ma"): a title standing between the credential and the member, and a member a particle chain (P2) had taken, are the credential's run's (S8), where the company could not reach them (S7). +- **A credential's run against its company** ("John Smith PhD Prof. Ma"): a title standing between the credential and the member, and a member a particle chain (P2) had taken, are the credential's run's (S8), where the company could not reach them (S7); so are a one-letter numeral and a name word, which end the company but not the run. A connective between the credential and a word after it is joined to the credential first (P3), so no run starts, except in a part a credential opens after a comma (C13); one further in is the run's. **Accepted:** diff --git a/docs/design/rules/S5-given-parts-trailing-slot-after-a-family-comma.md b/docs/design/rules/S5-given-parts-trailing-slot-after-a-family-comma.md index 596d88b7..a7e11465 100644 --- a/docs/design/rules/S5-given-parts-trailing-slot-after-a-family-comma.md +++ b/docs/design/rules/S5-given-parts-trailing-slot-after-a-family-comma.md @@ -10,7 +10,7 @@ "Doe, John DO" → suffix="DO" "Doe, John Ma" → middle="Ma" · boundary ``` -- **How far "ending the given part" reaches.** It reaches past the credentials behind it and past a trailing title, which is transparent to this reading as it is to the rest of the trailing reading (H5); a name word behind the word ends the reach, and so does a PARTICLE the suffix vocabulary does not also claim: it belongs to the family the comma already named and is taken there by a rule that runs after this reading is made (P6), so `Doe, John MA do` keeps its middle name though the capitals would otherwise have taken the word. A particle the suffix vocabulary DOES claim is looked past like any other credential, so `Doe, John MA vd` reads suffix `MA`, with `vd` attaching behind it. Where the reach ends, the word is an ordinary middle name, read in silence. +- **How far "ending the given part" reaches.** It reaches past the credentials behind it and past a trailing title, which is transparent to this reading as it is to the rest of the trailing reading (H5); a name word behind the word ends the reach, and so does a PARTICLE the suffix vocabulary does not also claim as an unambiguous suffix word (`do` is in its ambiguous half): it belongs to the family the comma already named and is taken there by a rule that runs after this reading is made (P6), so `Doe, John MA do` keeps its middle name though the capitals would otherwise have taken the word. A particle the suffix vocabulary DOES claim is looked past like any other credential, so `Doe, John MA vd` reads suffix `MA`, with `vd` attaching behind it. Where the reach ends, the word is an ordinary middle name, read in silence. ``` "Doe, John MA Smith" → middle="MA Smith" · boundary ``` diff --git a/docs/design/rules/S7-company-of-an-unambiguous-credential.md b/docs/design/rules/S7-company-of-an-unambiguous-credential.md index bf9242df..7ad9e7ee 100644 --- a/docs/design/rules/S7-company-of-an-unambiguous-credential.md +++ b/docs/design/rules/S7-company-of-an-unambiguous-credential.md @@ -5,7 +5,7 @@ **Then:** a member of the ambiguous set standing BEHIND an unambiguous credential in one run of suffix words — members of the class between them passing the run on — reads as the credential whatever its writing and whatever the count, at every trailing slot S4 names: the degree in front says what the run is. - **Only a credential in front speaks; one behind the member says nothing about it.** -- **What ends the run.** A connective (P3), a particle (P2) — a word of both the particle and the suffix vocabulary heads the family name behind it — and a single-letter roman numeral, in any case — one letter, the shape a bare middle initial is written in, where a multi-letter one such as 'III' speaks — speak for nothing and end the run; so does any name word. A particle of this class standing behind a credential is still spoken for: the exclusion is of the word in front. +- **What ends the run.** A connective (P3), a particle (P2) — a word of both the particle and the suffix vocabulary heads the family name behind it — and a single-letter roman numeral, in any case — one letter, the shape a bare middle initial is written in, where a multi-letter one such as 'III' speaks — speak for nothing and end the run; so does any name word. A particle of this class standing behind a credential is still spoken for: the exclusion is of the word in front. Where a credential's run starts (S8), a one-letter numeral and a name word behind the credential end the company but not the run ('John Smith PhD V Ma' and 'John Smith PhD Jones Ma' read wholly as suffix). A connective standing between the credential and a word after it is joined to the credential first (P3), so no run starts ('John Smith PhD y Ma' reads family 'PhD y Ma', 'Smith, John PhD and Ma' middle 'PhD and Ma'), except in a part a credential opens after a comma (C13), which is suffix to its end ('Smith, PhD and Ma'); a connective further in is the run's ('John Smith PhD Jones and Ma' reads suffix 'PhD Jones and Ma'), and a letter that is also a roman numeral, such as a mixed-case 'i', reads as the numeral, as a letter P8 reads as an initial is no connective at all. ``` "doe, jane v phd do" → suffix="phd do" ``` diff --git a/docs/design/rules/S9-unlisted-words-that-join-the-ambiguous-class-by-shape.md b/docs/design/rules/S9-unlisted-words-that-join-the-ambiguous-class-by-shape.md index 928723e2..eb10f6b6 100644 --- a/docs/design/rules/S9-unlisted-words-that-join-the-ambiguous-class-by-shape.md +++ b/docs/design/rules/S9-unlisted-words-that-join-the-ambiguous-class-by-shape.md @@ -2,7 +2,7 @@ **When:** an unlisted word written in capitals or in dotted chunks stands where a suffix may stand, as in "John Smith XYZ". -**Then:** an unlisted word joins the ambiguous class (S4) by SHAPE where the caller asks for it. +**Then:** an unlisted word joins the ambiguous class (S4) by SHAPE, in the positions each shape is admitted: the dotted shape by default, the caps shape by default in one position and elsewhere where the caller asks for it. - **The two shapes.** Two or more period-separated chunks is one such shape, admitted by default (S3); an unlisted all-caps alphabetic word of two or more letters, standing in a suffix position of a mixed-case name and belonging to no wordlist, is the other. - **Where the caps shape is admitted by default.** By default that second shape is admitted in one position only, the part after a comma with two or more name words before it (C4, C6): French and Korean records write the SURNAME in capitals, and that convention puts the capitals at the end of a name or before a comma, never after a comma behind a full name. diff --git a/nameparser/_pipeline/_classify.py b/nameparser/_pipeline/_classify.py index ff1ea582..707b21f9 100644 --- a/nameparser/_pipeline/_classify.py +++ b/nameparser/_pipeline/_classify.py @@ -204,8 +204,8 @@ def _tags_for(token: WorkToken, n: str, state: ParseState, # title as a whole ('Lt.Gov.', and by the ANY rule 'Mr.Smith'); # else ANY suffix chunk makes it a suffix ('JD.CPA'). Title wins # (v1's continue). The third and fourth are 2.4's by-shape halves - # (#516; rules/S9: "An unlisted word joins the ambiguous class - # (S4) by SHAPE where the caller asks for it") and reach only a + # (#516; rules/S9: "joins the ambiguous class (S4) by SHAPE, in + # the positions each shape is admitted") and reach only a # token NO vocabulary claimed: a dotted token of two or more # alphabetic chunks, and -- where its switch is on -- an unlisted # all-caps word. They are an `elif` chain diff --git a/nameparser/_types.py b/nameparser/_types.py index 126ecb82..8169731b 100644 --- a/nameparser/_types.py +++ b/nameparser/_types.py @@ -532,8 +532,8 @@ class AmbiguityKind(StrEnum): #: and reports. The particle chain is the commonest joiner but not #: the only one: the bound-given join takes a member into its pair #: the same way, so "Berg, abdul MA" reads given ``abdul MA`` in - #: silence. Where a particle the suffix vocabulary - #: does not also claim stands BEHIND it, the given part ends at + #: silence. Where a particle the suffix vocabulary does not + #: also claim as an unambiguous word stands BEHIND it, the given part ends at #: that particle as this walk reads it, and the attachment that #: moves the particle to the family runs a stage too late to #: re-open the question -- so "Doe, John MA do" keeps middle diff --git a/tests/v2/rules_budget.json b/tests/v2/rules_budget.json index dbdb7256..47fa3297 100644 --- a/tests/v2/rules_budget.json +++ b/tests/v2/rules_budget.json @@ -51,14 +51,14 @@ "R4": {"words": 913, "then_words": 651, "because_sentences": 1, "except": 4, "precedence": 0, "accepted": 3, "accepted_words": 552}, "R5": {"words": 250, "then_words": 154, "because_sentences": 2, "except": 0, "precedence": 0, "accepted": 0, "accepted_words": 0}, "S1": {"words": 274, "then_words": 26, "because_sentences": 1, "except": 2, "precedence": 2, "accepted": 0, "accepted_words": 0}, - "S2": {"words": 586, "then_words": 103, "because_sentences": 1, "except": 0, "precedence": 7, "accepted": 3, "accepted_words": 203}, + "S2": {"words": 641, "then_words": 103, "because_sentences": 1, "except": 0, "precedence": 7, "accepted": 3, "accepted_words": 203}, "S3": {"words": 467, "then_words": 265, "because_sentences": 3, "except": 2, "precedence": 2, "accepted": 1, "accepted_words": 87}, "S4": {"words": 298, "then_words": 156, "because_sentences": 2, "except": 0, "precedence": 3, "accepted": 0, "accepted_words": 0}, - "S5": {"words": 621, "then_words": 306, "because_sentences": 1, "except": 2, "precedence": 1, "accepted": 0, "accepted_words": 0}, + "S5": {"words": 632, "then_words": 317, "because_sentences": 1, "except": 2, "precedence": 1, "accepted": 0, "accepted_words": 0}, "S6": {"words": 337, "then_words": 121, "because_sentences": 2, "except": 0, "precedence": 2, "accepted": 0, "accepted_words": 0}, - "S7": {"words": 420, "then_words": 238, "because_sentences": 1, "except": 1, "precedence": 2, "accepted": 1, "accepted_words": 147}, + "S7": {"words": 570, "then_words": 388, "because_sentences": 1, "except": 1, "precedence": 2, "accepted": 1, "accepted_words": 147}, "S8": {"words": 354, "then_words": 240, "because_sentences": 1, "except": 1, "precedence": 1, "accepted": 0, "accepted_words": 0}, - "S9": {"words": 354, "then_words": 207, "because_sentences": 2, "except": 0, "precedence": 2, "accepted": 0, "accepted_words": 0}, + "S9": {"words": 376, "then_words": 229, "because_sentences": 2, "except": 0, "precedence": 2, "accepted": 0, "accepted_words": 0}, "S10": {"words": 401, "then_words": 140, "because_sentences": 3, "except": 4, "precedence": 2, "accepted": 2, "accepted_words": 73}, "T1": {"words": 74, "then_words": 18, "because_sentences": 1, "except": 1, "precedence": 0, "accepted": 0, "accepted_words": 0}, "T2": {"words": 43, "then_words": 14, "because_sentences": 1, "except": 0, "precedence": 0, "accepted": 0, "accepted_words": 0},