fix(#533): a maiden clause ends at a trailing credential, and reports either way - #534
Merged
Merged
Conversation
A maiden marker took every word after it up to the first suffix WORD
or the trailing numeral, and the ambiguous credential class was not
among the words that stopped it -- so the last trailing position in
the library where a member of that class was read in silence was the
end of a maiden clause. `parse("Jane Doe nee Smith MA")` gave maiden
'Smith MA' and reported nothing, where `John Smith MA` gives suffix
'MA' and reports. It was order-sensitive besides: `nee Smith MA PhD`
gave maiden 'Smith MA' while `nee Smith PhD MA` gave maiden 'Smith',
so whether the word was read at all depended on which side of the
unambiguous credential the writer put it.
The words a marker takes now end where a trailing credential begins,
where the rule that reads the name left standing reads the word as
one -- both as the name is written and as the take would leave it,
which is the same double question the trailing roman numeral is asked
and for the same reason: the count of words to spare includes the very
words the marker removes. Which rule does the reading depends on where
the clause stands. With no comma it is the trailing peel; after a
family comma it is the reading the end of the given part takes, where
the comma has already settled the count and the writing decides alone;
before that comma, and past a second one, no trailing rule reads those
words at all and the clause keeps them.
A member that is the ONLY word after the marker stays the maiden name,
whatever its writing says: the marker announces a name, and the rule
gives a word up only where a maiden name is left standing. That is a
clamp rather than a veto -- `Doe, J. nee MA ba` keeps maiden 'MA' and
reads suffix 'ba'.
Either reading is reported, on a channel of the walk's own: group
suppresses its particle-chain emitter after a family comma, and a
credential ending a clause is not that fork.
One walk, one peel: the numeral reading stops going through
`trailing_start` and calls the pair it wrapped, so the acronym half
needs no second peel and a maiden name that does not trigger got one
frame cheaper. #531's reading moves into
`_pieces.credential_at_the_given_slot`, shared by both callers rather
than spelled twice; measured over 12,630 parses in two processes, the
refactor is byte-identical, and it costs one frame on a family-comma
name carrying a class member.
Over every corpus name under six policies the change moves 0 roles and
adds 1 report. The agreement sweep it ships -- 2016 pairs of a name
with a clause against the same name without one -- went from 984
disagreements to 0 outside the one-case-head class, whose 114 members
are the accepted cost of `own_words` stopping at the marker.
The rows this adds put three names inside both `fix(#274)` and
`fix(#379)` in the 1.4.0 ledger, whose fields nest, so file order
alone would pick the winner and
test_every_order_decided_contest_is_declared goes red. The earlier
rule carries a precedes_narrower block saying why it wins, and the
negative control beside it records the contest either way; the
remaining ledger work is its own commit.
Review fixes, comments and tests only: `listed_lean`'s frame note
names its third caller, SUFFIX_OR_NAME's silent positions count four,
both maiden guards carry the negative controls they were measured
against, the shared predicate costs one frame PER MEMBER, the
`one_case` denominator is the recipe's own (10,740), and the span
property test compares spans -- with a count that keeps it from
being vacuous.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Written from the gate's own output rather than predicted beside the change. Over the corpus AS IT STOOD, #533 moves ONE name -- `John née Jones Smith Ma`, which keeps every field and gains a report -- so at 2.0.0 and 2.1.0 the fix(#445) rule that already carried it gains `_ambiguities` and nothing else, and at 2.2.0 and 2.3.0 it joins the report-only rule below. Everything else here is about the 29 corpus names this change's own rows admit. Those 29 are not all this change's, and each was measured at the parent 2f57ff2 as well as at its baseline to find out. Where the tree reads a name exactly as it read before #533, the rule names the change that DOES move it: fix(#531) gains `Doe, J. ba` and `Smith nee Jones, Jane MA`, fix(#436/#437) gains `Jane Doe nee Smith PhD MA` at the three 2.x baselines that still render its run with a comma, a fix(#424) rule carries `Jane Doe nee Smith V` at 2.0.0 and 2.1.0, and 1.4.0 gains two rules named for #274 -- one for the six names whose clause keeps a credential v1 read as a post-nominal, one for the name whose clause an UNAMBIGUOUS credential had already ended. A `fix(#533)` label on any of them would attribute a released reading to this change. One existing rule needed narrowing rather than widening. 1.4.0's `fix(#424/#445) accepted: the maiden walk keeps a bare acronym` was (?i)-anchored, so it reached `John née Jones Smith MA` as well as the Title-cased spelling -- and that name now does the opposite of what the rule says, the clause giving the MA up. The anchor is narrowed to the two spellings whose reading it describes, `JOHN NEE JONES SMITH MA PHD` joins it, and the caps-MA spelling gets a compound rule naming #445 and #533 together. The #533 rules themselves split by what they SAY rather than by field set: the clause giving a credential up; the clause keeping one and reporting it; the dotted by-shape spelling, which leaves v1 rather than restoring anything and is recorded as the accepted cost it is; the marker phrase; and the two names where the released member lands somewhere other than the trailing peel, silently, so their rule declares no `_ambiguities` to absorb a report with. At 1.4.0 the change's rows also create an order-decided contest, declared in commit 1 with a `precedes_narrower` block on the wider rule. Its `why` said "the whole of the diff is the marker leaving the name", which two of its three names falsify -- both also empty the `suffix` v1 read, which is in neither rule's fields -- so the block now says what the contest is over and where those two actually land. One name is a RESTORATION and its rule says so: `John Smith nee Jones R.A.I.` read suffix `R.A.I.` at 2.3.0, moved into the maiden name during this unreleased cycle when #516 retagged the token out of the certain-suffix class, and reads 2.3.0's way again here. It was in no corpus file, so no gate had ever compared it. The one radar name this change intended is classified rather than left on the radar unread, the way #531's caseless rule was: `田中 太郎 旧姓 佐藤 MA` at 2.1.0, 2.2.0 and 2.3.0, the marker's own rule already carrying it at 1.4.0 and 2.0.0. Radar unclassified goes 0/5/7/8/8 to 0/5/6/7/7. All five gates exit 0 with `unexplained: 0`: 1.4.0 corpus 1304 names; intentional diffs 508 2.0.0 corpus 1311 names; intentional diffs 455 2.1.0 corpus 1311 names; intentional diffs 367 2.2.0 corpus 1311 names; intentional diffs 223 2.3.0 corpus 1311 names; intentional diffs 108 No parser, case row or corpus file is touched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
rules.md#M2's statement gains the reader and the reason behind the credential stop commit 1 put in it: the double check stated once for both forks -- the count of words to spare includes the very words the marker removes -- and the reader named, the trailing rule where a trailing rule reads the part (no comma, and before a SUFFIX comma, measured to read the same way), the end of the given part after a family comma, nobody before that comma or past a second one. The lone-word floor is stated as the deliberate divergence it is from the `née V` / `née PhD` precedent, with the reason: that class carries no evidence of which it is. Reporting is stated at the boundary the tree actually holds -- a member ENDING a clause some rule reads is reported where the clause KEEPS it, while one it gives up reports only where the position it lands in reports, which `Doe, Dr. nee Smith MA` and `Berg, abdul nee Jones MA` would have falsified as a universal. #S2 and #S3 gain the slot in their enumerations; #P6 needs no change, the `do` reading here being #531's unchanged. A second Accepted row records the trailing-title boundary, where the two spellings disagree. Its two example lines were already corpus names, so corpus_rules.jsonl grows by two and no ledger claim moves. decisions.md gets one bullet under M2 amending the 2026-08-22 #424 entry -- which is left exactly as it landed -- and one under S2. The first records the double check, the reader table, the clamp and the two names a veto got wrong, the `do` pair and the accepted costs; it also re-measures the `one_case` plumbing those sites carry, which the 2026-09-18 entry recorded as inert under the numeral-only reading. The pair is the finding: 0 over the corpus as it stood, 36 on six names over the corpus with this change's own rows, the plumbing live either way. The second records the shared predicate, its frame price, and the one-case-head exception as the M2 instance of #492's deferred question. The release log gains one bullet. One correction in place: the #530 bullet said "the one slot this bullet left silent", which two bullets now close rather than one. docs/customize.rst's two 2.4 switch entries enumerate the positions each switch reaches, so both gain this slot. Three sweep findings beyond the plan's table, each a claim no test reads. The 1.4.0 ledger's fix(#424/#445) prose quoted the superseded Accepted row and said the fork is left to assign; it now says the WRITING declines the word. test_parser.py's "the walk takes the numeral only" is false of the tree, and the row under it is really about the member not being TRAILING, so it says that and gains the mirror case. And the judged-token sentence test_properties.py attributes to mechanisms.md is decisions.md#S2's, while the P3 sentence the same comment leans on says something P3 does not say -- both corrected to what those documents carry. mechanisms.md gains a field note for the trap commit 2 hit: a ledger rule EXPLAINS a diff and nothing checks that its sentence still DESCRIBES it, so a too-wide anchor kept `unexplained: 0` green over a name that had started reading the opposite way. Attribution against the parent found it; the gate could not. Review round: the design-docs review found a MOVER that reports nothing -- `Jane Doe, Jr nee Smith MA`, where the take leaves a post-nominals-only segment that is read whole -- which is the pre-existing no-name-word-in-front silence, written as a title's and reached by a post-nominal too; stated in M2 and S2, widened in _types.py, added to the M2 bullet and the release note, and pinned by a unit test rather than an example line, the name differing from this tree at all five baselines for reasons mostly not this change's. M2 and P6 now cite each other. The two _group.py denominators are re-measured: 36 of 10,752 (1,792 names) and 0 over 21,504, the latter with the probe shown live (site reached 394 times, tag admits 310). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #534 +/- ##
==========================================
+ Coverage 98.74% 98.76% +0.01%
==========================================
Files 45 45
Lines 3509 3554 +45
==========================================
+ Hits 3465 3510 +45
Misses 44 44 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…use keeps it A. The clause could give a word up "as a credential" and the word then landed in `given`, `middle` or `family` instead -- 1,446 violating parses of 855,108 at d97d3eb, 0 at the parent 2f57ff2, 0 here. The stop now requires the release to survive: GIVEN_SLOT needs a name word ahead of the member in the view it would leave, and either reader withdraws where a join below the marker pass would take the word (P2's chain, P5's bound-given join). `Doe, Prof. nee Smith A.B.`, `Jane Doe, Jr nee Smith MA`, `Doe, Dr. nee Smith MA`, `Berg, abdul nee Jones MA`, `Berg, Jane van der nee Smith DO` and `Jane Doe nee Smith DO DO` read as 2f57ff2 read them, and report. Pinned by a new property over an 8,466-parse grid (790 failures at d97d3eb). Silent movers vs the parent: 4,100 tokens in 59 shapes before, 0 now. B. Delimited clauses keep the whole span and report nothing, unchanged -- documented in rules.md#M2, _types.py and three case rows. C. assert_never on the reader (with a test that reaches it and pins len(TailReader) == 3 and group()'s mapping), required keyword-only `reader`/`maiden_ambiguities`, `at = left.index(stop)`, a frame-free membership assert in `credential_at_the_given_slot`, and MaidenIndices named beside MaidenTake. D. `len(head) == 1` fires 1,440 times in a population that holds the Ph. D. shape (the recorded 0 was a population artefact) and is kept: byte-identical over 905,796 parses but +15 frames on `BERG, ABDUL Z DOMU MA PH. D.`. `stop < trailing` and the emitter's `len(last) == 1` are REMOVED -- structural, 0 hits over 1,760,904 parses, byte-identical, frame-neutral. Comment fixes: the sibling site is `segment_suffix_reading`, `peeled.names` indexes `rest`, OPTION 1 is THE FIRST-WORD FLOOR, the floor's example now bears a clause. E. Agreement test widened to three markers x three policies (1,026 of 18,144, exactly 9x, digest-pinned as a SET); the span test gains two-member tails so the maiden emitter and assign's peel actually pair (1,944 parses, 0 before); a `maiden_clause` shape guards M2's view (4.05-4.12 clean, 12.2-12.6 on a per-piece rebuild); four missing rows added. The bare delimiter core ending a clause predates this change and is recorded in decisions.md rather than fixed. Suite 9351/324/4, mypy and ruff clean, call_count 412.00/449.00, `Doe, John MA` 311 and marker-less names +0, coverage 100% on the three modules touched, and all five differential gates end 0 unexplained. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
No behavior change: every reading over 222,208 names (the corpora, the case rows and 220,686 generated maiden-clause names) under six policies -- 1,333,248 rows, plus the facade -- is byte-identical to b45707a, and the reference frame counts hold at 412 / 449. - _pieces: `trailing_start` loses its `numeral_only` flag. It had one caller, the maiden walk's numeral re-ask, which now asks the `peel_walk` + `peel_trailing` pair and reads `.numeral`, as the walk's first numeral read already did. One frame cheaper on 'John nee Jones Smith V' (371 -> 370); no probe rose. - _group: the maiden emitter binds its token once. - Comments in _group, _pieces and _assign compressed to what the code does, the measurement and the reason; every measurement, population and price kept, the narration of earlier drafts removed. Rejected by measurement: a closure for the left / view / view_tags triple costs a frame on 5 of 12 probes, and the two views are never the same list (they differed 30 of 30 where both run), so there is no "compute once". mechanisms.md#ONE-PREDICATE-PER-QUESTION said `peel_trailing` has no caller outside `_pieces.py`; #533 gave it one, the maiden walk, and the entry now says why that is a reader of the peel rather than a second spelling of it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
derek73
added a commit
to IMGillusion/python-nameparser
that referenced
this pull request
Sep 22, 2026
…nd the ph removal is on the record Maintainer fix-up on PR derek73#521 after the merge from master, the eight items of the 2026-09-16 review comment: - decisions.md R4: the reach is remeasured on the merged tree with the recipe's comparator named (master's `_cap_word` at 23e52dc), and the two earlier snapshots are explained rather than replaced -- the `ph` removal took 13 forced movers back to `Ph. D.`, and the rows PRs derek73#530/derek73#532/derek73#534 added account for the rest of the drift. The exceptions map's job is stated as the spellings `str.capitalize()` gets wrong, not "non-all-caps" (II/III/IV are all-caps); the precedence over Mac/Mc and the role-not-tag reach of the gate are recorded. - decisions.md: an `Excluded (SUFFIX_ACRONYMS — ph)` block beside esq's, so a wordlist sweep does not put the fragment back. - rules.md R4: the non-acronym clause is qualified by the exceptions map (`john smith ii` -> `II`), the acronym repair's precedence over the Mac/Mc convention is stated and pinned by a `john smith mcse` row (`McSe` without it), and the Accepted paragraph names which clause it is about and states the acronym repair's contrast on a spliced suffix. - release_log.rst: the bullet moves from the shipped 2.3.0 section to 2.4.0 with the current digits, and `ph` leaving the set gets its own bullet in the rai/cha shape. - corpus_rules.jsonl regenerated (three new R4 rows). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #533.
What changes
A maiden marker's clause now ends before a trailing member of the ambiguous credential class (
ba,do,ed,jd,ma, dotted acronyms, and all-caps unlisted words underunlisted_caps_suffixes) where the reader that will read the name left behind takes that word as a credential — and the word reportssuffix-or-namewhichever way it reads. After #531 this was the last trailing position where the class was read in silence.Jane Doe nee Smith MA/Doe, Jane nee Smith MASmith MA, silentSmith, suffixMA, reports (1.4.0 read the suffix)JANE DOE NEE SMITH MA/jane doe nee smith maJane Doe nee Smith Ma/Jane Doe nee Yo-Yo MaJane Doe nee Smith MA PhDSmith MA, suffixPhDSmith, suffixMA PhD(now agrees withPhD MA)John née Jones Smith Ma(the corpus name #424 protected)Jones Smith MaJohn Smith nee Jones R.A.I.Jones R.A.I.R.A.I., reports — a restoration: 2.3.0 read the suffix and this unreleased cycle (#516) had broken itJane Doe nee MA/nee MA PhDMA, silentDoe, Jane nee Smith do/DoDoe, Jane nee Smith DOSmith DOSmith, suffixDOJane Doe nee MA Smith,nee Smith V,nee Smith PhD,(nee Smith) MADesign calls made with Derek
John van der Berg Vreads familyvan der Berg V— the particle chain and the maiden walk take a trailing numeral as a name word #424's double check extended to the acronym.John van der Berg Vreads familyvan der Berg V— the particle chain and the maiden walk take a trailing numeral as a name word #424 left the acronym out because the rule counts words the take removes (John née Jones Smith Ma→ familyMa). The check had to be stronger than the numeral's: it asks whether the reader takes that word, not whether it takes anything (JOHN NEE JONES SMITH MA PHDwould otherwise makeMAthe family name).Jane Doe nee MA), on purpose unlikenée V/née PhD, where certain suffix vocabulary makes the marker decline. Implemented as a clamp: the stop never takes the first word after the marker (Doe, J. nee MA ba→ maidenMA, suffixba).Doe, John MAreadMAas a credential, as 1.4.0 did, or at least report the fork? #531's given-part slot, including its floor and thedocarve-out (Doe, Jane nee Smith MA dokeepsMA do). That reading now lives in one shared predicate,_pieces.credential_at_the_given_slot, at a recorded cost of one frame per member asked (Doe, John MA310 → 311). A clause in the family segment or a third comma part has no reader and is untouched.DOE, JANE nee Smith Mareads the credential whereDOE, JANE Makeeps the name — the case fact is computed from the person's own words; 114 of 2,016 generated pairs, carried by the agreement test as a counted structural allowlist); trailing titles are not transparent inside a clause (nee Smith MA Prof.vsnee Smith Prof. MA— follow-up drafted); one-case records with a two-word birth surname (JANE DOE NEE YO-YO MA→ suffixMA, asJANE YO-YO MAreads).The invariant (from the PR review round): every word the clause gives up ends the parse in
suffix; otherwise the clause keeps it, and reports. The first version asked #531's slot even where the take removed the only name word that slot depends on, soDoe, Prof. nee Smith A.B.read givenA.B.andJane Doe, Jr nee Smith maread givenJr, silently; and a releasedDOcould be swallowed by a particle chain (Berg, Jane van der nee Smith DO). All of these now keep their maiden words, as master reads them, and report. That also retires the two silent movers an earlier draft accepted (Doe, Dr. nee Smith MA,Jane Doe, Jr nee Smith MA) andBerg, abdul nee Jones MA. A property test over 8,466 generated parses pins the invariant (790 failures on the pre-fix commit, 0 now); a 855,108-parse sweep finds 0 silent movers.Delimited clauses are settled by their delimiters (Derek's ruling):
Jane Doe (nee Smith MA)keeps the whole span as the maiden name and reports nothing, the writer having drawn the boundary;Jane Doe (nee Smith) MAreads suffixMAand reports. Unchanged behavior, now documented and pinned.Commits
fix(#533)—_maiden_take's stop, the reader selection, the shared predicate (proved behavior-neutral by oracle before the behavior change), a report channel of its own ingroup, 46 case rows + 1 flipped, unit and property tests, example lines, corpora and claims.test(#533)— ledgers at five baselines, each diff attributed by measuring against the parent: pre-existing readings go under the change that caused them, notfix(#533). A(?i)1.4.0 regex was silently claiming a name that now reads the opposite way; narrowed.docs(#533)— rules.md M2/S2/S3, decisions.md (### M2amending theJohn van der Berg Vreads familyvan der Berg V— the particle chain and the maiden walk take a trailing numeral as a name word #424 bullet without editing it;### S2), a mechanisms field note on that ledger trap, release log, customize.rst.fix(#533): review round— the invariant above with its two view conditions;assert_neveron the reader, the structure-to-reader mapping pinned by a test, required keyword parameters; two structurally dead branches removed and one kept as a measured-inert guard with a reproducible control; the agreement test now pins the exception SET by digest over 3 markers × 3 policies (1,026 of 18,144); the double-report test now actually produces the maiden + peel pair (0 → 1,944 parses); amaiden_clausebenchmark shape guards linearity (4.1 clean vs 12.4 on a per-piece rebuild).Verification
suffixexcept where the clause-less control reads the same; no token reported twice; 10 of 12 mutants killed, the two survivors kept as measured-inert guards with their negative controls recorded.John née Jones Smith Ma).🤖 Generated with Claude Code