From bd58998ec7a1f12de22142f68a330739d7a68e71 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Wed, 14 Oct 2026 00:30:55 -0700 Subject: [PATCH 1/2] Read junior and senior in other languages as suffixes (#661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add 31 spelled-out generational words to SUFFIX_WORDS -- hr/sr/bs, bg, ru, uk, cs/sk, fi, lt and hi, feminine forms beside the masculine -- so "Antonio Pavlović mlađi" reads family Pavlović, suffix mlađi, where 1.4.0 through 2.4.2 read middle Pavlović, family mlađi. Each word was checked for name collisions (tools/name_usage.py) and found in the trailing spaced position in real titles on its own language's Wikipedia. Excluded with reasons in decisions.md#generational-words: kıdemli, sóisearach/sinsearach, Hungarian ifj./id., Bulgarian млада, and the ml./мл./st. abbreviations. The #602 run start implemented rules.md#S2's "Latin" as isascii(), which the first accented members would have split: "stariji Jones" started the run and "mlađi Jones" did not. It now reads a non-ASCII word's letters by their Unicode names; S2 gains examples pinning both that and the non-Latin refusal ("Taro Tanaka さん Jones"), which nothing guarded before. Six CLDR corpus renderings (hr, sr) move, all toward CLDR's fields; snapshot and cldr_agreement re-recorded. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 +- docs/design/decisions.md | 10 +++++++ docs/design/rules.md | 9 +++++++ docs/release_log.rst | 4 +++ nameparser/_pipeline/_pieces.py | 17 ++++++++++-- nameparser/config/suffixes.py | 26 ++++++++++++++++++- tests/v2/cases.py | 22 ++++++++++++++++ tests/v2/test_cases.py | 10 ++++--- tools/differential/cldr_agreement.json | 36 +++++++------------------- tools/differential/corpus_rules.jsonl | 8 ++++++ tools/differential/snapshot.jsonl | 20 +++++++++----- 11 files changed, 124 insertions(+), 40 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 256b66d9..7e7f889e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -253,7 +253,7 @@ Add a dedicated `copy.deepcopy()` round-trip test for it too (see `test_regexes_ **Unknown-key attribute access on `TupleManager`/`RegexTupleManager` warns (1.4, #256) — 2.0 note: the warning became a hard `AttributeError` naming the known keys (shim `TupleManager`); the paragraph below describes the deleted v1 machinery and applies only when reading v1 history.** — a key not currently in the dict emits `DeprecationWarning` naming the miss and the known keys (`_warn_unknown_key`), before falling back to the same `None`/`EMPTY_REGEX` default as before; `.get()` stays silent. Dunder probes (`__deepcopy__`) still raise `AttributeError` outright, and single-underscore probes (`_repr_html_`, IPython's `_ipython_canary_method_should_not_exist_`, etc.) are excluded from the warning too — no real config key starts with `_`, so both guards just keep protocol/introspection probes from misfiring as "typo" warnings. This means internal parser code that reads `self.C.regexes.` unconditionally (e.g. `squash_bidi`'s `bidi`) now warns if a caller's custom `regexes` dict omits that key — a previously-silent partial-override pattern is on the same deprecation path as an actual typo. -**Adding a word to a config set** — first check the *other* sets for the same word (grep `nameparser/config/` or intersect the sets in a `python3 -c`). Real overlaps exist: `do`/`st`/`mc` ∈ `PARTICLES` ∩ `TITLES`/`SUFFIX_ACRONYMS`; `abd` = "ABD" ∈ `SUFFIX_ACRONYMS`; `abu` ∈ `PARTICLES` ∩ `BOUND_GIVEN_NAMES` (position-dependent: leading token → given-name join, mid-name → family-name join). Usually position-dependent and harmless, but can force a guard or an exclusion (the `last_base` all-particles guard; dropping `abd` from `BOUND_GIVEN_NAMES`). Then check the word against real names, not memory: `uv run python tools/name_usage.py ` counts its bearers as a given name and as a family name (US Census surnames, SSA given names, Wikidata with countries of citizenship). Pass a name as it is written (`McDonald`): Wikidata labels match exactly, and no rule rebuilds an internal capital from `mcdonald`. It reports and does not decide — read the counts against the position the set's claim acts on. +**Adding a word to a config set** — first check the *other* sets for the same word (grep `nameparser/config/` or intersect the sets in a `python3 -c`). Real overlaps exist: `do`/`st`/`mc` ∈ `PARTICLES` ∩ `TITLES`/`SUFFIX_ACRONYMS`; `abd` = "ABD" ∈ `SUFFIX_ACRONYMS`; `abu` ∈ `PARTICLES` ∩ `BOUND_GIVEN_NAMES` (position-dependent: leading token → given-name join, mid-name → family-name join). Usually position-dependent and harmless, but can force a guard or an exclusion (the `last_base` all-particles guard; dropping `abd` from `BOUND_GIVEN_NAMES`). Then check the word against real names, not memory: `uv run python tools/name_usage.py ` counts its bearers as a given name and as a family name (US Census surnames, SSA given names, Wikidata with countries of citizenship). Pass a name as it is written (`McDonald`): Wikidata labels match exactly, and no rule rebuilds an internal capital from `mcdonald`. It reports and does not decide — read the counts against the position the set's claim acts on. And check the predicates that test a member's SPELLING rather than its membership: they encode what the set's members happened to look like when they were written. `_pieces.starts_a_credential_run` implemented rules.md#S2's "Latin" as `isascii()`, exact until #661 added `mlađi`, which would have read `mlađi Jones` differently from `stariji Jones`; the design-docs review caught it, after a first draft had credited the test to the wrong clause of S2 and proposed deleting it on a mutation that moved nothing because nothing pinned it. Before adding the first member of a new script, or the first accented one, grep the set's consumers for `isascii`, `isalpha` and script tests, and read the rule each one implements. **Before adding a short/common word to `PARTICLES` globally**, test it mid-string against realistic 3-token names, not just check for English-word collisions: Korean/Vietnamese given names put a short syllable in the middle slot (`Park In Hwan`, `Nguyen To Nga`), and Western names put a bare initial there (`John V. Smith`). A word that looks safe ("nobody is named 'to'") can still swallow a real middle name/initial into the last name once it's a global prefix — confirmed regressions for `to`/`in`/`an`/`ten`/`then` and bare `v` this way (PR #191). diff --git a/docs/design/decisions.md b/docs/design/decisions.md index 55bfcd51..50eff4a0 100644 --- a/docs/design/decisions.md +++ b/docs/design/decisions.md @@ -819,6 +819,16 @@ Closes #606. No parser code moves: 41 words join TITLES and one, knt (Knight, be - **gravos was dropped, not excluded.** The issue listed it as the Latin-script Greek for Count; the spot-check could not confirm it is the Greek word at all, which is κόμης (kómis). Nothing was shown, so there is nothing to keep out — it is recorded here so that a later sweep starts from that rather than from the issue's table. κόμης itself is NOT added and NOT excluded: it is a Greek surname (~420 bearers in Greece, official lists writing the surname first), which by its counts sits inside the class above beside hrabia, and it was left out of this change for having no use in current Greek address rather than for its collision. Adding it is a decision still to make. - **Measurement (2026-10-04).** One corpus name moves, `Greve Anna`, given Greve to title Greve: the rules.md#H1 example this entry added, and the only one of its three examples that moves. Recompute: take the 41 entries of the #606 block in titles.py — the lines that are a quoted string, not the words quoted inside its comments, which name existing entries and excluded ones — parse every distinct name in the tools/differential/corpus*.jsonl glob (1,518 on the day) with the shipped parser and with `Lexicon.default().remove(titles=, suffix_acronyms={"knt"})`, and diff `as_dict()`. Before the rules.md examples were added, the same diff moved nothing over the 1,809 corpus lines of the tree before #604 merged, gravos still in the word list. The old readings the release-log bullet quotes were checked against the 1.4.0 and 2.3.0 wheels. +### generational-words — junior and senior in other languages (2026-10-14, #661) + +Closes #661. 31 spelled-out generational words join SUFFIX_WORDS, and one predicate moves with them (the run-start bullet below), so `Antonio Pavlović mlađi` reads family Pavlović, suffix mlađi, where 1.4.0 and 2.4.2 read middle Pavlović, family mlađi (checked against both wheels). The class is decided once — a word meaning junior/senior (or the Younger/the Elder) written spaced AFTER the name — and each word was then checked two ways. First, collisions: a suffix claim acts on the trailing word, so the risk is a borne name there, and `tools/name_usage.py` found none (US Census, SSA, Wikidata; nuorempi's one Wikidata bearer of each kind is noise, as the issue said). Second, usage, because the issue's list came partly from ilib's machine-translated test data: each word was found in the trailing spaced position in real titles on its own language's Wikipedia (2026-10-13, e.g. "Hans Holbein mlađi", "Плиний Младший", "Pieter Brueghel vanhempi", "बलबीर सिंह सीनियर"). The per-word list, grouped by language, is the #661 block in nameparser/config/suffixes.py. Feminine forms ship beside the masculine wherever the language has them (mlađa, младшая, молодша, jaunesnioji); lt vyresnioji is the grammatical feminine with no titled bearer found, and ships on grammar. ru/uk and cs/sk were not in the issue and were added on the same evidence (Derek, 2026-10-14). + +- **CLDR is corroboration, not a source.** Its person-name samples write mlađi/млађи (hr, sr — on a woman's name too), младши/младша (bg), ifj. (hu) and जूनियर (hi), but its `generation` field also holds translations of the field LABEL rather than of the word: isizukulwane (zu), जेनेरेसन (ne), نسل (ps) and jẹnereṣọn (pcm) all mean "generation". A word is not added because CLDR has it. The wider roster (fils, yngri, الابن and the rest) is open at [#679](https://github.com/derek73/python-nameparser/issues/679), to be vetted word by word the same way. +- **Excluded, each with its reason.** kıdemli (tr): a rank word (Kıdemli onbaşı); Turkish writes Jr. or a parenthesis, (oğul). sóisearach/sinsearach (ga): rank words too; Irish writes Óg and Mór, which are also inside given names (Seán Óg Ó hAilpín), so they are a separate decision and not taken here. ifj./id. (hu): written BEFORE the name (ifj. Kovács János, which H2's shape rule already reads as a title), and a bare `id` would make any trailing ID a suffix. млада (bg feminine of млади): Mlada is a borne given name, and under a family-first order the given name is the trailing word. ml./мл./st.: the abbreviations stay under the #269 deferral in suffixes.py, st. colliding with Saint besides. +- **Spaced only, and what that leaves.** bg, ru and uk write a modern Jr. hyphen-glued (Теодор Рузвелт-младши, Дональд Трамп-молодший), which no whole-token set reaches; for those three the gain is mainly the historical the Elder/the Younger names, written title case (Младший), which the lowercase fold matches. +- **The #602 run start reads the letters' script, not ASCII (Derek, 2026-10-14).** rules.md#S2 lets "an unambiguous Latin suffix word of two or more letters" start the run, and `_pieces.starts_a_credential_run` had implemented Latin as `letters.isascii()`, exact while no accented Latin word was suffix vocabulary. This entry adds the first ones (mlađi, mlađa, mladší, starší), so without a change `Pieter Bruegel stariji Jones` would read suffix 'stariji Jones' while `Antonio Pavlović mlađi Jones` kept family Jones -- one language, one construction, split by the `đ`. The test now asks a non-ASCII word whether every letter's Unicode name is a LATIN one; the ASCII test stays first, so an ASCII word pays no frame for it. Cyrillic and Devanagari generational words still do not start the run, which is the statement's "Latin" rather than a gap, and is what refuses the non-Latin honorifics (`Taro Tanaka さん Jones` keeps family Jones). The first draft of this entry credited the ASCII test to S2's honorific exclusion instead and proposed deleting it, on the evidence that deleting it moved no test and no corpus name. The design-docs review found the attribution wrong and the evidence empty: nothing pinned the test, and deleting it reads `Taro Tanaka さん Jones` as suffix 'さん Jones' and `Гай Плиний Старший Иванов` as suffix 'Старший Иванов'. Both are rules.md#S2 boundary examples now. #678 was filed on the first draft and corrected. +- **Measurement (2026-10-14).** Six corpus names move, all in corpus_cldr.jsonl and all the CLDR hr/sr renderings carrying mlađi/млађи, each gaining suffix mlađi (or млађи) from a name field; cldr_agreement records 14 changes, every one toward CLDR's fields. The bg and hi renderings do not move because those locales' formats drop the generation field. The run-start change moves no corpus name: of the 161 rows of tools/differential/cldr_fields.jsonl whose `generation` value `cldr_agreement.occurs()` in the text, none writing a non-ASCII generation word has a name word after it in the same part (two write given names after `II` across a comma, which ends the run's part). Recompute: `uv run python tools/differential/snapshot.py --since `. + ### cjk-comma-demotion — the script shapes are pure, the wrappers are tolerated (2026-09-01, #469) Closes #469, and continues the corpus-tier arc below rather than standing apart from it: the tier split gave the differential somewhere to WATCH a name without promising it, and this is the first doctrine narrowing to spend that. No parser behavior moves anywhere in it. Counts are this session's and every one is recomputable from the checked-in tree — `wc -l` over tools/differential/, `uv run python tools/differential/build_shapes_corpus.py --coverage`, and one `uv run python tools/differential/compare.py --baseline X` run per baseline, read off its `corpora:` and `corpus:` lines. diff --git a/docs/design/rules.md b/docs/design/rules.md index d886558b..8d0d0948 100644 --- a/docs/design/rules.md +++ b/docs/design/rules.md @@ -1238,6 +1238,11 @@ S2. Rationale: generational suffixes and credentials are recognized part in silence, unless paired initials speak only for each other. "John Smith Jr." → suffix="Jr." + "Antonio Pavlović mlađi" → family="Pavlović" + "Antonio Pavlović mlađi" → suffix="mlađi" + "Агриппина Младшая" → suffix="Младшая" + "Pavlović, Antonio mlađi" → suffix="mlađi" + "Stariji Bruegel" → given="Stariji" · boundary "John Smith M.A." → suffix="M.A." "John Smith PhD" → suffix="PhD" "John Ma" → family="Ma" · boundary @@ -1302,6 +1307,10 @@ S2. Rationale: generational suffixes and credentials are recognized "Mary Jane King Smith" → family="Smith" "Josep Carod i Rovira" → family="Carod i Rovira" "Mohamed Ali Abd Allah" → family="Allah" + "Antonio Pavlović mlađi Jones" → suffix="mlađi Jones" + "Pieter Bruegel stariji Jones" → suffix="stariji Jones" + "Гай Плиний Старший Иванов" → family="Иванов" · boundary + "Taro Tanaka さん Jones" → family="Jones" · boundary "John PhD Smith" → family="Smith" · boundary Accepted: a title before the credential keeps it a credential, and the name loses its surname exactly as it did before this diff --git a/docs/release_log.rst b/docs/release_log.rst index 08b0b37d..e6585a9f 100644 --- a/docs/release_log.rst +++ b/docs/release_log.rst @@ -4,6 +4,10 @@ Release Log nameparser 2.5 is under development. + **Behavior Changes** + + - **Add junior and senior in other languages as suffixes.** ``Antonio Pavlović mlađi`` gives last ``Pavlović``, suffix ``mlađi``, where 1.4.0 through 2.4.2 gave middle ``Pavlović``, last ``mlađi``; the words are Croatian, Serbian, Bosnian, Bulgarian, Russian, Ukrainian, Czech, Slovak, Finnish, Lithuanian and Hindi, written as a separate word after the name. :issue:`661` + * 2.4.2 - October 13, 2026 nameparser 2.4.2 fixes two readings: a name typed with decomposed accents or hangul, and the Hausa given name Shehu. diff --git a/nameparser/_pipeline/_pieces.py b/nameparser/_pipeline/_pieces.py index fabca60e..705f50db 100644 --- a/nameparser/_pipeline/_pieces.py +++ b/nameparser/_pipeline/_pieces.py @@ -475,7 +475,11 @@ def starts_a_credential_run(piece: Sequence[int], ptags: Set[str], `i` or `v` mid-name is an initial or a connective far more often than a numeral, and carries no `initial` tag), a connective, a word that is also bound given-name or particle vocabulary (`abd` - heads `Abd Allah`; `vd`, `mc`), and the non-Latin honorific words. + heads `Abd Allah`; `vd`, `mc`), and a word with a letter of another + script, which is what refuses the non-Latin honorifics (`Taro + Tanaka さん Jones` keeps family 'Jones'). Latin means the letters' + Unicode names, not ASCII: #661's `mlađi` starts the run as `stariji` + does (decisions.md#generational-words, #678). The split credential group merges ('Ph. D.', the one piece carrying the "suffix" piece tag) starts it as its one-token spelling 'PhD' @@ -500,7 +504,16 @@ def starts_a_credential_run(piece: Sequence[int], ptags: Set[str], return False # C-level, no generator frame per character letters = "".join(filter(str.isalpha, tok.text.lower())) - return len(letters) >= 2 and letters.isascii() + return len(letters) >= 2 and (letters.isascii() + or _all_latin(letters)) + + +def _all_latin(letters: str) -> bool: + """Whether every letter is a Latin one by its Unicode name. Asked + only of a non-ASCII word, so the ASCII test above keeps the common + case free of a frame per letter.""" + return all(unicodedata.name(c, "").startswith("LATIN ") + for c in letters) #: tags of suffix words another vocabulary claims as a name word or a diff --git a/nameparser/config/suffixes.py b/nameparser/config/suffixes.py index f8d8c73e..5fde4e5c 100644 --- a/nameparser/config/suffixes.py +++ b/nameparser/config/suffixes.py @@ -5,7 +5,8 @@ # pending the within-script collision vetting the issue asks for; # 'ст' especially is a plausible false-positive risk (many two- # letter Cyrillic abbreviations exist), and both are short enough - # to worry about. Not shipped in this pass. + # to worry about. Not shipped in this pass. The spelled-out words + # ship since #661 (the block below 'vet'); the abbreviations do not. 'esq', 'esquire', 'jr', @@ -28,6 +29,29 @@ 'ret', 'vet', + # #661: junior/senior in other languages, the spelled-out words + # written spaced AFTER the name ("Hans Holbein mlađi", "Плиний + # Младший"). Each was found in that position in real titles on its + # own language's Wikipedia, and none is borne as a name beyond noise + # (US Census, SSA, Wikidata via tools/name_usage.py; nuorempi has one + # Wikidata bearer each way). Feminine forms ship beside + # the masculine where the language has them. Left out, with reasons, + # in decisions.md#generational-words: kıdemli, sóisearach and + # sinsearach (rank words, not generations), Hungarian ifj./id. + # (written BEFORE the name), Bulgarian млада (a given name), and the + # abbreviations ml./мл./st. (the #269 deferral above). + 'mlađi', 'mlađa', 'stariji', 'starija', # hr/sr/bs Latin + 'млађи', 'млађа', 'старији', 'старија', # sr Cyrillic + 'младши', 'младша', 'старши', 'старша', # bg; старша is uk too + 'млади', 'стари', # bg the Younger/the Elder + 'младший', 'младшая', 'старший', 'старшая', # ru + 'молодший', 'молодша', # uk + 'mladší', 'starší', # cs/sk + 'nuorempi', 'vanhempi', # fi + 'jaunesnysis', 'jaunesnioji', # lt + 'vyresnysis', 'vyresnioji', # lt + 'जूनियर', 'जुनियर', 'सीनियर', # hi, junior in both u spellings + # #269 follow-up: Hebrew post-nominals, both gershayim spellings # (ASCII '"' and U+05F4); the mid-word quote is inert in # extraction, like the ד"ר title. Neither is ever a name. diff --git a/tests/v2/cases.py b/tests/v2/cases.py index 64db8a51..da8a9b19 100644 --- a/tests/v2/cases.py +++ b/tests/v2/cases.py @@ -981,6 +981,28 @@ def _check_cjk_shape_purity(self) -> None: ambiguities=("suffix-or-name",), notes="generational words start the run as credentials do " "(decided on #602). 2.3.0 read middle 'Smith Jr.'"), + Case("a_generational_word_in_another_language", "Antonio Pavlović mlađi", + {"given": "Antonio", "family": "Pavlović", "suffix": "mlađi"}, + classification="fix(#661)", + notes="junior/senior words of other languages are suffix " + "vocabulary. 1.4.0 and 2.4.2 read middle 'Pavlović', " + "family 'mlađi'"), + Case("a_generational_word_after_a_family_comma", + "Pavlović, Antonio mlađi", + {"given": "Antonio", "family": "Pavlović", "suffix": "mlađi"}, + classification="fix(#661)", + notes="1.4.0 and 2.4.2 read middle 'mlađi'"), + Case("a_feminine_generational_word_in_cyrillic", "Агриппина Младшая", + {"given": "Агриппина", "suffix": "Младшая"}, + classification="fix(#661)", + ambiguities=("given-or-family",), + notes="the feminine form ships beside the masculine, and the " + "title case of a historical name matches through the " + "fold. 1.4.0 and 2.4.2 read family 'Младшая'"), + Case("a_generational_word_in_devanagari", "बलबीर सिंह सीनियर", + {"given": "बलबीर", "family": "सिंह", "suffix": "सीनियर"}, + classification="fix(#661)", + notes="1.4.0 and 2.4.2 read middle 'सिंह', family 'सीनियर'"), Case("a_title_inside_the_run_is_a_title", "Eric H. Holder Jr. Attorney General", {"title": "Attorney General", "given": "Eric", "middle": "H.", diff --git a/tests/v2/test_cases.py b/tests/v2/test_cases.py index c172f596..a17b02b9 100644 --- a/tests/v2/test_cases.py +++ b/tests/v2/test_cases.py @@ -178,10 +178,12 @@ def test_the_field_sweep_sees_the_claims_it_checks() -> None: for case, order in _SWEPT_ROWS for a in _swept(case.id, order).ambiguities) # 1156 -> 1159 on 2026-10-11: the JOHN PROF. MA row (one - # suffix-or-name claim under each of the three orders). - assert claims == 1159, ( - f"the field sweep checks {claims} claims, recorded as 1159 on " - f"2026-10-11") + # suffix-or-name claim under each of the three orders); 1159 -> + # 1162 on 2026-10-14: the Агриппина Младшая row (#661, one + # given-or-family claim under each order). + assert claims == 1162, ( + f"the field sweep checks {claims} claims, recorded as 1162 on " + f"2026-10-14") #: Case.__post_init__'s shape checks, each probed for the message that diff --git a/tools/differential/cldr_agreement.json b/tools/differential/cldr_agreement.json index 1cd6e0b6..b8adba6e 100644 --- a/tools/differential/cldr_agreement.json +++ b/tools/differential/cldr_agreement.json @@ -4353,21 +4353,9 @@ "text": "Sinbad" }, "hr/nativeFull/givenFirst": { - "family": { - "expected": "Marić", - "got": "mlađi" - }, - "family_base": { - "expected": "Marić", - "got": "mlađi" - }, - "given_names": { - "expected": "Branko Hrvoje", - "got": "Branko Hrvoje Marić" - }, "suffix": { "expected": "mlađi prof.", - "got": "" + "got": "mlađi" }, "text": "dr. sc. Branko Hrvoje Marić mlađi, prof.", "title": { @@ -4376,13 +4364,9 @@ } }, "hr/nativeFull/surnameFirst": { - "given_names": { - "expected": "Branko Hrvoje", - "got": "Branko Hrvoje mlađi" - }, "suffix": { "expected": "mlađi prof.", - "got": "" + "got": "mlađi" }, "text": "Marić, dr. sc. Branko Hrvoje mlađi, prof.", "title": { @@ -10824,11 +10808,11 @@ }, "given_names": { "expected": "Александра Ева Сара", - "got": "ден Волф проф. др Александра Ева Сара млађи" + "got": "ден Волф проф. др Александра Ева Сара" }, "suffix": { "expected": "млађи дипл. инж.", - "got": "" + "got": "млађи" }, "text": "ван ден Волф проф. др Александра Ева Сара млађи, дипл. инж.", "title": { @@ -10884,11 +10868,11 @@ "sr/nativeFull/surnameFirst": { "given_names": { "expected": "Слађана Вера", - "got": "др Слађана Вера млађи" + "got": "др Слађана Вера" }, "suffix": { "expected": "млађи дипл. инж.", - "got": "" + "got": "млађи" }, "text": "Миленковић др Слађана Вера млађи, дипл. инж.", "title": { @@ -10948,11 +10932,11 @@ }, "given_names": { "expected": "Aleksandra Eva Sara", - "got": "den Volf prof. dr Aleksandra Eva Sara mlađi" + "got": "den Volf prof. dr Aleksandra Eva Sara" }, "suffix": { "expected": "mlađi dipl. inž.", - "got": "" + "got": "mlađi" }, "text": "van den Volf prof. dr Aleksandra Eva Sara mlađi, dipl. inž.", "title": { @@ -11004,11 +10988,11 @@ "sr_Latn/nativeFull/surnameFirst": { "given_names": { "expected": "Slađana Vera", - "got": "dr Slađana Vera mlađi" + "got": "dr Slađana Vera" }, "suffix": { "expected": "mlađi dipl. inž.", - "got": "" + "got": "mlađi" }, "text": "Milenković dr Slađana Vera mlađi, dipl. inž.", "title": { diff --git a/tools/differential/corpus_rules.jsonl b/tools/differential/corpus_rules.jsonl index f7fea4ef..bd526d74 100644 --- a/tools/differential/corpus_rules.jsonl +++ b/tools/differential/corpus_rules.jsonl @@ -22,6 +22,8 @@ "Anna Greve" "Anna z (domu) Nowak" "Anna z Nowak" +"Antonio Pavlović mlađi" +"Antonio Pavlović mlađi Jones" "Asst. Vice Chancellor John Smith" "Beethoven Ludwig van" "Beethoven, Ludwig van" @@ -334,8 +336,10 @@ "Nguyễn, Thị Vân" "Ortega y Gasset, Dr." "Ortega y Gasset, Ed" +"Pavlović, Antonio mlađi" "Ph. D. Van Johnson" "Ph. D., John" +"Pieter Bruegel stariji Jones" "Prince of Wales Jr" "Pérez, Juan Ó." "QC MP" @@ -417,8 +421,10 @@ "Smith, XYZ" "Smith, de Mesnil Jean" "Smith. John" +"Stariji Bruegel" "Steven Hardman, MD, DO, DDS" "Steven Hardman, RN - CRNA" +"Taro Tanaka さん Jones" "The Right Hon. the President of the Queen's Bench Division" "Van Buren, Ed" "Van Johnson" @@ -514,6 +520,8 @@ "van der Berg, PhD" "wang meng" "Ó. Pérez" +"Агриппина Младшая" +"Гай Плиний Старший Иванов" "Иван Петрович Абрамович" "Сидоров Иван Петрович" "Хосе И Мария Сантос" diff --git a/tools/differential/snapshot.jsonl b/tools/differential/snapshot.jsonl index 0e9f3c66..5c404602 100644 --- a/tools/differential/snapshot.jsonl +++ b/tools/differential/snapshot.jsonl @@ -140,6 +140,8 @@ {"name": "Anne Sofie Holm Hansen", "given": "Anne", "middle": "Sofie Holm", "family": "Hansen", "initials": "A. S. H. H."} {"name": "Annelise Weber", "given": "Annelise", "family": "Weber", "initials": "A. W."} {"name": "Annette Charlotte Freiherrin von und zu der Tann-Rathsamhausen", "given": "Annette", "middle": "Charlotte", "family": "Freiherrin von und zu der Tann-Rathsamhausen", "initials": "A. C. T."} +{"name": "Antonio Pavlović mlađi", "given": "Antonio", "family": "Pavlović", "suffix": "mlađi", "initials": "A. P."} +{"name": "Antonio Pavlović mlađi Jones", "given": "Antonio", "family": "Pavlović", "suffix": "mlađi Jones", "ambiguities": ["SUFFIX_OR_NAME"], "initials": "A. P."} {"name": "Ardelean, Daniel Ionescu Dragoș Ioan", "given": "Daniel", "middle": "Ionescu Dragoș Ioan", "family": "Ardelean", "initials": "D. I. D. I. A."} {"name": "Asmar", "given": "Asmar", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "A."} {"name": "Asmar", "order": "FAMILY_FIRST", "family": "Asmar", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "A."} @@ -1302,7 +1304,7 @@ {"name": "Mario", "given": "Mario", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "M."} {"name": "Mario", "order": "FAMILY_FIRST", "family": "Mario", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "M."} {"name": "Marić, Branko Hrvoje", "given": "Branko", "middle": "Hrvoje", "family": "Marić", "initials": "B. H. M."} -{"name": "Marić, dr. sc. Branko Hrvoje mlađi, prof.", "order": "FAMILY_FIRST", "title": "dr. sc. prof.", "given": "Branko", "middle": "Hrvoje mlađi", "family": "Marić", "initials": "B. H. m. M."} +{"name": "Marić, dr. sc. Branko Hrvoje mlađi, prof.", "order": "FAMILY_FIRST", "title": "dr. sc. prof.", "given": "Branko", "middle": "Hrvoje", "family": "Marić", "suffix": "mlađi", "initials": "B. H. M."} {"name": "Marja-Liisa Katariina Lehtola-Aalto kansanedustaja", "given": "Marja-Liisa", "middle": "Katariina Lehtola-Aalto", "family": "kansanedustaja", "initials": "M. K. L. k."} {"name": "Mark", "given": "Mark", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "M."} {"name": "Mark", "order": "FAMILY_FIRST", "family": "Mark", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "M."} @@ -1345,7 +1347,7 @@ {"name": "Mgr. Ing. Anna Karolína Eva Kristína von Kant Nováková Predná, CSc.", "title": "Mgr. Ing.", "given": "Anna", "middle": "Karolína Eva Kristína", "family": "von Kant Nováková Predná", "suffix": "CSc.", "initials": "A. K. E. K. K. N. P."} {"name": "Mgr. Ing. von Kant Nováková Predná Anna Karolína Eva Kristína, CSc.", "order": "FAMILY_FIRST", "title": "Mgr. Ing.", "given": "Kant", "middle": "Nováková Predná Anna Karolína Eva Kristína", "family": "von", "suffix": "CSc.", "ambiguities": ["PARTICLE_OR_GIVEN"], "initials": "K. N. P. A. K. E. K. v."} {"name": "Mike van der Velt", "given": "Mike", "family": "van der Velt", "initials": "M. V."} -{"name": "Milenković dr Slađana Vera mlađi, dipl. inž.", "order": "FAMILY_FIRST", "title": "dipl. inž.", "given": "dr", "middle": "Slađana Vera mlađi", "family": "Milenković", "initials": "d. S. V. m. M."} +{"name": "Milenković dr Slađana Vera mlađi, dipl. inž.", "order": "FAMILY_FIRST", "title": "dipl. inž.", "given": "dr", "middle": "Slađana Vera", "family": "Milenković", "suffix": "mlađi", "initials": "d. S. V. M."} {"name": "Milenković, Slađana Vera", "given": "Slađana", "middle": "Vera", "family": "Milenković", "initials": "S. V. M."} {"name": "Miler Kete", "order": "FAMILY_FIRST", "given": "Kete", "family": "Miler", "initials": "K. M."} {"name": "Miler, Kete", "given": "Kete", "family": "Miler", "initials": "K. M."} @@ -1474,6 +1476,7 @@ {"name": "Pablo Pérez, Sr. Miguel Ángel Juan Antonio", "title": "Sr.", "given": "Miguel", "middle": "Ángel Juan Antonio", "family": "Pablo Pérez", "initials": "M. Á. J. A. P. P."} {"name": "Pan Nowak Tomasz Jan", "order": "FAMILY_FIRST", "given": "Nowak", "middle": "Tomasz Jan", "family": "Pan", "initials": "N. T. J. P."} {"name": "Pan Tomasz Nowak", "given": "Pan", "middle": "Tomasz", "family": "Nowak", "initials": "P. T. N."} +{"name": "Pavlović, Antonio mlađi", "given": "Antonio", "family": "Pavlović", "suffix": "mlađi", "initials": "A. P."} {"name": "Pedro", "given": "Pedro", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "P."} {"name": "Pedro", "order": "FAMILY_FIRST", "family": "Pedro", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "P."} {"name": "Penkauskienė Sofija Rita", "order": "FAMILY_FIRST", "given": "Sofija", "middle": "Rita", "family": "Penkauskienė", "initials": "S. R. P."} @@ -1492,6 +1495,7 @@ {"name": "Ph. D., Prof. Dr. de la Cruz Alejandro Manuel César Mamani Jr.", "order": "FAMILY_FIRST", "title": "Prof. Dr.", "family": "Ph. D. de la Cruz Alejandro Manuel César Mamani", "suffix": "Jr.", "initials": "P. D. C. A. M. C. M."} {"name": "Piet", "given": "Piet", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "P."} {"name": "Piet", "order": "FAMILY_FIRST", "family": "Piet", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "P."} +{"name": "Pieter Bruegel stariji Jones", "given": "Pieter", "family": "Bruegel", "suffix": "stariji Jones", "ambiguities": ["SUFFIX_OR_NAME"], "initials": "P. B."} {"name": "Piras Giuanne Marcu", "order": "FAMILY_FIRST", "given": "Giuanne", "middle": "Marcu", "family": "Piras", "initials": "G. M. P."} {"name": "Piras, Giuanne Marcu", "given": "Giuanne", "middle": "Marcu", "family": "Piras", "initials": "G. M. P."} {"name": "Planta, Margaritta Marionna Maria de", "given": "Margaritta", "middle": "Marionna Maria", "family": "de Planta", "initials": "M. M. M. P."} @@ -1790,6 +1794,7 @@ {"name": "St Clair", "title": "St", "family": "Clair", "initials": "C."} {"name": "Stander Zelda Herman", "order": "FAMILY_FIRST", "given": "Zelda", "middle": "Herman", "family": "Stander", "initials": "Z. H. S."} {"name": "Stander, Zelda Herman", "given": "Zelda", "middle": "Herman", "family": "Stander", "initials": "Z. H. S."} +{"name": "Stariji Bruegel", "given": "Stariji", "family": "Bruegel", "initials": "S. B."} {"name": "Ste Marie", "family": "Ste Marie", "initials": "M."} {"name": "Stefan", "given": "Stefan", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "S."} {"name": "Stefan", "order": "FAMILY_FIRST", "family": "Stefan", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "S."} @@ -1835,6 +1840,7 @@ {"name": "Szabados, Máté Bendegúz", "given": "Máté", "middle": "Bendegúz", "family": "Szabados", "initials": "M. B. S."} {"name": "Tammik hr Mari-Ann Henri Roobert parlamendiliige", "order": "FAMILY_FIRST", "given": "hr", "middle": "Mari-Ann Henri Roobert parlamendiliige", "family": "Tammik", "initials": "h. M. H. R. p. T."} {"name": "Tammik, Mari-Ann Henri Roobert, parlamendiliige", "given": "Mari-Ann", "middle": "Henri Roobert", "family": "Tammik", "suffix": "parlamendiliige", "ambiguities": ["COMMA_STRUCTURE"], "initials": "M. H. R. T."} +{"name": "Taro Tanaka さん Jones", "given": "Taro", "middle": "Tanaka さん", "family": "Jones", "initials": "T. T. さ. J."} {"name": "Te Awanui-a-Rangi Black", "given": "Te", "middle": "Awanui-a-Rangi", "family": "Black", "ambiguities": ["PARTICLE_OR_GIVEN"], "initials": "T. A. B."} {"name": "Tham Jun", "given": "Tham", "family": "Jun", "initials": "T. J."} {"name": "Tham Jun Hoe", "given": "Tham", "middle": "Jun", "family": "Hoe", "initials": "T. J. H."} @@ -2094,7 +2100,7 @@ {"name": "dr. juan de la vega jr.", "title": "dr.", "given": "juan", "family": "de la vega", "suffix": "jr.", "initials": "j. v."} {"name": "dr. juan garcia III", "title": "dr.", "given": "juan", "family": "garcia", "suffix": "III", "initials": "j. g."} {"name": "dr. p. Gabrielė Aldona Petraitytė", "title": "dr.", "given": "p.", "middle": "Gabrielė Aldona", "family": "Petraitytė", "initials": "p. G. A. P."} -{"name": "dr. sc. Branko Hrvoje Marić mlađi, prof.", "title": "dr. sc. prof.", "given": "Branko", "middle": "Hrvoje Marić", "family": "mlađi", "initials": "B. H. M. m."} +{"name": "dr. sc. Branko Hrvoje Marić mlađi, prof.", "title": "dr. sc. prof.", "given": "Branko", "middle": "Hrvoje", "family": "Marić", "suffix": "mlađi", "initials": "B. H. M."} {"name": "e and e", "given": "e and e", "ambiguities": ["CONJUNCTION_OR_INITIAL"], "initials": "e. e."} {"name": "e j smith", "given": "e", "middle": "j", "family": "smith", "ambiguities": ["CONJUNCTION_OR_INITIAL"], "initials": "e. j. s."} {"name": "émile zola", "given": "émile", "family": "zola", "initials": "é. z."} @@ -2239,7 +2245,7 @@ {"name": "van den Volf Ada Kornelia Sezar Martin M.D. Ph.D.", "order": "FAMILY_FIRST", "given": "den Volf Ada Kornelia Sezar Martin", "family": "van", "suffix": "M.D. Ph.D.", "ambiguities": ["PARTICLE_OR_GIVEN"], "initials": "d. V. A. K. S. M. v."} {"name": "van den Volf Ada Kornelia Yeva Sofiya M.D. Ph.D.", "order": "FAMILY_FIRST", "given": "den Volf Ada Kornelia Yeva Sofiya", "family": "van", "suffix": "M.D. Ph.D.", "ambiguities": ["PARTICLE_OR_GIVEN"], "initials": "d. V. A. K. Y. S. v."} {"name": "van den Volf Becker Schmidt, Prof. Dr. Ada Kornelia Sezar Martin M.D. Ph.D.", "title": "Prof. Dr.", "given": "Ada", "middle": "Kornelia Sezar Martin", "family": "van den Volf Becker Schmidt", "suffix": "M.D. Ph.D.", "initials": "A. K. S. M. V. B. S."} -{"name": "van den Volf prof. dr Aleksandra Eva Sara mlađi, dipl. inž.", "order": "FAMILY_FIRST", "title": "dipl. inž.", "given": "den Volf prof. dr Aleksandra Eva Sara mlađi", "family": "van", "ambiguities": ["PARTICLE_OR_GIVEN"], "initials": "d. V. p. d. A. E. S. m. v."} +{"name": "van den Volf prof. dr Aleksandra Eva Sara mlađi, dipl. inž.", "order": "FAMILY_FIRST", "title": "dipl. inž.", "given": "den Volf prof. dr Aleksandra Eva Sara", "family": "van", "suffix": "mlađi", "ambiguities": ["PARTICLE_OR_GIVEN"], "initials": "d. V. p. d. A. E. S. v."} {"name": "van den Volfa Laima Kristīne", "order": "FAMILY_FIRST", "given": "den Volfa Laima Kristīne", "family": "van", "ambiguities": ["PARTICLE_OR_GIVEN"], "initials": "d. V. L. K. v."} {"name": "van den Volfa Laima Kristīne Eva Sofija", "order": "FAMILY_FIRST", "given": "den Volfa Laima Kristīne Eva Sofija", "family": "van", "ambiguities": ["PARTICLE_OR_GIVEN"], "initials": "d. V. L. K. E. S. v."} {"name": "van den Wolf Becker Schmidt Farf. Dr. Ada Cornelia Eva Sophia M.D. Ph.D.", "order": "FAMILY_FIRST", "given": "den Wolf Becker Schmidt Farf. Dr. Ada Cornelia Eva Sophia", "family": "van", "suffix": "M.D. Ph.D.", "ambiguities": ["PARTICLE_OR_GIVEN"], "initials": "d. W. B. S. F. D. A. C. E. S. v."} @@ -2341,6 +2347,7 @@ {"name": "Єва Марія Шевченко", "given": "Єва", "middle": "Марія", "family": "Шевченко", "initials": "Є. М. Ш."} {"name": "Ірына Алейнік", "given": "Ірына", "family": "Алейнік", "initials": "І. А."} {"name": "Јован Драган Поповић", "given": "Јован", "middle": "Драган", "family": "Поповић", "initials": "Ј. Д. П."} +{"name": "Агриппина Младшая", "given": "Агриппина", "suffix": "Младшая", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "А."} {"name": "Ада Карнэлія Сесар Марцін фон Бруль д-р", "given": "Ада", "middle": "Карнэлія Сесар Марцін фон Бруль", "family": "д-р", "initials": "А. К. С. М. ф. Б. д."} {"name": "Ада Корнелия фон Брюль Джуниор, тіс дәрігері", "given": "тіс", "middle": "дәрігері", "family": "Ада Корнелия фон Брюль Джуниор", "initials": "т. д. А. К. ф. Б. Д."} {"name": "Адлер Ирене", "order": "FAMILY_FIRST", "given": "Ирене", "family": "Адлер", "initials": "И. А."} @@ -2376,6 +2383,7 @@ {"name": "Волф, Ана Марија Цезар Мартин ван ден", "given": "Ана", "middle": "Марија Цезар Мартин ван ден", "family": "Волф", "initials": "А. М. Ц. М. в. д. В."} {"name": "Вольга Караліна Равенская", "given": "Вольга", "middle": "Караліна", "family": "Равенская", "initials": "В. К. Р."} {"name": "Вустер Бертрам Вилберфорс Ҳенру Роберт", "order": "FAMILY_FIRST", "given": "Бертрам", "middle": "Вилберфорс Ҳенру Роберт", "family": "Вустер", "initials": "Б. В. Ҳ. Р. В."} +{"name": "Гай Плиний Старший Иванов", "given": "Гай", "middle": "Плиний Старший", "family": "Иванов", "initials": "Г. П. С. И."} {"name": "Джон", "given": "Джон", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "Д."} {"name": "Джон", "order": "FAMILY_FIRST", "family": "Джон", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "Д."} {"name": "Джоунс, Зара Мери", "given": "Зара", "middle": "Мери", "family": "Джоунс", "initials": "З. М. Д."} @@ -2431,7 +2439,7 @@ {"name": "Марковић Ирена", "order": "FAMILY_FIRST", "given": "Ирена", "family": "Марковић", "initials": "И. М."} {"name": "Марковић, Ирена", "given": "Ирена", "family": "Марковић", "initials": "И. М."} {"name": "Мельник Анна", "order": "FAMILY_FIRST", "given": "Анна", "family": "Мельник", "initials": "А. М."} -{"name": "Миленковић др Слађана Вера млађи, дипл. инж.", "order": "FAMILY_FIRST", "title": "дипл. инж.", "given": "др", "middle": "Слађана Вера млађи", "family": "Миленковић", "initials": "д. С. В. м. М."} +{"name": "Миленковић др Слађана Вера млађи, дипл. инж.", "order": "FAMILY_FIRST", "title": "дипл. инж.", "given": "др", "middle": "Слађана Вера", "family": "Миленковић", "suffix": "млађи", "initials": "д. С. В. М."} {"name": "Миленковић, Слађана Вера", "given": "Слађана", "middle": "Вера", "family": "Миленковић", "initials": "С. В. М."} {"name": "Милер Кете", "order": "FAMILY_FIRST", "given": "Кете", "family": "Милер", "initials": "К. М."} {"name": "Милер, Кете", "given": "Кете", "family": "Милер", "initials": "К. М."} @@ -2517,7 +2525,7 @@ {"name": "Юрій", "order": "FAMILY_FIRST", "family": "Юрій", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "Ю."} {"name": "Яҡупова Ирина", "order": "FAMILY_FIRST", "given": "Ирина", "family": "Яҡупова", "initials": "И. Я."} {"name": "Яҡупова, Ирина", "given": "Ирина", "family": "Яҡупова", "initials": "И. Я."} -{"name": "ван ден Волф проф. др Александра Ева Сара млађи, дипл. инж.", "order": "FAMILY_FIRST", "title": "дипл. инж.", "given": "ден", "middle": "Волф проф. др Александра Ева Сара млађи", "family": "ван", "initials": "д. В. п. д. А. Е. С. м. в."} +{"name": "ван ден Волф проф. др Александра Ева Сара млађи, дипл. инж.", "order": "FAMILY_FIRST", "title": "дипл. инж.", "given": "ден", "middle": "Волф проф. др Александра Ева Сара", "family": "ван", "suffix": "млађи", "initials": "д. В. п. д. А. Е. С. в."} {"name": "ван ден Вольф Алтынай Сулайманова Асель Сартбаева Илимдин кандидаты", "order": "FAMILY_FIRST", "given": "ден", "middle": "Вольф Алтынай Сулайманова Асель Сартбаева Илимдин кандидаты", "family": "ван", "initials": "д. В. А. С. А. С. И. к. в."} {"name": "ван ден Вольф Бекер Шмидт, Проф. Доктор. Алтынай Сулайманова Асель Сартбаева Илимдин кандидаты", "title": "Проф. Доктор.", "given": "Алтынай", "middle": "Сулайманова Асель Сартбаева Илимдин кандидаты", "family": "ван ден Вольф Бекер Шмидт", "initials": "А. С. А. С. И. к. в. д. В. Б. Ш."} {"name": "вон Брухи Гонзалес Доминго Проф. Док. Ада Корнелиа Сезар Мартин", "order": "FAMILY_FIRST", "given": "Брухи", "middle": "Гонзалес Доминго Проф. Док. Ада Корнелиа Сезар Мартин", "family": "вон", "initials": "Б. Г. Д. П. Д. А. К. С. М. в."} From 222ddf97f44d42a46a144a861a5ea4d4cc22a245 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Wed, 14 Oct 2026 00:51:58 -0700 Subject: [PATCH 2/2] Record the script-drawn run-start refusal as accepted (#661 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The second review round found the run-start bullet presenting the refusal of Cyrillic and Devanagari generational words as what S2's "Latin" settles, though S2's stated reason for the refusal (an honorific carries no credential mark) is not true of them. Serbian splits by script: "Ana Horvat stariji Jones" reads suffix 'stariji Jones' and "Ана Хорват старији Џонс" family Џонс. Accepted under rules.md#S2 with that pair as its examples, since only a name word after the generational word reaches it; settling it is #679. Also scope the measurement bullet: the run-start change moves no name the corpus held before, its one mover being S2's own new example. Co-Authored-By: Claude Opus 5.5 --- docs/design/decisions.md | 4 ++-- docs/design/rules.md | 8 ++++++++ tools/differential/corpus_rules.jsonl | 2 ++ tools/differential/snapshot.jsonl | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/design/decisions.md b/docs/design/decisions.md index 50eff4a0..eacdcb80 100644 --- a/docs/design/decisions.md +++ b/docs/design/decisions.md @@ -826,8 +826,8 @@ Closes #661. 31 spelled-out generational words join SUFFIX_WORDS, and one predic - **CLDR is corroboration, not a source.** Its person-name samples write mlađi/млађи (hr, sr — on a woman's name too), младши/младша (bg), ifj. (hu) and जूनियर (hi), but its `generation` field also holds translations of the field LABEL rather than of the word: isizukulwane (zu), जेनेरेसन (ne), نسل (ps) and jẹnereṣọn (pcm) all mean "generation". A word is not added because CLDR has it. The wider roster (fils, yngri, الابن and the rest) is open at [#679](https://github.com/derek73/python-nameparser/issues/679), to be vetted word by word the same way. - **Excluded, each with its reason.** kıdemli (tr): a rank word (Kıdemli onbaşı); Turkish writes Jr. or a parenthesis, (oğul). sóisearach/sinsearach (ga): rank words too; Irish writes Óg and Mór, which are also inside given names (Seán Óg Ó hAilpín), so they are a separate decision and not taken here. ifj./id. (hu): written BEFORE the name (ifj. Kovács János, which H2's shape rule already reads as a title), and a bare `id` would make any trailing ID a suffix. млада (bg feminine of млади): Mlada is a borne given name, and under a family-first order the given name is the trailing word. ml./мл./st.: the abbreviations stay under the #269 deferral in suffixes.py, st. colliding with Saint besides. - **Spaced only, and what that leaves.** bg, ru and uk write a modern Jr. hyphen-glued (Теодор Рузвелт-младши, Дональд Трамп-молодший), which no whole-token set reaches; for those three the gain is mainly the historical the Elder/the Younger names, written title case (Младший), which the lowercase fold matches. -- **The #602 run start reads the letters' script, not ASCII (Derek, 2026-10-14).** rules.md#S2 lets "an unambiguous Latin suffix word of two or more letters" start the run, and `_pieces.starts_a_credential_run` had implemented Latin as `letters.isascii()`, exact while no accented Latin word was suffix vocabulary. This entry adds the first ones (mlađi, mlađa, mladší, starší), so without a change `Pieter Bruegel stariji Jones` would read suffix 'stariji Jones' while `Antonio Pavlović mlađi Jones` kept family Jones -- one language, one construction, split by the `đ`. The test now asks a non-ASCII word whether every letter's Unicode name is a LATIN one; the ASCII test stays first, so an ASCII word pays no frame for it. Cyrillic and Devanagari generational words still do not start the run, which is the statement's "Latin" rather than a gap, and is what refuses the non-Latin honorifics (`Taro Tanaka さん Jones` keeps family Jones). The first draft of this entry credited the ASCII test to S2's honorific exclusion instead and proposed deleting it, on the evidence that deleting it moved no test and no corpus name. The design-docs review found the attribution wrong and the evidence empty: nothing pinned the test, and deleting it reads `Taro Tanaka さん Jones` as suffix 'さん Jones' and `Гай Плиний Старший Иванов` as suffix 'Старший Иванов'. Both are rules.md#S2 boundary examples now. #678 was filed on the first draft and corrected. -- **Measurement (2026-10-14).** Six corpus names move, all in corpus_cldr.jsonl and all the CLDR hr/sr renderings carrying mlađi/млађи, each gaining suffix mlađi (or млађи) from a name field; cldr_agreement records 14 changes, every one toward CLDR's fields. The bg and hi renderings do not move because those locales' formats drop the generation field. The run-start change moves no corpus name: of the 161 rows of tools/differential/cldr_fields.jsonl whose `generation` value `cldr_agreement.occurs()` in the text, none writing a non-ASCII generation word has a name word after it in the same part (two write given names after `II` across a comma, which ends the run's part). Recompute: `uv run python tools/differential/snapshot.py --since `. +- **The #602 run start reads the letters' script, not ASCII (Derek chose the Latin-script test, 2026-10-14).** rules.md#S2 lets "an unambiguous Latin suffix word of two or more letters" start the run, and `_pieces.starts_a_credential_run` had implemented Latin as `letters.isascii()`, exact while no accented Latin word was suffix vocabulary. This entry adds the first ones (mlađi, mlađa, mladší, starší), so without a change `Pieter Bruegel stariji Jones` would read suffix 'stariji Jones' while `Antonio Pavlović mlađi Jones` kept family Jones -- one language, one construction, split by the `đ`. The test now asks a non-ASCII word whether every letter's Unicode name is a LATIN one; the ASCII test stays first, so an ASCII word pays no frame for it. The same script test is what refuses the non-Latin honorifics (`Taro Tanaka さん Jones` keeps family Jones), and it refuses this entry's Cyrillic and Devanagari generational words with them, though the reason S2 gives for the refusal -- an honorific carries no credential mark -- is not true of them. So Serbian splits by script: `Ana Horvat stariji Jones` reads suffix 'stariji Jones' and `Ана Хорват старији Џонс` family Џонс, and after a comma C1 asks the same predicate, so `Ана Хорват, старији Џонс` reads given 'старији'. ACCEPTED for this entry (Derek, 2026-10-14) and stated at rules.md#S2's Accepted clause: the vocabulary does not mark which suffix words are honorifics, a script test is the only line available, and only a name word written after the generational word reaches it. Settling it is open at [#679](https://github.com/derek73/python-nameparser/issues/679), where the non-Latin generational words multiply. The first draft of this entry credited the ASCII test to S2's honorific exclusion instead and proposed deleting it, on the evidence that deleting it moved no test and no corpus name. The design-docs review found the attribution wrong and the evidence empty: nothing pinned the test, and deleting it reads `Taro Tanaka さん Jones` as suffix 'さん Jones' and `Гай Плиний Старший Иванов` as suffix 'Старший Иванов'. Both are rules.md#S2 boundary examples now. #678 was filed on the first draft and corrected. +- **Measurement (2026-10-14).** Six corpus names move, all in corpus_cldr.jsonl and all the CLDR hr/sr renderings carrying mlađi/млађи, each gaining suffix mlađi (or млађи) from a name field; cldr_agreement records 14 changes, every one toward CLDR's fields. The bg and hi renderings do not move because those locales' formats drop the generation field. The run-start change moves no name the corpus held before this entry; its one mover is S2's own `Antonio Pavlović mlađi Jones`. Of the 161 rows of tools/differential/cldr_fields.jsonl whose `generation` value `cldr_agreement.occurs()` in the text, none writing a non-ASCII generation word has a name word after it in the same part (two write given names after `II` across a comma, which ends the run's part). Recompute: `uv run python tools/differential/snapshot.py --since `. ### cjk-comma-demotion — the script shapes are pure, the wrappers are tolerated (2026-09-01, #469) diff --git a/docs/design/rules.md b/docs/design/rules.md index 8d0d0948..6b1f3362 100644 --- a/docs/design/rules.md +++ b/docs/design/rules.md @@ -1362,6 +1362,14 @@ S2. Rationale: generational suffixes and credentials are recognized and unchanged (decisions.md#v1-xfail-triage: `king` stays a title, for the addressing forms). "Dr Jr" → suffix="Jr" + Accepted: the non-Latin refusal is drawn by script, because the + vocabulary does not say which suffix words are honorifics, so a + generational word in another script does not start the run + either, and Serbian reads by the script it is written in. Only a + name word written after the generational word reaches the + difference (decisions.md#generational-words, open at #679). + "Ana Horvat stariji Jones" → suffix="stariji Jones" + "Ана Хорват старији Џонс" → family="Џонс" · boundary history: decisions.md#S2 · interacts: H1, H2, H3, H5, C1, C2, S3, P1, P2, P3, P5, P6, M2 · implemented: nameparser/_pipeline/_assign.py, 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 diff --git a/tools/differential/corpus_rules.jsonl b/tools/differential/corpus_rules.jsonl index bd526d74..6c5dd75c 100644 --- a/tools/differential/corpus_rules.jsonl +++ b/tools/differential/corpus_rules.jsonl @@ -11,6 +11,7 @@ "Ali Ahmad Vali oglu" "Ali Ahmad Vali oglu Jr." "Ali Ahmad oglu" +"Ana Horvat stariji Jones" "Andrew" "Andrew (Andy) Perkins" "Andrew Perkins (Andy)" @@ -521,6 +522,7 @@ "wang meng" "Ó. Pérez" "Агриппина Младшая" +"Ана Хорват старији Џонс" "Гай Плиний Старший Иванов" "Иван Петрович Абрамович" "Сидоров Иван Петрович" diff --git a/tools/differential/snapshot.jsonl b/tools/differential/snapshot.jsonl index 5c404602..1c3b8bb3 100644 --- a/tools/differential/snapshot.jsonl +++ b/tools/differential/snapshot.jsonl @@ -95,6 +95,7 @@ {"name": "Amy E Maid", "given": "Amy", "middle": "E", "family": "Maid", "initials": "A. E. M."} {"name": "Amy E. Maid", "given": "Amy", "middle": "E.", "family": "Maid", "initials": "A. E. M."} {"name": "An tOll. An Dr. Brigid Aoibhe Sophia de Bhulbh", "given": "An", "middle": "tOll. An Dr. Brigid Aoibhe Sophia", "family": "de Bhulbh", "initials": "A. t. A. D. B. A. S. B."} +{"name": "Ana Horvat stariji Jones", "given": "Ana", "family": "Horvat", "suffix": "stariji Jones", "ambiguities": ["SUFFIX_OR_NAME"], "initials": "A. H."} {"name": "Ana Marija Vera Kovač", "given": "Ana", "middle": "Marija Vera", "family": "Kovač", "initials": "A. M. V. K."} {"name": "Ana Torres", "given": "Ana", "family": "Torres", "initials": "A. T."} {"name": "Andersen Irene", "order": "FAMILY_FIRST", "given": "Irene", "family": "Andersen", "initials": "I. A."} @@ -2359,6 +2360,7 @@ {"name": "Амосова Ирина", "order": "FAMILY_FIRST", "given": "Ирина", "family": "Амосова", "initials": "И. А."} {"name": "Амосова, Ирина", "given": "Ирина", "family": "Амосова", "initials": "И. А."} {"name": "Ана Иванова Димитрова", "given": "Ана", "middle": "Иванова", "family": "Димитрова", "initials": "А. И. Д."} +{"name": "Ана Хорват старији Џонс", "given": "Ана", "middle": "Хорват старији", "family": "Џонс", "initials": "А. Х. с. Џ."} {"name": "Анна Мельник", "given": "Анна", "family": "Мельник", "initials": "А. М."} {"name": "Аталан", "given": "Аталан", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "А."} {"name": "Аталан", "order": "FAMILY_FIRST", "family": "Аталан", "ambiguities": ["GIVEN_OR_FAMILY"], "initials": "А."}