Skip to content

HumanName('Scott E. Werner').initials() drops the middle initial — the facade reads E as the conjunction #462

Description

@derek73
HumanName("Scott E. Werner").initials()   # 'S. W.'    -- the middle initial is gone
HumanName("john E. smith").initials()     # 'j. s.'
HumanName("Juan Y. Garcia").initials()    # 'J. G.'

E and Y are the Latin one-letter members of CONJUNCTIONS, and initials() filters conjunctions out. A middle or family initial that happens to be one of those letters is silently dropped.

A v1 parity break, shipped since 2.0.0

Measured on the released wheels:

input 1.4.0 2.0.0 2.1.0
Scott E. Werner S. E. W. S. W. S. W.
john E. smith j. E. s. j. s. j. s.
Juan Y. Garcia J. Y. G. J. G. J. G.

mechanisms.md#FACADE-CONTRACT promises that 1.4-warning-free code keeps working through 2.x "EXCEPT release-log-classified fixes". No release-log bullet classifies this.

The 2.0 core gets it right — only the facade regressed

Same library, same name, two answers:

parse("Scott E. Werner").initials()        # 'S. E. W.'   correct
HumanName("Scott E. Werner").initials()    # 'S. W.'      wrong

The boundary is intact, which is what makes this precise

Only the dotted form is affected. A bare one-letter conjunction is still correctly dropped on both versions:

'maria y lopez'  ->  'm. l.'    1.4.0 and 2.x agree
'john e smith'   ->  'j. s.'    1.4.0 and 2.x agree

So this is not "conjunctions should initial". It is that v1 distinguished the conjunction e from the initial E. by the period, and the facade lost that distinction.

Reach, measured over the differential corpus

Comparing 1.4.0 against 2.2.0dev over the 1094-name corpus, restricted to the 865 names that parse byte-identically on both (so a parse change cannot be mistaken for this one), and comparing flattened letter sequences so 2.x's initials_list regrouping does not count:

14 of those 865 lose an initial, and every one is a middle E:

Amy E Maid · Amy E. Maid · Doctor, Jane E. · E Anne D,Leonardo
JOSE E MARIA SANTOS · Joe E. Smith · John E Smith · Jose E Maria Santos
Jose E. Maria Santos · Rt. Hon. Paul E. Mary · Smith, John E, III, Jr
US Magistrate-Judge Elizabeth E Campbell · john e. smith · scott e. werner

A fifteenth name, de los Santos (lSS), is not this defect — los is in particles, not conjunctions, and that change is #402/#404's, already classified.

The Cyrillic half is sharper than the Latin one

The shipped single-letter conjunctions are &, e, y, и, й, і, و:

HumanName("Ivan И. Petrov").initials()   # 'I. P.'        facade
parse("Ivan И. Petrov").initials()       # 'I. И. P.'     core

_vocab.is_initial narrows by script repertoire (#320) precisely so an initial-shaped Cyrillic letter reads as an initial. The facade's predicate consults neither the shape half nor the repertoire half.

Cause

nameparser/_facade.py:474:

def _is_conjunction(self, text: str) -> bool:
    self._resolve()
    return _normalize(text) in self._lexicon.conjunctions

v1's is_conjunction excluded initials. _process_initial's comment four lines below states it copies "v1 parser.py:427 verbatim", and the copy dropped the carve-out.

The rule is stated and pinned elsewhere in the tree — nameparser/_render.py's _cap_word comment says "v1's is_conjunction excludes initials: 'E.' in 'Scott E. Werner' is an initial, not the conjunction 'e' (pinned live 2026-07-17)", using this very name. The core honors it; the facade does not.

Suggested fix

Give _facade._is_conjunction the initial carve-out the core already applies, so the facade and parse() agree. nameparser/_render.py's _INITIAL is the same shape test, and tests/v2/test_regex_sync.py already pins it against nameparser/_pipeline/_vocab.py.

Worth checking with it: _is_conjunction is one of the v1 hook names listed in _facade.py's hook roster, so a subclass may override it — whether the carve-out belongs in _is_conjunction or at its call site in _process_initial is a real question, and the hook contract should decide it rather than convenience.

Also worth a look while there: whether any other _facade predicate dropped a v1 carve-out the same way. _is_conjunction was found by checking one path; the roster has seven more.

Found reviewing #458, which fixed the equivalent re-derivation in the core's _cap_word.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions