Skip to content

nameparser 2.0 implementation#288

Draft
derek73 wants to merge 136 commits into
masterfrom
v2/core-foundation
Draft

nameparser 2.0 implementation#288
derek73 wants to merge 136 commits into
masterfrom
v2/core-foundation

Conversation

@derek73

@derek73 derek73 commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Implementation branch for the 2.0 design — see the RFC in #285 and the umbrella issue #284 for the design discussion and feedback questions. Implementation learnings that change the design will land as amendment commits on #285, per the plan described there.

⚠️ Merges only at 2.0.0. This branch raises the Python floor to >=3.11 (#257) and drops the typing_extensions conditional dependency (nameparser now has zero runtime dependencies). Master must remain able to cut 1.4.x patch releases for Python 3.10 users, so this PR stays a draft until the 2.0.0 release.

Progress

  • Core data & config modelSpan, Role, Token, Ambiguity/AmbiguityKind, ParsedName; Lexicon, Policy/PolicyPatch/apply_patch, Locale. Frozen, slotted, hashable, picklable dataclasses with eager fail-loud validation; ~110 dedicated tests under tests/v2/.
  • Rendering (render() / initials() / capitalized() / __str__)
  • Parse pipeline + Parser / parse() / parser_for() / matches() + shared case table
  • v1 HumanName facade + CONSTANTS shim (existing test corpus as regression harness)
  • Locale packs (ru, tr_az) + differential-parse harness

Notes on what's here so far

🤖 Generated with Claude Code

derek73 and others added 30 commits July 12, 2026 12:46
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v2 core design uses enum.StrEnum (3.11+). Full #257 (dropping
typing_extensions, CI matrix) remains tracked on the issue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Validate that all replace() field values are str (user error if None)
- Append missing-role synthetics in canonical Role order, not kwargs order
- Unquote return type annotation (postponed annotations in effect)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Casefolded seven-component tuple in canonical Role order for dedup,
dict keys, and sorting. Semantic layer comparison; __eq__ remains strict.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds tests/v2/test_layering.py to mechanically enforce the conventions
doc's import layering and the public v2 export surface. Appends the v2
core types (Span, Role, Token, Ambiguity, AmbiguityKind, ParsedName,
Lexicon, Policy, PolicyPatch, PatronymicRule, UNSET, GIVEN_FIRST,
FAMILY_FIRST, FAMILY_FIRST_GIVEN_LAST, Locale) to nameparser/__init__.py
alongside the existing v1 HumanName export.

