Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix(titles): swami, guru, baba, lama join the given-name titles
The four renunciate titles were in the TITLES-only block, so
rules.md#H1 familied the one name word behind them: "Swami
Vivekananda" read family 'Vivekananda' with no given name at every
release since 1.4.0. rules.md#H's Background has stated the criteria
since the docs landed -- membership in the given-name-title list
follows HOW THE TITLE ADDRESSES, and whether an empty family is
correct output follows SURNAME RETENTION -- and renunciation answers
both at once. So they move, and "Swami Vivekananda" reads given
'Vivekananda', family ''.

rabbi and imam stay out: those traditions keep surnames, and the
control row surname_retaining_title_keeps_the_family pins "Rabbi
Cohen" at family 'Cohen'. venerable stays in TITLES only -- Buddhist
monastics drop the family name, Anglican archdeacons and Catholic
candidates keep it, and this set has no way to say "sometimes".

Four corpus names move, all from the radar-tier corpus_issues.jsonl
and all the same way: 'Swami Vivekananda', 'Guru Nanak', 'Baba Ramdev'
and 'Lama Zopa'. The boundary holds and is pinned by the ledger's
_MUST_NOT_MATCH probes -- 'Swami Vivekananda Saraswati' and 'Guru
Gobind Singh' keep their family names, H1's fold needing exactly one
name word -- and 'Md Abdul Karim' does not move. TITLES membership is
unchanged: TITLES is GIVEN_NAME_TITLES | {...}, so the words only
changed which side of the union they enter from.

Gate: 364 / 258 / 170 / 32 intentional at 1.4.0 / 2.0.0 / 2.1.0 /
2.2.0, 0 unexplained, 0 radar unclassified.

Closes #346

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
  • Loading branch information
