Skip to content

Commit 75cd103

Browse files
derek73claude
andcommitted
review round: PR #515 findings
Three review agents on the whole PR, plus the maintainer's clarification of the criterion: the decision weighs how common a word is as a borne name against how common it is as a credential -- the name reading dominating removes the entry (rai, cha), rough balance earns the ambiguous marking (ba, the ma shape), the credential dominating keeps it unambiguous. Length is a correlate, not the test; every statement of the criterion now says so. Prose: customize.rst no longer claims an ambiguous marking "only adds a flag" (it moves the comma and bracketed forms, and this PR's own "Smith, BA" is the counterexample); the ba release note states the comma and bracketed costs with their before-values; the wordlist comment stops stating the R.A.I. accident as a rule of removal and carries the ba/se/om/mc/vd reasons where a sweep meets them; AGENTS.md's collision digits are recomputed; rules.md#P6 stops pointing at the emptied Open block. Tests and rosters: Donald Mc is fix(suffix-routing), not parity, and Aishwarya Rai is parity, not fix -- both checked against the 1.4.0 wheel; "Smith, BA" and "John Smith C.H.A." are pinned; _WATCHED_DIFFS gains the two sole-watched arrivals the removal created; the guard roster gains the comma-truncation probes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 31ce529 commit 75cd103

9 files changed