Turns on component-flag mypy strictness for the four v2 modules and
check_untyped_defs for tests/v2, then fixes the resulting test-only
type mismatches by using the precise constructed types (Span(...),
frozenset({...}), tuple-of-pairs) where the literal type didn't matter
to the test, and adding narrow # type: ignore[arg-type] comments only
where a test deliberately exercises runtime coercion/validation of an
intentionally mismatched static type (e.g. Token span/Ambiguity kind
coercion tests, PatronymicRule string coercion, dict aliasing test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pickle.dumps(Lexicon.default()) raised TypeError because the default
slots-dataclass pickle path serializes the _cap_map MappingProxyType.
Ship every other slot and rebuild the proxy from the canonical
capitalization_exceptions tuple on load. Parser (Plan 3) is picklable
by construction per the core spec, and a Parser holds a Lexicon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
requires-python is >=3.11 on this branch, so the 3.10 job can no
longer install the package (uv sync either fails or silently
substitutes a managed 3.11). The rest of #257 (typing_extensions,
classifiers) stays tracked on the issue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With requires-python at >=3.11, Self imports directly from typing and
the conditional dependency can never activate; ruff (UP035/UP036)
flags the dead version block once the floor is raised.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v1 suite is fully annotated (since #250 put tests/ under mypy), so
pyproject carries no per-file ANN ignores; the new tests/v2 modules
must be annotated too or 'ruff check' fails in CI. Also drops an
unused import ruff flagged (F401).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
derek73 and others added 30 commits July 16, 2026 23:48
158 dual-run cases -> 67 tests (the empty_attribute_default dual-run
parametrization died in the previous batch commit; per-run count 79 -> 67).

Bucket A (deleted machinery):
- test_unpickle_legacy_state_without_derived_sets: pinned __setstate__
  backfill of the v1 per-parse _derived_* sets and the is_title predicate,
  both gone with the v1 parser.
- the six is_prefix/is_conjunction/is_suffix list-fallthrough tests: the v1
  predicate methods are not part of the facade (#280 hook list).
- test_get_full_name_attribute_references_internal_lists: pinned the v1
  full_name getter re-rendering from the live *_list state; 2.0 deliberately
  returns the stored input string (spec section 2 mutation semantics, pinned
  in tests/v2/test_facade.py::test_field_assignment_str_list_none) and the
  *_list attributes are snapshots without setters (spec section 2 exc. 1).

Bucket B (scheduled removals now raising / silently changed):
- #245 bytes: both HumanName(bytes) and the full_name setter now
  pytest.raises(TypeError, match="decode").
- #223 ==/hash: rewritten to assert the identity semantics (distinct
  instances unequal, strings never match, hash == object default, no
  warnings), with matches()/comparison_key() shown as the replacements.
- #258 slices/__setitem__: slice access raises TypeError naming the issue;
  item assignment raises TypeError for every key; str-key reads stay silent.
- #255: given_names empty expectation is '' outright.
- Constants(regexes=...) raises TypeError pointing at Policy (deliberate
  2.0 divergence, migration spec section 3 uniform rule).

3 of 67 tests still fail on one confirmed pipeline bug (SUFFIX_COMMA
detection tests all post-comma segments where v1 tested only parts[1];
repro 'Dr. John P. Doe-Ray, CLU, CFP, LUTC'), so the file stays in
collect_ignore_glob with a pointer comment; it is mypy-clean and leaves
the mypy exclude. test_constants.py stays ignored, blocked on a wider
shim-regression inventory (see conftest comment).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ulary

M12 reconciliation surfaced two more Plan-3 misports, both pinned
live against v1.4 (2026-07-16):

- the suffix-comma structure is decided by parts[1] ALONE
  (parser.py:1318); v2 wrongly required every post-first segment to
  be suffix-shaped, so one unrecognized credential ('LUTC') collapsed
  the whole parse into FAMILY_COMMA. Non-suffix tails now ride along
  as suffixes with the COMMA_STRUCTURE flag (v1 consumed them
  silently; the flag is the v2 value-add).
- v1's period-joined derivation (parse_pieces): a token with an
  interior period whose chunks include ANY title is a title
  ('Lt.Gov.', 'Mr.Smith'), else ANY suffix chunk makes it a suffix
  ('JD.CPA'). Title wins. Ported into classify with the ANY rule
  kept deliberately.

Six case rows pin the matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
 + operators)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Also lifts test_python_api.py's collect_ignore_glob entry: its 3 held
tests pass after the suffix-comma and period-joined-vocabulary pipeline
fixes (7bf53f3), making the file fully green (67/67).

