A leading particle deliberately does not chain — that is what makes Van Johnson read as given Van, family Johnson rather than as one surname. But the exception is keyed on piece index 0, so a title displaces the particle out of leading position and the chain fires. Identical name text then parses differently depending on whether a title precedes it:
Van Johnson given='Van' family='Johnson'
Dr. Van Johnson given='' family='Van Johnson'
Sir Van Johnson given='Van Johnson' family='' (no surname at all)
The last row compounds it: with a given-name title, post_rules rule 1's carve-out then hands the whole chain to given, so the name ends with no surname.
Decision: titles are transparent to the leading-particle exception
A title is not part of the name, so it should not change whether the name begins with a particle. Dr. Van Johnson and Sir Van Johnson should read as Van Johnson does.
The change is one line in nameparser/_pipeline/_group.py — the chain loop's if k == 0 becomes "the first piece that is not a title".
Measured: prototype vs current
Changes — every one is a titled name whose first name-piece is a particle:
current prototype
Dr. Van Johnson family='Van Johnson' given='Van' family='Johnson'
Dr. Van Johnson Smith family='Van Johnson Smith' given='Van' middle='Johnson' family='Smith'
Mr. Van Nguyen family='Van Nguyen' given='Van' family='Nguyen'
Sir Van Johnson given='Van Johnson' family='' given='Van' family='Johnson'
Sir Van Johnson Smith given='Van Johnson Smith' given='Van' middle='Johnson' family='Smith'
Sir de Mesnil given='de Mesnil' family='' given='' family='de Mesnil'
Sheik Abu Bakar given='Abu Bakar' family='' given='Abu' family='Bakar'
Unchanged, including the exact shapes v1 worried about:
Mr. Smith / Mrs. Smith / Mr. Nguyen / Mr. John Smith
Sir Ian / Sir Ian McKellen / King Henry
Van Johnson / de Mesnil / de Mesnil Garcia (no title)
Dr. de Mesnil
Sheik abdul salam
This fixes a v1-era xfail that says it cannot be fixed
tests/test_first_name.py:
@pytest.mark.xfail
def test_first_name_is_prefix_if_three_parts(self) -> None:
"""Not sure how to fix this without breaking Mr and Mrs"""
hn = HumanName("Mr. Van Nguyen")
self.m(hn.first, "Van", hn)
self.m(hn.last, "Nguyen", hn)
The prototype makes it pass. And it does not break Mr and Mrs — every bare Mr./Mrs. Surname shape is byte-identical, because those have no particle to displace. v1's concern was about the fix it had in mind, not about this one.
And it reverses a case row that pins the opposite
tests/v2/cases.py:
Case("titled_ambiguous_particle_chains", "Dr. Van Johnson",
{"title": "Dr.", "family": "Van Johnson"},
ambiguities=("particle-or-given",),
notes="... a leading title shifts Van off the given position, the prefix
chain fires, and the fork is reported from group rather than
assign (v1 parity on the fields)")
Mr. Van Nguyen and Dr. Van Johnson are the same shape. The repository currently pins one as broken-and-unfixable and the other as correct-by-v1-parity. This resolves the contradiction in favour of the xfail. The row must be rewritten to given='Van', family='Johnson'.
Note "v1 parity on the fields" is accurate — 1.4.0 does give last='Van Johnson' — but v1 also ships the xfail calling the same shape wrong, so parity is not the tiebreaker it appears to be.
The fork is still reported either way: under the change Dr. Van Johnson emits particle-or-given from _assign instead of from _group, i.e. from the same place the untitled Van Johnson reports it.
Blast radius
Nine test failures on the prototype, all coherent:
| test |
why |
test_first_name_is_prefix_if_three_parts |
the xfail now passes — this is the fix |
titled_ambiguous_particle_chains + its facade twin |
the case row above; rewrite |
4 × test_chained_particle_detail_* |
_group's PARTICLE_OR_GIVEN emitter loses its only reachable case, since titles were what displaced the particle |
test_ambiguous_particle_reports_both_branches_of_its_fork |
same cause |
test_add_title |
fallout; investigate |
Whether _group's emitter becomes dead code needs checking — if a non-title piece can never precede a chaining particle, the emitter and its tests should go.
Scope and verification
Changes default-order output — the first change in this sequence to move the default-order corpus, byte-identical through #354, #358 and #361. Needs a tools/differential pass at all three baselines with ledger entries.
Related
A leading particle deliberately does not chain — that is what makes
Van Johnsonread as givenVan, familyJohnsonrather than as one surname. But the exception is keyed on piece index 0, so a title displaces the particle out of leading position and the chain fires. Identical name text then parses differently depending on whether a title precedes it:The last row compounds it: with a given-name title,
post_rulesrule 1's carve-out then hands the whole chain togiven, so the name ends with no surname.Decision: titles are transparent to the leading-particle exception
A title is not part of the name, so it should not change whether the name begins with a particle.
Dr. Van JohnsonandSir Van Johnsonshould read asVan Johnsondoes.The change is one line in
nameparser/_pipeline/_group.py— the chain loop'sif k == 0becomes "the first piece that is not a title".Measured: prototype vs current
Changes — every one is a titled name whose first name-piece is a particle:
Unchanged, including the exact shapes v1 worried about:
This fixes a v1-era xfail that says it cannot be fixed
tests/test_first_name.py:The prototype makes it pass. And it does not break Mr and Mrs — every bare
Mr./Mrs. Surnameshape is byte-identical, because those have no particle to displace. v1's concern was about the fix it had in mind, not about this one.And it reverses a case row that pins the opposite
tests/v2/cases.py:Mr. Van NguyenandDr. Van Johnsonare the same shape. The repository currently pins one as broken-and-unfixable and the other as correct-by-v1-parity. This resolves the contradiction in favour of the xfail. The row must be rewritten togiven='Van',family='Johnson'.Note "v1 parity on the fields" is accurate — 1.4.0 does give
last='Van Johnson'— but v1 also ships the xfail calling the same shape wrong, so parity is not the tiebreaker it appears to be.The fork is still reported either way: under the change
Dr. Van Johnsonemitsparticle-or-givenfrom_assigninstead of from_group, i.e. from the same place the untitledVan Johnsonreports it.Blast radius
Nine test failures on the prototype, all coherent:
test_first_name_is_prefix_if_three_partstitled_ambiguous_particle_chains+ its facade twintest_chained_particle_detail_*_group'sPARTICLE_OR_GIVENemitter loses its only reachable case, since titles were what displaced the particletest_ambiguous_particle_reports_both_branches_of_its_forktest_add_titleWhether
_group's emitter becomes dead code needs checking — if a non-title piece can never precede a chaining particle, the emitter and its tests should go.Scope and verification
Changes default-order output — the first change in this sequence to move the default-order corpus, byte-identical through #354, #358 and #361. Needs a
tools/differentialpass at all three baselines with ledger entries.Related
given/middleunder family-first orders. Independent mechanism (name_order, no title), family-first only.Sheik Abu Bakarregresses here fromgiven='Abu Bakar'togiven='Abu',family='Bakar'. That case works today only becauseabuhappens to be a particle as well as a bound given name —Sheik abdul salamshows bound-given alone does not chain. Tracked separately.de Mesnil Juanbe all surname, or familyde Mesnilplus givenJuan? #364,Mesnil Garcia destrands the particle under FAMILY_FIRST but folds it under FAMILY_FIRST_GIVEN_LAST #365 — unrelated; no chain is involved in either.