Lines changed: 141 additions & 58 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ The library has two layers: `nameparser/config/` (data) and `nameparser/parser.p
253253

254254
**Design philosophy — positional and language-agnostic.** The parser assigns parts by *position* plus small sets of words that join to neighbors; it never detects language. A name's language can't be reliably inferred from Latin-script transliteration ("Ali" is Arabic or Italian; "Van"/"Della"/"Bin" are first names in some cultures, particles in others), so language-specific rules belong in opt-in `Constants` config, never global defaults. Many "wrong for language X" reports (#133, #150, #130, #85, #103, #146, #83) are irreducible ambiguities — e.g. `de Mesnil` (want last name) vs `Van Johnson` (want first name) are the same `[prefix][word]` shape. Before adding a rule, confirm it doesn't break the opposite case (run the full suite — Portuguese and "Van Johnson" tests are the usual canaries). **The one scoped exception (2.1, #271/#272): script-conditional behavior is permitted exactly where the SCRIPT ITSELF — not statistics about it — determines the convention.** The never-detect-language rule above is about Latin *transliteration*, where the signal genuinely is destroyed; native script is a different question, and it is answered per behavior rather than per script. Five defaults fall out of it, plus a sixth that applies the same not-a-guess standard to specific WORDS rather than to a script (#308's honorific peel, below). Wholly-Han, wholly-Hangul and kana-licensed names read family-first (`Policy.script_orders`) — no language detection needed, because zh and ja both write family-first in native script, so order cannot be misread even though the language is unknowable. Unspaced hangul splits into surname + given name (`config/surnames.py` ships the Korean census list as DEFAULT vocabulary) — nothing but Korean is written in hangul and the surnames are a closed census set, and the vocabulary is self-selecting besides: a hangul entry can only ever match hangul text. Hiragana licenses Japanese (#272) — a name whose characters stay inside Han∪kana while carrying at least one kana cannot be Chinese (the kana rules it out) and is not a transcription (foreign names are transcribed in katakana ALONE, マイケル has no kanji), so 高橋みなみ and 山田 エミ read family-first too; mechanically they resolve to the HIRAGANA entry, the license's carrier key. PURE katakana is excluded and keeps the positional default: マイケル・ジャクソン is a transcribed foreign name in its source order. And the 间隔号 U+00B7 (#298) is the transcription marker for scripts that HAVE no transcription script: a name it divides (威廉·莎士比亚 — flanked by classified characters on both sides, so Catalan's Gal·la is untouched) keeps its source order and never segments — the orthography names the convention, exactly as pure katakana does, with the divider carrying the signal instead of the script. And a listed CJK honorific glued to the END of a name token is split off it (#308) — 田中さん is 田中 plus さん — on the same orthography-settles-it test, narrowed for the glued position: an entry peels only where it can never end a name, so 씨/님/さん/様/先生 peel while 양/군/氏/博士/殿 stay spaced-only (김지양 and 田中博士 are names, and ~90 Japanese surnames end in 殿) and 君 is in NEITHER set (王君 is a complete Chinese name), though its kana spelling くん peels. Like the nakaguro's tokenize-level separation described next, it is reached by neither policy opt-out — but for its own reason: the vocabulary carries the license itself rather than borrowing the script's, so `segment_scripts` has nothing to say about it. Since #312 it also crosses the 间隔号, which still stops the surname split standing right beside it: it answers where a name DIVIDES into surname and given, and the peel never asks that question. Whether it also crosses the FAMILY comma is tolerated rather than settled: the 2026-09-01 demotion (rules.md#W3) narrowed that half from contract to best-effort, since no CJK writing system's own convention puts a comma between family and given at all — so `김, 민준씨` reads today exactly as the spaced `김 민준씨` does (family 김, given 민준, suffix 씨) while the split stands down as before, but that reading is watched on the differential's radar tier rather than pinned as contract. Its site is accordingly the name-bearing segment runs — `segments[:2]` under a family comma, and `segments[0]` as before otherwise, the family comma being the one structure that splits the name itself across two runs, with the honorific as often glued to the given side as to the family. That is the whole reach and nothing past it (`김, 민준 지훈씨` peels; `김, 민준, 지훈씨` and `김,, 민준씨` do not, both landing in a third run), and whether `segments[1]` is name text at all is now ASKED rather than inferred from the structure — `segment` does not guarantee it, since a one-word part before the comma reads as FAMILY_COMMA even when the part after it is entirely suffix-shaped, and the peel walking into such a run took `V.` for its site, found no listed tail and abandoned (#319). The question is `segment`'s own suffix-comma predicate, lifted into `_vocab.is_wholly_suffix` so the two stages cannot drift: a wholly suffix-shaped second run is declined and the scan stays in `segments[0]`, so `田中さん, V.` and `田中さん, Ph. D.` give さん up as `田中さん, PhD` always did. The test is necessary but NOT sufficient, and the second condition is not decoration: every honorific tail is also a suffix word, so a glued honorific is itself part of what makes its run read as suffix-shaped, and declining a run that holds the ONLY site loses the peel outright. `segments[0]` must therefore offer a peel site of its own before the second run is declined — `이, J.씨` and `선생님, J.씨` pass the suffix test and are scanned anyway, keeping the pre-#319 reading, while `김민준씨, J.씨` has a site on both sides and peels the person's own 씨 rather than the junk one behind the comma. Uniform in the PEEL, that is — where the credential itself lands is `assign`'s question and still differs by spelling (`V.` → `given`, `PhD` and `Ph. D.` → `suffix`). Not `_is_post_nominal` pluralized: the run predicate says yes both to what the token predicate vetoes (`V.`, `V`, `I` — the class the defect was reported as) and to what the token predicate never sees at all, since `period_joined_vocab` and the delimiter routes are the run predicate's alone (`Msc.Ed.` and `J.씨` reach it that way, and `田中さん, Msc.Ed.` moves with the rest). `Policy(lenient_comma_suffixes=False)` drops this call to the strict token test too — so those three read as name text again and keep the pre-#319 answer, while `Ph. D.` peels under the knob regardless, its merged `phd` passing the strict test. `田中さん, 太郎` is unchanged, and not because of its comma — the honorific there is not at the end of the name, 太郎 is. The nakaguro belongs to the same doctrine but is decided a layer down: U+30FB and its halfwidth twin U+FF65 separate tokens like whitespace, unconditionally and in tokenize, so neither policy opt-out (`script_orders={}`, `segment_scripts=()`) reaches it — the codepoints are CJK-only and appear in no other script's names, which is what licenses a tokenize-level rule where U+00B7 (also the Catalan punt volat, interior to Gal·la) needs the flanked-by-classified-script guard `_tokenize_region` gives it (#298). Han segmentation stays OPT-IN (`locales.ZH` for Chinese, `locales.JA` for Japanese) — a zh surname list corrupts Japanese kanji names, since 高 is a common Chinese surname and 高橋一郎 would split 高+橋一郎 where the correct reading is 高橋+一郎; no surname list divides a kanji name at all, so `locales.JA` activates the stage and a pluggable `Parser(segmenter=...)` does the dividing. Latin-script input is never touched by any of this: "Kim Min-jun" is genuinely order-ambiguous and stays governed by `name_order` and opt-in packs. Before adding a script-conditional rule, work out which of the three it is — certain, certain for this one behavior only, or a statistical guess wearing a script's clothes.
255255

256-
**A constant's membership is a question you may reopen.** Proposing that a word be ADDED, REMOVED or MOVED between vocabulary sets is ordinary design work — a shipped entry is not evidence that anyone judged it. `SUFFIX_ACRONYMS` arrived in `af5bdab` as a bulk Wikipedia import never reviewed against surname collisions: 575 of its 579 alphabetic entries leave `family` empty in `"John <word>"` against four ambiguous-gated exceptions, and `rai` (#342), `ba`, `cha`, `sa`, `se`, `om` and `mc` are all borne as surnames (measured 2026-08-23). When a fix starts to look like new machinery, check the vocabulary first. Criterion: `decisions.md#vocabulary-collisions`, with #360's positional qualifier.
256+
**A constant's membership is a question you may reopen.** Proposing that a word be ADDED, REMOVED or MOVED between vocabulary sets is ordinary design work — a shipped entry is not evidence that anyone judged it. `SUFFIX_ACRONYMS` arrived in `af5bdab` as a bulk Wikipedia import never reviewed against surname collisions: 572 of its 577 alphabetic entries leave `family` empty in `"John <word>"` against five ambiguous-gated exceptions (recomputed 2026-09-07; the fifth is `ba`), and `sa`, `se` and `om` are borne as surnames (measured 2026-08-23), as were `rai`, `cha`, `ba` and `mc` before `decisions.md#suffix-acronym-collisions` decided all four — `rai` and `cha` removed, `ba` marked ambiguous, `mc` left alone as no borne name at all. When a fix starts to look like new machinery, check the vocabulary first. Criterion: `decisions.md#vocabulary-collisions`, with #360's positional qualifier.
257257

