Skip to content

Read a joined credential in brackets as its words written apart (#1088) - #1100

Merged
derek73 merged 5 commits into
masterfrom
fix/issue-1088-bracket-joined-credential
Nov 6, 2026
Merged

derek73 merged 5 commits into
masterfrom
fix/issue-1088-bracket-joined-credential

Conversation

@derek73

@derek73 derek73 commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Closes #1088.

A credential joined by a slash or a hyphen now reads, inside brackets or quotes, as its words written apart with a comma do. Before this change, John Smith (MD, PhD) read suffix MD, PhD, but John Smith (MD/PhD) read nickname MD/PhD, even though rules/S3 reads the bare John Smith MD/PhD as the suffix.

Input master this PR Its words written apart
John Smith (MD/PhD) nickname MD/PhD suffix MD/PhD suffix MD, PhD
Smith (MD/PhD), John nickname suffix suffix
John Smith (Jr/Sr) nickname suffix suffix
John (MD-PhD) Smith nickname nickname nickname (S1's initials Except)
Justin (JP/MD) Stearns nickname nickname nickname
John (NP-C) Smith suffix suffix (listed whole: one word)
John Smith (MD/Bob) nickname nickname (S3 refuses it bare)
Jean (M.A,) Smith nickname, no report nickname, reports suffix-or-nickname as Jean (M.A) Smith

Rule changes

  • rules/S1 (a bracketed credential is not a nickname):
    • Its set-apart word now asks suffix_as_written, the question a bare word is asked, so a joined credential S3 reads is a word of such a clause.
    • The initials Except asks each chunk of a joined word, so before a name word the two spellings agree. This was decided after review, as option A (parity).
    • A credential the vocabulary lists whole (NP-C, SHRM-CP) is one word and is never split.
    • A new Accepted bullet records where the two spellings still differ, each by a rule of its own: a member of the ambiguous class whose position S13 does not settle ((M.A./PhD) suffix vs (M.A., PhD) nickname), and a word that is a title too ((PhD/Sr) John Smith suffix vs (PhD, Sr) John Smith title Sr).
  • rules/S13 (a bracketed member settled by its writing):
    • A member left the nickname is reported, and a comma the clause keeps on it is no part of it.
    • The member's dotted test is now one predicate, _vocab.dotted_member. Before, post_rules held two hand copies.
  • decisions/S1 has a Standing line and a dated 2026-11-05 entry holding the decision, the options put, the moves and the measurements. decisions/S13 has a Log line.

What it moves

All measured 2026-11-05 against master 4109b02, each tree's nameparser.__file__ asserted.

  • Snapshot: none of its 6,945 existing entries (6,736 distinct names) moves. None of its 181 names holding a delimiter pair has a joined word inside a clause, so the snapshot cannot see this shape. It gains the rule's 14 new examples as new entries.
  • Grid: 30 clause words × 14 shapes × 3 name orders, 1,260 names. 399 move, 133 under each order. The exact templates are in the decisions entry. Every move is one of three kinds:
    • a joined credential now read as the suffix;
    • a period-final joined word before a name word now read as the nickname (John (M.D./Ph.D.) Smith read middle in 1.4.0 through 2.8);
    • the member report.
  • Local corpora (snapshot.py --local <source> --base <4109b026>):
    • ctgov-officials, 3 moves: Franck Pages, Professor (MD-PHD) and two like it, now suffix where they read nickname.
    • fec-individuals, 1 move: FRANTZ (CAPT USN-RET), JOSEPH now reads title CAPT, suffix USN-RET.
    • psc-individuals and orcid-researchers: none.
  • Lost report: John Smith (MD/PhD, MBA) now reads suffix with no report. Master reported suffix-or-nickname there, because mba was marked ambiguous on 2026-11-03; 2.8.2 did not.

Frames

The reference row is unchanged at 306.80 calls/parse on py3.11, so _CALL_BASELINE does not move and #1089 can lower it from the same figure. On the clause shapes, master → this PR:

Name master this PR
John Smith (Andy) 214 214
Smith, John (MA) 242 234
John Smith (PhD, MBA) 271 268
Jean (M.A) Smith 242 246
John Smith (MD/PhD) 221 244 (now reads a suffix)

Tests

  • Case rows in tests/v2/cases.py:
    • every comma shape: after the name, inside it, before it, alone, before and after a family comma, before and after a suffix comma, an empty head, and in quotes;
    • a family-first order;
    • the period escape extract keeps for a dotted member;
    • the kept-comma members;
    • the listed-whole credentials;
    • the refusals.
  • Guard tests in tests/v2/pipeline/test_post_rules.py, each with a recorded negative control:
    • A bracketed word after the name is the suffix wherever the bare word is one. Control: 7 words read differently on master. S3's refusals are pinned apart from the predicate the test asks.
    • A joined credential reads as its words written apart, over 2,310 (pair, shape, order) rows. Control: 540 rows read apart with the chunk loop off.
    • Two caller-lexicon tests for the title-and-member paths.
  • Recorded counts updated: the field-sweep claim count (1615 → 1624 on the merged tree: Should Smith y V, John keep the V in the family name, as Smith Jones y V, John does? #1093 and this PR each added nine claims), test_layering's _UNGUARDED_READS (128 → 123 → 128 across the commits, net unchanged) and rules_budget.json (S1 and S13 grow; each new clause reads a signal its rule already reads, S3's joined chunks or N1's comma).

Merged with master at 510d1aa (#1093, #1094, #1096, #1099). On the merged tree, corpus_rules.jsonl rebuilds byte-identical, the snapshot matches, rules_budget.json re-records unchanged, and both negative controls re-take at their recorded values (540 of 2,310 rows; 7 words).

Checks run: uv run pytest, uv run pytest -n0 -m serial, uv run mypy, uv run ruff check, and py3.15 (uv run --python 3.15 pytest -q, in an environment of its own). All pass.

Reviews

Each ran as a subagent on the diff; every finding was fixed or is listed here.

Review Outcome
Code review (pr-review-toolkit:code-reviewer) Found that a joined word diverged from its split spelling before a name word. The maintainer chose parity (A), which is implemented.
Test coverage (pr-test-analyzer) Found the rules corpus stale, extract's member check unguarded by any test, and gaps in comma shapes, orders and kept-comma members. All added; extract's check is now caught by two rows.
Silent failures (silent-failure-hunter) Found that a caller lexicon listing m.a as a title lost the S13 report. Fixed with dotted_member, plus a test.
Type design (type-design-analyzer) The "member" kind is the right shape, and mypy enforces that it never reaches set_apart. Took its optional SetApartKind alias.
Comments (comment-analyzer) Every history claim verified. Fixed the parity wording, one comment about the escape, and a duplicated comment.
Design docs (design-docs-reviewer), round 1 Found the parity gap, a snapshot denominator copied from another entry, an unreproducible population count, and no issue link. Re-counted with a recipe; the issue is drafted.
/simplify (reuse, simplification, efficiency, altitude) Applied inline necessary-half pre-checks, a gate on the initials walk, a flattened chunk loop and the member fold. Skipped two suggestions that would bring back an inline copy of the dotted test.
Second round on the fix commits: code review Found the parity fix splitting credentials listed whole (NP-C, SHRM-CP), the "widened check narrows" shape. Fixed; a sweep of all 70 listed words with a joiner (560 names) matches master.
Second round on the fix commits: design docs Found an unrecorded member-chunk difference, the title-too site stated too narrowly, a wrong period-final claim, a wrong 2.8 comparison, and no grid recipe. All corrected.
Third round on ed403c2: code review Clean: the gate means exactly "listed whole"; 4,023 parses, of which 108 move, all CP-C, MT-BC, NP-C and SHRM-CP before a name word, back to master's suffix; every rule example and decisions claim checked against master, 2.8.2 and 1.4.0. It noted, out of scope: a joined word holding a listed hyphenated credential (PhD/NP-C) is refused by S3 bare, as on master.

Known gaps, left out

  • An interior-period credential in brackets (John Smith (Msc.Ed)) still reads as a nickname, though bare it reads suffix. It goes through a different predicate (S3's any-chunk period test). An issue is drafted; the decisions entry's pointer gets its number once it is filed.
  • A dotted member as a chunk of a joined word reports nothing: John (M.A/PhD) Smith reports no suffix-or-nickname, where John (M.A, PhD) Smith does. This predates the change.

🤖 Generated with Claude Code

derek73 and others added 4 commits November 5, 2026 13:51
…1088)

rules/S1's set-apart word now asks `suffix_as_written`, the question a
bare word is asked, so `John Smith (MD/PhD)`, `John (MD-PhD) Smith` and
`Smith (MD/PhD), John` read suffix where they read nickname, as
`(MD, PhD)` already did. A member of the ambiguous class written with
a period inside it is asked first and answered "member", so rules/S13
keeps its position test; post_rules' two copies of that dotted test go,
and the report on a member left the nickname leaves off a comma the
clause keeps (`Jean (M.A,) Smith` reports suffix-or-nickname).

Snapshot 0 of 6,939 moves; grid 447 of 1,260; local corpora: ctgov 3,
FEC 1, PSC and ORCID none (decisions/S1, 2026-11-05).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

Parity, as decided: rules/S1's initials Except asks each chunk of a
credential joined by a slash or a hyphen (`_vocab.reads_as_initials`),
so `John (MD-PhD) Smith` and `Justin (JP/MD) Stearns` stay the nickname
before a name word, as `(MD, PhD)` and `(JP, MD)` do, while
`John (Jr/Sr) Smith` reads suffix as `(Jr, Sr)` does. A two-spelling
guard holds the pair alike over 2,310 rows (540 apart with the chunk
loop off).

Review fixes: `_vocab.dotted_member` is S13's one dotted test, asked by
the settling and the report, so a caller listing `m.a` as a title keeps
the report; `SetApartKind` names the kind set; new case rows for every
comma shape, a family-first order, the lost `(MD/PhD, MBA)` report, the
kept-comma members and the period escape extract keeps for a dotted
member; the guard pins S3's refusals apart from its oracle; caller-lexicon
tests for the title-and-member paths. decisions/S1 re-counts the
snapshot (6,945 entries, 6,736 distinct; 181 with a delimiter pair) with
its recipe; corpus_rules.jsonl and snapshot.jsonl gain the nine new rule
examples, no existing entry moving.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the /simplify pass: `set_apart_kind` asks the suffix sets and the
joiners inline ahead of `suffix_as_written`, the predicate's necessary
half, so a nickname word pays no frame; the initials test is skipped for
a clause after the last name word (a name word after it stands behind
that word) and for a title word; `reads_as_initials`' one-level
recursion is flat, `_shaped_as_initials` taking a member as the
credential it is settled as; post_rules folds a member straight to the
suffix. No reading moves (the 1,260-name grid and the snapshot are
identical); `John Smith (Andy)` costs 214 frames as on master, the
reference row 306.80.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nces (#1088)

The second-round code review found the parity fix splitting a credential
the vocabulary lists whole: `John (NP-C) Smith` and `John (SHRM-CP) Smith`
read the nickname, CP and C taken for initials, where master reads the
suffix. `reads_as_initials` now splits only a word S3 joins; the 70
listed words holding a joiner read as master does in four positions
(560 names), and two case rows pin it.

The second-round design-docs review: rules/S1 gains an Accepted bullet
for the two classes where a joined word reads otherwise than its words
written apart (a member S13 does not settle, a title-too word at either
title site) and the listed-whole qualifier with an `NP-C` boundary;
decisions/S1 corrects the period-final move (a nickname no release
read), the `(MD/PhD, MBA)` comparison (master reported it, 2.8.2 did
not) and writes the grid's exact templates. The snapshot gains the five
new examples; no existing entry moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@derek73 derek73 added this to the 3.0 milestone Nov 6, 2026
@derek73 derek73 self-assigned this Nov 6, 2026
…ket-joined-credential

# Conflicts:
#	tests/v2/test_cases.py
@derek73
derek73 merged commit 21a4fe1 into master Nov 6, 2026
13 checks passed
@derek73
derek73 deleted the fix/issue-1088-bracket-joined-credential branch November 6, 2026 01:04
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.

Should John Smith (MD/PhD) read suffix MD/PhD, as John Smith (MD, PhD) reads suffix MD, PhD?

1 participant