test_constants.py: 240 dual-run cases -> 94 tests (the dual-run
parametrization died with #255; per-run 120 -> 94).

Bucket A (deleted machinery):
- the suffixes_prefixes_titles cached-union property block (~13 tests of
  cache priming/invalidation incl. the timeit performance guard and the
  is_rootname consistency pair); change tracking is the shim generation
  counter, covered in tests/v2/test_config_shim.py. Two contracts
  survived and are re-pinned on parsing surface: post-unpickle mutations
  are honored, and wholesale manager replacement re-wires tracking.
- .elements raw-set accessor uses rewritten to set(...) (operators also
  return plain set now); _repr_collection_attrs/_repr_scalar_attrs class
  internals replaced by test-owned name lists.
- __setstate__ missing-field validation test (the shim deliberately
  accepts partial state to load v1.4 blobs).
- the legacy-unpickle warn-once pair (#279's warning machinery is gone).

Bucket B (scheduled removals now raising):
- #255 empty_attribute_default: assignment raises AttributeError; the
  six None-mode behavior tests collapse to one removal test (scalar
  pickle/copy round-trip intent re-pinned on string_format).
- #243: SetManager() call -> TypeError not callable; missing-member
  remove() -> KeyError (mixed-args call still applies the present
  removal).
- #245/#263: add_with_encoding -> AttributeError; bytes add() ->
  TypeError with decode hint.
- #256: unknown tuple-manager keys -> AttributeError naming known keys;
  regexes proxy raises naming the missing key; sunder probes now report
  absent (AttributeError) instead of returning manager defaults.
- #261: constants=None raises TypeError at constructor, positional, and
  C-setter entry points (class renamed ConstantsNoneRemovalTests).
- deliberate 2.0 divergences pinned: custom nickname delimiters raise
  pointing at Policy (spec section 3); plain-iterable set-field
  assignment auto-wraps in a validated SetManager where v1 demanded a
  pre-built one (bare strings still raise, #241 stays impossible);
  class-not-instance constants message no longer carries the "did you
  mean Constants()" hint.

Bucket C (shared-CONSTANTS mutation):
- test_can_change_global_constants wraps its shared mutation in
  pytest.deprecated_call (it exercises exactly that deprecation).

2 of 94 tests still fail on one remaining v1.4-parity gap -- the shim
Constants.__init__ lacks the patronymic_name_order/middle_name_as_last
bool kwargs -- so the file stays in collect_ignore_glob and the mypy
exclude with a pointer comment; remove both once the kwargs land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
patronymic_name_order and middle_name_as_last were v1.4 constructor
kwargs (docs/customize.rst doctests use them); the M12 reconciliation
of test_constants.py caught the gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Constants(patronymic_name_order=True) / Constants(middle_name_as_last=
True) work again (c1b5730), so the two held ConstantsReprTests pass:
94/94 green. Removes the file's collect_ignore_glob and mypy-exclude
entries and settles the three mypy items its un-excluding surfaced
(|= re-wrap annotations, a dict[str, object] state annotation, and a
note that the 2.0 TupleManager subscripts via dict's __class_getitem__
rather than v1's Generic).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
23 tests -> 22, all green.

Bucket B (scheduled removals / deliberate divergences):
- the two #254 None-mode scrubbing regressions merge into one residual
  test: the None display mode is gone (#255), but literal 'None'/'Nonez'
  name text must still survive formatting unscathed.
- regexes.emoji/.bidi = False opt-outs raise TypeError in 2.0 (migration
  spec section 3 uniform rule); the tests now pin the error naming the
  Policy(strip_emoji/strip_bidi=False) replacements.

Bucket C (shared-CONSTANTS mutation):
- the four *_constants_attribute tests (string_format, capitalize_name,
  force_mixed_case_capitalization, both) move onto a private Constants
  passed as constants= -- the migration-guide idiom -- instead of
  mutating the shared singleton.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
33 tests -> 31, all green.

Bucket B: the two #255 None-mode regressions (literal-"None"
interpolation, all-empty fallback) collapse to one test pinning the 2.0
invariant -- a fully-empty initials() renders ''.

Bucket C: the three *_constants tests (initials_format,
initials_delimiter, delimiter+separator+format combo) move onto a
private Constants passed as constants= instead of mutating the shared
singleton; the fixture-restore sentinel test keeps asserting the shared
default is ' '.

Bucket E: the #232 doubled-space test's direct middle_list assignment
becomes field assignment; the v1 hole it pinned (unnormalized *_list
elements crashing initials) is unreachable in 2.0 -- list snapshots plus
setter whitespace normalization -- so it now pins the normalized result.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… bug

The file needs no reconciliation edits (20 pass, 1 pre-existing v1
xfail), but one test fails on a render-layer parity bug: _render.py's
_cap_word lowercases every conjunction-set word, missing v1
is_conjunction's is_an_initial exclusion, so an initial that doubles as
a conjunction stays lowercase:

    HumanName('scott e. werner').capitalize() -> 'Scott e. Werner'
    (v1: 'Scott E. Werner'; 'e' is in CONJUNCTIONS)

The file stays in collect_ignore_glob with a pointer comment; it is
mypy-clean and leaves the mypy exclude.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The file needs no reconciliation edits (8 pass, 2 pre-existing v1
xfails), but one test fails on a confirmed pipeline parity bug: in the
family-comma format, a trailing suffix inside the family segment is not
split out of it.

    Parser().parse('Smith Jr., John') -> family='Smith Jr.', suffix=''
    (v1: last='Smith', first='John', suffix='Jr.')

Stays in collect_ignore_glob with a pointer comment; mypy-clean, so it
leaves the mypy exclude.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
57 tests: the stale 'U.S. District Judge' xfail is removed -- the 2.0
period-joined title derivation makes the long-desired expectation hold
(logged in docs/superpowers/plans/notes-m12-diffs.md for the M14
expected-changes list). 54 pass, 1 pre-existing v1 xfail remains.

2 tests fail on confirmed pipeline parity bugs, so the file stays in
collect_ignore_glob (mypy-clean, leaves the mypy exclude):

1. _lexicon._normalize strips INTERIOR periods, so 'J.R.' normalizes to
   'jr' -- a TITLES vocabulary entry; v1's lc() kept 'j.r' unmatched.
     HumanName('Smith, J.R.') -> title='J.R.'
     (v1: first='J.R.', last='Smith')

2. _assign's nickname rule (plan deviation #2) counts name pieces AFTER
   title peeling; v1's p_len==1 counted the whole segment before it.
     HumanName('Xyz. (Bud) Smith') -> last='Smith'
     (v1: title='Xyz.', first='Smith', nickname='Bud')

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All pinned live against v1.4 (2026-07-17):

- _normalize now matches v1's lc(): casefold + EDGE periods only.
  'J.R.' no longer collapses to 'jr' and hits the periodless titles
  vocabulary. Suffix-ACRONYM membership alone uses the period-free
  form ('M.D.' -> 'md'), mirroring v1's is_suffix, which removed
  periods only for the acronym test. The capitalization-exception
  lookup tries both forms (v1 cap_word parity), keeping
  'phd' -> 'Ph.D.' idempotent.
- _cap_word's conjunction lowering excludes initial-shaped words
  (v1 is_conjunction): 'scott e. werner' capitalizes to
  'Scott E. Werner'; an uppercase lone 'Y' stays 'Y'.
- FAMILY_COMMA's family segment peels per-piece suffixes after the
  first piece ('Smith Jr., John' -> family=Smith, suffix=Jr.).
- the lone-piece nickname rule counts the segment BEFORE title
  peeling ('Xyz. (Bud) Smith' -> title, given, nickname).

Six case rows pin the matrix; the three stale v2 unit tests that
pinned the pre-fix semantics are updated with v1 citations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All four queued parity bugs are fixed (2c45150); the three held files
run green (85 pass, 5 xfail). Two xfail flips settled in the process:

- 'Andrews, M.D.': the family-comma suffix peel makes the v1 xfail's
  long-desired expectation hold (last='Andrews', suffix='M.D.'; v1
  parsed first='M.D.') -- marker removed, diff logged in
  docs/superpowers/plans/notes-m12-diffs.md as
  fix(comma-family/suffix-routing).
- 'U.S. District Judge ...': the interim interior-period normalization
  that briefly made this chain was itself the 'Smith, J.R.' bug; with
  v1's lc() restored the name matches v1 again, so the xfail marker is
  restored with a comment recording why.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The file needs no bucket A/B/C edits (30 pass, 2 pre-existing v1
xfails); all 24 failures are component-value diffs traced to six root
causes, each reproduced on the core -- reported for fixing, not
classified:

B1 two-word trailing suffix: 'Jack Ma' -> given='Jack', suffix='Ma',
   family='' (v1: last='Ma'). Same shape the fix(suffix-routing) case
   row pins intentionally for 'Johnson PhD'; needs a decision on
   suffix-shaped words that are also common surnames.
B2 suffix-comma segments split per word: 'John Smith, V MD' ->
   suffix='V, MD' (v1 kept the whole segment: 'V MD'); also 'Jr. MD',
   'Q.C. M.P.'.
B3 split/period-joined suffix segments fail SUFFIX_COMMA detection:
   'John Smith, Ph. D.' and 'John Doe, Msc.Ed.' -> family swallows the
   whole head, given='' (v1: first/last split + suffix).
B4 #144 family-comma trailing suffix_not_acronyms rule missing:
   'Smith, John V' -> middle='V'; 'Maier, Amy Lauren I' ->
   middle='Lauren I' (v1: suffix).
B5 suffix-in-parens/quotes routing (#111, shipped 1.3) absent:
   'Andrew Perkins (MBA)' / 'MBA' / "MBA" -> nickname (v1: suffix);
   'Lon (Jr.) Williams', 'Col. (Ret.) Smith', '(Mgr.)', and the
   suffix_acronyms_ambiguous customization all diverge with it.
B6 suffix_delimiter edges: a trailing delimiter ('John Doe, MD-PhD-',
   delimiter '-') kills detection entirely; a multi-word side leaks the
   delimiter token into suffix ('MD, PhD, -, FACS, Fellow' vs the
   pinned drop rule); the delimiter-unset degenerate 'Steven Hardman,
   RN - CRNA' diverges from v1's documented limitation (first='-' vs
   first='RN').

Stays in collect_ignore_glob with a summary comment; mypy-clean, so it
leaves the mypy exclude.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rity)

CI's sphinx doctests caught this unported v1 rule: parse_nicknames
leaves suffix-shaped delimited content -- an unambiguous suffix word,
an unambiguous period-free acronym, or anything ending in a period --
in place for normal parsing ('Andrew Perkins (MBA)' -> suffix=MBA,
not nickname). The v2 spelling masks only the two delimiter spans so
the inner content joins the main token stream (spans keep indexing
the original, anti-#100). Two case rows pin it.

The three customize.rst doctest blocks that taught the removed
regexes.emoji/bidi and custom-delimiter-pattern idioms now teach the
2.0 replacements (Policy strip flags; Policy delimiter pairs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
58 tests: one bucket-C rewrite (the custom-title fold test moves its
'gunny' title onto a private Constants instead of mutating the shared
singleton); 57 pass.

1 test fails on the same B1 root cause already reported for
test_suffixes -- two-word trailing-suffix routing:

    HumanName('Anh Do') -> suffix='Do', family=''
    (v1: first='Anh', last='Do'; 'do' is the D.O. suffix acronym)

Stays in collect_ignore_glob next to test_suffixes' entry; mypy-clean,
so it leaves the mypy exclude.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The file needs no bucket edits (37 pass, 2 pre-existing v1 xfails); one
test fails on the join-stage sibling of the already-fixed _cap_word
bug: the classify-stage "conjunction" tag lacks v1 is_conjunction's
is_an_initial exclusion, so an initial that doubles as a conjunction is
joined into its neighbors. The group-stage #11 carve-out only exempts
the bare single letter ('e'), not the initial form ('e.').

    HumanName('john e. smith') -> first='john e. smith'
    (v1: first='john', middle='e.', last='smith')

Stays in collect_ignore_glob with a pointer comment; mypy-clean, so it
leaves the mypy exclude.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…flip

39 tests -> 35; 33 pass, 1 v1 xfail.

Bucket A/B (custom-delimiter mechanism removed, spec section 3):
- test_add_custom_nickname_delimiter becomes the raises pin (TypeError
  naming Policy); the remove/multiple/ambiguous-through-custom variants
  die with the mechanism they exercised.
- the regexes-override live-resolution test becomes a raises pin too
  (regexes item assignment raises, same uniform rule).
- the unresolvable-string-sentinel ValueError-at-parse test becomes a
  TypeError-at-assignment pin (2.0 enforces the invariant earlier).
- as_dict()['maiden'] empty expectation is '' outright (#255).

Held (bucket D, reported): the documented both-buckets misuse case
flipped precedence -- with 'parenthesis' in BOTH delimiter buckets, v1
processed nickname first (nickname='Johnson'); 2.0 gives
maiden='Johnson'. The v1 test exists precisely to catch this silent
change; needs fix-or-classify. File stays in collect_ignore_glob;
mypy-clean, leaves the mypy exclude.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 tests -> 4, all green; lifted from both skip lists.

Bucket B: the custom-regexes-without-commas shatter guard died with its
mechanism -- Constants(regexes=...) raises TypeError in 2.0 (migration
spec section 3 uniform rule), already pinned in test_python_api.py's
test_override_regex_raises. The four non-ASCII comma tests (#265) pass
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All pinned live against v1.4 (2026-07-17) unless classified:

- 'ma'/'do' join suffix_acronyms_ambiguous (data): common surnames
  need periods to read as credentials -- 'Jack Ma' keeps its family
  name while the approved fix(suffix-routing) rows keep theirs. The
  set's doc comment gains the surname criterion and the 2.0 gating
  note. Release-log item.
- classify's conjunction tag excludes initial-shaped tokens (v1
  is_conjunction): 'john e. smith' keeps its middle initial.
- the period-joined derivation moves to _vocab.period_joined_vocab,
  shared by classify AND segment's suffix-comma detection
  ('John Doe, Msc.Ed.'); chunk-level membership is v1's is_suffix
  (bare ambiguous acronyms count within period-joined tokens).
- segment's detection collapses an adjacent Ph./D. pair into one
  suffix unit (v1 extracted the credential pre-parse via fix_phd).
- extract iterates the nickname bucket first: when one delimiter
  pair sits in both buckets, the nickname reading wins (v1 order).
- the FAMILY_COMMA walk restores v1's order -- the first post-comma
  piece is the given BEFORE any suffix check ('Hardman, RN - CRNA')
  -- with one classified fix(comma-family) deviation: a LONE
  unambiguous suffix piece routes to suffix ('Andrews, M.D.').
  The trailing piece of a two-part name takes the lenient test
  (v1 #144: 'Smith, John V' -> suffix=V).
- tail comma segments render as ONE suffix entry each: continuation
  tokens take the 'joined' tag (space-join), a delimiter core inside
  a segment separates entries and is dropped, and a lone-core
  segment stays whole (v1 expand semantics).

Ten case rows pin the matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The batch-3 bug queue is fixed (c131dde): all four held files run green
(184 pass, 5 xfail). Two last touches in test_suffixes:

- Bucket D with classification: 'John Doe, MD-PhD-' (delimiter '-')
  keeps the no-space delimiter-core token whole, suffix='MD-PhD-' --
  fix(suffix-delimiter-rendering), same rule as the pinned 'RN/CRNA'
  case row; logged in docs/superpowers/plans/notes-m12-diffs.md.
- Bucket C: the constants-level suffix_delimiter test moves onto a
  private Constants instead of mutating the shared singleton.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- test_east_slavic_patronymic_order (26) and
  test_turkic_patronymic_order (39): all green; the only edits are
  typing -- the regex-inspection tests read patterns through the 2.0
  regexes proxy (typed object), now cast once at module level.
  Lifted from both skip lists.
- test_bound_first_names: bucket A -- the two is_bound_first_name
  predicate tests died with the v1 parsing hooks (#280); vocabulary
  behavior stays pinned through the parsing tests. 1 test held on a
  join-guard bug: the FAMILY_COMMA bound-given join does not fire when
  it would consume the whole post-comma segment
  ('salem, abdul salam' -> first='abdul'; v1: 'abdul salam').
- test_middle_name_as_last: no bucket edits; 4 tests held on a
  fold-order bug -- v1 PREPENDED middle_list to last_list so comma and
  rotated forms converge ('Hassan, Mohamad Ahmad Ali' ->
  last='Ahmad Ali Hassan'); v2 renders the folded family in token
  order ('Hassan Ahmad Ali').

Both held files stay in collect_ignore_glob with repro comments;
mypy-clean, so they leave the mypy exclude.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Its sole subject, nameparser.parser.group_contiguous_integers, was a v1
join_on_conjunctions helper; the 2.0 pipeline's group stage merges by
piece index with no such utility, and the swap left no export. Nothing
survives to port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The three-code-paths variation bank (180+ names, cross-checked across
no-comma / lastname-comma / suffix-comma spellings) passes wholesale;
only two v1-internals touches were needed:

- Bucket B (#255): the forced empty_attribute_default='' step is gone
  -- empty attributes are always '' in 2.0, which is exactly what the
  format strings needed.
- Bucket A: the private hn._members iteration becomes the public
  as_dict() key set (same seven fields).

Lifted from both skip lists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 117-case brute-force bank passes wholesale against the facade with
zero edits. With it, every v1 test file is reconciled and mypy-clean,
so the TEMPORARY [tool.mypy] exclude is deleted entirely; the conftest
collect_ignore_glob shrinks to its final remnant -- the two files each
held on one confirmed parity bug (bound-given whole-segment join;
middle_name_as_last fold order), to be deleted when those land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- bound given names join freely in FAMILY_COMMA's post-comma segment
  (v1 reserve_last=False, parser.py:1366): 'salem, abdul salam' ->
  given 'abdul salam'. _group_segment takes the per-segment
  bound_required (3 main / 2 post-comma / 0 family segment, which v1
  never bound-joined).
- middle_as_family renders in v1's prepend order: folded tokens carry
  vocab:folded-middle and the family view orders them before the
  original family tokens (spans cannot reorder, anti-#100) --
  'Hassan, Mohamad Ahmad Ali' -> family 'Ahmad Ali Hassan', matching
  the no-comma and rotated forms. The stage-ownership contract gains
  tags for post_rules accordingly.

With these, every v1 test file is reconciled: the conftest
collect_ignore_glob is GONE (the mypy exclude went earlier). The full
v1 suite runs against the facade: 1160 passed, 12 xfailed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 2.0 modules section gains the facade layer: the re-export swap of
nameparser.parser/config, the _facade/_config_shim mechanisms
(dirty-tracking, v1-shaped pickle, #280 hook warning), the layering
rule, and the differential-harness pointer. The v1 #256
extension-pattern paragraph is marked historical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants