Skip to content

Commit 95b74b4

Browse files
derek73claude
andcommitted
test: add coverage for trailing suffix_not_acronyms in lastname-comma format
Add tests for 'V' (same ambiguity as 'I'), no-middle case, single-letter middle-initial-before-suffix pattern, and an xfail documenting the known limitation when an explicit comma-suffix segment is present. Also tighten the is_suffix_at_lastname_comma_end docstring to explain the parts parameter semantics and the three conditions that must hold. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f5d383e commit 95b74b4

2 files changed

Lines changed: 50 additions & 9 deletions

File tree

nameparser/parser.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -505,14 +505,21 @@ def are_suffixes_after_comma(self, pieces: Iterable[str]) -> bool:
505505
return True
506506

507507
def is_suffix_at_lastname_comma_end(self, piece: str, nxt: str | None, parts: list[str]) -> bool:
508-
"""True when a suffix_not_acronyms member is the final token in the
509-
post-comma segment of a lastname-comma name with no explicit
510-
comma-separated suffix (e.g. 'Maier, Amy Lauren I').
511-
512-
When parts[2] exists the caller already has an explicit suffix, making
513-
the trailing single-letter more likely a middle initial
514-
(e.g. 'Doe, Rev. John V, Jr.'), so the guard len(parts)==2 excludes
515-
that case.
508+
"""True when ``piece`` is a suffix_not_acronyms member that should be
509+
treated as a suffix at the end of ``parts[1]`` (the post-comma segment)
510+
in a lastname-comma name, where ``parts`` is the full comma-split of the
511+
name string.
512+
513+
Returns True only when all three conditions hold:
514+
- ``nxt is None``: piece is the last token in the post-comma segment
515+
- ``len(parts) == 2``: no ``parts[2]`` suffix segment exists
516+
- ``lc(piece) in suffix_not_acronyms``
517+
518+
When ``parts[2]`` exists the caller already declared an explicit suffix
519+
via comma (e.g. 'Doe, Rev. John V, Jr.'), making the trailing token more
520+
likely a middle initial; ``len(parts) == 2`` excludes that case.
521+
Used as an OR alternative to ``is_suffix()`` for pieces that
522+
``is_suffix()`` would reject via ``is_an_initial()``.
516523
"""
517524
return (nxt is None
518525
and len(parts) == 2

tests/test_suffixes.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,47 @@ def test_suffix_with_periods_with_lastname_comma(self) -> None:
167167
self.m(hn.suffix, "Msc.Ed.", hn)
168168

169169
def test_roman_numeral_i_lastname_comma_format(self) -> None:
170-
# trailing 'I' in lastname-comma format must be a suffix, not a middle initial (issue #144)
170+
# 'I' is in suffix_not_acronyms; trailing suffix_not_acronyms members in
171+
# "Lastname, First Middle Suffix" format (no comma-suffix segment) must
172+
# parse as suffix, not middle initial (issue #144)
171173
hn = HumanName("Maier, Amy Lauren I")
172174
self.m(hn.first, "Amy", hn)
173175
self.m(hn.middle, "Lauren", hn)
174176
self.m(hn.last, "Maier", hn)
175177
self.m(hn.suffix, "I", hn)
176178

179+
def test_roman_numeral_v_lastname_comma_format(self) -> None:
180+
# 'V' shares the same suffix_not_acronyms ambiguity as 'I' (issue #144)
181+
hn = HumanName("Smith, John V")
182+
self.m(hn.first, "John", hn)
183+
self.m(hn.middle, "", hn)
184+
self.m(hn.last, "Smith", hn)
185+
self.m(hn.suffix, "V", hn)
186+
187+
def test_roman_numeral_i_no_middle_lastname_comma_format(self) -> None:
188+
# no middle name: trailing 'I' must still be a suffix (issue #144)
189+
hn = HumanName("Smith, John I")
190+
self.m(hn.first, "John", hn)
191+
self.m(hn.middle, "", hn)
192+
self.m(hn.last, "Smith", hn)
193+
self.m(hn.suffix, "I", hn)
194+
195+
def test_roman_numeral_i_after_single_initial_lastname_comma_format(self) -> None:
196+
# single-letter middle initial followed by trailing 'I' (reporter pattern, issue #144)
197+
hn = HumanName("Chang, Andy C I")
198+
self.m(hn.first, "Andy", hn)
199+
self.m(hn.middle, "C", hn)
200+
self.m(hn.last, "Chang", hn)
201+
self.m(hn.suffix, "I", hn)
202+
203+
@pytest.mark.xfail
204+
def test_roman_numeral_i_with_explicit_suffix_comma_known_limitation(self) -> None:
205+
# When an explicit suffix comma is present (len(parts)==3), the trailing 'I'
206+
# is conservatively left in middle to avoid misclassifying true initials.
207+
# This is a known limitation of is_suffix_at_lastname_comma_end (issue #144).
208+
hn = HumanName("Maier, Amy I, Jr.")
209+
self.m(hn.suffix, "I, Jr.", hn)
210+
177211
def test_suffix_delimiter_default_on_constants(self) -> None:
178212
from nameparser.config import CONSTANTS
179213
self.assertIs(CONSTANTS.suffix_delimiter, None)

0 commit comments

Comments
 (0)