Skip to content

Commit 8d5f769

Browse files
derek73claude
andauthored
fix: recognize single-letter roman numeral suffixes in suffix-comma format (closes #136) (#174)
* fix: recognize single-letter suffix_not_acronyms (e.g. 'V') in suffix-comma format (closes #136) Roman numeral suffixes like 'V' are single uppercase letters, which the initial regex also matches. In suffix-comma format ("John Ingram, V") the post-comma position is unambiguous, so the initial guard should not block recognition. Add are_suffixes_after_comma() that bypasses the initial check for suffix_not_acronyms members and use it at the suffix-comma detection site, leaving is_suffix() unchanged for the no-comma path where 'V' mid-name should still be treated as an initial. * test: add coverage for suffix_not_acronyms members in suffix-comma format Add tests for 'I' (same single-letter ambiguity as 'V'), a mixed suffix-comma case (V MD), and two suffix_not_acronyms members together (V Jr.). Also tighten the are_suffixes_after_comma docstring to describe the short-circuit mechanism accurately and enumerate the full scope of affected suffix_not_acronyms entries. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1fe391c commit 8d5f769

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

nameparser/parser.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,23 @@ def are_suffixes(self, pieces: Iterable[str]) -> bool:
487487
return False
488488
return True
489489

490+
def are_suffixes_after_comma(self, pieces: Iterable[str]) -> bool:
491+
"""Like are_suffixes, but pieces found in suffix_not_acronyms are
492+
accepted unconditionally without passing through is_suffix().
493+
494+
Used when detecting suffix-comma format (e.g. "John Ingram, V") where
495+
the post-comma position is unambiguous. This covers all
496+
suffix_not_acronyms members (i, ii, iii, iv, v, jr, sr, etc.),
497+
case-insensitively, including single-letter entries that is_suffix()
498+
would otherwise reject via is_an_initial().
499+
"""
500+
for piece in pieces:
501+
if lc(piece) in self.C.suffix_not_acronyms:
502+
continue
503+
if not self.is_suffix(piece):
504+
return False
505+
return True
506+
490507
def is_rootname(self, piece: str) -> bool:
491508
"""
492509
Is not a known title, suffix or prefix. Just first, middle, last names.
@@ -686,7 +703,7 @@ def parse_full_name(self) -> None:
686703

687704
post_comma_pieces = self.parse_pieces(parts[1].split(' '), 1)
688705

689-
if self.are_suffixes(parts[1].split(' ')) \
706+
if self.are_suffixes_after_comma(parts[1].split(' ')) \
690707
and len(parts[0].split(' ')) > 1:
691708

692709
# suffix comma:

tests/test_suffixes.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,35 @@ def test_two_suffixes_lastname_comma_format(self) -> None:
3434
# NOTE: this adds a comma when the original format did not have one.
3535
self.m(hn.suffix, "Jr., MD", hn)
3636

37+
def test_roman_numeral_v_suffix_comma_format(self) -> None:
38+
# suffix-comma position is unambiguous: 'V' must be a suffix, not a single-letter initial
39+
hn = HumanName("John W. Ingram, V")
40+
self.m(hn.first, "John", hn)
41+
self.m(hn.middle, "W.", hn)
42+
self.m(hn.last, "Ingram", hn)
43+
self.m(hn.suffix, "V", hn)
44+
45+
def test_roman_numeral_i_suffix_comma_format(self) -> None:
46+
# 'I' has the same single-letter ambiguity as 'V'
47+
hn = HumanName("John W. Smith, I")
48+
self.m(hn.first, "John", hn)
49+
self.m(hn.middle, "W.", hn)
50+
self.m(hn.last, "Smith", hn)
51+
self.m(hn.suffix, "I", hn)
52+
53+
def test_suffix_not_acronym_then_acronym_suffix_comma_format(self) -> None:
54+
# single-letter suffix_not_acronyms entry followed by an acronym suffix
55+
hn = HumanName("John Smith, V MD")
56+
self.m(hn.first, "John", hn)
57+
self.m(hn.last, "Smith", hn)
58+
self.m(hn.suffix, "V MD", hn)
59+
60+
def test_two_suffix_not_acronyms_suffix_comma_format(self) -> None:
61+
hn = HumanName("John Smith, V Jr.")
62+
self.m(hn.first, "John", hn)
63+
self.m(hn.last, "Smith", hn)
64+
self.m(hn.suffix, "V Jr.", hn)
65+
3766
def test_two_suffixes_suffix_comma_format(self) -> None:
3867
hn = HumanName("Franklin Washington, Jr. MD")
3968
self.m(hn.first, "Franklin", hn)

0 commit comments

Comments
 (0)