Skip to content

Commit 4ef0ee2

Browse files
derek73claude
andcommitted
fix(#540): the toolkit review round
Three reviewers read the pull request (code, tests, comments); this is what they found. A credential run behind a suffix comma that ends in meng or lac is no longer wholly suffix-shaped, so C1 reads the comma as a family comma and the run's words fall into the post-comma name slots: "John Smith, PhD MEng" reads given 'PhD', middle 'MEng', family 'John Smith', and no case form saves it. It is the path "john smith, phd ma" already takes; the release note and decisions.md said "a title-shaped credential reads as a title", which was one corner of it. Two rows pin it, a union-fields ledger rule classifies it at every baseline, and the note's workaround now states what was measured: a lone credential, or a second comma before the run, keeps the credential reading; capitals do not. "Wang M.Eng." reads family 'M.Eng.': S2's period gate counts a member as unambiguous only written one period per letter, and MEng and LAc are the first members whose conventional dotted spelling is chunked. DECIDED (Derek, 2026-09-25): accepted and recorded rather than widening the gate; the question goes to the follow-up issue with the comma re-read. "Nguyen Van Lac" (family 'Van Lac', the gain, moving against 1.4.0 too) and "John Smith MEng PhD" (the cost with a credential behind it) were unpinned and now have rows and rules; the family rule gains a superstring probe, since dropping its anchors had passed every guard. Two claims were false on the wheels: "every release gave suffix 'PhD MEng'" (through 2.2 the joiner was a comma, 'PhD, MEng'), and the dated Excluded bullet's present-tense "Both read as a suffix", whose supersession note now follows it. suffixes.py's comment gains S2's third clause (not Title-case in a mixed-case name), its docstring stops enumerating readers, cases.py's roster comment points at the constant, and AGENTS.md's _initials sentence says what compare.py does: roles AND ambiguity kinds must agree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 24d528e commit 4ef0ee2

14 files changed

Lines changed: 598 additions & 119 deletions

‎AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

‎docs/design/decisions.md‎

Lines changed: 3 additions & 3 deletions
Large diffs are not rendered by default.

‎docs/design/rules.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,8 @@ S2. Rationale: generational suffixes and credentials are recognized
10431043
"tran lac" → family="lac"
10441044
"john smith meng" → suffix="meng"
10451045
"john smith MEng" → family="MEng"
1046+
"Nguyen Van Lac" → family="Van Lac"
1047+
"Wang M.Eng." → family="M.Eng."
10461048
"Smith, MA" → suffix="MA"
10471049
"Smith, Ma" → given="Ma"
10481050
"Doe, John MA" → suffix="MA"

‎docs/release_log.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Release Log
1212

1313
- **Fix a credential acronym that is also a surname being read by position alone.** ``HumanName("Jack MA")`` gives suffix ``MA`` where 2.0 through 2.3 gave last ``MA``, and ``John Smith Ma`` gives last ``Ma`` where they gave suffix ``Ma``. In a name written in more than one case, an ambiguous acronym written in capitals is written the way a credential is written and is read as one even where removing it leaves no surname; one written in any other cased form that is not wholly lower is written the way a surname is written and stays one even where there are words to spare (``John Smith ma`` and ``John Smith ed`` -- all lower, no contrast -- give suffix ``ma``/``ed`` instead). A name written wholly in one case says nothing either way and keeps the reading it had: ``JOHN SMITH MA`` is still a credential, ``ANH DO`` still a surname, ``jack ma`` still a surname. The same reading reaches the comma forms, where the words-to-spare count is now a count of NAME words: ``Smith, MA`` gives last ``Smith``, suffix ``MA``; ``Smith Jr., MA`` keeps last ``Smith``; and ``John Smith, MA``, ``John Smith, Ed``, ``john smith, ma`` and ``JOHN SMITH, MA`` all give a suffix again, which is what 1.4.0 read and 2.0 through 2.3 did not. ``Jack Ma`` and ``Anh Do`` are unchanged. The LEAN is inert on a caseless script, but the comma count above is not -- it asks name-word count, not case -- so ``마틴 킹, MA`` and ``田中 太郎, MA`` also give a suffix again (1.4.0 parity on the suffix, two pre-comma name words each) while the single-token ``毛泽东, MA`` does not move, having no case to write a contrast in either way. See the ``S2`` entry of ``docs/design/decisions.md`` (closes #289)
1414

15-
- **Fix a bare trailing Meng or Lac being read as a credential and losing the family name: meng and lac are now acronyms that are also ordinary names.** ``HumanName("wang meng")`` gives first ``wang``, last ``meng``, and ``parse()`` reports a suffix-or-name ambiguity, where every release from 2.0.0 through 2.3.0 gave suffix ``meng`` and no last name; 1.4.0 read last ``meng``, so this is 1.4.0's answer plus the flag. ``li meng`` and ``tran lac`` move the same way, ``Wang, Meng`` gives first ``Meng``, last ``Wang`` again, and ``Parser(policy=Policy(name_order=FAMILY_FIRST)).parse("Wang Meng")`` gives given ``Meng`` where 2.0.0 through 2.3.0 gave family ``Wang``, suffix ``Meng`` and no given name. With a full name in front the credential reading stays: ``john smith meng`` and ``nguyen van lac`` keep suffix ``meng`` and ``lac``, now flagged. But a Title-case ``Nguyen Van Lac`` gives last ``Van Lac`` where every release gave last ``Van``, suffix ``Lac``. The cost is the marking's own, and it falls on the conventional spellings: a ``MEng`` or ``LAc`` written that way, in a name written in more than one case, is read the way ``John Smith Ma`` is (above), so ``John Smith MEng`` gives middle ``Smith``, last ``MEng``, where every release gave suffix ``MEng``. A credential run ending in one of them goes the same way: ``Mary Jones PhD MEng`` gives middle ``Jones PhD``, last ``MEng``, where every release gave suffix ``PhD MEng`` (``rules.md#S2``'s declined pick stops the walk, the cost ``Doe, John MA Ma`` already pays). After a comma ``Smith, MEng`` and ``Smith, meng`` give first ``MEng`` and ``meng`` (1.4.0's reading, not the suffix 2.0 through 2.3 gave) and ``Smith, John MEng`` gives middle ``MEng`` (every release gave suffix ``MEng``), and a bracketed ``John Smith (MEng)`` falls through to nickname, where every release gave suffix ``MEng``. After a family comma a title-shaped credential in front of it is read as a title with no report: ``Jane Doe, MS LAc`` gives title ``MS``, first ``LAc``, last ``Jane Doe``, where every release gave suffix ``MS LAc``; the same path already reads ``Jane Doe, MS Ma`` that way. Write ``MENG``, or put a lone credential after a comma behind a full name (``John Smith, MEng``), to keep the credential reading. Meng is a common Chinese surname and given name, Lac a Vietnamese given name (``Nguyen Van Lac``) and a French surname; see the ``suffix-acronym-collisions`` entry of ``docs/design/decisions.md`` (closes #540)
15+
- **Fix a bare trailing Meng or Lac being read as a credential and losing the family name: meng and lac are now acronyms that are also ordinary names.** ``HumanName("wang meng")`` gives first ``wang``, last ``meng``, and ``parse()`` reports a suffix-or-name ambiguity, where every release from 2.0.0 through 2.3.0 gave suffix ``meng`` and no last name; 1.4.0 read last ``meng``, so this is 1.4.0's answer plus the flag. ``li meng`` and ``tran lac`` move the same way, ``Wang, Meng`` gives first ``Meng``, last ``Wang`` again, and ``Parser(policy=Policy(name_order=FAMILY_FIRST)).parse("Wang Meng")`` gives given ``Meng`` where 2.0.0 through 2.3.0 gave family ``Wang``, suffix ``Meng`` and no given name. With a full name in front the credential reading stays: ``john smith meng`` and ``nguyen van lac`` keep suffix ``meng`` and ``lac``, now flagged. But a Title-case ``Nguyen Van Lac`` gives last ``Van Lac`` where every release gave last ``Van``, suffix ``Lac``. The cost is the marking's own, and it falls on the conventional spellings: a ``MEng`` or ``LAc`` written that way, in a name written in more than one case, is read the way ``John Smith Ma`` is (above), so ``John Smith MEng`` gives middle ``Smith``, last ``MEng``, where every release gave suffix ``MEng``. A credential run ending in one of them goes the same way: ``Mary Jones PhD MEng`` gives middle ``Jones PhD``, last ``MEng``, where every release read the whole run as a suffix (``PhD, MEng`` through 2.2, ``PhD MEng`` in 2.3); ``John Smith MEng PhD`` gives middle ``Smith``, last ``MEng``, suffix ``PhD`` (``rules.md#S2``'s declined pick stops the walk, the cost ``Doe, John MA Ma`` already pays). ``Wang M.Eng.`` gives last ``M.Eng.``, where 2.0 through 2.3 gave suffix ``M.Eng.``: the period gate counts a member as unambiguous only written one period per letter, and ``M.Eng.`` is chunked; ``John Smith M.Eng.`` keeps the suffix. After a comma ``Smith, MEng`` and ``Smith, meng`` give first ``MEng`` and ``meng`` (1.4.0's reading, not the suffix 2.0 through 2.3 gave) and ``Smith, John MEng`` gives middle ``MEng`` (every release gave suffix ``MEng``), and a bracketed ``John Smith (MEng)`` falls through to nickname, where every release gave suffix ``MEng``. A credential run after a comma whose last word is one of them re-reads the comma as a family comma, whatever its letter case: ``John Smith, PhD MEng`` gives first ``PhD``, middle ``MEng``, last ``John Smith`` and ``john smith, phd meng`` gives first ``phd``, last ``john smith``, suffix ``meng``, where every release read the run as a suffix; a title-listed credential in front reads as a title instead (``Jane Doe, MS LAc`` gives title ``MS``, first ``LAc``, unflagged). The path is the one ``john smith, phd ma`` already takes. A lone credential after a comma behind a full name (``John Smith, MEng``) keeps the credential reading, and so does a second comma before a run that ends in one (``John Smith, PhD, MEng`` keeps suffix ``PhD, MEng``); writing it in capitals does not (``JOHN SMITH, PHD MENG`` still re-reads the comma). Meng is a common Chinese surname and given name, Lac a Vietnamese given name (``Nguyen Van Lac``) and a French surname; see the ``suffix-acronym-collisions`` entry of ``docs/design/decisions.md`` (closes #540)
1616

1717
- **New Policy field unlisted_dotted_suffixes, on by default: a dotted acronym nobody has listed is read by position.** ``HumanName("John Smith X.Y.Z.")`` gives suffix ``X.Y.Z.`` where every release gave last ``X.Y.Z.``, while ``Jack X.Y.Z.`` keeps its surname, the same words-to-spare rule a listed acronym takes -- and both readings are reported. Case is irrelevant here: the periods are the signal, so ``john smith x.y.z.`` reads the same way. Words the vocabulary does know are untouched (``M.A.``, ``Ph.D.``, ``A.B.C.``), a single trailing period is still not this shape (``John Smith Xyz.`` keeps last ``Xyz.``), and a dotted run at the FRONT of a name is untouched (``J.R.R. Tolkien``). One accident retires with it: a dotted word whose only vocabulary matches were SINGLE ASCII CHARACTERS -- the roman numerals the suffix list holds, and the lone digit ``2`` -- was reading as a generational suffix, so ``Jack X.Y.I.`` gives last ``X.Y.I.`` again, as 1.4.0 read it, while ``Msc.Ed.``, ``JD.CPA`` and ``Lt.Gov.`` are unchanged. The digit is why a dotted VERSION STRING moves with them and moves SILENTLY: ``John Smith 1.4.2`` gives last ``1.4.2`` where 2.3 gave suffix ``1.4.2``, and ``John Smith, 1.4.2`` gives first ``1.4.2``, last ``John Smith``. Such a token reports nothing at any policy -- it is no acronym either, the shape reading wanting every chunk alphabetic -- and a version string read as a credential was the same accident this retirement removes. That retirement is NOT behind this switch and stands either way -- setting it to ``False`` reads an unlisted dotted word as name material by position instead (``John Smith X.Y.Z.`` keeps last ``X.Y.Z.``), the pre-2.4 reading for THAT half alone. See the ``S2`` and ``suffix-acronym-collisions`` entries of ``docs/design/decisions.md`` (closes #516)
1818

‎nameparser/config/suffixes.py‎

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -211,14 +211,11 @@
211211
"""
212212
SUFFIX_ACRONYMS_AMBIGUOUS = frozenset({
213213
# Suffix acronyms that also commonly work as given-name nicknames on
214-
# their own (e.g. "Ed", "JD"). Two readers in 2.x, not the single v1
215-
# one this comment used to name: _extract._suffix_shaped, deciding
216-
# whether parenthesized/quoted content is a nickname or a suffix
217-
# (content matching one of these stays a nickname, the more common
218-
# reading in ambiguous, delimiter-only context), and _vocab's
219-
# suffix_as_written, which excludes the ambiguous subset from plain
220-
# acronym membership so the period gate below is not dead code.
221-
# _classify also tags membership as "vocab:suffix-ambiguous".
214+
# their own (e.g. "Ed", "JD"). Read wherever the pipeline decides an
215+
# acronym's bare reading (rules.md#S2) and by the delimited-content
216+
# escape (content matching one of these stays a nickname, the more
217+
# common reading in ambiguous, delimiter-only context). _classify
218+
# also tags membership as "vocab:suffix-ambiguous".
222219
#
223220
# When adding a new entry to SUFFIX_ACRONYMS, also add it here only if
224221
# the exact letter sequence could plausibly be someone's name on its
@@ -227,9 +224,11 @@
227224
# (e.g. 'mba', 'cpa', 'phd') don't need an entry. In 2.0 this set
228225
# also gates bare recognition: a bare ambiguous acronym reads as
229226
# the credential only with words to spare in front of it, or
230-
# written in capitals inside a mixed-case name, and the parse
231-
# reports the fork where it decides one (rules.md#S2), so
232-
# 'Jack Ma' keeps its family name.
227+
# written in capitals inside a mixed-case name; inside a
228+
# mixed-case name any other cased form that is not wholly lower
229+
# ('Ma', 'MEng') reads as the name even with words to spare --
230+
# rules.md#S2. The parse reports the fork where it decides one,
231+
# so 'Jack Ma' keeps its family name.
233232
#
234233
# The other half of the criterion, added 2026-09-07 with #342.
235234
# Being borne at all is only the entry ticket; what decides among
@@ -304,8 +303,7 @@
304303
Acronym suffixes from SUFFIX_ACRONYMS that also plausibly collide with a
305304
word borne as a name -- a given name, nickname or surname. Not a
306305
partition of SUFFIX_ACRONYMS -- a small, standalone exception list, read
307-
by the delimited-content escape in ``_pipeline/_extract.py`` and by
308-
``_pipeline/_vocab.py``'s period gate.
306+
wherever the pipeline decides an acronym's bare reading (rules.md#S2).
309307
310308
"""
311309
SUFFIX_ACRONYMS = frozenset({

‎tests/v2/cases.py‎

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,8 +491,8 @@ def _check_cjk_shape_purity(self) -> None:
491491
# decisions.md#suffix-acronym-collisions and it compares
492492
# FREQUENCIES: how common the word is as a borne name in the
493493
# trailing position against how common it is as a credential.
494-
# Rough balance earns the ambiguous marking (ba, do, ed, jd, ma,
495-
# and since #540 lac and meng);
494+
# Rough balance earns the ambiguous marking (the members of
495+
# SUFFIX_ACRONYMS_AMBIGUOUS; the frozenset is the roster);
496496
# the name reading dominating REMOVES the entry, a caller adding
497497
# it back with Lexicon.default().add(suffix_acronyms={"cha"});
498498
# the credential dominating leaves it unambiguous. Length is a
@@ -696,6 +696,68 @@ def _check_cjk_shape_purity(self) -> None:
696696
"it as a middle name. 1.4.0 read given 'MEng' too, so "
697697
"this row also restores 1.4.0's reading",
698698
shape=2),
699+
Case("comma_credential_run_ending_in_meng_re_reads_the_comma",
700+
"John Smith, PhD MEng",
701+
{"given": "PhD", "middle": "MEng", "family": "John Smith"},
702+
classification="fix(#540)",
703+
ambiguities=("suffix-or-name",),
704+
notes="the run behind a suffix comma is no longer wholly "
705+
"suffix-shaped once its last word is a bare ambiguous "
706+
"member, so C1 reads the comma as a family comma and "
707+
"the first credential as the given name. Every "
708+
"release read suffix 'PhD MEng' -- the pre-existing "
709+
"'john smith, phd ma' path, which #540 routes two "
710+
"more words into. A follow-up issue is to be filed",
711+
shape=3),
712+
Case("comma_lower_credential_run_ending_in_meng_re_reads_the_comma",
713+
"john smith, phd meng",
714+
{"given": "phd", "family": "john smith", "suffix": "meng"},
715+
classification="fix(#540)",
716+
ambiguities=("suffix-or-name",),
717+
notes="the same re-read in one case -- the count then "
718+
"peels 'meng' with a word to spare, so the writing's "
719+
"case does not save it, and neither does the "
720+
"all-caps 'JOHN SMITH, PHD MENG' (given 'PHD', "
721+
"family 'JOHN SMITH', suffix 'MENG')",
722+
shape=3),
723+
Case("title_case_lac_behind_a_particle_is_the_name",
724+
"Nguyen Van Lac",
725+
{"given": "Nguyen", "family": "Van Lac"},
726+
classification="fix(#540)",
727+
ambiguities=("suffix-or-name",),
728+
notes="the mixed-case name's Title-case member leans "
729+
"'name' with words to spare (S2), the gain for a "
730+
"Vietnamese name; every release from 1.4.0 read "
731+
"family 'Van', suffix 'Lac'",
732+
shape=1),
733+
Case("the_accepted_cost_survives_a_credential_behind_it",
734+
"John Smith MEng PhD",
735+
{"given": "John", "middle": "Smith", "family": "MEng",
736+
"suffix": "PhD"},
737+
classification="fix(#540)",
738+
ambiguities=("suffix-or-name",),
739+
notes="the declined pick stops the walk, and the "
740+
"unambiguous credential behind it is still peeled; "
741+
"every release read suffix 'MEng PhD' ('MEng, PhD' "
742+
"through 2.2)",
743+
shape=1),
744+
Case("dotted_meng_with_nothing_to_spare_is_the_name",
745+
"Wang M.Eng.",
746+
{"given": "Wang", "family": "M.Eng."},
747+
ambiguities=("suffix-or-name",),
748+
notes="S2's period gate counts a member as unambiguous "
749+
"only written one period per letter ('M.A.'), and "
750+
"MEng and LAc are the first members whose "
751+
"conventional dotted spelling is chunked, so "
752+
"'M.Eng.' with nothing to spare is the family name; "
753+
"accepted and recorded (Derek, 2026-09-25). 1.4.0 "
754+
"read the same family, unflagged, for a different "
755+
"reason -- its two-piece rule (a lone word after the "
756+
"given name is the family), not S2's gate -- so this "
757+
"row is parity. 'John Smith M.Eng.' keeps the suffix "
758+
"by the count and reports; the period-gate question "
759+
"goes to the follow-up issue",
760+
shape=1),
699761
Case("leading_meng_is_a_given_name", "meng li",
700762
{"given": "meng", "family": "li"},
701763
notes="the control: the marking acts only at the slots S2 "

0 commit comments

Comments
 (0)