Skip to content

Case and shape decide a bare ambiguous credential acronym (#289, #516) - #530

Merged
derek73 merged 12 commits into
masterfrom
claude/289-516-case-signal
Sep 19, 2026
Merged

derek73 merged 12 commits into
masterfrom
claude/289-516-case-signal

Conversation

@derek73

@derek73 derek73 commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Closes #289, closes #516

A bare ambiguous credential acronym is read by the evidence the writing carries, not by position alone.

#289: written case, switch-free, listed set only

input before after
Jack MA family MA suffix MA
John Smith Ma suffix Ma middle Smith, family Ma
Smith, MA given MA family Smith, suffix MA
John Smith, MA given MA, family John Smith given John, family Smith, suffix MA (1.4.0 restored)
John Smith, Ed, john smith, ma, JOHN SMITH, MA given suffix (1.4.0 restored, all three)
JOHN SMITH MA, ANH DO, jack ma, Jack Ma, 毛泽东, MA unchanged

A name written wholly in one case carries no contrast and keeps the positional rule, and a member written wholly in lowercase carries no lean. At a comma the words-to-spare count is of NAME words and decides first, so Smith Jr., MA keeps its family; the lean is read only where the count leaves the word a name.

#516: two Policy switches with different defaults

unlisted_dotted_suffixes (default True): a token of two or more alphabetic period-separated chunks that no vocabulary claims joins the class by shape, and position decides. John Smith X.Y.Z. gives suffix, Jack X.Y.Z. keeps its family, both reported. The roman-chunk accident retires narrowly whether or not the switch is on, so Jack X.Y.I. reads family again while Msc.Ed., JD.CPA, Lt.Gov. and J.씨 keep their readings.

unlisted_caps_suffixes (default False): the same for an all-caps word found in no wordlist, in a mixed-case name. Opt-in because Jean Pierre DUPONT gives family Pierre, suffix DUPONT with it on, and a swallowed family name is the worse failure. Two-word Jean DUPONT and Minjun KIM keep their family either way.

Reports. No new AmbiguityKind. Every decision at these slots emits SUFFIX_OR_NAME, and the comma's own decision about such a word (credential run or given name) is reported for the first time. The attachment fork after a family comma, Berg, Jan vd, already reported. One report goes away: Steven Hardman, MD, DO, DDS stops reporting comma-structure.

Accepted costs, recorded at decisions.md#S2. Freiherr von Berg MA moves to family von Berg, suffix MA. abdul Smith Jr Ma reads Jr as a middle name, since the surname lean stops the peel before the suffix behind it.

Decisions made during execution, for your review. Each is a dated bullet in decisions.md#S2.

  • A caseless script is inert only as a lean. The comma name-word count still applies, so 마틴 킹, MA and 田中 太郎, MA read suffix MA again, as 1.4.0 did. 毛泽东, MA is unmoved.
  • The comma report tracks the fork consulted, not the lean, as the trailing slot always has. Smith, ma, 毛泽东, MA and Smith, MA PhD each report once.
  • The comma-structure predicate never sees a by-shape class. Admitting it lost the family on Smith Jr., A.B..
  • Shape chunks must be alphabetic, so 1.4 is not an acronym, and the shape is refused in scripts with no period abbreviations, so John Smith 田.中. keeps its family.
  • "Unlisted" means in no wordlist of any kind, and the multi-word run (John Smith, LEED AP) is the caps class's alone.

Shape of the change. One fact promoted to ParseState.one_case, one own-words helper sharing classify's marker tagger, one lean predicate read at three sites, and one tag for a claim that comes from the writing rather than the vocabulary.

Ten commits. Six build commits: the prerequisite, the lean, the dotted switch, the all-caps switch, rules.md with the corpora and the five ledgers, and the design and release record. The three behavior commits are deliberately red on the ledger digest guard and on the rules.md example lines they move; the ledger commit is the recovery point. Four review commits follow, all green.

Whole-PR review round. Five reviewers went over the finished branch, and two more verified the fixes. What changed as a result:

  • The suffix-or-name report is restored where a particle chain absorbs an acronym the lean declined. John van der Berg Ma had gone silent.
  • The script-segmentation stage now reads the recorded case fact, so Kim김민준씨, MA peels its honorific the way Kim김민준씨, PhD does.
  • A dotted credential in a third comma segment no longer gains a comma-structure report (John Smith, MD, R.A.I.).
  • "In no wordlist" is derived from the lexicon's own field roster, so a caller's added surname is respected by the all-caps switch.
  • The two new Policy fields moved to the end of the dataclass, so positional 2.3 calls keep their meaning.
  • A listed member in a custom lexicon can no longer be shape-tagged and lose its lean.

Recorded rather than changed, in decisions.md#S2: digit-dotted tokens such as John Smith 1.4.2 move out of the suffix reading silently, and the comma report covers the first word after a family comma but not the trailing slot of the given part. That slot is a 2.0-era reading this PR does not move (Doe, John MA reads middle MA; 1.4.0 read suffix), and it is recorded as open.

Verification. Full suite green, mypy and ruff clean, frame count unchanged on the reference name with _CALL_BASELINE untouched, and the differential at 1.4.0, 2.0.0, 2.1.0, 2.2.0 and 2.3.0 with 0 unexplained. Seven radar rows at 2.3.0 are this change's own unclassified suffix-or-name reports.

🤖 Generated with Claude Code

derek73 and others added 6 commits September 17, 2026 20:18
The fact #527 computed as a local in classify becomes a field beside
`order`: the suffix slot, the post-comma slot and the tail-segment
reading all consult it, and two stages deciding it apart is what
recording it prevents.

`_pieces.own_words(state)` is the span both sites ask for -- the
name's own word texts and the maiden-clause cut. classify hands over
the marker-run map it already built and keeps its answer byte for
byte; a caller that runs before those tags exist omits it and
`_vocab.first_marker_head` walks the texts instead. The two spellings
can disagree about the CUT where a phrase-entry head completes no run
('z' of 'z domu'); measured over the 1174 corpus entries, that is two
names, and the one-case verdict differs on none.

rules.md#P3's `implemented:` list gains `_pieces.py`, which is where
its own-words doctrine now lives, and its own-words paragraph says
the span is shared with S2 and C1 rather than being P3's alone. No
example line moves.

No behavior change: every corpus name parses identically, the
differential is clean at the default baseline, and the frame count
moves by the helper's own call.

Refs #289, #516

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
In a name written in more than one case, an ALL-CAPS member of
`suffix_acronyms_ambiguous` is written the way a credential is
written: `Jack MA` gives given 'Jack', suffix 'MA' where it gave
family 'MA'. A member in any other cased form is written the way a
surname is written and stays one even with words to spare:
`John Smith Ma` gives family 'Ma' where it gave suffix 'Ma'. A
lowercase member carries no contrast, a name written wholly in one
case carries none at all, and both take the words-to-spare count
exactly as before -- `JOHN SMITH MA`, `ANH DO` and `jack ma` do not
move.

The same reading reaches the comma forms, at three sites rather than
two: the trailing peel, the post-comma given slot, and the wholly-
suffix test segment asks of its own tail segments. `Smith, MA` gives
family 'Smith', suffix 'MA'; `Steven Hardman, MD, DO, DDS` stops
reporting `comma-structure`, its third segment now reading as the
credential run it is. At a comma the words-to-spare count is of NAME
words, so `Smith Jr., MA` keeps its family where a token count would
have handed it to `given`, and it reaches the listed set in any case,
which restores 1.4.0's reading of `John Smith, Ed`, `john smith, ma`
and `JOHN SMITH, MA`.

Every decision at these slots reports the existing SUFFIX_OR_NAME,
including -- for the first time in this library -- on a comma path.

Accepted and pinned rather than repaired: `abdul Smith Jr Ma` reads
`Jr` as a middle name, the surname lean breaking the peel at `Ma`
before the genuine suffix behind it is reached.

Refs #289

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Policy.unlisted_dotted_suffixes, default True: a token of two or
more period-separated chunks that no vocabulary claims joins the
ambiguous credential class by SHAPE, and the words-to-spare rule then
reads it. `John Smith X.Y.Z.` gives suffix 'X.Y.Z.' where it gave
family; `Jack X.Y.Z.` keeps its family and gains the report the fork
always deserved. Case is irrelevant here -- the periods are the
signal -- so `john smith x.y.z.` reads as its mixed-case twin.
Whole-token vocabulary still wins ('M.A.', 'Ph.D.', 'A.B.C.'), a
single trailing period is still not this shape, and a leading dotted
run is untouched. Switch off: name material everywhere, as 2.3 did
for a token no chunk claimed -- the roman-chunk retirement is not
behind this switch -- and the fork is still reported.

The roman-chunk accident retires NARROWLY with it: the chunk rule no
longer fires where every chunk the vocabulary matches is a single
ASCII character, which is measured to be {'2', 'i', 'v'} -- so
`Jack X.Y.I.` reads family again (1.4.0's reading) while 'Msc.Ed.',
'JD.CPA', 'Lt.Gov.' and 'J.씨' keep their chunk-derived readings. The
wide retirement was measured and rejected: it costs a real credential
for nothing this change wants. A bare digit chunk is never an acronym
by shape either ('Smith, 1.4', 'John Smith 1.4' stay name material,
unchanged) -- the shape verdict requires every chunk to be alphabetic.

The class claim says where it came from: `shape:acronym` rides beside
`vocab:suffix-ambiguous`, in a new namespace, because `vocab:` records
membership and the word is not in the vocabulary. The comma form's
own candidate test (`_vocab.ambiguous_class_candidate`) admits a
by-shape member the same way, so `John Smith, A.B.` flips to the
credential run and `Smith, A.B.` reports the fork it declines.
`is_wholly_suffix` never admits the shape class directly -- doing so
bypassed the words-to-spare count entirely and, combined with C1's
legacy token-count disjunct, misread `Smith Jr., A.B.` as a one-word
given; the comma form's structure decision and report both go
through `ambiguous_class_candidate` alone.

Two follow-ups from the previous commit's review, since they touch
the same helper: `_pieces.leans_credential` is renamed `listed_lean`
(the name now says what it returns), and a parametrized test pins
`_vocab.is_title_shaped` and `_pieces.is_leading_title` in agreement
over the union of their own example tables.

Refs #516

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`Policy.unlisted_caps_suffixes`, default False: with it on, an
unlisted all-caps word of two or more letters with no interior period,
in a name written in more than one case, joins the ambiguous
credential class by shape and the words-to-spare rule reads it --
`John Smith XYZ` gives suffix 'XYZ', `John Smith, XYZ` gives given
'John', family 'Smith', suffix 'XYZ'. Off, nothing changes and nothing
is reported.

Off by DEFAULT, and the asymmetry with the dotted half is the
decision rather than an oversight: an all-caps surname is a real
writing convention that shape cannot separate from a credential.
`Jean DUPONT`, `Jean Pierre DUPONT` and `Minjun KIM` read as family
names at 1.4.0, at 2.3 and at this default, and `Jean Pierre DUPONT`
gives family 'Pierre', suffix 'DUPONT' with the switch on. A
swallowed family name is the worse failure, and default-off is 1.4.0
parity for that whole class.

The predicate is `isalpha() and isupper()` at two characters or more,
so a digit anywhere disqualifies the token and a single capital stays
an initial; UNLISTED excludes anything whole-token suffix vocabulary
claims, `MC` among them.

The all-caps half is the first shape this class wears across MORE
THAN ONE token: `_vocab.ambiguous_class_candidate` gains a `one_case`
parameter, and `_segment.py`'s structure decision generalizes its
single-token candidate test to a whole post-comma RUN, so
`John Smith, LEED AP` reads suffix 'LEED AP' (rules.md#C1's
`deviates: #291` line coming true) while its one-pre-comma-word twin,
`Smith, LEED AP`, still declines the structure flip and reports
through assign's existing family-comma emitter. The membership test
is the one candidate check in this class that needs the case fact at
all; the run test is gated behind the switch and tried only after the
listed/dotted check declines, so a non-candidate comma name
(`Smith, John`) still never forces it -- measured 0 frames on that
name and its siblings at the default, and 0 on the reference name.
`is_wholly_suffix` deliberately still does not see this class, by
shape or by run, matching the dotted half's own decision.

`tests/v2/test_facade_cases.py`'s `_UNTRANSLATED`/`_CORE_ONLY_IDS`
gain the new field and its case rows, the same treatment
`unlisted_dotted_suffixes` got: no v1 spelling exists for a by-shape
credential class.

Refs #516

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
rules.md#S2 gains the written-case evidence beside its count, says
the case is read SECOND at a family comma, carries the opt-in
all-caps shape, and retires the Accepted sentence this arc repairs
("with words to spare, a bare ambiguous acronym reads as a suffix
even beside an East Asian surname it more likely belongs to");
`Jack Wei Ma` moves with it. rules.md#S3 says the chunk rule no
longer fires where every matched chunk is a single ASCII character,
that an unlisted multi-chunk word is read by position, and what the
by-shape class excludes -- a non-alphabetic chunk, a script that
writes no abbreviations, a lone trailing period. rules.md#C1 carries
the comma-quiet exception, scoped to this class, counts NAME words,
puts that count ahead of the written case, and retires both "Both
modes consult the vocabulary alone" and the claim that this is the
one place a comma path reports at all -- C2's flag reports a shape
the parse could not recognize, which `AmbiguityKind`'s docstring now
says too. rules.md#P2, #P5 and #H5's `Ma`/`MA` example lines move
with the readings, and the two Policy switches reach the doc through
the annotation slot, with their off-by-default readings executable
beside the default ones.

Every diff is classified at all five baselines, with the gate's own
output as the authority: new fix(#289) and fix(#516) role rules and
one `_ambiguities` rule per 2.x ledger, fix(#424)'s `fields`
re-scoped for the third reading of `Freiherr von Berg MA`,
fix(#425)'s re-scoped where the surname lean stops one of its two
names diffing at all, fix(#316) gaining the one-case spelling H5
needs to keep demonstrating its own transparency clause, and the esq
rule declared `dormant` where the by-shape class restores a
baseline's own reading by a different route. Dated amendments where
a comment rested on "the two-word Jack Ma is kept intact" -- still
true for that spelling, false for its all-caps twin -- and where
`radar unclassified` being 0 everywhere was load-bearing.

Refs #289, #516

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
decisions.md gains a `### S2` entry -- the first under that key -- for
the whole arc: the lean and the argument for keeping the judged token
in the span, the caseless inertness, the comma extension and its reach
to the listed set, the two switches and why their defaults differ, the
narrow chunk retirement and the wide one that was measured and
rejected, the three reading sites, where the fact lives and the two
options refused on ordering and on bug surface, and the two accepted
costs. Amendments where a landed entry rested on something this change
makes false: the parking lot of #suffix-acronym-collisions is closed,
its roman-numeral bullet retired narrowly, #P3's out-of-scope line
amended, and #ma-do's "the two-word Jack Ma is kept intact" scoped to
the spelling it names. rules.md#S2 and #S3 gain the `history:
decisions.md#S2` trailer the new heading now resolves, and the two
`decisions.md#P3` code citations that recorded the one-case fact
(_classify.py, _state.py) re-point to `#S2`, their better home.

mechanisms.md names `one_case` as STATE-OFFSET-CHANNELS' third
channel and no new entry is owed; the decisions entry says why.
ONE-PREDICATE-PER-QUESTION's roster gains `caps_shape_candidate`, the
one function three sites now share for the all-caps shape test.
AGENTS.md's trailing-abbreviation paragraph gains the trailing shape
rule it said did not exist, for MULTI-chunk dotted words only.
customize.rst documents both switches and rewrites the `Smith, BA`
cost, which is now the one-word family in a case-silent spelling and
nothing wider; usage.rst's `parse("Jack MA").family` doctest becomes
`.suffix`; the release log carries four bullets.

Refs #289, #516

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@derek73 derek73 added this to the 2.4 milestone Sep 18, 2026
@derek73 derek73 added bug enhancement docs Documentation fixes and updates labels Sep 18, 2026
@derek73 derek73 self-assigned this Sep 18, 2026
@codecov

codecov Bot commented Sep 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.73%. Comparing base (1f78bef) to head (22b7783).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #530      +/-   ##
==========================================
+ Coverage   98.69%   98.73%   +0.04%     
==========================================
  Files          45       45              
  Lines        3366     3475     +109     
==========================================
+ Hits         3322     3431     +109     
  Misses         44       44              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

derek73 and others added 5 commits September 18, 2026 13:19
… the wordlist roster

Ten findings from a whole-PR review, every one measured before and
after.

A lost report. Where the prefix chain absorbed an acronym the S2 peel
had DECLINED, nobody reported the fork: assign reports from the peel's
picks and a pick reaches it only as a lone piece, so 'John van der
Berg Ma', 'John de Ma', 'Dr. John van Smith Ma', 'John van Smith Ma
Jr.' and 'John Smith Mc Ma' each lost the flag rules.md#S2 promises
for either reading. The emitter now sits at the chain's own merge
(mechanisms.md#AMBIGUITY-AT-THE-DECISION-SITE); roles are unchanged.
It declines where the PARTICLE run took the word instead ('anh van
do', 'anh van mc'), that being P6's fork rather than S2's, and the
`prefix()` call is tested last so the reference name stays at 412.

Two comma readings. script_segment asked `is_wholly_suffix` of the
post-comma run without the case fact segment had just recorded, so
'Kim김민준씨, MA' left the person's own 씨 unpeeled while 'Kim김민준씨,
PhD' peeled it — one name, two credential spellings, two parses. And
segment's tail-segment test is blind to the by-shape class, so the
narrow roman retirement gave 'John Smith, MD, R.A.I.' a
COMMA_STRUCTURE flag 2.3 did not raise; a by-shape class-run test
quiets it, narrowed to exclude LISTED members so the lean's own
negative control ('STEVEN HARDMAN, MD, DO, DDS') still measures
something.

The wordlist roster. `caps_shape_candidate`'s exclusion named eleven
of `_lexicon._VOCAB_FIELDS`' thirteen, so
`Lexicon.default().add(surnames={"dupont"})` still read 'Jean Pierre
DUPONT' as suffix DUPONT — a caller listing a word as a SURNAME and
getting a credential. The roster is now derived from the field tuple,
with a per-field test.

Also: a LISTED dotted ambiguous acronym no longer gets `shape:acronym`
(it silenced the caller's own case lean); `ambiguous_class_candidate`
loses the dead `one_case` parameter and its unreachable caps tail, and
the agreement test's caps rows point at `caps_shape_candidate`, which
is the live predicate; `Policy.unlisted_dotted_suffixes` and
`unlisted_caps_suffixes` move to the END of Policy and PolicyPatch,
where they no longer re-bind 2.3's positional arguments, and the
bool-validation roster is derived from `dataclasses.fields` (the twin
loop in tests had drifted and named neither); `one_case` is REQUIRED
on the four piece predicates that own it; the two lean predicates
carry a `Lean` Literal and `strict_equality` covers `nameparser.*`;
and "vocab:suffix-ambiguous" is a constant beside SHAPE_ACRONYM_TAG.

Measured: 412/449 unchanged, the default comma harness unchanged at
176/207/233/257/225, and all five gates exit 0 with 0 unexplained.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tests only. Every expected value is measured, and three of them are
not what the review predicted.

Case rows for the live branches no row named: classify's caps branch
reading the NAME-level case past a maiden clause ('née JONES XYZ'),
the one-case comma gate at one token and at a run, `all()` rather than
`any()` over a caps run ('John Smith, LEED BA'), name_word_count's
title and suffix arms ('Mr Smith, Ma', 'Smith Jr, Ma'), the caps
exclusion end to end for three wordlists, `unlisted_dotted_suffixes`
off at all three slots, the declared-order comma flip, and the
script-agnostic claim in the caps switch's docstring measured in
Cyrillic and in accented Latin.

Three rows record ACCEPTED silent moves rather than new behavior: a
digit chunk ('John Smith 1.4.2', 'Smith, 1.4.2', 'John Smith, 1.4.2')
falls out of both the chunk claim and the shape class, so it moves
suffix -> family, and post-comma to `given`, with no report at any
policy. rules.md#S3's narrow retirement is why, and a version string
read as a credential was the same accident it removes.

Two more are rough edges recorded as measured: `Policy(lenient_comma_
suffixes=False)` reads 'Smith V., Ma' as given Smith / family V. /
suffix Ma, and 'Smith V, Ma' as given Smith / suffix 'V, Ma' --
consistent with master's 'Smith V., PhD' under the same knob, so the
knob's reading of 'V.' rather than the credential class's.

Three findings the review's own claims did not survive:
'Andrew Perkins (X.Y.Z.)' now reads suffix, because period-final
delimited content is RELEASED with role None and so is ordinary
trailing material -- which narrows the note on
caps_switch_does_not_reach_delimited_content rather than contradicting
it. 'Smith, A.B.' reports at the default, so it belongs in
test_assign's exactly-one-report loop and the stale reason is gone.
And of the three `_group` `one_case` plumbing sites called inert, only
TWO are: dropping the argument at the maiden walk's pair moves 0 of
14,028 parses (2,338 names under six policies), while dropping it at
the chain-tail measure moves 18 -- 'John van der Berg Ma', 'John de
Ma' and 'Freiherr von Berg MA' under every one of the six. Both
figures are recorded at the call site.

Also: an invariant that `tag_marker_runs` answers in ascending index
order, which `own_words` depends on and nothing said; the
`_MUST_NOT_MATCH` probes for the #516 dotted rule's three unprobed
gates; and the report detail pinned verbatim for the branch that reads
the word as a name.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
decisions.md#S2 gains six dated bullets: what the round moved with
before/after on every name; the DIGIT-DOTTED tokens that move
silently and are accepted; the comma report's actual reach and the
given-segment trailing slot recorded as an open follow-up; the strict
comma knob's rough edge; the "three inert sites" prediction that
survived only two-thirds of its own measurement; and the frame band
after the round. The blast-radius bullet is re-measured, two sets of
figures under one date being the thing it exists to prevent.

Two universal claims are NARROWED to what ships rather than the
emitters widened to match them. `AmbiguityKind.SUFFIX_OR_NAME`'s
member docstring now names the slots it is emitted at and the one it
is not, and rules.md#S3's "and the same at a comma" says which parts
of a comma form it means: `Doe, John MA` reads middle `MA` silently,
in this release as in every other, and that is a boundary rather than
an oversight.

rules.md amendments for the round's own behavior: C2 states the
tail-segment quiet and both ways it is narrow, P2 states the chain's
report and its particle-run exception, W3 states that the honorific
peel reads S2's case evidence. Every clause has an example line; four
of them were added on a self-review pass that found a statement
disagreeing with its own example ('John Smith, MD, Ma' leans NAME,
which is not "leans nothing"), a by-SHAPE clause reaching further
than the code (the caps half does not quiet a tail segment; only the
dotted one does), and two claims with no witness at all.

Comment drift, each verified by a run: assign's "three sites" is
four; its comma-report block no longer says the by-shape member needs
a switch to be reported, nor that two pieces of `Smith, MA PhD`
report; segment's stage header declares the four reads it had left
out and says it reports the structure FLIP rather than "the comma
decided"; the _pieces importer list and its layering entry say four
stages, not two; `_state`'s two rosters are re-counted; classify's
`_NO_INITIALS` sentence no longer claims nothing varies by it, and
the block header describes all four readings in the block; the
one-frame measurement has one home instead of three; and
UNBALANCED_DELIMITER's member now carries the clause the enum
docstring said it did.

Ledger comments: the `esq` rule's "what is gone is its diff" is
scoped per baseline, its own dormant string having said otherwise at
the 2.x three; and "one set for all five ledgers" gains the carve-out
that has been true since 'John Prof. MA' was held out of the 1.4.0
copy. Plan labels ("commit E", "Task 22 Step 2", "Switch A") are
reworded to name the Policy field or the mechanism; the sweep leaves
only pre-existing `#272 Task N` hits.

User docs: the release log's roman-numeral wording becomes "a single
ASCII character" with the digit's consequence spelled out, customize
matches, and the reports inventory names the restored report, both
that go away, and the slot that stays silent. sphinx doctest 253/0,
html -W exit 0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…npinned branches

Ten findings from a verification pass over the review round, each
re-measured on this tree and, where a "before" or an "every release"
was claimed, on the 2.3.0 and 1.4.0 wheels.

Three claims were false about a released version and are corrected
from measurement rather than narrowed:

- `Freiherr von Berg X.Y.I.` was listed among the names that went
  silent. It GAINED a report: 2.3.0 read family `von Berg`, suffix
  `X.Y.I.` with `particle-or-given` alone, and the tree reads family
  `von Berg X.Y.I.` with `suffix-or-name` beside it. The role moved
  into a slot that does not report and the chain emitter reported
  instead.
- The release log claimed TWO reports go away and named
  `John Smith, MD, R.A.I.` as one. It reports nothing at 2.3.0 and
  only ever carried the flag inside this PR's own development. Over
  the corpus exactly ONE is lost (`Steven Hardman, MD, DO, DDS`) and
  one is SWAPPED (`Jack X.Y.I.`, `given-or-family` →
  `suffix-or-name`); the full lost/gained inventory and its recompute
  recipe are in decisions.md#S2.
- "as every release has" for `Doe, John MA`: 1.4.0 read it as a
  SUFFIX and 2.0.0 onwards as a middle name, so the silence at the
  given part's trailing slot is a 2.0-era reading. Corrected in
  release_log.rst and _types.py, and recorded in decisions.md#S2's
  open bullet, where it makes the slot a 1.4.0 parity question as
  well as a noise one.

Three rules stated a reach wider than the parser's. S2's ambiguity
flag is narrowed to the slots that report, with `Doe, John MA` as the
boundary; S3's comma clause said "before a family comma and after it"
and is now the FIRST piece after a family comma and the part before a
SUFFIX comma, all six readings measured; P2's Accepted clause was
scoped to the unambiguous particles (vd, mc) while behavior carries a
`do` carve-out — `suffix_acronyms_ambiguous ∩ particles` is exactly
{do} — now stated by name with a cased boundary row.

Two branches were unpinned and now fail without their code. The chain
emitter's BY-SHAPE tag: replacing the two-tag test with the listed tag
alone passed all 8624 tests, because classify writes both tags at the
default; under `unlisted_dotted_suffixes=False` it silences three
names, and a row for `John van der Berg X.Y.Z.` under that policy
pins it. C2's second narrowing: the caps shape does not reach a tail
segment, pinned by `John Smith, MD, XYZ` under the caps switch, which
fails if `caps_shape_candidate` is folded into the class run. Both
confirmed by runtime monkeypatch with `_pipeline.STAGES` rebound.

Two counts did not reproduce. The review round added TEN corpus names,
not eight, and this pass adds three more (1231 → 1234 at the gate);
the recompute is compare.py's own `_load_entries` over the glob,
deduped, diffed against the same list at an older commit. And the
`one_case` plumbing sweep's population of 2,338 names / 14,028 parses
does not follow from its stated recipe by any reading — the distinct
union is 1,642 / 9,852 — so both are restated here and at the call
sites in _group.py. The FINDING survived re-measurement unchanged: 0
and 18, the same three names, under all six policies.

Also: _assign.py's header claimed the family-comma emitter was the
kind's first appearance on a comma path, which _post_rules.py has
falsified since before this PR (`Berg, Jan vd` reports at 2.3.0), and
called _group.py's "a fifth emitter" when the library has seven. The
claim is dropped and the ordinal replaced with the three modules.

`Doe, John MA` enters the contract corpus and differs from 1.4.0, so
expected_since_1.4.0.toml gains one literal-anchored rule whose
comment says the reading is 2.0-era and this PR did not move it.
Rosters updated from the guard's own messages.

Five gates exit 0; 8638 tests, mypy, ruff, 412/449, doctests and both
sphinx builds clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Several sites claimed this PR brings the comma paths' first-ever
READING report. False: P6's family-comma attachment fork
(nameparser/_pipeline/_post_rules.py) has reported SUFFIX_OR_NAME
since 2.3, measured on this tree and at master 1f78bef --
parse("Berg, Jan vd") gives given 'Jan', family 'vd Berg',
ambiguities=['suffix-or-name'] -- and AmbiguityKind.SUFFIX_OR_NAME's
own docstring already lists that case. What is new in 2.4 is
narrower: the comma's OWN structure/credential decision now reports,
where it was silent before. Rescoped nameparser/_types.py,
_pipeline/_segment.py, _pipeline/_assign.py, the matching test
comments, the four differential ledger comments, docs/release_log.rst's
bullet lead, rules.md#C1's statement, and decisions.md's S2 entry to
say so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
No behavior change: every corpus and case-row reading (1448 names,
four policy combinations, the bare parse and the facade) is
byte-identical to e40ac73, and the reference frame counts hold at
412 / 449. The opt-in caps path sheds one frame (212 -> 211,
244 -> 243), the single-use `run_candidate` closure.

- _segment: `suffixy` and `leaning_suffixy` become one closure with a
  `case` default; `run_candidate` is inlined into the caps-run guard.
- _state: `_AMBIGUOUS_CREDENTIAL_TAGS` moves beside the two tags it
  is built from, and _assign asks it instead of spelling the same
  question as an `or` of two `in` tests.
- _vocab: `name_word_count` drops a single-use local.
- Comments: decision blocks trimmed to what the code does, the
  measurement and the reason; the `Dr. Smith, Ed` / `Xyz. Smith, Ed`
  divergence is told once, at `is_title_shaped`; two stale reader
  rosters corrected; three notes about a comment's previous wording
  removed.

Declined: moving `own_words` into `_vocab` --
mechanisms.md#ONE-PREDICATE-PER-QUESTION puts a predicate over token
roles in `_pieces`.

decisions.md#S2's open follow-up now cites #531.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@derek73
derek73 merged commit cc78c96 into master Sep 19, 2026
11 checks passed
@derek73
derek73 deleted the claude/289-516-case-signal branch September 19, 2026 01:58
derek73 added a commit to IMGillusion/python-nameparser that referenced this pull request Sep 22, 2026
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 derek73#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 derek73#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 derek73#492's
deferred question.

The release log gains one bullet. One correction in place: the derek73#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(derek73#424/derek73#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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug docs Documentation fixes and updates enhancement

Projects

None yet

1 participant