Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/design/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ the fullwidth-colon marker (旧姓:佐藤 arrives as one word; the head-peel q
INV6's off-switch exemption is NOT widened, and the reason is the one AGENTS.md gives for not widening an exemption a grid cannot reach: measured 2026-09-20, 1,680 rows of that grid carry both a class letter and a marker and 0 of them put the letter after the marker, its generator placing the connective among the name's own words. A clause link has its own grid instead.
TWO SECOND-ORDER MOVEMENTS, both recorded rather than repaired. The two one-case spellings part company, which is P3's own Accepted clause reaching a taken marker rather than a declined one: classify leaves a letter after a marker to the mixed-case rule, so `JANE DOE NEE PUIG I SOLER` never moved (the capital is an initial, and the initial veto kept the walk going all along) while `jane doe nee puig i soler` goes from family 'doe i soler' to maiden 'puig i soler'. And a report is GAINED where a longer clause ends on an ambiguous credential: `Jane Doe nee Puig i Ma` keeps maiden 'Puig i Ma' and says `suffix-or-name` where 0fbcaa0b read family 'Doe i Ma' in silence — the clause's own emitter, reaching a word the truncation had put out of its reach. A THIRD, found by the second review and the mirror of that one: a report is LOST where the clause takes a class member it used to end at. `Jane Doe nee MA i Soler` reads maiden 'MA i Soler' in silence where the parent 46651750 read maiden 'MA' with a `suffix-or-name` on it. That FOLLOWS from the link no longer ending the clause and is not a second decision: the report is the walk's own, raised on the LAST word the clause kept where a trailing rule was asked about it, and with the link joining, 'MA' has a name word behind it and is no longer that word — which is exactly what `Jane Doe nee MA Smith` has always done with the same acronym. The `y` twin is the oracle and it AGREES, in fields and in reports: `Jane Doe nee MA y Soler` reads maiden 'MA y Soler' in silence at the parent and here. Measured 2026-09-20 over the review grid, 24 names lose the report this way and all 24 agree with their `y` twin on every field and every report.

- 2026-09-22 #397 follow-up — A DELIMITER CORE PAST THE CLAUSE'S FIRST WORD PASSES FOR THE NAME WORD BESIDE A LINK, RECORDED AS A DEVIATION RATHER THAN REPAIRED. The link exception above wants "a connective standing between two name words of the clause", and a separator the caller declared through `Policy.extra_suffix_delimiters` is structure, not a name word — so a link with one beside it joins nothing and should end the clause like any other suffix word. Between the marker and the clause's first word that already holds, the bound refusing the core before either piece test is asked. PAST that first word it does not: the core is an ordinary index to the run walk, which steps over connectives and nothing else, so it stands in for the name word on the link's left and the clause runs on past a title it would otherwise stop at. MEASURED 2026-09-22 under `extra_suffix_delimiters=(" - ",)`: `Smith, John, PhD née Puig Mr. - i Soler` reads maiden 'Puig Mr. i Soler' where its separator-less twin `Smith, John, PhD née Puig Mr. i Soler` stops at 'Puig Mr.'. THE POPULATION is the branch's own sweep, recorded with the code it describes (2026-09-21, corpus ∪ cases.py ∪ the property grids ∪ a 50,925-name generated set with cores, under thirteen core-bearing policies): the predicate is asked about a core in 51,072 of 900,023 calls, the answer differs from a core-skipping reading in 8,094 parses over 1,278 texts, and 1,824 of those move `maiden` on 288 texts — none of the 288 reachable at the default policy, `extra_suffix_delimiters` being empty there. NOT REPAIRED HERE: the fix threads the core set through three call sites into the run walk and moves the parent's reading as well, which makes it its own change rather than a rider on a review round. Open: #538. PINNED TWICE MEANWHILE. rules.md#M2 carries the shape as a `deviates: #538` example under an `extra_suffix_delimiters-dash` annotation — the first entry `tests/v2/rules_doc.py`'s registry has had for that field, named after the Policy field and carrying the delimiter in the suffix because the field's value is a set rather than a flag. And `tests/v2/pipeline/test_group.py::test_a_core_beside_a_link_wrongly_passes_for_a_word_until_538` holds the pair at the piece level, named so nobody reads it as the contract. ONE COST OF THE DOC EXAMPLE, worth knowing before the repair lands: its string enters `corpus_rules.jsonl`, where the differential gate parses it with the DEFAULT facade — no delimiter declared, so the dash is an ordinary name word and #538's reading is off the path entirely. It moves there for the 2026-09-20 link fix instead, and is classified as that at all five baselines: added to the `fix(#397) a link inside a maiden clause stays in the birth name` alternation at the four 2.x ledgers (suffix 'PhD i Soler' → 'PhD', maiden 'Puig Mr. -' → 'Puig Mr. - i Soler', identical at each), and given its own rule at 1.4.0, where v1 had no maiden markers and read the whole suffix-comma tail as one suffix.


### N3 — the lone-word nickname rule

Expand Down Expand Up @@ -378,6 +380,11 @@ The reconciled v1-style banks (`tests/test_*.py`) carried eight `@pytest.mark.xf
ACCEPTED, AND THE HONEST STATEMENT OF THE ONE-CASE HALF, written out because this branch's own first commit message overclaimed that one-case names keep their reading. `i` is in the MARKED subset, so in a name written wholly in one case it reads as an INITIAL and reports `conjunction-or-initial`, exactly as `e` has since #383/#479. For "JOSEP CAROD I ROVIRA" and "josep carod i rovira" the fields are indeed unchanged and only the report is new. But a one-case name reports wherever a bare `i`/`I` stands among the NAME'S OWN WORDS, which is a great many more names than the Catalan ones — "JOHN I SMITH" and "john i smith" one report each, "JOHN SMITH I" and "john smith i" two, "HENRY I" and "henry i" three. A letter inside a maiden clause is read by the clause's rules and stays silent, exactly as `e` does ("JANE DOE NEE I JONES" reports nothing), which is the own-words scope the 2026-09-13 #383/#479 entry above defines. And in ALL-LOWER names the initial reading MOVES FIELDS wherever the parent read the lower-case letter as the generation. Each such name now reads as its ALL-CAPS twin already did: "rovira, i" gives given "i" where it gave suffix "i" ("ROVIRA, I" already gave given "I"); "john smith i jr" gives middle "smith" with family "i" where it gave family "smith" with suffix "i jr" ("JOHN SMITH I JR" already did); "maier, amy i, jr." gives middle "i" with suffix "jr." where it gave suffix "i, jr." (the corpus name "Maier, Amy I, Jr." reads middle "I" and does not move); and "josep de carod i rovira" gives family "de carod i rovira" where it gave middle "de carod i" with family "rovira". None of those four is a corpus name; all are measured 2026-09-20 on this tree and against the parent's.
NO CHANGE TO THE EXCLUDED BLOCK, recorded as a decision rather than left implicit (Derek, 2026-09-20). It now lists "y" against TWO marked letters rather than one, and that is still right: the argument that put "y" outside is about "y" — the commonest Hispanic compound and this library's oldest fixture — while "i" matches "e" exactly, a bare I initial being as common as a bare E.

- 2026-09-21 #397 follow-up — AMENDS the "AND THE CONDITION IS ASKED ONCE PER RUN" paragraph above, whose frame figures stand as measured at `6048eb5d`. BOTH SIDES ARE NOW ONE CALL. A connective is placed to join only where a name word stands on EACH side of it, so neither caller ever wanted one side's answer: `_name_word_beside(k, step, ...)` was asked twice and the two answers ANDed at the call site, in the `frozen` loop and again in `_link_joins_inside_the_clause`. It is `_group._between_name_words(k, lo, hi, ...)` now and answers for both — one Python frame per generational connective instead of two, with `step` and its ternary gone. Nothing else moved: the left arm still short-circuits the right, and the arrays, the bounds and the two piece tests are the same tests in the same order. Byte-identical over 3,931,700 parses — 157,188 names (the corpus, cases.py, the property grids and a run-heavy generated set) under six lexicons and four policies, plus the facade surface and the #528 no-parse paths — same sha256 for all twenty-six digest rows and the dumps compare equal.
THE OLD NAME STILL STANDS WHERE IT WAS WRITTEN, and this sentence is the grep trail: `_name_word_beside` is the spelling in decisions.md#M2's 2026-09-20 #397 bullet and in the "NOT FIXED AND RECORDED INSTEAD" paragraph above, and dated entries are not edited, so neither is corrected — a search for either name reaches the whole arc. One place the old name was NOT a citation and is renamed: `tests/v2/test_properties.py`'s per-side mirror, now `_name_word_on_the_side`. That helper is a deliberately independent second implementation, reading spans and off-switch roles where the parser reads pieces and tags, and carrying the implementation's own name made it read as a call into the thing it checks rather than as a model of it.
THE FIGURES, AND THE INTERPRETER, because the pair above spliced two and this one does not. Re-measured 2026-09-21 on py3.11 through `tests/v2/test_benchmark.py`'s own `_frames_for` shape, `b9ed1429` → `9fd84463` → this tree: `Josep Carod i Rovira` 311 → 314 → 313, `Josep Lluis Carod i III` 377 → 381 → 380, `Jane Doe nee Puig i Soler` 315 → 320 → 319, `John Quincy Adams i MA Prof.` 438 → 443 → 442, and the clause guard's own pair 1,125 → 875 → 859 at sixteen links and 6,741 → 2,651 → 2,587 at sixty-four (3.01x, re-pinned in the test). `tools/perf/call_count.py` is unmoved at parse=406.00 facade=443.00 — its reference name carries no link — and so are `John Smith` 172, `Smith, John` 203, `Juan Garcia y Lopez` 291, `John and Jane Smith` 293, `Jane Doe nee Smith` 245 and `Jane Doe nee Smith PhD` 340, none of which reaches the predicate. WHICH OF THE EARLIER FIGURES REPRODUCE, stated exactly where this sentence first said only that one half "very nearly" did: re-measured 2026-09-22 on py3.11, the short-name pairs (304 → 307, 307 → 312) do not reproduce at all, and of the run-of-64 pair (6,741 → 2,652) the LEFT half does and the right does not — `b9ed1429` reads 6,741 on the nose, while `6048eb5d`, the tree its 2,652 was taken on, reads 2,651 here, the same figure `9fd84463` reads. So the splice runs through a single arrow rather than between two of them, which is exactly the spliced table `tools/perf/call_count.py`'s docstring was written about. Timings are unmoved: `"Josep " + "i " * n + "Rovira"` and `"Jane Doe nee Puig " + "i " * n + "Soler"` both read 1.96-2.02x per doubling from n=200 to n=1,600, 11.36ms and 9.39ms at the top end against 11.58ms and 9.58ms at `9fd84463`.
AND THE FOLD BOUGHT A TEST, which is the part worth keeping: while the predicate was called once per side, a mutation of the suffix or the title test hit BOTH sides at once and the left-hand rows killed it, so the right-hand halves were never separately covered — their own rows (`Josep Lluis Carod i Jr.`, `i Mr.`) stand at the END of the name, where `hi` refuses them before either piece test is asked. Folded, the two sides mutate independently and both right-hand tests survived the whole suite. `test_a_credential_or_honorific_mid_name_on_the_right_too` is the pair that kills them, and every arm of the folded predicate now dies by a named test (recorded in its docstring).

- Provenance: the single-letter-connective guard is v1's fix for Google Code issue 11 ("john e smith", 2013, commit 33676c9) — the "#11" citations that circulated pointed at a GitHub accident, not the real source. Recorded so the archaeology stays done.

Excluded (Lexicon.conjunctions_ambiguous, the marked half of nameparser/config/conjunctions.py — an entry here reads as an INITIAL in a name written wholly in one case):
Expand Down
10 changes: 10 additions & 0 deletions docs/design/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,16 @@ M2. Rationale: a maiden marker announces that what follows it is the
does not.
"Jane Doe nee Smith MA Prof." → maiden="Smith MA Prof." · boundary
"Jane Doe nee Smith Prof. MA" → maiden="Smith Prof." · boundary
Deviation: the link exception asks for a name word on each side,
and a separator the caller declared is structure rather than a
name word — so a link with one beside it is joining nothing and
ends the clause like any other suffix word. A declared separator
standing inside the clause, past its first word, is read as that
name word instead, and the clause runs on across a link it should
have ended at. The same clause written without the separator,
which leaves the title as the word on the link's left, does end
there.
"Smith, John, PhD née Puig Mr. - i Soler" extra_suffix_delimiters-dash → maiden="Puig Mr." deviates: #538 (today: maiden="Puig Mr. i Soler")
history: decisions.md#M2 · interacts: P2, P3, P5, P6, R2, M1, S2, H1, H5 · implemented: nameparser/_pipeline/_group.py

M3. Rationale: an enclosure says nothing about whether it means
Expand Down
Loading
Loading