derek73 and claude committed Sep 7, 2026
commit 3263a8d583a0a507eae821cbd4b1a9bfa5b96710
20 changes: 16 additions & 4 deletions nameparser/config/titles.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,33 @@
GIVEN_NAME_TITLES = frozenset({
'aunt',
'auntie',
# #346: the renunciate class -- 'baba' here, and 'guru', 'lama',
# 'swami' in their own alphabetical places below. All four were in
# the TITLES-only block until 2026-09-06. Two separate criteria
# both say yes (rules.md#H Background): the title addresses by the
# GIVEN name, which is what membership here means, and the
# traditions renounce the surname, which is what makes the empty
# family the right output for "Swami Vivekananda". 'rabbi' and
# 'imam' pass the first and fail the second, so they stay out;
# 'venerable' stays in TITLES only, because the traditions using it
# split on surname retention (Buddhist monastics drop the family
# name, Anglican archdeacons keep it) and this set has no way to
# say "sometimes".
'baba',
'brother',
'dame',
'father',
'guru',
'king',
'lama',
'maid',
'master',
'mother',
'pope',
'queen',
'sir',
'sister',
'swami',
'uncle',
'sheikh',
'sheik',
Expand Down Expand Up @@ -142,7 +158,6 @@
'author',
'award-winning',
'ayatollah',
'baba',
'bailiff',
'ballet',
'bandleader',
Expand Down Expand Up @@ -355,7 +370,6 @@
'großfürst',
'group',
'guitarist',
'guru',
'gyani',
'gysgt',
'hajji',
Expand Down Expand Up @@ -400,7 +414,6 @@
'kingdom',
'knowledge',
'lady',
'lama',
'lamido',
'law',
'lawyer',
Expand Down Expand Up @@ -664,7 +677,6 @@
'superior',
'supreme',
'surgeon',
'swami',
'swordbearer',
'sysselmann',
'tax',
Expand Down
25 changes: 25 additions & 0 deletions tests/v2/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -2552,6 +2552,31 @@ def _check_cjk_shape_purity(self) -> None:
{"family": "Smith", "suffix": "MD"},
classification="fix(#296)",
notes="the third leg of 'position decides' for 'md'"),
# -- #346: the renunciate class. A title of religious renunciation
# addresses the GIVEN name AND belongs to a tradition that
# abolishes the surname, so rules.md#H1's given-name-title fold is
# the right reading and family="" is the right output. The two
# criteria are separate (rules.md#H Background) and the control
# row below is what keeps them separate.
Case("renunciate_title_keeps_the_given_name", "Swami Vivekananda",
{"title": "Swami", "given": "Vivekananda"},
classification="fix(#346)",
notes="pins the #346 membership change the ledger "
"classifies: 'swami' moved from the TITLES-only block "
"into GIVEN_NAME_TITLES, so H1's fold fires and the "
"family is empty where 1.4.0 through 2.2.0 read family "
"'Vivekananda'. The fold itself is already pinned by "
"the Sir John rows; this is the name the release note "
"advertises"),
Case("surname_retaining_title_keeps_the_family",
"Rabbi Cohen",
{"title": "Rabbi", "family": "Cohen"},
notes="makes rules.md#H Background's 'Rabbi Cohen' sentence "
"executable -- no other row parses a Rabbi. 'rabbi' "
"addresses by title and the tradition keeps surnames, "
"so it stays out of GIVEN_NAME_TITLES and H1 families "
"the one name word. The control that keeps 'addresses "
"by the given name' and 'has no surname' separate"),
Case("audit_do_leading_is_a_name", "Do Nguyen",
{"given": "Do", "family": "Nguyen"},
ambiguities=("particle-or-given",),
Expand Down
42 changes: 42 additions & 0 deletions tests/v2/test_ledger_guards.py
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,15 @@ def test_case_shape_ids_exist_in_the_inventory() -> None:
"fix(#436/#437) the space-separated post-nominal run compounds with the bound-given reserve":
("abdul Smith V", "abdul Smith Jr Ma", "abdul Smith Jr",
"Smith Jr V"),
# #346's rule is a literal alternation of four names, so a
# widening reaching only names the corpora lack would leave
# _CORPUS_CLAIMS unmoved. These probes are the wall, and each is a
# name that must not move: two name words after the title (H1 does
# not fold), a surname-retaining title, and the bare renunciate
# title standing alone.
"fix(#346) a renunciate title and one name word leave the name a given name":
("Swami Vivekananda Saraswati", "Guru Gobind Singh",
"Rabbi Cohen", "Swami"),
}


Expand Down Expand Up @@ -1508,6 +1517,15 @@ class _LatinCopy(NamedTuple):
"The Rt Hon Kenneth Clarke QC MP, HMG",
"Washington Jr\\. MD, Franklin", "abdul Smith Jr Ma",
"abdul Smith Jr V"}),
# #346's movers, one corpus name per alternative -- a list of
# names, not a copy of any wordlist, so there is no vocabulary for
# it to drift from. The rule's subject is a SHAPE the vocabulary
# participates in (a renunciate title plus exactly one name word),
# and a member copying GIVEN_NAME_TITLES would reach 'Swami
# Vivekananda Saraswati' and 'Guru Gobind Singh', which do not
# move. One set, identical in all four ledgers.
frozenset({"Baba Ramdev", "Guru Nanak", "Lama Zopa",
"Swami Vivekananda"}),
# fix(#445)'s movers, one corpus name per alternative -- a list of
# names, not a copy of any wordlist, so there is no vocabulary for
# it to drift from. Two sets because the ledgers group the nine
Expand Down Expand Up @@ -1994,6 +2012,12 @@ def _claim(rule: dict) -> _Claim:
# change the row here before it reached the gate.
"fix(#436/#437) a space-separated post-nominal run renders with spaces, not commas":
_Claim(10, ('suffix',), "30f5314a2662", None),
# #346's alternation. Four corpus names, `family` and
# `given` together: the fold moves both roles at once, so a
# widening taking one alone would change the roles here
# before it reached the gate.
"fix(#346) a renunciate title and one name word leave the name a given name":
_Claim(4, ('family', 'given'), "a3399ee7b21e", None),
"fix(A2) content-free input names nobody, so every role empties":
_Claim(5, ('given',), "1af8d718688b", None),
"fix(#335) a marker-led clause leaves the one name word its bare reading":
Expand Down Expand Up @@ -2202,6 +2226,12 @@ def _claim(rule: dict) -> _Claim:
# change the row here before it reached the gate.
"fix(#436/#437) a space-separated post-nominal run renders with spaces, not commas":
_Claim(10, ('suffix',), "30f5314a2662", None),
# #346's alternation. Four corpus names, `family` and
# `given` together: the fold moves both roles at once, so a
# widening taking one alone would change the roles here
# before it reached the gate.
"fix(#346) a renunciate title and one name word leave the name a given name":
_Claim(4, ('family', 'given'), "a3399ee7b21e", None),
# The compound rule, at the two baselines where 'abdul Smith
# Jr V' already diffs {family, given} under fix(#401) and the
# widened diff leaves that rule's `fields`. Three roles here
Expand Down Expand Up @@ -2371,6 +2401,12 @@ def _claim(rule: dict) -> _Claim:
# change the row here before it reached the gate.
"fix(#436/#437) a space-separated post-nominal run renders with spaces, not commas":
_Claim(10, ('suffix',), "30f5314a2662", None),
# #346's alternation. Four corpus names, `family` and
# `given` together: the fold moves both roles at once, so a
# widening taking one alone would change the roles here
# before it reached the gate.
"fix(#346) a renunciate title and one name word leave the name a given name":
_Claim(4, ('family', 'given'), "a3399ee7b21e", None),
# The four one-name CJK rules, literal-anchored, at the two
# baselines where the render is the whole of what moved. A
# reach of 1 is one _CORPUS_CLAIMS cannot police on its own --
Expand All @@ -2394,6 +2430,12 @@ def _claim(rule: dict) -> _Claim:
# change the row here before it reached the gate.
"fix(#436/#437) a space-separated post-nominal run renders with spaces, not commas":
_Claim(10, ('suffix',), "30f5314a2662", None),
# #346's alternation. Four corpus names, `family` and
# `given` together: the fold moves both roles at once, so a
# widening taking one alone would change the roles here
# before it reached the gate.
"fix(#346) a renunciate title and one name word leave the name a given name":
_Claim(4, ('family', 'given'), "a3399ee7b21e", None),
# The four one-name CJK rules, literal-anchored, at the two
# baselines where the render is the whole of what moved. A
# reach of 1 is one _CORPUS_CLAIMS cannot police on its own --
Expand Down
29 changes: 29 additions & 0 deletions tools/differential/expected_since_1.4.0.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,35 @@ issue = "fix(#436/#437) a space-separated post-nominal run renders with spaces,
name_regex = "^(?:JOHN DOE PHD MD|John Doe MD PhD|John Smith MD PhD|John Smith Mc V|Kenneth Clarke QC MP|Smith, John PhD I\\.|The Rt Hon Kenneth Clarke QC MP, HMG|Washington Jr\\. MD, Franklin|abdul Smith Jr Ma|abdul Smith Jr V)$"
fields = ["suffix"]