258258
**Sweep the forms a change can reach: comma shapes, then `name_order`.** No comma, a FULL name before the comma, and a ONE-WORD name before the comma are three paths, and the third is the miss — #429, #430 and #432 are all that path disagreeing with the full-name path on inputs the full-name path reads correctly. For orders the sweep already exists (`tests/v2/test_cases.py` runs every row under all three) but carries one assertion, R2's family partition, so it checks nothing a new change moves; coverage there has been vacuous before (PR #394's review found the suite passed with `name_order` discarded from grouping). Parse your change's names in each comma shape and each order, and read the ones you did not predict.
259259

docs/customize.rst

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,23 @@ plausible as ordinary name words on their own (an acronym suffix that
190190
doubles as a nickname, a particle that doubles as a given name). They
191191
don't add new vocabulary by themselves; they narrow how an existing
192192
entry is read when it appears alone. If you're not sure whether a word
193-
you're adding is one of these ambiguous cases, mark it ambiguous — a
194-
wrong ambiguous marking only adds a flag to a reading that was going
195-
to be taken anyway, while a wrong unambiguous claim silently picks the
196-
less likely reading and can lose a real person's surname. The other
193+
you're adding is one of these ambiguous cases, weigh how often it is a
194+
name against how often it is the credential. Marking it ambiguous is
195+
not free in either direction: an ambiguous acronym counts as a suffix
196+
only when written with its periods, so the bare spelling reads as a
197+
name and the parse reports the fork, and the comma form moves with it
198+
— ``Smith, BA`` reads first ``BA`` rather than suffix ``BA``, and a
199+
bracketed ``John Smith (BA)`` falls through to nickname parsing. A
200+
wrong unambiguous claim takes the credential reading silently and can
201+
lose a real person's surname. For ``particles_ambiguous`` the default
202+
runs the other way: a particle that is not borne as a given name
203+
belongs in the never-given half, which is where ``mc`` and ``ste``
204+
were moved (#360). The other
197205
direction is to leave the word out of ``suffix_acronyms`` altogether,
198-
which is the right answer for a credential nobody is asking for: a
199-
long, specialized acronym that collides with a common surname earns
200-
removal rather than a marking, and a caller who needs it adds it back
206+
which is the right answer when the name reading is the far more common
207+
one: an acronym whose credential is tenuous or specialized beside a
208+
common surname earns removal rather than a marking, and a caller who
209+
needs it adds it back
201210
with ``Lexicon.default().add(suffix_acronyms={"cha"})``. That is what
202211
the default vocabulary did with ``rai`` and ``cha`` in 2.3. (The same
203212
conservatism is why ``dean`` above isn't in the default vocabulary in

0 commit comments

Comments
 (0)