From f2c0d9d5f1d0e5fe7c5cb56c88c0b7b59584c90b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 19:19:39 +0000 Subject: [PATCH 1/5] docs(decisions): Standing blocks for fourteen small decisions files (#762) A1, A2, D1, D2, N1, N2, N3, T1, T3, T4, phd-merge, suffix-field-composition, two-input-invariants and v1-xfail-triage each open with a ## Standing block and a ## Log heading, and leave tests/v2/decisions_without_standing.json. Edit-kind-1 notes on three heads whose entries a later decision reversed in part: A1's #626 (its comma-credential wording, by #793), N2's #273 (its per-pair scan, by the same day's 05fe693 entry) and phd-merge's #371 (its revise() rendering, by #511). v1-xfail-triage gains a dated line for its undated #485 bullet, dated by the triage commit d02dc41e (2026-09-01) that wrote it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RaPAyHYi8MDM6GXXjtVPe9 --- docs/design/decisions/A1-ambiguity-report.md | 15 ++++++++++++++- docs/design/decisions/A2-empty-name.md | 11 +++++++++++ .../D1-segmenterless-activation-warning.md | 10 ++++++++++ .../D2-construction-raises-parse-never-does.md | 10 ++++++++++ .../decisions/N1-default-delimiter-pairs.md | 13 +++++++++++++ ...er-quotes-and-shared-character-conventions.md | 12 +++++++++++- .../decisions/N3-lone-word-nickname-rule.md | 11 +++++++++++ .../decisions/T1-separators-not-joiners.md | 9 +++++++++ .../decisions/T3-interpuncts-flank-guard.md | 11 +++++++++++ .../decisions/T4-bare-letter-as-an-initial.md | 15 +++++++++++++++ docs/design/decisions/phd-merge.md | 14 +++++++++++++- .../design/decisions/suffix-field-composition.md | 10 ++++++++++ docs/design/decisions/two-input-invariants.md | 11 +++++++++++ docs/design/decisions/v1-xfail-triage.md | 15 +++++++++++++++ tests/v2/decisions_without_standing.json | 16 +--------------- 15 files changed, 165 insertions(+), 18 deletions(-) diff --git a/docs/design/decisions/A1-ambiguity-report.md b/docs/design/decisions/A1-ambiguity-report.md index e213fc30..03ccf2bb 100644 --- a/docs/design/decisions/A1-ambiguity-report.md +++ b/docs/design/decisions/A1-ambiguity-report.md @@ -1,9 +1,22 @@ # A1 — the ambiguity report +## Standing + +- A report whose fork the rest of the parse resolved to neither branch is withdrawn in assemble, the one place that sees final roles: a connective-or-initial letter roled suffix or title loses that report and keeps its own, so `JOHN QUINCY SMITH I` reports `suffix-or-name` alone — 2026-09-20 #397. +- The decision stays at its emitter and assemble words the field from the word's final role (`PendingAmbiguity.field_tail`), so a report names the field its word lands in whatever rule moved it: `, Ma Dr.` reports 'Ma' "read as a family name", and `Kim Min Do` under FAMILY_FIRST reports 'Do' read as a family name; a placeholder in the detail was declined — 2026-10-08 #626. +- A report on a part past the second comma names no field: `John Smith, Jr., Dr. Bart` reports "segment 'Dr. Bart' beyond the recognized comma structures; read best-effort"; naming every field the part's words land in was declined — 2026-10-10 #629. +- The listed-member report after a family comma names the role the comma binds, worded at the emitter: `John, Ma.` reads title 'Ma.' and reports "read as a title" — 2026-10-21 #793. + +Elsewhere: P8's "connects nothing", withdrawn once over final roles in assemble, is decisions/S10's 2026-10-18 #722's /simplify pass entry. + +Open: none in this file. + +## Log + - 2026-09-20 #397 (second review) — A REPORT MUST NOT CONTRADICT THE READING BESIDE IT, and the shape could not arise before this cycle. `classify`'s `conjunction-or-initial` fork offers exactly two readings and its detail says which it took ("it is read as an initial"). Where the rest of the parse then roles that letter SUFFIX, the fork resolved to NEITHER branch: `JOHN QUINCY SMITH I` carried the connective-or-initial beside a `suffix-or-name` saying the same token reads as a generational suffix, and `john smith i`, `HENRY I`, `SMITH, JOHN I`, `JOSEP CAROD I ROVIRA III` and `abdul e i` were the same. `i` is the first word that is both a MARKED connective and suffix vocabulary, so no parse before #397 could reach it — which is why the rule is written down now rather than having been. DECIDED: the report is WITHDRAWN where the role is SUFFIX or TITLE, in `_pipeline/_assemble.py`, the one place that sees final roles and the ambiguity list together — and beside a withdrawal that was already there, for a report whose referents did not survive assembly. That is the narrow reading of mechanisms.md#AMBIGUITY-AT-THE-DECISION-SITE rather than an exception to it: classify still owns the fork and still takes it where it is taken; what goes is a report whose subject the parse went on to read as something else. Emitting later was the alternative and was rejected as more invasive for the same answer — the fork's inputs (the case class, the own-words span) live in classify and moving the emitter moves them. MEASURED: over the review's 43,925-name grid under eight configurations, 9,806 rows on 1,637 distinct names lose exactly one `conjunction-or-initial` and nothing else — no report of any other kind moves and none is gained. On the deduped corpus-union-cases population the only name affected is `JOHN QUINCY SMITH I`, a row this round added, so the ledger's three-name `fix(#397) the Catalan link reports a connective-or-initial in a one-case name` rule is untouched: all three of its names role the letter MIDDLE and all three still report. The invariant is `tests/v2/test_properties.py::test_no_report_contradicts_the_reading_beside_it`, which states it twice — no connective-or-initial on a token roled SUFFIX or TITLE, and no token named by two reports whose details assert different readings — and fails on 8,204 rows at `dc3bdf9c`, 0 here. -- 2026-10-08 #626 — A REPORT NAMES THE FIELD ITS WORD LANDS IN. Six of assign's reports worded the field from the role the word held at assign (`read as a given name`), and rules in post_rules still move a word after that: H1 moves the name behind a title to the family, P1's family-first fold takes the particle run and one unit and lays the rest out by the order, P6's attachment takes a trailing name word into the family at the end of a name read family-first, and under opt-in policies the patronymic rotations (O1, O2) and `middle_as_family`'s fold (O3) move one too. So `Attorney General of Minnesota` reported 'General of Minnesota' "read as a given name by convention" with the unit in the family (since 2.3.0, #491's join clause); `Kim Min Do` under FAMILY_FIRST told the caller 'Do' was "read as a middle name" with 'Do' in the family, and `de Kim Ma` under FAMILY_FIRST said 'Ma' was a middle name with 'Ma' the given name (both this cycle, since #289 made the Title-case word a name word); `, Ma Dr.` said 'Ma' after the comma was "read as the given name" with 'Ma' in the family (this cycle, since #289 added that comma report; 2.3.0 reads the same fields and reports nothing); and `Van Ivan Petrovich` under the East Slavic patronymic rule said the leading 'Van' was "read as a given name" after O1 rotated it into the family (at 2.3.0 too). #614's second review had fixed one instance of the class (`Freiherr von Berg Ma`) by moving its report to the stage that knows the outcome. +- 2026-10-08 #626 (its literal wording for the comma's credential reading superseded in part by #793 below) — A REPORT NAMES THE FIELD ITS WORD LANDS IN. Six of assign's reports worded the field from the role the word held at assign (`read as a given name`), and rules in post_rules still move a word after that: H1 moves the name behind a title to the family, P1's family-first fold takes the particle run and one unit and lays the rest out by the order, P6's attachment takes a trailing name word into the family at the end of a name read family-first, and under opt-in policies the patronymic rotations (O1, O2) and `middle_as_family`'s fold (O3) move one too. So `Attorney General of Minnesota` reported 'General of Minnesota' "read as a given name by convention" with the unit in the family (since 2.3.0, #491's join clause); `Kim Min Do` under FAMILY_FIRST told the caller 'Do' was "read as a middle name" with 'Do' in the family, and `de Kim Ma` under FAMILY_FIRST said 'Ma' was a middle name with 'Ma' the given name (both this cycle, since #289 made the Title-case word a name word); `, Ma Dr.` said 'Ma' after the comma was "read as the given name" with 'Ma' in the family (this cycle, since #289 added that comma report; 2.3.0 reads the same fields and reports nothing); and `Van Ivan Petrovich` under the East Slavic patronymic rule said the leading 'Van' was "read as a given name" after O1 rotated it into the family (at 2.3.0 too). #614's second review had fixed one instance of the class (`Freiherr von Berg Ma`) by moving its report to the stage that knows the outcome. DECIDED: the DECISION stays at its site and the WORDING of the field moves to assemble, the one place that knows final roles (the same reasoning as the 2026-09-20 withdrawal above). An emitter whose detail names a field a later rule could change writes the sentence up to the field and hands the rest as `PendingAmbiguity.field_tail` (`""` where the field ends the sentence); assemble joins them around `FIELD_READING` of the first referent's final role. All six such emitters in assign use it -- H4's all-suffix name, title-or-name's join clause, O5's lone word, the trailing peel's picks, the leading ambiguous particle and the comma report on the word after a family comma -- including those right on every measured input, so no later rule can make them wrong. A report in a name emptied for want of content is withdrawn rather than shipped half-worded (no emitter reaches that today). A placeholder in the detail was declined: token text can carry any characters, and a name containing the placeholder would corrupt its own report. Reports worded where the field is settled keep their literal wording: a comma's credential reading, the suffix run's and the numeral fork's, H4's "the name" and P6's own "joins the family". The comma report's given-side wording moves from "the given name" to "a given name" with the rest. MISSED, then caught in review: the first draft converted five emitters and its grid had no empty-head comma with a trailing title, the shape AGENTS.md's comma sweep already asks for; four of the review's five readers found the sixth emitter on `, Ma Dr.`. The draft also named only H1 and P6 as movers; P1's fold was found by stepping the stages, and the review of the fix found the opt-in movers O1 to O3, which no grid over the three orders reaches -- reverting the leading-particle emitter passed the whole suite until rows under those policies pinned it. MEASURED 2026-10-08, each report's field phrase compared against its tokens' final roles. RECIPE: texts are the 1,536 names of `tools/differential/corpus*.jsonl` and 347 `tests/v2/cases.py` texts (the table at 0c54662b, the parent of #614's first commit, less the corpus names; today's table adds rows this entry's review wrote for the shapes the grid missed, so it reads more wrong claims on master, 6 more as measured on the review), plus every head in {'John', 'John Smith', 'John Q. Smith', 'Mary Ann Smith', 'John van Smith', 'Juan de la Vega', 'John van', 'anh van', 'Jan Freiherr von Berg', 'Freiherr von Berg', 'Freiherr St', 'St', 'Dr. St', 'Freiherr', 'Dr.', 'Mr. and Mrs.', 'Attorney General of', 'abdul salam', 'Juan y Garcia', 'Jane Doe nee van der Berg', 'Dr. John Smith', 'JOHN SMITH', 'john smith', 'Smith', 'de Mesnil', 'Dr. de Mesnil', 'Kim Min', 'Kim Min Jun', 'Ortega y Gasset', 'van', 'Do', ''} followed by one or two words (repetition allowed) from the list [PhD, MA, Ma, DO, Do, do, vd, mc, Jr, III, V, V., VI, X.Y.Z., Ph. D., Esq., Prof., Sir, Ed, van, and, Jones, ba, John, Smith, Minnesota, de, St, Freiherr], space-joined and stripped; 20,000 draws after Python's `random.seed(626)`, each `random.choice(heads)`, then `random.randint(3, 4)` words each `random.choice(words)`, space-joined and stripped, both lists in the order written here; then, unstripped, `{head}, {word}`, `Smith, {head} {word}` with any double space replaced by one, and `{word}, {head}` for every head and word, and `, {w1} {w2}` and `(Bob), {w1} {w2}` for every ordered pair of words; the empty text dropped and duplicates removed -- 53,517 texts; each parsed under the three orders, so the opt-in movers are outside these figures; a claim is a match of `(reads? as|joins) ()` for the phrases `_FIELD_CLAIMS` in tests/v2/test_cases.py lists plus "the given name", and it is wrong when its tokens' roles are not among the fields the phrase names. On master (26cdb891) 2,725 of 89,092 claims were wrong, in three emitters: title-or-name's join clause (1,575), the trailing peel's picks (1,114) and the comma report (36); on the tree, 0. The comparator is master, which words the field at assign; the tree words it at assemble. Fingerprint against master over the differential corpora, master's case-table names and a seeded comma grid (13,090 names) under six policies (the three orders, `lenient_comma_suffixes=False`, a `/` suffix delimiter and `CapsSuffixes.OFF`): no role, kind, initials or rendering moves; the details that change are 6,395 comma reports on 1,086 names reworded from "the given name" to "a given name", 85 on 15 names now naming the family they land in, and 40 title-or-name join reports on 10 names now naming the family. Pinned by `test_a_report_names_the_field_its_word_lands_in` in tests/v2/test_cases.py, over every case row under all three orders except the locale rows (a row with its own policy only as declared), whose recorded control is 14 failures on master, and by `test_the_field_sweep_sees_the_claims_it_checks`, which records how many claims that sweep checks; the opt-in movers by the rows `Van Ivan Petrovich` (East Slavic), `Van Ali Veli oglu` (Turkic) and `Do Bishop Do` (FAMILY_FIRST with `middle_as_family`), which the sweep reads as declared. diff --git a/docs/design/decisions/A2-empty-name.md b/docs/design/decisions/A2-empty-name.md index b809f6ba..2f0335cc 100644 --- a/docs/design/decisions/A2-empty-name.md +++ b/docs/design/decisions/A2-empty-name.md @@ -1,4 +1,15 @@ # A2 — the empty name +## Standing + +- An input with no letter or digit parses to the empty name, where v1 kept `parse(".")` as given '.': `.` and `.,` read every field empty — 2026-07 PR #288. +- A report born from the punctuation survives the emptying, so malformed input stays distinguishable from blank input: `(` reports `unbalanced-delimiter` — 2026-07 PR #288. + +Elsewhere: #629's declined field-naming report would have withdrawn a report from a name emptied of content (`", , -"`), against this rule; decisions/A1's 2026-10-10 #629 entry. + +Open: none in this file. + +## Log + - 2026-07 (v2 core, PR #288) — v1 kept parse(".") as first="."; 2.0 empties content-free input instead. The born-empty-ambiguity survival was a review fix in the rc1 arc: an unbalanced-delimiter report must outlive the emptying, or malformed input becomes indistinguishable from blank input. diff --git a/docs/design/decisions/D1-segmenterless-activation-warning.md b/docs/design/decisions/D1-segmenterless-activation-warning.md index 0eabaa0c..d20b5b9e 100644 --- a/docs/design/decisions/D1-segmenterless-activation-warning.md +++ b/docs/design/decisions/D1-segmenterless-activation-warning.md @@ -1,5 +1,15 @@ # D1 — the segmenterless-activation warning +## Standing + +- `parser_for(locales.JA)` without a segmenter warns and does not raise: the inert registration is itself a pinned property, and a warning is filterable by the caller who wants it; the Japanese install hint appears only when a Japanese script is among the dead ones, a hangul-only gap getting the generic remedies — 2026-08 #337, WARN rather than raise +- The warning re-emits from `parser_for`'s frame, so the location reported is the caller's own call — 2026-08 #337, the warning exists because +- The message offers code that type-checks, `Policy(segment_scripts=frozenset())`, pinned by a test, with the bare tuple literal in the denylist test — 2026-08-07 #339. + +Open: none in this file. + +## Log + - 2026-08 #337 — WARN rather than raise, deliberately: registering the JA pack without the extra must stay safe (the inert registration is itself a pinned property), and a warning is filterable by the caller who wants exactly that inertness. The Japanese install hint is conditional — it appears only when a Japanese script is among the dead scripts; a hangul-only gap gets the generic remedies. - 2026-08 #337 — the warning exists because parser_for(locales.JA) without segmenter= used to build a parser that silently behaved like a working one minus the feature; it re-emits from the parser_for frame so the reported location is the caller's own call. - 2026-08-07 #339 — diagnostics that hand the reader code must hand code that type-checks: the message's offered deactivation used a bare tuple literal for segment_scripts — an arg-type error under mypy in a py.typed package — and became frozenset(), pinned by a test asserting the offered spelling. The known-bad spelling is in the denylist test. The structural fact behind the recurrence: autodoc renders docstrings into the API reference, so docs/*.rst is NOT the boundary of "the docs" — a guide and the reference can teach different spellings on the same rendered page, invisibly to any .rst-only sweep. diff --git a/docs/design/decisions/D2-construction-raises-parse-never-does.md b/docs/design/decisions/D2-construction-raises-parse-never-does.md index 2f0539e1..e4e76c9f 100644 --- a/docs/design/decisions/D2-construction-raises-parse-never-does.md +++ b/docs/design/decisions/D2-construction-raises-parse-never-does.md @@ -1,5 +1,15 @@ # D2 — construction raises, parse never does +## Standing + +- Every raise in the locale-apply path is a plain `TypeError` or `ValueError`, so the rewrap with the locale's code cannot break on an exotic exception signature — 2026-07 PR #288. + +The two Declined lines below (2026-07-27) stand as written: no zh-vs-ja kind, and `order` stays reserved and unemitted for script-resolved names, while `segmentation` fires on a genuine multi-split fork (`夏侯惇` and `남궁민수` under `locales.ZH` report it). + +Open: none in this file. + +## Log + - 2026-07 (v2 core, PR #288) — every raise in the locale-apply path is a plain TypeError/ValueError so the wrap-with-locale-code rewrap cannot break on exotic exception signatures. Declined (ambiguity kinds for script-resolved names, 2026-07-27): diff --git a/docs/design/decisions/N1-default-delimiter-pairs.md b/docs/design/decisions/N1-default-delimiter-pairs.md index bc2458bc..297ed0d4 100644 --- a/docs/design/decisions/N1-default-delimiter-pairs.md +++ b/docs/design/decisions/N1-default-delimiter-pairs.md @@ -1,5 +1,18 @@ # N1 — the default delimiter pairs +## Standing + +- The halfwidth corner brackets `「」` are a default nickname pair, with the matching v1 key `halfwidth_corner_brackets` (a v1 `Constants` restored from a pickle keeps the keys it was saved with), and like every pair they are not script-gated: `山田 「タロー」 タロウ` reads family '山田', given 'タロウ', nickname 'タロー', and `John 「Jack」 Smith` nickname 'Jack' — 2026-10-03 #597. +- A comma inside a delimited clause is word text and divides nothing: `Jane (Janey, Jo) Smith` reads nickname 'Janey, Jo', and a punctuation-only clause reads as its punctuation, a nickname rules/N3 counts (`Jones (,)` → family 'Jones', no report) — 2026-10-12 #653. +- A comma after a maiden marker unmarks it, standing alone or glued to either word, asked through one predicate by M3 and M1's drop: `Jane (née , Jones) Smith` reads nickname 'née , Jones'; dropping the comma with the marker was declined, and a trailing comma rides as written (`Jane (née Jones,) Smith` → maiden 'Jones,') — 2026-10-12 #653. +- Accepted: a whitespace run inside a clause collapses to one space, and the v1 `nickname_list` keeps one element per word, the comma riding on its word — 2026-10-12 #653. + +Elsewhere: the typographic pairs themselves (#273) and the leftmost-match scan that reads them are decisions/N2's 2026-07-18 entries; the curly single quotes' absence from the default pairs is decisions/vocabulary-exclusions'. + +Open: none in this file. + +## Log + - 2026-10-03 #597 — THE HALFWIDTH CORNER BRACKETS ARE A DEFAULT NICKNAME PAIR. `「」` (U+FF62/U+FF63) join `DEFAULT_NICKNAME_DELIMITERS` beside `「」`, and the v1 facade gains the matching `halfwidth_corner_brackets` key, so `HumanName` reads them as `parse()` does. They are the same punctuation as `「」` in JIS X 0201's narrower encoding, the legacy data halfwidth katakana comes from (#594), where they were the only corner brackets available, and they appear in no non-CJK text. Before this every release kept the clause in the name, brackets and all — a middle where it stood spaced (`山田 「タロー」 タロウ`), part of one given word where it was glued (`山田「タロー」太郎`) — and since #594 the bracket also cost the spaced name its order: the halfwidth kana now classify, but the unclassified bracket kept `山田 「タロー」 タロウ` from being wholly East Asian script, so it read given 山田 (in released versions the unclassified kana did that on their own). A v1 `Constants` restored from a pickle keeps the keys it was saved with and so does not gain the pair, the same policy #273's pairs followed: restored configuration is never widened behind the caller's back. A pair is not script-gated, so `John 「Jack」 Smith` reads nickname Jack as `John 「Jack」 Smith` does. Nothing else needs a halfwidth twin: Unicode has no halfwidth form of `『』` or of the fullwidth parentheses. MEASURED 2026-10-03: no corpus line held a halfwidth bracket before this change, so the three `fix(#597)` case rows are the whole population, and they move at every baseline. RECOMPUTE: parse `山田 「タロー」 タロウ`, `山田「タロー」太郎` and `John 「Jack」 Smith` here and on each baseline from PyPI and read `nickname` beside the name fields. - 2026-10-12 #653 — A DELIMITED CLAUSE KEEPS ITS COMMA. `Jane (Janey, Jo) Smith` reads nickname 'Janey, Jo' again, as 1.4.0 did, where 2.0.0 through 2.4.0 gave 'Janey Jo'. The comma was lost in tokenize, not in the view: `_tokenize_region` serves the main stream and the extracted regions alike, and treated `COMMA_CHARS` as a separator in both, although only the main stream records a segmentation offset for it -- inside a region it divided the clause into words and recorded nothing, so the view's space join had no comma left to keep. It shipped that way with the first tokenizer (7e341a95, 2026-07-12) and no entry chose it; `;`, `/` and `&` were always kept. Now a comma separates only where it records an offset, so inside a clause it is word text on the word it touches (`Janey,`), and the view's space join restores the writer's spacing (`Janey,Jo`, `Janey , Jo` and `,Jo` all read as 1.4.0 read them). WHAT ELSE MOVED, all by the same token change: a marker glued to a comma is no marker inside a clause, as it already was in the main stream (`Jane Smith née, Jones` reads no maiden), so under `maiden_delimiters` `Jane (née, Jones) Smith` keeps maiden 'née, Jones' where the dropped comma had let the drop pass take 'née'; and a phrase marker divided by a comma inside a clause no longer matches, which the M Background already stated (`Maria Kowalska (z, domu Nowak)` keeps maiden 'z, domu Nowak' under `maiden_delimiters`; rules/M1 now pins it). A punctuation-only clause reads as its punctuation (`Jane (,) Smith` → nickname ','), as `(;)` and `(-)` already did and as 1.4.0 did — and a name holding one now has a nickname for rules/N3 to count, so `Jones (,)` reads family 'Jones' with no ambiguity report where 2.x read given 'Jones', reporting GIVEN_OR_FAMILY since #449 in 2.3 (1.4.0: last 'Jones', nickname ','; the same for `(,) Jones` and `Jones ","`). A COMMA AFTER A MARKER UNMARKS IT (rules/M3, decided in review 2026-10-12): once the clause kept its comma, a spaced one after the marker (`Jane (née , Jones) Smith`) passed M3's whitespace test, since ',' was a word after the marker, and the drop pass then took the marker and left maiden ', Jones'; `(née , ) Smith` gave maiden ','. Rather than read the spaced comma as glued `née,` is not, M3 and M1's drop now ask the one predicate `_vocab.comma_follows_marker` of the word after the marker run, so a comma standing alone or glued to either word unmarks it: `Jane (née , Jones) Smith` reads nickname 'née , Jones', which is 1.4.0's reading, where 2.2 through 2.4 read maiden 'Jones'; under `maiden_delimiters` the clause keeps its marker, `(née ,Jones)` reading maiden 'née ,Jones'. Declined: dropping the comma with the marker, which restores 2.4's maiden 'Jones' for the spaced comma while the glued `(née, Jones)` stays a nickname, so the two spellings would disagree. A trailing comma rides as written (`(née Jones,)` → maiden 'Jones,'). The two sites hand the predicate different strings — extract a whitespace word, group a token — so it reads past any leading character that is neither a comma nor a letter or digit: a second review found `Jane (née \u200f, Jones) Smith`, visually the spaced-comma name, read maiden 'née , Jones' while it tested only the first character, M3 seeing the right-to-left mark tokenize drops and group the bare comma (the same for an emoji or a nakaguro before the comma). cases.py's marker_followed_by_a_comma_behind_a_bidi_mark_is_no_marker pins it. ACCEPTED: a whitespace run inside a clause still collapses to one space (1.4.0 kept 'Janey,Jo Bo' verbatim), and the v1 `nickname_list` stays one element per word as it has been since 2.0 (1.4.0 gave one per clause), the comma now riding on its word (`['Janey,', 'Jo']`) as `;` always did. THE DROP-PASS WITNESS MOVED: `marker_glued_to_punctuation_keeps_the_clause_a_nickname` had been the only case row reaching the `role is not Role.MAIDEN` branch of `_group.group`'s drop pass, and it reached it through the dropped comma; `marker_divided_by_a_nakaguro_keeps_the_clause_a_nickname` (`佐藤 花子 "旧姓・田中" (旧姓 鈴木)`) reaches it through rules/T2's nakaguro instead, and is now the one row that fails with the branch removed (measured 2026-10-12). MEASURED 2026-10-12: before the rules.md examples landed no corpus name held a comma inside a clause, and the gate at 2.4.0 read 0 diffs with the fix applied; with them, the two examples are the 2 intentional diffs at each of 2.0.0 through 2.4.0 under the `fix(#653)` rule, and parity at 1.4.0. The witness row joins `corpus_cjk.jsonl` and diffs at 1.4.0, 2.0.0 and 2.1.0 only, classified under the #335 rules there (a new literal-keyed `fix(#335/#272)` rule at 1.4.0, one more literal in the existing #335 rules at 2.0.0 and 2.1.0); every baseline's gate exits 0. diff --git a/docs/design/decisions/N2-same-character-quotes-and-shared-character-conventions.md b/docs/design/decisions/N2-same-character-quotes-and-shared-character-conventions.md index a0737750..cb479527 100644 --- a/docs/design/decisions/N2-same-character-quotes-and-shared-character-conventions.md +++ b/docs/design/decisions/N2-same-character-quotes-and-shared-character-conventions.md @@ -1,6 +1,16 @@ # N2 — same-character quotes and shared-character conventions -- 2026-07-18 #273 (7ee6e3a) — the typographic delimiter pairs shipped with a per-pair full-text scan; three independent review agents broke it the same day: with two shared-character conventions genuinely present, the earlier-sorted pair stole the close and the legitimate match dropped silently, zero ambiguity. +## Standing + +- The typographic delimiter pairs ship as default delimiters — 2026-07-18 #273. +- Conventions that share a character are read by one interleaved leftmost-match scan, the leftmost valid opener winning (`Hans „Erster“ und “Zweiter” Müller` → nickname 'Erster Zweiter'), and a dangling-open report on a character inside another pair's match is filtered out — 2026-07-18 (05fe693, the same day) +- The word-internal carve-out covers only the straight apostrophe, the one delimiter character real names carry mid- and end-of-word: `Mari' Aube'` reads family "Aube'" with no report, while `Mari" Aube"` reports `unbalanced-delimiter` twice — 2026-07-18 (05fe693, the same day) + +Open: none in this file. + +## Log + +- 2026-07-18 #273 (7ee6e3a) (its per-pair scan superseded the same day by the 05fe693 entry below) — the typographic delimiter pairs shipped with a per-pair full-text scan; three independent review agents broke it the same day: with two shared-character conventions genuinely present, the earlier-sorted pair stole the close and the legitimate match dropped silently, zero ambiguity. - 2026-07-18 (05fe693, the same day) — the interleaved leftmost-match restructure replaced it (an altitude review called it the right depth); the author's own first fix — post-hoc offset filtering of unbalanced candidates alone — was superseded within the day and survives only as the bulk-recorded dangler filter rules/N2 now names. The word-internal carve-out covers ONLY the straight apostrophe because it is the one delimiter character that occurs mid- and end-of-word in real names; Derek's distinguishing test: with double quotes the same shape ("Mari\" Aube\"") is genuinely ambiguous, while Mari' Aube' is not. A cached delimiter-charset prescreen landed in the same commit, measured: no-delimiter names parse faster than the pre-#273 baseline. Declined: diff --git a/docs/design/decisions/N3-lone-word-nickname-rule.md b/docs/design/decisions/N3-lone-word-nickname-rule.md index 052c590e..9d6676cd 100644 --- a/docs/design/decisions/N3-lone-word-nickname-rule.md +++ b/docs/design/decisions/N3-lone-word-nickname-rule.md @@ -1,4 +1,15 @@ # N3 — the lone-word nickname rule +## Standing + +- The rule fires only where the nickname accompanies exactly one piece in total, decided in assignment where the count is settled, and a title counts against it: `'Smitty' Jones` reads family 'Jones', while `'Smitty' Dr. Jones` reaches H1, which since #410 names the family, so it reads family 'Jones' too — 2026-07 PR #288. +- The bare spelling's one name word reads as the family name by M4 (2026-08-27, #445), which reaches the bracketed and the bare spelling alike, and the rule text carries no history — 2026-10-19 #761. + +Elsewhere: a punctuation-only clause is a nickname this rule counts (`Jones (,)` → family 'Jones'), decisions/N1's 2026-10-12 #653 entry. + +Open: none in this file. + +## Log + - 2026-07 (v2 core, PR #288; recorded plan deviation #2 of the core plan) — v1's rule counted pieces before grouping; the v2 port fires only when the nickname accompanies exactly ONE piece in total — a title counts against it, so "'Smitty' Dr. Jones" reaches H1 with a title and one name word left standing — through 2.1 that meant given="Jones" with the family empty, and since #410 (2026-08-25) H1 names the family, so it reads family="Jones". The count is unchanged; what moved is what happens after it declines. The rule lives in assignment because that is where the piece count is settled. (An earlier wording here said "one non-title piece", predicting the opposite output; the coherence review measured the truth.) - 2026-10-19 #761 (skeleton migration) — the rule text no longer carries its history. Its last Accepted bullet said the bare spelling's one name word had been read as the family name "since #445": that date belongs to M4, which arrived on 2026-08-27 and reaches the bracketed and the bare spelling alike (decisions/M4's #445 entries). diff --git a/docs/design/decisions/T1-separators-not-joiners.md b/docs/design/decisions/T1-separators-not-joiners.md index c8a3fcc8..acb2f004 100644 --- a/docs/design/decisions/T1-separators-not-joiners.md +++ b/docs/design/decisions/T1-separators-not-joiners.md @@ -1,4 +1,13 @@ # T1 — separators, not joiners +## Standing + +- An ignorable character is a separator, never removed with its neighbors joined as v1's squash_emoji and squash_bidi did: `A😀B` reads given 'A', family 'B' — 2026-07 PR #288. +- The `strip_bidi=False` half of the opt-out carries no example, by decision; with it `John‏Smith` (a right-to-left mark between the words) reads given 'John‏Smith', the mark kept — 2026-10-20 #761. + +Open: none in this file. + +## Log + - 2026-07 (v2 core, PR #288) — v1's squash_emoji/squash_bidi REMOVED the character and joined its neighbors. (v1.3.0 had no bidi handling at all: squash_bidi entered late v1 via #266, 2026-07-07, on the emoji precedent's shape.) ('A😀B' → 'AB'); v2 makes an ignorable character a separator ('A😀B' → 'A', 'B'). The unavoidable consequence of every part being an exact positioned piece of the input: with no rewriting stage, nothing can splice two half-words together. - 2026-10-20 (Derek), #761 — the `strip_bidi=False` half of the opt-out carries no example, by decision: the example would be a name holding an invisible direction mark (`John‏Smith`, which reads given `John‏Smith` with the marks kept), and that is not worth a new policy annotation in `tests/v2/rules_doc.py`'s registry. diff --git a/docs/design/decisions/T3-interpuncts-flank-guard.md b/docs/design/decisions/T3-interpuncts-flank-guard.md index 668b9f0f..48fdd88c 100644 --- a/docs/design/decisions/T3-interpuncts-flank-guard.md +++ b/docs/design/decisions/T3-interpuncts-flank-guard.md @@ -1,5 +1,16 @@ # T3 — the interpunct's flank guard +## Standing + +- U+00B7 divides only between classified-script characters, being also the Catalan punt volat interior to names (`Gal·la Smith` → given 'Gal·la'), while the nakaguro needs no such guard — 2026-07-30 #298, U+00B7 divides only +- The scope is the codepoint, not a blanket "dot = transcription" rule: U+00B7 records the transcription fact and suppresses division (`威廉·莎士比亚` → given '威廉', family '莎士比亚', in written order), and U+30FB and U+FF65 stay pure separators whose pieces the script licence reads (`高橋・一郎` → family '高橋'); cross-convention input reads by the codepoint it was typed with — 2026-07-30 #298, codepoint scope + +The Declined line below stands as written: a half-flanked interpunct (`王·Smith`) stays in its word and reports nothing of its own. + +Open: none in this file. + +## Log + - 2026-07-30 #298 — codepoint scope, chosen over a blanket "dot = transcription" rule that would have flipped 高橋・一郎, a correct and deliberately pinned #272 reading: U+00B7 records the transcription fact and suppresses division; U+30FB/U+FF65 stay pure separators whose pieces the script license reads (rule W4's interpunct Except and its katakana-middle-dot Then bullet demonstrate both). Cross-convention input reads by the codepoint it was actually typed with — a chosen limitation, recorded in #298's comments. diff --git a/docs/design/decisions/T4-bare-letter-as-an-initial.md b/docs/design/decisions/T4-bare-letter-as-an-initial.md index fc1ee6a1..b91989a4 100644 --- a/docs/design/decisions/T4-bare-letter-as-an-initial.md +++ b/docs/design/decisions/T4-bare-letter-as-an-initial.md @@ -1,5 +1,20 @@ # T4 — the bare letter as an initial +## Standing + +- In a name written wholly in one case a bare letter is tagged an initial wherever its capital, one ASCII capital, would be, so the two spellings read alike: `j nee smith` reads given 'j' as `J NEE SMITH` reads given 'J', and `jose j y lopez` middle 'j y'; `é` and `ж`, whose capitals are no initial, read as before in both spellings — 2026-10-18 #723 +- A connective letter among the name's own words stays P8's, the vocabulary answering before this rule is asked (`JOSE I LOPEZ` reports `conjunction-or-initial`) — 2026-10-18 #723 +- A letter among the own words asks the own words' case (`j nee Smith` → given 'j'), and a letter outside them asks the whole name's: `DR. NÉE JUAN y LOPEZ` reads family 'JUAN y LOPEZ' and `MARY SMITH NEE v JONES` middle 'SMITH NEE v', family 'JONES' — 2026-10-18 #723's code review +- The strict suffix test vetoes a one-case lower-case letter as it vetoes its capital: under `Policy(lenient_comma_suffixes=False)` `john smith, v` reads given 'v' as `JOHN SMITH, V` reads given 'V', and by default `田中さん v` reads given '田中さん', family 'v' — 2026-10-18 #727 +- A maiden clause's numeral is not held to its capitals, an Accepted clause of the rule: `john smith nee x x` reads suffix 'x', maiden 'x', as 2.2.0 through 2.4.2 read it — 2026-10-18 #731 reverted +- The written shape of an initial is rules/T5's, and this rule keeps the case contrast — 2026-10-19 + +Not standing: 2026-10-18 #731; the maiden take's one-case candidate test, reverted by the entry after it. + +Open: none in this file. + +## Log + - 2026-10-18, #723 — A ONE-CASE BARE LETTER IS THE INITIAL ITS CAPITAL IS. #721 applied S10's "a name written wholly in one case says nothing about any letter in it" to the one-letter roman numerals only (decisions/S10, the Open line of its /simplify entry), so a bare lowercase `j` was a name word where `J` was an initial, and every rule that reads an initial differently read the two spellings of one name differently: M4 (`j nee smith` family 'j', `J NEE SMITH` given 'J'), O5 (a lone `j` reported `given-or-family`, `J` did not), and P3's count, which passes over an initial (`jose j y lopez` family 'j y lopez', `JOSE J Y LOPEZ` middle 'J Y', family 'LOPEZ', which 1.4.0 read too). The rule is now stated once, for every letter, and the numeral case is an instance of it: in a name written in one case, a bare letter is tagged an initial wherever its capital would be. Its capital is asked of the bare-capital shape (one ASCII capital), so `é` and `ж`, whose capitals are no initial, read as before in both spellings. `ı` and `ſ`, whose capitals are `I` and `S`, are tagged initials too; `ı`'s twin is not a true one, since `I` is also the marked Catalan connective and `ı` is in no vocabulary (`JOSE I LOPEZ` reports `conjunction-or-initial`, `jose ı lopez` nothing). Measured against 16ee2851 (PR #726) on a scratch grid not committed (letters a–z, `é` and `ж` in 35 shapes, three heads and all three orders, each lowercase text against its `.upper()`): 16ee2851 disagreed on 504 of 7,794 distinct pairs, this tree on none, and no capital spelling moved. The committed invariant is `test_a_one_case_name_reads_as_its_capitals` (tests/v2/test_properties.py), whose grid gains the issue's shapes and the letters `j`, `y` and `e`; its stated controls are the recompute. WHAT MOVES: lower-case letters in a name whose own words are written in one case, each onto its capital twin's reading — wholly lower-case names, and (since the case question is asked of the own words, P3) a one-case name beside a clause of another case, so `j nee Smith` reads given 'j' as `J nee Smith` does. In three classes — a lone letter loses O5's report, a letter before a maiden marker keeps the given name M4 would have taken, and a letter beside a single-letter link is passed over by P3's count, so the link in what is then a three-word name stays a name word. No corpus name moved (the snapshot and the CLDR, GND and native-script agreement records are unchanged). And P8's Accepted paragraph (P3's until the 2026-10-19 split) on a declined marker loses its "one shape" in which the name's own words read differently in the two spellings: `dr. née juan y lopez` read family 'juan y lopez' where `DR. NÉE JUAN Y LOPEZ` reads middle 'JUAN Y', because the `y` past the declined marker is outside the own words P8's fork reads; it is this rule's letter now, and reads as its capital does. And one move the snapshot cannot see (R4): case repair of a connective letter in a one-case maiden clause, now tagged an initial as its capital is, so `jose lopez nee y garcia` repairs to `Jose Lopez (Y Garcia)` where it gave `(y Garcia)` — the same tag in the two spellings that P8's rewritten paragraph intends. No corpus name's `capitalized()` moved (the design-docs review compared every one across the two trees). diff --git a/docs/design/decisions/phd-merge.md b/docs/design/decisions/phd-merge.md index 44d13746..3fbebb75 100644 --- a/docs/design/decisions/phd-merge.md +++ b/docs/design/decisions/phd-merge.md @@ -1,8 +1,20 @@ # phd-merge — the "Ph. D." split +## Standing + +- `Ph. D.` tokenizes as two words and is merged back by vocabulary, v1's fix_phd, so the spaced and unspaced spellings read alike after a name: `John Ph. D. Smith` reads suffix 'Ph. D.' — 2026-07 PR #288. +- The merge declines at the first TOKEN of the input, a suffix never opening the string (rules/S2): `Ph. D. Van Johnson` reads title 'Ph.', given 'D.', family 'Van Johnson', 1.4.0's reading, while a quoted clause before it leaves it merging (`"Bob" Ph. D. John Smith` → suffix 'Ph. D.') and a run opening the part after a family comma merges too (`Smith, Ph. D. Jr.` → suffix 'Ph. D. Jr.') — 2026-08-31 #371 +- Accepted: a title before the pair keeps the credential reading, so `Sir Ph. D. Van Johnson` reads given 'Van Johnson' with an empty family, 1.4.0 parity — 2026-08-31 #371 + +Elsewhere: `Parser.revise(suffix="Ph. D.")` renders 'Ph. D.', the merge still not firing in a field value but rules/R1's entry pass joining the pair, decisions/R1's 2026-09-06 #511 entry. + +Open: none in this file. + +## Log + - 2026-07 (v2 core, PR #288; recorded plan deviation #1 of the core plan) — "Ph. D." tokenizes as two words and is merged back by vocabulary (v1 fix_phd), so the spaced and unspaced spellings read alike. (Scoped by #371 below: alike everywhere EXCEPT at the head of the string, where the merge declines and `Ph. D. Van Johnson` reads title 'Ph.' where `Ph.D. Van Johnson` reads given 'Ph.D.'.) -- 2026-08-31 #371 (the merge declines at the head) — a suffix never BEGINS a name: position outranks the vocabulary match, and `rules/S2` now states it. The merge is what made a leading credential possible at all — every other suffix-shaped word standing first already falls out as a title (H2's abbreviation clause, or TITLES membership: `Jr.`, `MD`, `Esq.`, `Sr.`) or as a name word (`PhD`, `III`), so the `Ph.`/`D.` pair was the only shape that reached the defect. It reached it by emptying the family: `Ph. D. Van Johnson` read given 'Van Johnson' with no surname at all, which is #371's report. +- 2026-08-31 #371 (the merge declines at the head) (its revise() rendering superseded in part by #511, decisions/R1) — a suffix never BEGINS a name: position outranks the vocabulary match, and `rules/S2` now states it. The merge is what made a leading credential possible at all — every other suffix-shaped word standing first already falls out as a title (H2's abbreviation clause, or TITLES membership: `Jr.`, `MD`, `Esq.`, `Sr.`) or as a name word (`PhD`, `III`), so the `Ph.`/`D.` pair was the only shape that reached the defect. It reached it by emptying the family: `Ph. D. Van Johnson` read given 'Van Johnson' with no surname at all, which is #371's report. THIS RESTORES 1.4.0 RATHER THAN DEVIATING FROM IT — on three of the four names, and the fourth is named below. v1's fix_phd healed the pair by regex on the raw string, `\s(ph\.?\s+d\.?)`, whose leading `\s` made a head match impossible while it fired everywhere else, mid-name as readily as trailing. (A first draft of this entry said "only where it TRAILED", which is false in both directions and would license narrowing the merge to the trailing position — `John Ph. D. Smith` reads suffix 'Ph. D.' at 1.4.0 and here.) At the head v1 split it — measured on the released wheel, `Ph. D. Van Johnson` gives title 'Ph.', first 'D.', last 'Van Johnson', which is exactly what this change gives. So `expected_since_1.4.0.toml` LOSES its fix(leading-credential) rule — and READ THE DIAGNOSIS, not just the error: the gate reported it SHADOWED, not reverted. `compare.py` separates the two ("shadowed -- an earlier rule claimed every diff it would have" versus "reverted -- matches no diffing name") because the fixes differ. Three of its four names became parity and the fourth, `Ph. D., Jr.`, still diverges under `fix(comma-precomma-family)`, which sits above it. #426 is the precedent for dropping a shadowed rule rather than keeping it. A first draft of this entry generalized EXPLAINED NOTHING into "how a parity restoration announces itself", which would teach the next maintainer to delete a shadowed rule whose behavior is still diverging and the 2.0.0 and 2.1.0 ledgers GAIN one, claiming the same four names, the same five roles and the same digest. What shipped in 2.0 was a regression from applying the merge unconditionally, not a reading anyone chose. OPENING THE NAME IS NOT OPENING A PIECE LIST, and the first draft got that wrong: a credential run legitimately opens segment 1 after a family comma (`Smith, Ph. D. Jr.`), which C1 reads as a listing rather than a name beginning. Testing `k == 0` alone broke fifteen tests and took the gate red at all three baselines. The caller passes the distinction in, since `_group_segment` cannot see which segment it holds. ACCEPTED, and found by review rather than by design: a TITLE before the pair keeps the credential reading, because `k` is a piece index and a title is a piece. `Sir Ph. D. Van Johnson` still reads given 'Van Johnson' with an empty family — #371's own symptom, surviving one word to the left. It is 1.4.0 parity, and the rule states it as a boundary rather than deviating: "the first piece of the name" cannot be computed before this merge, since H2's abbreviation test is true of `Ph.` itself, so a scan that stepped over titles would step over the very piece being judged. rules/S2 therefore says OPENS THE STRING, which is what the code holds and what v1 held. diff --git a/docs/design/decisions/suffix-field-composition.md b/docs/design/decisions/suffix-field-composition.md index 3274346f..6c93a696 100644 --- a/docs/design/decisions/suffix-field-composition.md +++ b/docs/design/decisions/suffix-field-composition.md @@ -1,5 +1,15 @@ # suffix-field-composition — three kinds of thing in one field +## Standing + +- `suffix` stays one field holding generational words, credentials and postnominal honorifics; the composition is recorded, with the command that re-counts it, instead of splitting the field — 2026-08-16 #326. + +Elsewhere: generation and credential are separate kinds in the vocabulary, though they still compile into the one field, decisions/vocabulary-model's 2026-10-15/16 entry (item 3). + +Open: [#326](https://github.com/derek73/python-nameparser/issues/326) — whether `suffix` splits into generation and credentials; whether the honorifics belong in `suffix` at all is deliberately undecided here. + +## Log + - 2026-08-16 (suffix keystone; #326) — measured composition of suffix_words (40 entries): 11 generational (i, ii, iii, iv, v, jr, jnr, sr, snr, junior, 2), 5 neither (dr, esq, esquire, ret, vet), and 24 POSTNOMINAL HONORIFICS — 20 CJK (さん, さま, くん, ちゃん, 様, 殿, 氏, 先生, 博士, 教授, 女士, 小姐, 씨, 양, 군, 님, 박사, 박사님, 교수님, 선생님) and 4 Hebrew (ז"ל, ז״ל, שליט"א, שליט״א). The honorifics are the LARGEST group. - Decision: do NOT split the field. Record the composition; #296, #291, #325 and #289 proceed on their own terms rather than diff --git a/docs/design/decisions/two-input-invariants.md b/docs/design/decisions/two-input-invariants.md index da1dfc33..7d6038cb 100644 --- a/docs/design/decisions/two-input-invariants.md +++ b/docs/design/decisions/two-input-invariants.md @@ -1,5 +1,16 @@ # two-input-invariants — checking a rule against something that is not the code +## Standing + +- A rule is checked against an invariant relating two DIFFERENT inputs, which consults no rule statement, so it can find a rule that is wrong (docs/design/AGENTS.md's axis 11) — 2026-08-31 #473. +- The worked invariant: `Family, Given Particle` under the default order and `Family Given Particle` under FAMILY_FIRST parse alike for every particle but `do`, `mc` and `vd`, the particle-and-suffix words, the families and givens being drawn from outside the particle vocabulary; `tests/v2/test_order_correspondence.py` holds it, asserting the disagreeing set is exactly those three — 2026-08-31 #473. + +Elsewhere: why #473's "0 of 216 → 216 of 216" figure cannot be trusted is decisions/P6's. + +Open: none in this file. + +## Log + - 2026-08-31 (#473; earned by #461 and #466) — AGENTS.md axis 11. Axes 1–10 compare a document to another document, to its own examples, or to the implementation, so all ten can only find code that disagrees with a rule; none can find a rule that is wrong. #461 was backed out and #466 closed unmerged for exactly that reason, a month apart, each after a review round that found real defects and missed the premise beneath them. An invariant relating two DIFFERENT inputs consults no rule statement and is not blind that way. - The worked invariant: for a name written `Family Given Particle`, the family-comma writing under the default order and the comma-less writing under FAMILY_FIRST must parse alike. Measured 2026-08-31 on 9f1d46e, comparing the non-empty role fields over `nameparser.config.particles.PARTICLES` × six families × six givens: it holds on all 36 pairs for 67 of the 70 particles, and fails on all 36 for `do`, `mc` and `vd` — precisely the words that are also suffix vocabulary, which rules/P6's Accepted consequence already names as the contested memberships. The invariant found that set from behavior alone, having read no rule. diff --git a/docs/design/decisions/v1-xfail-triage.md b/docs/design/decisions/v1-xfail-triage.md index 45e6c8b3..167b4468 100644 --- a/docs/design/decisions/v1-xfail-triage.md +++ b/docs/design/decisions/v1-xfail-triage.md @@ -4,6 +4,20 @@ The reconciled v1-style banks (`tests/test_*.py`) carried eight `@pytest.mark.xf uv run python -c "from nameparser import HumanName; [print(repr(s), repr(HumanName(s))) for s in ('Maier, Amy I, Jr.', 'Dr King Jr', 'King, Dr Jr', 'Ahmad ben Husain', \"The Right Hon. the President of the Queen's Bench Division\")]" +## Standing + +- A trailing `I` before a further comma stays a middle initial, the commoner reading: `Maier, Amy I, Jr.` reads middle 'I', suffix 'Jr.', where `Maier, Amy I` reads suffix 'I' (rules/C9) — 2026-09-01 (Derek's triage) +- `king` stays in TITLES for the addressing forms: `Dr King Jr` reads title 'Dr', family 'King', suffix 'Jr' (H3's peel floor, since 2026-09-08) with `title-or-name` reported, and `King, Dr Jr` reads the same fields — 2026-09-01 (Derek's triage) +- `ben` stays out of the particles, colliding with the given name Ben where the particle claim acts: `Ahmad ben Husain` reads middle 'ben', family 'Husain' — 2026-09-01 (Derek's triage) +- An input that is all titles takes its last title word as the name and reports `title-or-name` (rules/H4): `The Right Hon. the President of the Queen's Bench Division` reads family 'Division' — 2026-09-01 (Derek's triage) +- The two surviving markers are #490's, cited by issue: `E.T. Smith` and `U.S. District Judge Marc Thomas Treadwell`, dotted initials against dotted vocabulary; #489's `Her Majesty Queen Elizabeth` (given 'Elizabeth') was fixed on 2026-09-08 — 2026-09-01 +- `juan garcia III` repairs to `Juan Garcia III` and its test is an ordinary pin — 2026-09-23 #492. +- #485's `xfail:` corpus-label prefix is not adopted: a surviving marker carries its issue number, and a decided reading moving is a classified change — 2026-09-01 #485. + +Open: [#490](https://github.com/derek73/python-nameparser/issues/490) — dotted initials against dotted title and credential vocabulary. + +## Log + - 2026-09-01 (Derek's triage) — NOT FIXED, four. The aspiration is rejected, the marker is gone, and the test now pins what the parser does with the grounds recorded beside it so nobody re-derives the rejected proposal: - `Maier, Amy I, Jr.` — v1 wanted suffix "I, Jr."; it reads given Amy, middle I, family Maier, suffix "Jr.". A middle initial `I` is far more common than an ordinal I borne without a Sr./Jr.-style companion, so where an explicit suffix comma has already named the suffix, the trailing I stays a middle initial. That is #vocabulary-collisions' commonality reasoning applied to a SHAPE rather than to a word — the criterion is written per-word, and the extension is named here rather than smuggled in. **This amends rules/C1 (rules/C9 since #728) in the same diff.** C1's one-character clause said a bare single letter behind a name word is the generation, full stop, which predicts exactly the suffix "I, Jr." being rejected here. The discriminator the parser has always applied and C1 omitted is the FURTHER COMMA: `Maier, Amy I` reads suffix 'I' while `Maier, Amy I, Jr.` reads middle 'I', and `Smith, John V` against `Smith, John V, Jr.` is the same pair on a name C1 already exemplifies — all four measured. So the rule was incomplete, not the behavior: a rules.md defect rather than a parser one, which is the call this file already made for `'Donald mc'` in the fields-only arc entry (2026-08-27, #451). The qualifier is normative prose carrying no example line of its own; C1's Accepted block (rules/C9's since #728) records why and names the bank pair as its executable witness. - `Dr King Jr` — **FIXED 2026-09-08 (the #316/#489 bundle), and this is the one bullet of the four that moved out of NOT FIXED.** v1 wanted title 'Dr', family 'King', suffix 'Jr', and that is what the parse gives; the leading title peel now leaves a name word a suffix cannot be (#H3). The bullet is amended in place rather than moved, because the half it DECIDED is untouched and is still the reason the shape exists at all. What follows is the 2026-09-01 record, with the reading it describes corrected where the fix falsified it. It read title 'Dr King', family 'Jr' then. `king` stays in the titles vocabulary: it is there for the addressing forms ("King Charles"), and taking it out to serve the surname reading trades a common use for a rarer one, which is the direction #vocabulary-collisions cuts. TITLES has no ambiguous subset and no AmbiguityKind, so — as with MAIDEN_MARKERS and `roz` — the only two expressions of the criterion available here are ship and do not ship; #348 is the open work that would give this set a third answer. The comma format is the road to the surname reading, and is now pinned alongside rather than left as prose — `King, Dr Jr` reads title 'Dr', family 'King', suffix 'Jr'. The test cites [#27](https://github.com/derek73/python-nameparser/issues/27), which is closed; this is the decision it never got. @@ -15,5 +29,6 @@ The reconciled v1-style banks (`tests/test_*.py`) carried eight `@pytest.mark.xf - [#490](https://github.com/derek73/python-nameparser/issues/490) — `E.T. Smith` (`tests/test_conjunctions.py::test_two_initials_conflict_with_conjunction`) and `U.S. District Judge Marc Thomas Treadwell` (`tests/test_titles.py::test_chained_title_first_name_title_is_initials`). One issue for two tests deliberately: each test's own comment names the other's shape as what blocks a fix — dotted initials against dotted title and credential vocabulary — so they are one question, and fixing either alone is what has failed before. - [#492](https://github.com/derek73/python-nameparser/issues/492) — `capitalize()` leaves `juan garcia III` lowercase (`tests/test_capitalization.py::test_capitalization_exception_for_already_capitalized_III_KNOWN_FAILURE`). The `_KNOWN_FAILURE` suffix is kept: it is still true, and beside the annotated reason it reads as redundant rather than misleading. - 2026-09-23 [#492](https://github.com/derek73/python-nameparser/issues/492) — FIXED (the #459/#492/#478 bundle): `juan garcia III` repairs to `Juan Garcia III`, R5's gate leaving the suffixes out (decisions/R5, 2026-09-23). The marker is gone and the test is renamed `tests/test_capitalization.py::test_capitalization_exception_for_already_capitalized_III`, the `_KNOWN_FAILURE` suffix the bullet above kept being no longer true. Two markers remain, both #490's, and the marker counts above — this entry's heading, the FIX CANDIDATES line and the #485 bullet — were amended in place on this date, following the #489 fix's precedent. `tools/differential/corpus.jsonl` still carries the old test name as a label: that file is v1's own suite harvested at a pinned ref (tools/differential/README.md), so the label names where the row came from and is left as it is. +- 2026-09-01 #485 — #485'S `xfail:` LABEL PREFIX IS NOT ADOPTED: the bullet below, written in this triage. - [#485](https://github.com/derek73/python-nameparser/issues/485) is superseded. It proposed prefixing corpus labels with `xfail:` so a radar diff on a name like `Dr King Jr` reads as a known-bad parse improving rather than a regression. The premise was that a v1 xfail marker is a usable triage signal; after this triage it is not, because the marker no longer tracks the disposition — `Dr King Jr` is now a PIN, so a radar diff on it means a decided reading moved, which is precisely the signal the prefix would have suppressed. The four that remain carry issue numbers, which is the same information in a place that cannot go stale against the pinned historical ref. The 2026-09-08 fix STRENGTHENS this rather than unsettling it, and the argument is worth stating because the fix looks at first like the counterexample: `Dr King Jr` moved, so a diff on it is exactly what #485's prefix predicted — but it moved by DECISION, with a ledger rule and a rewritten pin, which is a classified change and not a known-bad parse quietly improving. Had the prefix shipped, that diff would have read as the aspiration finally coming true and nobody would have been asked to classify it. Two markers remain, both #490's (re-counted 2026-09-23, #492), and they still carry issue numbers. diff --git a/tests/v2/decisions_without_standing.json b/tests/v2/decisions_without_standing.json index 6551a6c0..d2fcb805 100644 --- a/tests/v2/decisions_without_standing.json +++ b/tests/v2/decisions_without_standing.json @@ -2,30 +2,16 @@ "_comment": "decisions files allowed to lack a ## Standing block: those created before the rule, C1's block landing on 2026-10-19 (C12 and vocabulary-model were created that day, before it). A file leaves the list the day it gains a block, or the day the index lists it under Reference lists; the list must stay inside STANDING_EXEMPT_ORIGINAL in tests/v2/design_docs.py, so it never holds a key it did not start with and a new file never joins it (tests/v2/test_design_docs.py::test_a_decisions_file_off_the_list_opens_with_standing; #762 decides the rest).", "exempt": [ "3-0-reevaluations", - "A1", - "A2", - "D1", - "D2", - "N1", - "N2", - "N3", "R1", "R2", "R3", "R5", - "T1", - "T3", - "T4", "comma-suffix-arc", "comparison-surface", "deviates-registry", "differential-ledger", "parse-cost", - "phd-merge", "render-default", - "review-agent-single-source", - "suffix-field-composition", - "two-input-invariants", - "v1-xfail-triage" + "review-agent-single-source" ] } From b9213bfa913f35806c975700db39e39cb8b57568 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 19:36:57 +0000 Subject: [PATCH 2/5] docs(decisions): fix review findings on the Standing blocks (#762) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - phd-merge: the spellings read alike only for a trailing pair; mid-name the merged piece starts no credential run (decisions/S7 #544, decisions/S8). Narrowed the Standing line, added an Elsewhere line, and marked the #288 head under edit kind 1. The Accepted line now scopes the lost surname to a given-name title (`Dr. Ph. D. Van Johnson` keeps family 'Van Johnson'). - rules/S2: the same scope in its Accepted bullet, which the tree contradicted for any title but a given-name one. Budget re-recorded (S2 accepted_words 203 -> 207: four words scoping an existing clause, no new signal). - N3: M4's reading moves to Elsewhere, and #761 to Not standing. - T4: drop the Not standing line for #731, which is already marked SUPERSEDED under edit kind 1. - T3: "suppresses the surname split", not "division". - D2: 남궁민수 reports segmentation by default, not under locales.ZH. - v1-xfail-triage: H4's title-or-name report moves to Elsewhere (2026-09-07 #491). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RaPAyHYi8MDM6GXXjtVPe9 --- .../decisions/D2-construction-raises-parse-never-does.md | 2 +- docs/design/decisions/N3-lone-word-nickname-rule.md | 4 ++-- docs/design/decisions/T3-interpuncts-flank-guard.md | 2 +- docs/design/decisions/T4-bare-letter-as-an-initial.md | 2 -- docs/design/decisions/phd-merge.md | 8 ++++---- docs/design/decisions/v1-xfail-triage.md | 6 ++++-- ...-suffix-word-ends-a-name-and-never-opens-the-string.md | 2 +- tests/v2/rules_budget.json | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/design/decisions/D2-construction-raises-parse-never-does.md b/docs/design/decisions/D2-construction-raises-parse-never-does.md index e4e76c9f..cb383e20 100644 --- a/docs/design/decisions/D2-construction-raises-parse-never-does.md +++ b/docs/design/decisions/D2-construction-raises-parse-never-does.md @@ -4,7 +4,7 @@ - Every raise in the locale-apply path is a plain `TypeError` or `ValueError`, so the rewrap with the locale's code cannot break on an exotic exception signature — 2026-07 PR #288. -The two Declined lines below (2026-07-27) stand as written: no zh-vs-ja kind, and `order` stays reserved and unemitted for script-resolved names, while `segmentation` fires on a genuine multi-split fork (`夏侯惇` and `남궁민수` under `locales.ZH` report it). +The two Declined lines below (2026-07-27) stand as written: no zh-vs-ja kind, and `order` stays reserved and unemitted for script-resolved names, while `segmentation` fires on a genuine multi-split fork (`夏侯惇` under `locales.ZH` reports it, and `남궁민수` by default). Open: none in this file. diff --git a/docs/design/decisions/N3-lone-word-nickname-rule.md b/docs/design/decisions/N3-lone-word-nickname-rule.md index 9d6676cd..fff041bf 100644 --- a/docs/design/decisions/N3-lone-word-nickname-rule.md +++ b/docs/design/decisions/N3-lone-word-nickname-rule.md @@ -3,9 +3,9 @@ ## Standing - The rule fires only where the nickname accompanies exactly one piece in total, decided in assignment where the count is settled, and a title counts against it: `'Smitty' Jones` reads family 'Jones', while `'Smitty' Dr. Jones` reaches H1, which since #410 names the family, so it reads family 'Jones' too — 2026-07 PR #288. -- The bare spelling's one name word reads as the family name by M4 (2026-08-27, #445), which reaches the bracketed and the bare spelling alike, and the rule text carries no history — 2026-10-19 #761. +Not standing: 2026-10-19 #761, the skeleton migration, carried out. -Elsewhere: a punctuation-only clause is a nickname this rule counts (`Jones (,)` → family 'Jones'), decisions/N1's 2026-10-12 #653 entry. +Elsewhere: the bare spelling's one name word reads as the family name by M4, which reaches the bracketed and the bare spelling alike, decisions/M4's 2026-08-27 #445 entries; a punctuation-only clause is a nickname this rule counts (`Jones (,)` → family 'Jones'), decisions/N1's 2026-10-12 #653 entry. Open: none in this file. diff --git a/docs/design/decisions/T3-interpuncts-flank-guard.md b/docs/design/decisions/T3-interpuncts-flank-guard.md index 48fdd88c..2f4aa871 100644 --- a/docs/design/decisions/T3-interpuncts-flank-guard.md +++ b/docs/design/decisions/T3-interpuncts-flank-guard.md @@ -3,7 +3,7 @@ ## Standing - U+00B7 divides only between classified-script characters, being also the Catalan punt volat interior to names (`Gal·la Smith` → given 'Gal·la'), while the nakaguro needs no such guard — 2026-07-30 #298, U+00B7 divides only -- The scope is the codepoint, not a blanket "dot = transcription" rule: U+00B7 records the transcription fact and suppresses division (`威廉·莎士比亚` → given '威廉', family '莎士比亚', in written order), and U+30FB and U+FF65 stay pure separators whose pieces the script licence reads (`高橋・一郎` → family '高橋'); cross-convention input reads by the codepoint it was typed with — 2026-07-30 #298, codepoint scope +- The scope is the codepoint, not a blanket "dot = transcription" rule: U+00B7 records the transcription fact and suppresses the surname split, the name keeping its written order (`威廉·莎士比亚` → given '威廉', family '莎士比亚'), and U+30FB and U+FF65 stay pure separators whose pieces the script licence reads (`高橋・一郎` → family '高橋'); cross-convention input reads by the codepoint it was typed with — 2026-07-30 #298, codepoint scope The Declined line below stands as written: a half-flanked interpunct (`王·Smith`) stays in its word and reports nothing of its own. diff --git a/docs/design/decisions/T4-bare-letter-as-an-initial.md b/docs/design/decisions/T4-bare-letter-as-an-initial.md index b91989a4..2bad481b 100644 --- a/docs/design/decisions/T4-bare-letter-as-an-initial.md +++ b/docs/design/decisions/T4-bare-letter-as-an-initial.md @@ -9,8 +9,6 @@ - A maiden clause's numeral is not held to its capitals, an Accepted clause of the rule: `john smith nee x x` reads suffix 'x', maiden 'x', as 2.2.0 through 2.4.2 read it — 2026-10-18 #731 reverted - The written shape of an initial is rules/T5's, and this rule keeps the case contrast — 2026-10-19 -Not standing: 2026-10-18 #731; the maiden take's one-case candidate test, reverted by the entry after it. - Open: none in this file. ## Log diff --git a/docs/design/decisions/phd-merge.md b/docs/design/decisions/phd-merge.md index 3fbebb75..2398a9f3 100644 --- a/docs/design/decisions/phd-merge.md +++ b/docs/design/decisions/phd-merge.md @@ -2,17 +2,17 @@ ## Standing -- `Ph. D.` tokenizes as two words and is merged back by vocabulary, v1's fix_phd, so the spaced and unspaced spellings read alike after a name: `John Ph. D. Smith` reads suffix 'Ph. D.' — 2026-07 PR #288. +- `Ph. D.` tokenizes as two words and is merged back by vocabulary, v1's fix_phd, into one suffix piece, so a trailing `Ph. D.` reads as a trailing `PhD` does (`John Smith Ph. D.` → suffix 'Ph. D.') and the merged piece is a suffix mid-name too (`John Ph. D. Smith` → suffix 'Ph. D.', where `John PhD Smith` keeps middle 'PhD') — 2026-07 PR #288. - The merge declines at the first TOKEN of the input, a suffix never opening the string (rules/S2): `Ph. D. Van Johnson` reads title 'Ph.', given 'D.', family 'Van Johnson', 1.4.0's reading, while a quoted clause before it leaves it merging (`"Bob" Ph. D. John Smith` → suffix 'Ph. D.') and a run opening the part after a family comma merges too (`Smith, Ph. D. Jr.` → suffix 'Ph. D. Jr.') — 2026-08-31 #371 -- Accepted: a title before the pair keeps the credential reading, so `Sir Ph. D. Van Johnson` reads given 'Van Johnson' with an empty family, 1.4.0 parity — 2026-08-31 #371 +- Accepted: a title before the pair keeps the credential reading, and behind a given-name title the name then has no family: `Sir Ph. D. Van Johnson` reads given 'Van Johnson' with an empty family, 1.4.0 parity, while `Dr. Ph. D. Van Johnson` reads family 'Van Johnson', H1 naming the family behind any other title — 2026-08-31 #371 -Elsewhere: `Parser.revise(suffix="Ph. D.")` renders 'Ph. D.', the merge still not firing in a field value but rules/R1's entry pass joining the pair, decisions/R1's 2026-09-06 #511 entry. +Elsewhere: the merged pair starts no credential run in a main segment, so the two spellings part there (`John Smith Ph. D. Jones` → family 'Jones', suffix 'Ph. D.', where `John Smith PhD Jones` reads suffix 'PhD Jones'; `John Smith Ph. D. MEng` keeps family 'MEng'), decisions/S7's 2026-09-27 (Derek), #544 entry and decisions/S8's Elsewhere line; `Parser.revise(suffix="Ph. D.")` renders 'Ph. D.', the merge still not firing in a field value but rules/R1's entry pass joining the pair, decisions/R1's 2026-09-06 #511 entry. Open: none in this file. ## Log -- 2026-07 (v2 core, PR #288; recorded plan deviation #1 of the core plan) — "Ph. D." tokenizes as two words and is merged back by vocabulary (v1 fix_phd), so the spaced and unspaced spellings read alike. (Scoped by #371 below: alike everywhere EXCEPT at the head of the string, where the merge declines and `Ph. D. Van Johnson` reads title 'Ph.' where `Ph.D. Van Johnson` reads given 'Ph.D.'.) +- 2026-07 (v2 core, PR #288; recorded plan deviation #1 of the core plan) (its spellings reading alike superseded in part by #544, decisions/S7, and #602, decisions/S8: the merged pair starts no credential run in a main segment) — "Ph. D." tokenizes as two words and is merged back by vocabulary (v1 fix_phd), so the spaced and unspaced spellings read alike. (Scoped by #371 below: alike everywhere EXCEPT at the head of the string, where the merge declines and `Ph. D. Van Johnson` reads title 'Ph.' where `Ph.D. Van Johnson` reads given 'Ph.D.'.) - 2026-08-31 #371 (the merge declines at the head) (its revise() rendering superseded in part by #511, decisions/R1) — a suffix never BEGINS a name: position outranks the vocabulary match, and `rules/S2` now states it. The merge is what made a leading credential possible at all — every other suffix-shaped word standing first already falls out as a title (H2's abbreviation clause, or TITLES membership: `Jr.`, `MD`, `Esq.`, `Sr.`) or as a name word (`PhD`, `III`), so the `Ph.`/`D.` pair was the only shape that reached the defect. It reached it by emptying the family: `Ph. D. Van Johnson` read given 'Van Johnson' with no surname at all, which is #371's report. THIS RESTORES 1.4.0 RATHER THAN DEVIATING FROM IT — on three of the four names, and the fourth is named below. v1's fix_phd healed the pair by regex on the raw string, `\s(ph\.?\s+d\.?)`, whose leading `\s` made a head match impossible while it fired everywhere else, mid-name as readily as trailing. (A first draft of this entry said "only where it TRAILED", which is false in both directions and would license narrowing the merge to the trailing position — `John Ph. D. Smith` reads suffix 'Ph. D.' at 1.4.0 and here.) At the head v1 split it — measured on the released wheel, `Ph. D. Van Johnson` gives title 'Ph.', first 'D.', last 'Van Johnson', which is exactly what this change gives. So `expected_since_1.4.0.toml` LOSES its fix(leading-credential) rule — and READ THE DIAGNOSIS, not just the error: the gate reported it SHADOWED, not reverted. `compare.py` separates the two ("shadowed -- an earlier rule claimed every diff it would have" versus "reverted -- matches no diffing name") because the fixes differ. Three of its four names became parity and the fourth, `Ph. D., Jr.`, still diverges under `fix(comma-precomma-family)`, which sits above it. #426 is the precedent for dropping a shadowed rule rather than keeping it. A first draft of this entry generalized EXPLAINED NOTHING into "how a parity restoration announces itself", which would teach the next maintainer to delete a shadowed rule whose behavior is still diverging and the 2.0.0 and 2.1.0 ledgers GAIN one, claiming the same four names, the same five roles and the same digest. What shipped in 2.0 was a regression from applying the merge unconditionally, not a reading anyone chose. diff --git a/docs/design/decisions/v1-xfail-triage.md b/docs/design/decisions/v1-xfail-triage.md index 167b4468..6fef0c32 100644 --- a/docs/design/decisions/v1-xfail-triage.md +++ b/docs/design/decisions/v1-xfail-triage.md @@ -7,13 +7,15 @@ The reconciled v1-style banks (`tests/test_*.py`) carried eight `@pytest.mark.xf ## Standing - A trailing `I` before a further comma stays a middle initial, the commoner reading: `Maier, Amy I, Jr.` reads middle 'I', suffix 'Jr.', where `Maier, Amy I` reads suffix 'I' (rules/C9) — 2026-09-01 (Derek's triage) -- `king` stays in TITLES for the addressing forms: `Dr King Jr` reads title 'Dr', family 'King', suffix 'Jr' (H3's peel floor, since 2026-09-08) with `title-or-name` reported, and `King, Dr Jr` reads the same fields — 2026-09-01 (Derek's triage) +- `king` stays in TITLES for the addressing forms: `Dr King Jr` reads title 'Dr', family 'King', suffix 'Jr' (H3's peel floor, since 2026-09-08), and `King, Dr Jr` reads the same fields — 2026-09-01 (Derek's triage) - `ben` stays out of the particles, colliding with the given name Ben where the particle claim acts: `Ahmad ben Husain` reads middle 'ben', family 'Husain' — 2026-09-01 (Derek's triage) -- An input that is all titles takes its last title word as the name and reports `title-or-name` (rules/H4): `The Right Hon. the President of the Queen's Bench Division` reads family 'Division' — 2026-09-01 (Derek's triage) +- An input that is all titles takes its last title word as the name, a convention accepted rather than defended: `The Right Hon. the President of the Queen's Bench Division` reads family 'Division' — 2026-09-01 (Derek's triage) - The two surviving markers are #490's, cited by issue: `E.T. Smith` and `U.S. District Judge Marc Thomas Treadwell`, dotted initials against dotted vocabulary; #489's `Her Majesty Queen Elizabeth` (given 'Elizabeth') was fixed on 2026-09-08 — 2026-09-01 - `juan garcia III` repairs to `Juan Garcia III` and its test is an ordinary pin — 2026-09-23 #492. - #485's `xfail:` corpus-label prefix is not adopted: a surviving marker carries its issue number, and a decided reading moving is a classified change — 2026-09-01 #485. +Elsewhere: the `title-or-name` report on the word the title peel leaves standing (`Dr King Jr`, `The Right Hon. … Division`) is decisions/H4's 2026-09-07 #491 entry. + Open: [#490](https://github.com/derek73/python-nameparser/issues/490) — dotted initials against dotted title and credential vocabulary. ## Log diff --git a/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md b/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md index c4e3d6c1..1b625804 100644 --- a/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md +++ b/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md @@ -51,7 +51,7 @@ **Accepted:** -- A title before the credential keeps it a credential, and the name loses its surname exactly as it did before the clause that a suffix never opens the string — `Sir Ph. D. Van Johnson` reads given `Van Johnson` with an empty family. That is 1.4.0's reading, and that clause cannot reach it: the first piece of the NAME is not computable before this decision, since the abbreviation test H2 uses is true of `Ph.` itself, so a scan that stepped over titles would step over the very piece being judged. +- A title before the credential keeps it a credential, and behind a given-name title the name loses its surname exactly as it did before the clause that a suffix never opens the string — `Sir Ph. D. Van Johnson` reads given `Van Johnson` with an empty family. That is 1.4.0's reading, and that clause cannot reach it: the first piece of the NAME is not computable before this decision, since the abbreviation test H2 uses is true of `Ph.` itself, so a scan that stepped over titles would step over the very piece being judged. - An unambiguous suffix is consumed even when that leaves no family name at all. ``` "Smith Jr." → family="" diff --git a/tests/v2/rules_budget.json b/tests/v2/rules_budget.json index 47fa3297..aefbcbe4 100644 --- a/tests/v2/rules_budget.json +++ b/tests/v2/rules_budget.json @@ -51,7 +51,7 @@ "R4": {"words": 913, "then_words": 651, "because_sentences": 1, "except": 4, "precedence": 0, "accepted": 3, "accepted_words": 552}, "R5": {"words": 250, "then_words": 154, "because_sentences": 2, "except": 0, "precedence": 0, "accepted": 0, "accepted_words": 0}, "S1": {"words": 274, "then_words": 26, "because_sentences": 1, "except": 2, "precedence": 2, "accepted": 0, "accepted_words": 0}, - "S2": {"words": 641, "then_words": 103, "because_sentences": 1, "except": 0, "precedence": 7, "accepted": 3, "accepted_words": 203}, + "S2": {"words": 641, "then_words": 103, "because_sentences": 1, "except": 0, "precedence": 7, "accepted": 3, "accepted_words": 207}, "S3": {"words": 467, "then_words": 265, "because_sentences": 3, "except": 2, "precedence": 2, "accepted": 1, "accepted_words": 87}, "S4": {"words": 298, "then_words": 156, "because_sentences": 2, "except": 0, "precedence": 3, "accepted": 0, "accepted_words": 0}, "S5": {"words": 632, "then_words": 317, "because_sentences": 1, "except": 2, "precedence": 1, "accepted": 0, "accepted_words": 0}, From 7fba561bb725bb5353c60a2216572fe6e08c3195 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 19:41:20 +0000 Subject: [PATCH 3/5] docs(decisions): second-round review fixes on the Standing blocks (#762) - phd-merge's #288 entry: a kind-2 correction. "Read alike" never held mid-name (`John Ph. D. Smith` suffix, `John PhD Smith` middle, the same at 1.4.0 and 2.0.0). The kind-1 note keeps only #544, which parted the `Ph. D. MEng` pair. - phd-merge's Accepted line keeps #371's own Sir case. The `Dr.` contrast moves to Elsewhere (decisions/P2 #760), and the S8 pointer names its #602 entry. - rules/S2 and phd-merge: the lost surname belongs to a title run ending in a given-name title (`Sir Dr. Ph. D. Van Johnson` keeps the family). S2 accepted_words re-recorded, 207 -> 212. - T3: the declared order, not the written one; FAMILY_FIRST reverses the interpunct name. - v1-xfail-triage: `Dr King Jr` reports since H3's #316 floor. - N3: a blank line before Not standing. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RaPAyHYi8MDM6GXXjtVPe9 --- docs/design/decisions/N3-lone-word-nickname-rule.md | 1 + docs/design/decisions/T3-interpuncts-flank-guard.md | 2 +- docs/design/decisions/phd-merge.md | 6 +++--- docs/design/decisions/v1-xfail-triage.md | 2 +- ...S2-suffix-word-ends-a-name-and-never-opens-the-string.md | 2 +- tests/v2/rules_budget.json | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/design/decisions/N3-lone-word-nickname-rule.md b/docs/design/decisions/N3-lone-word-nickname-rule.md index fff041bf..cec59bec 100644 --- a/docs/design/decisions/N3-lone-word-nickname-rule.md +++ b/docs/design/decisions/N3-lone-word-nickname-rule.md @@ -3,6 +3,7 @@ ## Standing - The rule fires only where the nickname accompanies exactly one piece in total, decided in assignment where the count is settled, and a title counts against it: `'Smitty' Jones` reads family 'Jones', while `'Smitty' Dr. Jones` reaches H1, which since #410 names the family, so it reads family 'Jones' too — 2026-07 PR #288. + Not standing: 2026-10-19 #761, the skeleton migration, carried out. Elsewhere: the bare spelling's one name word reads as the family name by M4, which reaches the bracketed and the bare spelling alike, decisions/M4's 2026-08-27 #445 entries; a punctuation-only clause is a nickname this rule counts (`Jones (,)` → family 'Jones'), decisions/N1's 2026-10-12 #653 entry. diff --git a/docs/design/decisions/T3-interpuncts-flank-guard.md b/docs/design/decisions/T3-interpuncts-flank-guard.md index 2f4aa871..ad6a2993 100644 --- a/docs/design/decisions/T3-interpuncts-flank-guard.md +++ b/docs/design/decisions/T3-interpuncts-flank-guard.md @@ -3,7 +3,7 @@ ## Standing - U+00B7 divides only between classified-script characters, being also the Catalan punt volat interior to names (`Gal·la Smith` → given 'Gal·la'), while the nakaguro needs no such guard — 2026-07-30 #298, U+00B7 divides only -- The scope is the codepoint, not a blanket "dot = transcription" rule: U+00B7 records the transcription fact and suppresses the surname split, the name keeping its written order (`威廉·莎士比亚` → given '威廉', family '莎士比亚'), and U+30FB and U+FF65 stay pure separators whose pieces the script licence reads (`高橋・一郎` → family '高橋'); cross-convention input reads by the codepoint it was typed with — 2026-07-30 #298, codepoint scope +- The scope is the codepoint, not a blanket "dot = transcription" rule: U+00B7 records the transcription fact and suppresses the surname split, the name keeping the declared order rather than the script's (by default `威廉·莎士比亚` → given '威廉', family '莎士比亚'), and U+30FB and U+FF65 stay pure separators whose pieces the script licence reads (`高橋・一郎` → family '高橋'); cross-convention input reads by the codepoint it was typed with — 2026-07-30 #298, codepoint scope The Declined line below stands as written: a half-flanked interpunct (`王·Smith`) stays in its word and reports nothing of its own. diff --git a/docs/design/decisions/phd-merge.md b/docs/design/decisions/phd-merge.md index 2398a9f3..12adccde 100644 --- a/docs/design/decisions/phd-merge.md +++ b/docs/design/decisions/phd-merge.md @@ -4,15 +4,15 @@ - `Ph. D.` tokenizes as two words and is merged back by vocabulary, v1's fix_phd, into one suffix piece, so a trailing `Ph. D.` reads as a trailing `PhD` does (`John Smith Ph. D.` → suffix 'Ph. D.') and the merged piece is a suffix mid-name too (`John Ph. D. Smith` → suffix 'Ph. D.', where `John PhD Smith` keeps middle 'PhD') — 2026-07 PR #288. - The merge declines at the first TOKEN of the input, a suffix never opening the string (rules/S2): `Ph. D. Van Johnson` reads title 'Ph.', given 'D.', family 'Van Johnson', 1.4.0's reading, while a quoted clause before it leaves it merging (`"Bob" Ph. D. John Smith` → suffix 'Ph. D.') and a run opening the part after a family comma merges too (`Smith, Ph. D. Jr.` → suffix 'Ph. D. Jr.') — 2026-08-31 #371 -- Accepted: a title before the pair keeps the credential reading, and behind a given-name title the name then has no family: `Sir Ph. D. Van Johnson` reads given 'Van Johnson' with an empty family, 1.4.0 parity, while `Dr. Ph. D. Van Johnson` reads family 'Van Johnson', H1 naming the family behind any other title — 2026-08-31 #371 +- Accepted: a title before the pair keeps the credential reading, so `Sir Ph. D. Van Johnson` reads given 'Van Johnson' with an empty family, 1.4.0 parity — 2026-08-31 #371 -Elsewhere: the merged pair starts no credential run in a main segment, so the two spellings part there (`John Smith Ph. D. Jones` → family 'Jones', suffix 'Ph. D.', where `John Smith PhD Jones` reads suffix 'PhD Jones'; `John Smith Ph. D. MEng` keeps family 'MEng'), decisions/S7's 2026-09-27 (Derek), #544 entry and decisions/S8's Elsewhere line; `Parser.revise(suffix="Ph. D.")` renders 'Ph. D.', the merge still not firing in a field value but rules/R1's entry pass joining the pair, decisions/R1's 2026-09-06 #511 entry. +Elsewhere: the merged pair starts no credential run in a main segment, so the two spellings part there (`John Smith Ph. D. Jones` → family 'Jones', suffix 'Ph. D.', where `John Smith PhD Jones` reads suffix 'PhD Jones'; `John Smith Ph. D. MEng` keeps family 'MEng'), decisions/S8's 2026-10-04 (Derek), #602 entry and decisions/S7's 2026-09-27 (Derek), #544 entry; the empty family is a given-name title's alone, a title run addressing as its last title does, so `Dr. Ph. D. Van Johnson` reads family 'Van Johnson', decisions/P2's 2026-10-19 #760 entry; `Parser.revise(suffix="Ph. D.")` renders 'Ph. D.', the merge still not firing in a field value but rules/R1's entry pass joining the pair, decisions/R1's 2026-09-06 #511 entry. Open: none in this file. ## Log -- 2026-07 (v2 core, PR #288; recorded plan deviation #1 of the core plan) (its spellings reading alike superseded in part by #544, decisions/S7, and #602, decisions/S8: the merged pair starts no credential run in a main segment) — "Ph. D." tokenizes as two words and is merged back by vocabulary (v1 fix_phd), so the spaced and unspaced spellings read alike. (Scoped by #371 below: alike everywhere EXCEPT at the head of the string, where the merge declines and `Ph. D. Van Johnson` reads title 'Ph.' where `Ph.D. Van Johnson` reads given 'Ph.D.'.) +- 2026-07 (v2 core, PR #288; recorded plan deviation #1 of the core plan) (its spellings reading alike after a name superseded in part by #544, decisions/S7: the merged pair is outside the no-comma company walk, so `John Smith Ph. D. MEng` keeps family 'MEng' where `John Smith PhD MEng` reads suffix 'PhD MEng') — "Ph. D." tokenizes as two words and is merged back by vocabulary (v1 fix_phd), so the spaced and unspaced spellings read alike at the end of a name. (Corrected 2026-10-22, #762: this said they read alike everywhere, which never held mid-name, where the merged piece is a suffix and a bare `PhD` a name word, at 1.4.0 and 2.0.0 as on the tree: `John Ph. D. Smith` reads suffix 'Ph. D.', `John PhD Smith` middle 'PhD'.) (Scoped by #371 below: not at the head of the string either, where the merge declines and `Ph. D. Van Johnson` reads title 'Ph.' where `Ph.D. Van Johnson` reads given 'Ph.D.'.) - 2026-08-31 #371 (the merge declines at the head) (its revise() rendering superseded in part by #511, decisions/R1) — a suffix never BEGINS a name: position outranks the vocabulary match, and `rules/S2` now states it. The merge is what made a leading credential possible at all — every other suffix-shaped word standing first already falls out as a title (H2's abbreviation clause, or TITLES membership: `Jr.`, `MD`, `Esq.`, `Sr.`) or as a name word (`PhD`, `III`), so the `Ph.`/`D.` pair was the only shape that reached the defect. It reached it by emptying the family: `Ph. D. Van Johnson` read given 'Van Johnson' with no surname at all, which is #371's report. THIS RESTORES 1.4.0 RATHER THAN DEVIATING FROM IT — on three of the four names, and the fourth is named below. v1's fix_phd healed the pair by regex on the raw string, `\s(ph\.?\s+d\.?)`, whose leading `\s` made a head match impossible while it fired everywhere else, mid-name as readily as trailing. (A first draft of this entry said "only where it TRAILED", which is false in both directions and would license narrowing the merge to the trailing position — `John Ph. D. Smith` reads suffix 'Ph. D.' at 1.4.0 and here.) At the head v1 split it — measured on the released wheel, `Ph. D. Van Johnson` gives title 'Ph.', first 'D.', last 'Van Johnson', which is exactly what this change gives. So `expected_since_1.4.0.toml` LOSES its fix(leading-credential) rule — and READ THE DIAGNOSIS, not just the error: the gate reported it SHADOWED, not reverted. `compare.py` separates the two ("shadowed -- an earlier rule claimed every diff it would have" versus "reverted -- matches no diffing name") because the fixes differ. Three of its four names became parity and the fourth, `Ph. D., Jr.`, still diverges under `fix(comma-precomma-family)`, which sits above it. #426 is the precedent for dropping a shadowed rule rather than keeping it. A first draft of this entry generalized EXPLAINED NOTHING into "how a parity restoration announces itself", which would teach the next maintainer to delete a shadowed rule whose behavior is still diverging and the 2.0.0 and 2.1.0 ledgers GAIN one, claiming the same four names, the same five roles and the same digest. What shipped in 2.0 was a regression from applying the merge unconditionally, not a reading anyone chose. diff --git a/docs/design/decisions/v1-xfail-triage.md b/docs/design/decisions/v1-xfail-triage.md index 6fef0c32..4f72920f 100644 --- a/docs/design/decisions/v1-xfail-triage.md +++ b/docs/design/decisions/v1-xfail-triage.md @@ -14,7 +14,7 @@ The reconciled v1-style banks (`tests/test_*.py`) carried eight `@pytest.mark.xf - `juan garcia III` repairs to `Juan Garcia III` and its test is an ordinary pin — 2026-09-23 #492. - #485's `xfail:` corpus-label prefix is not adopted: a surviving marker carries its issue number, and a decided reading moving is a classified change — 2026-09-01 #485. -Elsewhere: the `title-or-name` report on the word the title peel leaves standing (`Dr King Jr`, `The Right Hon. … Division`) is decisions/H4's 2026-09-07 #491 entry. +Elsewhere: the `title-or-name` report on the word the title peel leaves standing (`Dr King Jr`, `The Right Hon. … Division`) is decisions/H4's 2026-09-07 #491 entry, reaching `Dr King Jr` since decisions/H3's 2026-09-08 #316 floor left `King` standing. Open: [#490](https://github.com/derek73/python-nameparser/issues/490) — dotted initials against dotted title and credential vocabulary. diff --git a/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md b/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md index 1b625804..92953da2 100644 --- a/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md +++ b/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md @@ -51,7 +51,7 @@ **Accepted:** -- A title before the credential keeps it a credential, and behind a given-name title the name loses its surname exactly as it did before the clause that a suffix never opens the string — `Sir Ph. D. Van Johnson` reads given `Van Johnson` with an empty family. That is 1.4.0's reading, and that clause cannot reach it: the first piece of the NAME is not computable before this decision, since the abbreviation test H2 uses is true of `Ph.` itself, so a scan that stepped over titles would step over the very piece being judged. +- A title before the credential keeps it a credential, and behind a title run ending in a given-name title the name loses its surname exactly as it did before the clause that a suffix never opens the string — `Sir Ph. D. Van Johnson` reads given `Van Johnson` with an empty family. That is 1.4.0's reading, and that clause cannot reach it: the first piece of the NAME is not computable before this decision, since the abbreviation test H2 uses is true of `Ph.` itself, so a scan that stepped over titles would step over the very piece being judged. - An unambiguous suffix is consumed even when that leaves no family name at all. ``` "Smith Jr." → family="" diff --git a/tests/v2/rules_budget.json b/tests/v2/rules_budget.json index aefbcbe4..fcb1ff2a 100644 --- a/tests/v2/rules_budget.json +++ b/tests/v2/rules_budget.json @@ -51,7 +51,7 @@ "R4": {"words": 913, "then_words": 651, "because_sentences": 1, "except": 4, "precedence": 0, "accepted": 3, "accepted_words": 552}, "R5": {"words": 250, "then_words": 154, "because_sentences": 2, "except": 0, "precedence": 0, "accepted": 0, "accepted_words": 0}, "S1": {"words": 274, "then_words": 26, "because_sentences": 1, "except": 2, "precedence": 2, "accepted": 0, "accepted_words": 0}, - "S2": {"words": 641, "then_words": 103, "because_sentences": 1, "except": 0, "precedence": 7, "accepted": 3, "accepted_words": 207}, + "S2": {"words": 641, "then_words": 103, "because_sentences": 1, "except": 0, "precedence": 7, "accepted": 3, "accepted_words": 212}, "S3": {"words": 467, "then_words": 265, "because_sentences": 3, "except": 2, "precedence": 2, "accepted": 1, "accepted_words": 87}, "S4": {"words": 298, "then_words": 156, "because_sentences": 2, "except": 0, "precedence": 3, "accepted": 0, "accepted_words": 0}, "S5": {"words": 632, "then_words": 317, "because_sentences": 1, "except": 2, "precedence": 1, "accepted": 0, "accepted_words": 0}, From 8f872bf5f4a4be8bc6574a78f5d96cbb9a105872 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 19:46:09 +0000 Subject: [PATCH 4/5] docs(decisions): third-round review fixes (#762) - phd-merge's #288 correction: the spellings never read alike at the head either (1.4.0 title 'Ph.', 2.0.0 suffix 'Ph. D.', against given 'Ph.D.'). #371 changed the head reading; it did not end an agreement. - phd-merge's Elsewhere: the addressing clause points at decisions/H1's 2026-09-08 #489 entry, not P2's #760. - rules/S2: "a title run that addresses by the given name (H1)" rather than restating H1's predicate without its whole-key arm. S2 accepted_words re-recorded, 212 -> 214. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RaPAyHYi8MDM6GXXjtVPe9 --- docs/design/decisions/phd-merge.md | 4 ++-- .../S2-suffix-word-ends-a-name-and-never-opens-the-string.md | 2 +- tests/v2/rules_budget.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/design/decisions/phd-merge.md b/docs/design/decisions/phd-merge.md index 12adccde..3ceab59a 100644 --- a/docs/design/decisions/phd-merge.md +++ b/docs/design/decisions/phd-merge.md @@ -6,13 +6,13 @@ - The merge declines at the first TOKEN of the input, a suffix never opening the string (rules/S2): `Ph. D. Van Johnson` reads title 'Ph.', given 'D.', family 'Van Johnson', 1.4.0's reading, while a quoted clause before it leaves it merging (`"Bob" Ph. D. John Smith` → suffix 'Ph. D.') and a run opening the part after a family comma merges too (`Smith, Ph. D. Jr.` → suffix 'Ph. D. Jr.') — 2026-08-31 #371 - Accepted: a title before the pair keeps the credential reading, so `Sir Ph. D. Van Johnson` reads given 'Van Johnson' with an empty family, 1.4.0 parity — 2026-08-31 #371 -Elsewhere: the merged pair starts no credential run in a main segment, so the two spellings part there (`John Smith Ph. D. Jones` → family 'Jones', suffix 'Ph. D.', where `John Smith PhD Jones` reads suffix 'PhD Jones'; `John Smith Ph. D. MEng` keeps family 'MEng'), decisions/S8's 2026-10-04 (Derek), #602 entry and decisions/S7's 2026-09-27 (Derek), #544 entry; the empty family is a given-name title's alone, a title run addressing as its last title does, so `Dr. Ph. D. Van Johnson` reads family 'Van Johnson', decisions/P2's 2026-10-19 #760 entry; `Parser.revise(suffix="Ph. D.")` renders 'Ph. D.', the merge still not firing in a field value but rules/R1's entry pass joining the pair, decisions/R1's 2026-09-06 #511 entry. +Elsewhere: the merged pair starts no credential run in a main segment, so the two spellings part there (`John Smith Ph. D. Jones` → family 'Jones', suffix 'Ph. D.', where `John Smith PhD Jones` reads suffix 'PhD Jones'; `John Smith Ph. D. MEng` keeps family 'MEng'), decisions/S8's 2026-10-04 (Derek), #602 entry and decisions/S7's 2026-09-27 (Derek), #544 entry; the empty family belongs to a title run that addresses by the given name (`Sir`), so `Dr. Ph. D. Van Johnson` reads family 'Van Johnson', decisions/H1's 2026-09-08 #489 entry; `Parser.revise(suffix="Ph. D.")` renders 'Ph. D.', the merge still not firing in a field value but rules/R1's entry pass joining the pair, decisions/R1's 2026-09-06 #511 entry. Open: none in this file. ## Log -- 2026-07 (v2 core, PR #288; recorded plan deviation #1 of the core plan) (its spellings reading alike after a name superseded in part by #544, decisions/S7: the merged pair is outside the no-comma company walk, so `John Smith Ph. D. MEng` keeps family 'MEng' where `John Smith PhD MEng` reads suffix 'PhD MEng') — "Ph. D." tokenizes as two words and is merged back by vocabulary (v1 fix_phd), so the spaced and unspaced spellings read alike at the end of a name. (Corrected 2026-10-22, #762: this said they read alike everywhere, which never held mid-name, where the merged piece is a suffix and a bare `PhD` a name word, at 1.4.0 and 2.0.0 as on the tree: `John Ph. D. Smith` reads suffix 'Ph. D.', `John PhD Smith` middle 'PhD'.) (Scoped by #371 below: not at the head of the string either, where the merge declines and `Ph. D. Van Johnson` reads title 'Ph.' where `Ph.D. Van Johnson` reads given 'Ph.D.'.) +- 2026-07 (v2 core, PR #288; recorded plan deviation #1 of the core plan) (its spellings reading alike after a name superseded in part by #544, decisions/S7: the merged pair is outside the no-comma company walk, so `John Smith Ph. D. MEng` keeps family 'MEng' where `John Smith PhD MEng` reads suffix 'PhD MEng') — "Ph. D." tokenizes as two words and is merged back by vocabulary (v1 fix_phd), so the spaced and unspaced spellings read alike at the end of a name. (Corrected 2026-10-22, #762: this said they read alike everywhere, which never held mid-name or at the head, at 1.4.0 and 2.0.0 as on the tree: mid-name the merged piece is a suffix and a bare `PhD` a name word (`John Ph. D. Smith` reads suffix 'Ph. D.', `John PhD Smith` middle 'PhD'), and at the head `Ph. D. Van Johnson` read title 'Ph.' at 1.4.0 and suffix 'Ph. D.' at 2.0.0 where `Ph.D. Van Johnson` read given 'Ph.D.'.) (Scoped by #371 below: #371 changed the head reading, where the merge now declines and `Ph. D. Van Johnson` reads title 'Ph.' where `Ph.D. Van Johnson` reads given 'Ph.D.'.) - 2026-08-31 #371 (the merge declines at the head) (its revise() rendering superseded in part by #511, decisions/R1) — a suffix never BEGINS a name: position outranks the vocabulary match, and `rules/S2` now states it. The merge is what made a leading credential possible at all — every other suffix-shaped word standing first already falls out as a title (H2's abbreviation clause, or TITLES membership: `Jr.`, `MD`, `Esq.`, `Sr.`) or as a name word (`PhD`, `III`), so the `Ph.`/`D.` pair was the only shape that reached the defect. It reached it by emptying the family: `Ph. D. Van Johnson` read given 'Van Johnson' with no surname at all, which is #371's report. THIS RESTORES 1.4.0 RATHER THAN DEVIATING FROM IT — on three of the four names, and the fourth is named below. v1's fix_phd healed the pair by regex on the raw string, `\s(ph\.?\s+d\.?)`, whose leading `\s` made a head match impossible while it fired everywhere else, mid-name as readily as trailing. (A first draft of this entry said "only where it TRAILED", which is false in both directions and would license narrowing the merge to the trailing position — `John Ph. D. Smith` reads suffix 'Ph. D.' at 1.4.0 and here.) At the head v1 split it — measured on the released wheel, `Ph. D. Van Johnson` gives title 'Ph.', first 'D.', last 'Van Johnson', which is exactly what this change gives. So `expected_since_1.4.0.toml` LOSES its fix(leading-credential) rule — and READ THE DIAGNOSIS, not just the error: the gate reported it SHADOWED, not reverted. `compare.py` separates the two ("shadowed -- an earlier rule claimed every diff it would have" versus "reverted -- matches no diffing name") because the fixes differ. Three of its four names became parity and the fourth, `Ph. D., Jr.`, still diverges under `fix(comma-precomma-family)`, which sits above it. #426 is the precedent for dropping a shadowed rule rather than keeping it. A first draft of this entry generalized EXPLAINED NOTHING into "how a parity restoration announces itself", which would teach the next maintainer to delete a shadowed rule whose behavior is still diverging and the 2.0.0 and 2.1.0 ledgers GAIN one, claiming the same four names, the same five roles and the same digest. What shipped in 2.0 was a regression from applying the merge unconditionally, not a reading anyone chose. diff --git a/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md b/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md index 92953da2..524ad257 100644 --- a/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md +++ b/docs/design/rules/S2-suffix-word-ends-a-name-and-never-opens-the-string.md @@ -51,7 +51,7 @@ **Accepted:** -- A title before the credential keeps it a credential, and behind a title run ending in a given-name title the name loses its surname exactly as it did before the clause that a suffix never opens the string — `Sir Ph. D. Van Johnson` reads given `Van Johnson` with an empty family. That is 1.4.0's reading, and that clause cannot reach it: the first piece of the NAME is not computable before this decision, since the abbreviation test H2 uses is true of `Ph.` itself, so a scan that stepped over titles would step over the very piece being judged. +- A title before the credential keeps it a credential, and behind a title run that addresses by the given name (H1) the name loses its surname exactly as it did before the clause that a suffix never opens the string — `Sir Ph. D. Van Johnson` reads given `Van Johnson` with an empty family. That is 1.4.0's reading, and that clause cannot reach it: the first piece of the NAME is not computable before this decision, since the abbreviation test H2 uses is true of `Ph.` itself, so a scan that stepped over titles would step over the very piece being judged. - An unambiguous suffix is consumed even when that leaves no family name at all. ``` "Smith Jr." → family="" diff --git a/tests/v2/rules_budget.json b/tests/v2/rules_budget.json index fcb1ff2a..786fb45f 100644 --- a/tests/v2/rules_budget.json +++ b/tests/v2/rules_budget.json @@ -51,7 +51,7 @@ "R4": {"words": 913, "then_words": 651, "because_sentences": 1, "except": 4, "precedence": 0, "accepted": 3, "accepted_words": 552}, "R5": {"words": 250, "then_words": 154, "because_sentences": 2, "except": 0, "precedence": 0, "accepted": 0, "accepted_words": 0}, "S1": {"words": 274, "then_words": 26, "because_sentences": 1, "except": 2, "precedence": 2, "accepted": 0, "accepted_words": 0}, - "S2": {"words": 641, "then_words": 103, "because_sentences": 1, "except": 0, "precedence": 7, "accepted": 3, "accepted_words": 212}, + "S2": {"words": 641, "then_words": 103, "because_sentences": 1, "except": 0, "precedence": 7, "accepted": 3, "accepted_words": 214}, "S3": {"words": 467, "then_words": 265, "because_sentences": 3, "except": 2, "precedence": 2, "accepted": 1, "accepted_words": 87}, "S4": {"words": 298, "then_words": 156, "because_sentences": 2, "except": 0, "precedence": 3, "accepted": 0, "accepted_words": 0}, "S5": {"words": 632, "then_words": 317, "because_sentences": 1, "except": 2, "precedence": 1, "accepted": 0, "accepted_words": 0}, From 66101191adf327c8802dbeaf23856aabc72b9dc5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 19:48:40 +0000 Subject: [PATCH 5/5] docs(decisions): phd-merge's #371 note points at the head change rather than calling it a scoping (#762) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RaPAyHYi8MDM6GXXjtVPe9 --- docs/design/decisions/phd-merge.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/decisions/phd-merge.md b/docs/design/decisions/phd-merge.md index 3ceab59a..497ee7a6 100644 --- a/docs/design/decisions/phd-merge.md +++ b/docs/design/decisions/phd-merge.md @@ -12,7 +12,7 @@ Open: none in this file. ## Log -- 2026-07 (v2 core, PR #288; recorded plan deviation #1 of the core plan) (its spellings reading alike after a name superseded in part by #544, decisions/S7: the merged pair is outside the no-comma company walk, so `John Smith Ph. D. MEng` keeps family 'MEng' where `John Smith PhD MEng` reads suffix 'PhD MEng') — "Ph. D." tokenizes as two words and is merged back by vocabulary (v1 fix_phd), so the spaced and unspaced spellings read alike at the end of a name. (Corrected 2026-10-22, #762: this said they read alike everywhere, which never held mid-name or at the head, at 1.4.0 and 2.0.0 as on the tree: mid-name the merged piece is a suffix and a bare `PhD` a name word (`John Ph. D. Smith` reads suffix 'Ph. D.', `John PhD Smith` middle 'PhD'), and at the head `Ph. D. Van Johnson` read title 'Ph.' at 1.4.0 and suffix 'Ph. D.' at 2.0.0 where `Ph.D. Van Johnson` read given 'Ph.D.'.) (Scoped by #371 below: #371 changed the head reading, where the merge now declines and `Ph. D. Van Johnson` reads title 'Ph.' where `Ph.D. Van Johnson` reads given 'Ph.D.'.) +- 2026-07 (v2 core, PR #288; recorded plan deviation #1 of the core plan) (its spellings reading alike after a name superseded in part by #544, decisions/S7: the merged pair is outside the no-comma company walk, so `John Smith Ph. D. MEng` keeps family 'MEng' where `John Smith PhD MEng` reads suffix 'PhD MEng') — "Ph. D." tokenizes as two words and is merged back by vocabulary (v1 fix_phd), so the spaced and unspaced spellings read alike at the end of a name. (Corrected 2026-10-22, #762: this said they read alike everywhere, which never held mid-name or at the head, at 1.4.0 and 2.0.0 as on the tree: mid-name the merged piece is a suffix and a bare `PhD` a name word (`John Ph. D. Smith` reads suffix 'Ph. D.', `John PhD Smith` middle 'PhD'), and at the head `Ph. D. Van Johnson` read title 'Ph.' at 1.4.0 and suffix 'Ph. D.' at 2.0.0 where `Ph.D. Van Johnson` read given 'Ph.D.'.) (The head reading is #371's, below: the merge now declines there and `Ph. D. Van Johnson` reads title 'Ph.' where `Ph.D. Van Johnson` reads given 'Ph.D.'.) - 2026-08-31 #371 (the merge declines at the head) (its revise() rendering superseded in part by #511, decisions/R1) — a suffix never BEGINS a name: position outranks the vocabulary match, and `rules/S2` now states it. The merge is what made a leading credential possible at all — every other suffix-shaped word standing first already falls out as a title (H2's abbreviation clause, or TITLES membership: `Jr.`, `MD`, `Esq.`, `Sr.`) or as a name word (`PhD`, `III`), so the `Ph.`/`D.` pair was the only shape that reached the defect. It reached it by emptying the family: `Ph. D. Van Johnson` read given 'Van Johnson' with no surname at all, which is #371's report. THIS RESTORES 1.4.0 RATHER THAN DEVIATING FROM IT — on three of the four names, and the fourth is named below. v1's fix_phd healed the pair by regex on the raw string, `\s(ph\.?\s+d\.?)`, whose leading `\s` made a head match impossible while it fired everywhere else, mid-name as readily as trailing. (A first draft of this entry said "only where it TRAILED", which is false in both directions and would license narrowing the merge to the trailing position — `John Ph. D. Smith` reads suffix 'Ph. D.' at 1.4.0 and here.) At the head v1 split it — measured on the released wheel, `Ph. D. Van Johnson` gives title 'Ph.', first 'D.', last 'Van Johnson', which is exactly what this change gives. So `expected_since_1.4.0.toml` LOSES its fix(leading-credential) rule — and READ THE DIAGNOSIS, not just the error: the gate reported it SHADOWED, not reverted. `compare.py` separates the two ("shadowed -- an earlier rule claimed every diff it would have" versus "reverted -- matches no diffing name") because the fixes differ. Three of its four names became parity and the fourth, `Ph. D., Jr.`, still diverges under `fix(comma-precomma-family)`, which sits above it. #426 is the precedent for dropping a shadowed rule rather than keeping it. A first draft of this entry generalized EXPLAINED NOTHING into "how a parity restoration announces itself", which would teach the next maintainer to delete a shadowed rule whose behavior is still diverging and the 2.0.0 and 2.1.0 ledgers GAIN one, claiming the same four names, the same five roles and the same digest. What shipped in 2.0 was a regression from applying the merge unconditionally, not a reading anyone chose.