# #346: swami, guru, baba and lama moved from the TITLES-only block
# into GIVEN_NAME_TITLES on 2026-09-06. rules.md#H1's Accepted clause
# -- "a given-name title plus one name word leaves the family empty"
# -- now reaches them, so the one name word behind the title is the
# GIVEN name and the family is empty, where every baseline read it as
# the family. Both roles move together and `fields` declares both: a
# name whose TITLE moved would arrive UNEXPLAINED, as it should.
#
# Four corpus names, all from the radar-tier corpus_issues.jsonl and
# all moving the same way. The two- and three-word spellings are the
# boundary and are _MUST_NOT_MATCH probes: 'Swami Vivekananda
# Saraswati' and 'Guru Gobind Singh' keep their family names, because
# H1's fold requires exactly one name word, and a rule reaching them
# would stand ready to explain the very regression that boundary
# exists to catch.
#
# An anchored alternation of the four NAMES rather than a shape,
# under the ledger discipline expected_since_1.4.0.toml's header
# states for all four files: keep each entry's `name_regex` as tight
# as the diff allows. Four names moved, so a four-name list is the
# tightest expression of that diff; anything shape-shaped is wider
# than what was measured. It copies no wordlist either, which is what
# _NOT_A_VOCABULARY_COPY in tests/v2/test_ledger_guards.py records;
# _CORPUS_CLAIMS pins the reach at 4 with its digest.
[[change]]
issue = "fix(#346) a renunciate title and one name word leave the name a given name"
name_regex = "^(?:Baba Ramdev|Guru Nanak|Lama Zopa|Swami Vivekananda)$"
fields = ["family", "given"]

[[change]]
issue = "fix(#271/#272/#298) native-script CJK: family-first order, hangul segmentation, the kana license and the dots"
# '毛 泽东', '김민준': script_orders flips first/last for a name written
Expand Down
29 changes: 29 additions & 0 deletions tools/differential/expected_since_2.0.0.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,35 @@ issue = "fix(#436/#437) a space-separated post-nominal run renders with spaces,
name_regex = "^(?:JOHN DOE PHD MD|John Doe MD PhD|John Smith MD PhD|John Smith Mc V|Kenneth Clarke QC MP|Smith, John PhD I\\.|The Rt Hon Kenneth Clarke QC MP, HMG|Washington Jr\\. MD, Franklin|abdul Smith Jr Ma|abdul Smith Jr V)$"
fields = ["suffix"]

# #346: swami, guru, baba and lama moved from the TITLES-only block
# into GIVEN_NAME_TITLES on 2026-09-06. rules.md#H1's Accepted clause
# -- "a given-name title plus one name word leaves the family empty"
# -- now reaches them, so the one name word behind the title is the
# GIVEN name and the family is empty, where every baseline read it as
# the family. Both roles move together and `fields` declares both: a
# name whose TITLE moved would arrive UNEXPLAINED, as it should.
#
# Four corpus names, all from the radar-tier corpus_issues.jsonl and
# all moving the same way. The two- and three-word spellings are the
# boundary and are _MUST_NOT_MATCH probes: 'Swami Vivekananda
# Saraswati' and 'Guru Gobind Singh' keep their family names, because
# H1's fold requires exactly one name word, and a rule reaching them
# would stand ready to explain the very regression that boundary
# exists to catch.
#
# An anchored alternation of the four NAMES rather than a shape,
# under the ledger discipline expected_since_1.4.0.toml's header
# states for all four files: keep each entry's `name_regex` as tight
# as the diff allows. Four names moved, so a four-name list is the
# tightest expression of that diff; anything shape-shaped is wider
# than what was measured. It copies no wordlist either, which is what
# _NOT_A_VOCABULARY_COPY in tests/v2/test_ledger_guards.py records;
# _CORPUS_CLAIMS pins the reach at 4 with its digest.
[[change]]
issue = "fix(#346) a renunciate title and one name word leave the name a given name"
name_regex = "^(?:Baba Ramdev|Guru Nanak|Lama Zopa|Swami Vivekananda)$"
fields = ["family", "given"]

[[change]]
issue = "fix(#271/#272/#298) native-script CJK: family-first order, hangul segmentation, the kana license and the dots"
# The 2.1 East Asian defaults, in the fields they move name pieces
Expand Down
29 changes: 29 additions & 0 deletions tools/differential/expected_since_2.1.0.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,35 @@ issue = "fix(#436/#437) the glued honorific and the generational suffix are one
name_regex = "^김민준씨 Jr\\.$"
fields = ["suffix"]

# #346: swami, guru, baba and lama moved from the TITLES-only block
# into GIVEN_NAME_TITLES on 2026-09-06. rules.md#H1's Accepted clause
# -- "a given-name title plus one name word leaves the family empty"
# -- now reaches them, so the one name word behind the title is the
# GIVEN name and the family is empty, where every baseline read it as
# the family. Both roles move together and `fields` declares both: a
# name whose TITLE moved would arrive UNEXPLAINED, as it should.
#
# Four corpus names, all from the radar-tier corpus_issues.jsonl and
# all moving the same way. The two- and three-word spellings are the
# boundary and are _MUST_NOT_MATCH probes: 'Swami Vivekananda
# Saraswati' and 'Guru Gobind Singh' keep their family names, because
# H1's fold requires exactly one name word, and a rule reaching them
# would stand ready to explain the very regression that boundary
# exists to catch.
#
# An anchored alternation of the four NAMES rather than a shape,
# under the ledger discipline expected_since_1.4.0.toml's header
# states for all four files: keep each entry's `name_regex` as tight
# as the diff allows. Four names moved, so a four-name list is the
# tightest expression of that diff; anything shape-shaped is wider
# than what was measured. It copies no wordlist either, which is what
# _NOT_A_VOCABULARY_COPY in tests/v2/test_ledger_guards.py records;
# _CORPUS_CLAIMS pins the reach at 4 with its digest.
[[change]]
issue = "fix(#346) a renunciate title and one name word leave the name a given name"
name_regex = "^(?:Baba Ramdev|Guru Nanak|Lama Zopa|Swami Vivekananda)$"
fields = ["family", "given"]

[[change]]
issue = "fix(#369) a given-name title licenses the bound given-name join with one word to spare"
# 'Sheik abdul salam': rules.md#P5 -- "or after a given-name title,
Expand Down
29 changes: 29 additions & 0 deletions tools/differential/expected_since_2.2.0.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,35 @@ issue = "fix(#436/#437) the glued honorific and the generational suffix are one
name_regex = "^김민준씨 Jr\\.$"
fields = ["suffix"]

# #346: swami, guru, baba and lama moved from the TITLES-only block
# into GIVEN_NAME_TITLES on 2026-09-06. rules.md#H1's Accepted clause
# -- "a given-name title plus one name word leaves the family empty"
# -- now reaches them, so the one name word behind the title is the
# GIVEN name and the family is empty, where every baseline read it as
# the family. Both roles move together and `fields` declares both: a
# name whose TITLE moved would arrive UNEXPLAINED, as it should.
#
# Four corpus names, all from the radar-tier corpus_issues.jsonl and
# all moving the same way. The two- and three-word spellings are the
# boundary and are _MUST_NOT_MATCH probes: 'Swami Vivekananda
# Saraswati' and 'Guru Gobind Singh' keep their family names, because
# H1's fold requires exactly one name word, and a rule reaching them
# would stand ready to explain the very regression that boundary
# exists to catch.
#
# An anchored alternation of the four NAMES rather than a shape,
# under the ledger discipline expected_since_1.4.0.toml's header
# states for all four files: keep each entry's `name_regex` as tight
# as the diff allows. Four names moved, so a four-name list is the
# tightest expression of that diff; anything shape-shaped is wider
# than what was measured. It copies no wordlist either, which is what
# _NOT_A_VOCABULARY_COPY in tests/v2/test_ledger_guards.py records;
# _CORPUS_CLAIMS pins the reach at 4 with its digest.
[[change]]
issue = "fix(#346) a renunciate title and one name word leave the name a given name"
name_regex = "^(?:Baba Ramdev|Guru Nanak|Lama Zopa|Swami Vivekananda)$"
fields = ["family", "given"]

[[change]]
issue = "fix(#462) the facade keeps an initial-shaped conjunction letter"
# 'Scott E. Werner', 'Amy E Maid', 'Jose E. Maria Santos':
Expand Down