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
6 changes: 3 additions & 3 deletions AGENTS.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/design/decisions/3-0-reevaluations.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Promoted 2026-08-15 from session memory (Derek's 2026-07-30 ask; promotion appro
## Standing

- 3.0 carries no v1 compatibility layer: `HumanName`, `nameparser.parser`, `nameparser.util`, `nameparser.config`'s five shim names and the five 1.x word-list names 2.2 retired are gone, so every surface speaks `given` and `family`, a configuration is a `Lexicon` and a `Policy` handed to a `Parser`, and a pickle is a core type's — 2026-10-28 #357, REMOVAL.
- Render's default format space-joins its fields as v1's did, so an interpunct transcription renders space-joined (`str(parse("威廉·莎士比亚"))` is `'威廉 莎士比亚'`) and a caller reinstates the dot with a format of their own; 3.0's default follows the order a name was read in and keeps its written separators, decided in decisions/vocabulary-model's #754 output-model entry, item 6 — 2026-08-15; 2026-10-28 #754 (Derek), THE RENDER SIGNATURE.
- Render's default no longer space-joins its fields as v1's did: 3.0's default follows the order a name was read in and keeps its written separators (`str(parse("威廉·莎士比亚"))` is `'威廉·莎士比亚'`, where 2.x gave `'威廉 莎士比亚'`), decided in decisions/vocabulary-model's #754 output-model entry, item 6, and stated as rules/R6 — 2026-08-15; 2026-10-28 #754 (Derek), THE RENDER SIGNATURE.
- A `.pyi` stub typing `Policy.__init__` wide and its attributes narrow is a 3.0 candidate, deferred from #334 for want of a signature list with its own sync test — 2026-08-15.
- The vocabulary DATA modules keep `nameparser.config` as their home in 3.0, and it holds them and nothing else — 2026-10-28 #357 (Derek); the five shim names left it, and the five 1.x word-list names 2.2 retired went with their alias bridge — 2026-10-28 #357, REMOVAL.
- Pickle-guard layout breaks landing in a minor release are shim-era friction; the guarded raise stands on its own — 2026-08-15.
Expand Down
3 changes: 2 additions & 1 deletion docs/design/decisions/R3-initials-honor-the-fold.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- A connective contributes nothing where it is joining, a part holding another name word for it to join, the part's working particles set aside, and initials like any name word where its part holds nothing else; one rule for every group, asked of the whole part and never by a word count (`John and Jane Smith` → 'J. J. S.', `Juan y Garcia` → 'J. y. G.', `Juan Velasquez y Garcia` → 'J. V. G.', `Jon Dough and` → 'J. D.', `Juan de y` → 'J. y.') — 2026-09-20 #461.
- The fact is decided once per part and recorded on the token as UNJOINED_CONJUNCTION_TAG, a marker separate from UNJOINED_TAG so `family_base`, `family_particles` and case repair do not move; the core view reads it, and `Parser.revise` recomputes both marks (`revise(n, family='de y')` → 'J. y.' for `Juan Smith`) — 2026-09-20 #461.
- The given group's answer is a deliberate 1.4.0 parity break (1.4.0 'J a J. S.' for `John and Jane Smith`), and a lone connective's initial restores 1.4.0's own answer (`JUAN Y GARCIA` → 'J. Y. G.', `محمد و علي` → 'م. و. ع.') — 2026-09-20 #461.
- An initial is the shortest start of its word that composes to the composed word's first character, written in the form the writer used (`e\u0301mile zola` → 'e\u0301. z.', `マイケル シ\u3099ャクソン` → 'マ. シ\u3099.'), a first code point excluded from composition kept as written (`\u095bाकिर` → '\u095b.', its nukta kept); composing the initial to NFC and taking the first letter with its following marks were declined — 2026-10-02 #585.
- An initial is the shortest start of its word that composes to the composed word's first character, written in the form the writer used (`e\u0301mile zola` → 'e\u0301. z.'), a first code point excluded from composition kept as written (`\u095bाकिर` → '\u095b.', its nukta kept); composing the initial to NFC and taking the first letter with its following marks were declined — 2026-10-02 #585.
- A word is a particle only where its position makes it one, so a listed particle standing as a name word initials (`Nguyen, Van Le` → 'V. L. N.', `Anh Do` → 'A. D.') — 2026-10-19 (Derek), #761.

Not standing: the skeleton migration, carried out — 2026-10-19 #761 (skeleton migration).
Expand Down Expand Up @@ -53,3 +53,4 @@ R3's earlier history is under `decisions/R2`, which this entry does not repeat:
- 2026-10-19 (Derek), #761 — A PARTICLE IS A PARTICLE BY POSITION. "Particles contribute nothing" read as contradicting `Nguyen, Van Le` → "V. L. N.", where the vocabulary's `van` initials. It does not: whether a word is a particle is settled by where it stands in the name, not by membership in the set, and `Van` there is a given name. A Then bullet now says so, carrying `Nguyen, Van Le` and `Anh Do`. No reading moved.
- 2026-10-28 #357 — THE FACADE'S INITIALS VIEW GOES WITH THE FACADE (decisions/3-0-reevaluations' 2026-10-28 #357, REMOVAL entry), so the parsed name's `initials()` is the one view of this rule. What the 2026-09-01 #462 and 2026-09-13 #528 entries decided for `HumanName.initials()` — its tag reading and vocabulary fallback, the private-`Constants` and pickle, copy and keyword-constructor costs, the `_process_initial` hook break, the `太` fallback and the `Ph. D., John` grouping difference ('J. P D.' there, 'J. P. D.' here) — has no view left to bind, and rules/R3 drops its facade bullet. The facade's fallback was the one place a spliced field initialed by the vocabulary: measured 2026-10-28, `john smith` with middle `e` spliced in gave 'j. s.' on the facade, and `parse('john smith').replace(middle='e').initials()` gives 'j. e. s.', the core view initialing every word of a spliced field (rules/R3's Accepted bullet opening "This rule reads a part the parser read"). The snapshot's three `facade` rows were those `Ph. D.` initials. Open #464, a `Parser.initials` crossing, stands as the route to answering a spliced field under the parse's vocabulary. SUPERSEDES: the 2026-09-01 #462 and 2026-09-13 #528 entries, which decided the facade's view, and the 2026-09-20 #461 entry's facade half, the facade reading the mark and its no-parse paths keeping the older reading.
- 2026-10-28 #939 — A CONNECTIVE BETWEEN PARTICLES: rules/R3's all-particles Except sets aside a connective that is not also particle vocabulary, standing between the part's words, as rules/R2 does, so `Fritz Freiherr und von Bar` should initial 'F. F. v. B.' where it gives 'F. u.', a deviation #939 tracks; decided and measured in decisions/R2's 2026-10-28 #754 (Derek), A CONNECTIVE AMONG PARTICLES entry.
- 2026-10-29 #754 — THE #585 KANA EXAMPLE LEAVES rules/R3. `マイケル シ\u3099ャクソン` → 'マ. シ\u3099.' (the 2026-10-02 #585 entry) gives '' since rules/R7 (a word in a script that writes no initials gives none, decisions/R7), and hangul, the other script whose syllables are written as letters, writes no initials either. The composed-spelling bullet keeps its separate-parts clause, which a Tamil vowel letter written with its length mark still reaches (`\u0b92\u0bd7வையார் கண்ணன்` → '\u0b92\u0bd7. க.', measured 2026-10-29), and takes that and a Latin letter with two accents (`E\u0302\u0301 Nguyen` → 'E\u0302\u0301. N.') as its examples.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# R6 — the default rendering follows the written name

## Standing

- The default `str()` and `render()` lay a name out in `ParsedName.order`, the order it was read in, and keep the separator written between two words side by side, in the same order, both as written and as laid out, normalized by kind; a family comma is never kept, and a comma between the family and its suffix is (decisions/vocabulary-model's #754 output-model entry, item 6; the never-kept family comma narrowed to one between name words by the A SUFFIX COMMA entry below) — 2026-10-28 #754 (Derek).
- `render(spec: str | None = None)`: `None` is the order-aware default, and a caller's spec renders exactly as written, so passing 2.x's default spec keeps given-first output (decisions/3-0-reevaluations, THE RENDER SIGNATURE) — 2026-10-28 #754 (Derek).
- A comma with only a suffix after it stays as a suffix comma, whatever comma the parse recorded, so `Smith, Jr.` renders 'Smith, Jr.' — 2026-10-29 #754 (Derek), A SUFFIX COMMA.
- Two words are side by side as written when they are consecutive in `ParsedName.tokens`; a comma between two name words is the family comma, dropped also where P1's fold leaves its pair side by side; credentials written apart keep the suffix field's comma join — 2026-10-29 #754.

Open: whether a comma between two titles written after a comma, which the layout moves to the front, is kept, as the decided text's "one comma the parse reads" keeps it today, though it reads back as a family comma (`John Smith, Jr., Dr., Prof.` → 'Dr., Prof. John Smith, Jr.', and with one comma `John Smith Dr., Prof.` → 'Dr., Prof. John Smith'), or dropped, keeping a comma only before a suffix; rules/R6 records today's reading as Accepted. Also: how a pack's CLDR-shaped render pattern is reached, (a) through a convention recorded on the name or (b) through the parser (decisions/vocabulary-model's #754 render-patterns entry); this rule reads `order` alone and forecloses neither.

## Log

- 2026-10-28 #754 (Derek) — DECIDED, as item 6 of decisions/vocabulary-model's #754 output-model entry, which holds the reasons and the measurements: the default rendering follows the order a name was read in and keeps its written separators in a normalized form, through `render(spec: str | None = None)`, a family-first layout keeping the title first and the suffix last. This file records the rule that decision made, rules/R6.
- 2026-10-29 #754 — IMPLEMENTED (sub-phase 4e). `ParsedName.order` is the order assign read the name under, `GIVEN_FIRST` after a family comma and the declared `name_order` where no positional read happened; it carries `ADDED_IN = "3.0.0"`, so a 2.x pickle loads with `None` and renders exactly as 2.x did, by 2.x's default spec, separators not kept (the decided "a 2.x pickle rendering as today"; a first build laid such a name out given-first but kept its separators, so a 2.8.0 pickle of `John Smith, Jr.` rendered 'John Smith, Jr.' where 2.8.0 gave 'John Smith Jr.', found by review), and it is left out of `==` and `comparison_key()`, validated as `Policy.name_order` is. Where the decided text left a choice, the reading taken: two words are side by side as written when they are consecutive in `ParsedName.tokens`, so a marker word the parse dropped, or a field `replace()` spliced in, leaves the pair apart and takes the layout's joiner; the family comma, which the parse records nowhere on the name, is told by its neighbours, a comma between two name words being one, which drops it also after P1's fold (`Smith, de Mesnil Jean` → 'Smith de Mesnil Jean', a rendering read back given-first, the decided text's cost); credentials written apart keep the suffix field's comma join (`John Smith, MD, PhD` → 'John Smith, MD, PhD') and credentials written side by side keep their space (`John Smith MD PhD` → 'John Smith MD PhD'), as the suffix field already joined them (rules/R1); and a comma between two titles written after a comma, which the layout moves to the front, is kept, as the decided text reads, at the cost of its read-back, left open above. A first build kept a comma only before a suffix, a narrowing of the decided text a review found to depart from it in that title shape alone, so it was withdrawn rather than recorded; the second review found the shape's reach wider than first written, a one-comma name reaching it too (`John Smith Dr., Prof.`, its old rendering 'Dr. Prof. John Smith' reading back exactly), in no corpus entry but in every one of 108 two-part renderings of a generated grid that keep such a comma. Measured 2026-10-29 over the committed corpus's 6,556 distinct entries (`snapshot.corpus_entries()`), each under its declared order, against `9210f221` in a detached worktree, both `nameparser.__file__` paths asserted, each entry's `str()`, `initials()` and `parse(str(name)).as_dict()` taken with the same parser in each tree and compared: `str()` moves for 1,034 entries (543 of them only in `corpus_cldr.jsonl`), 548 laid out family-first again (497 declared `FAMILY_FIRST`, 2 `FAMILY_FIRST_GIVEN_LAST`, 49 by script order), 241 keeping a written nothing, dot or full-width comma between two words, 231 keeping a comma (before a suffix, or between two titles), 13 both of the last two, and one an Arabic comma before a suffix kept where the suffix field had joined with an ASCII one; `initials()` moves for 545 (97 only in `corpus_cldr.jsonl`), every one a Han, Hangul or kana initial dropped (rules/R7); `parse(str(name))` reads back differently for 1,318 entries on the base and 491 on this change, the 4 failing now that read back on the base being the title-comma shape (a count over the corpus, whose one one-comma instance is rules/R6's own example). The snapshot, which sees `str()` only through a case view where repair changes the text, moved `initials` for 538 of the entries both records hold, `capitalized_force` for 139 and `capitalized` for 20.
- 2026-10-29 #754 (Derek), A SUFFIX COMMA — A COMMA WITH ONLY A SUFFIX AFTER IT STAYS AS A SUFFIX COMMA, WHATEVER STRUCTURE THE PARSE RECORDED. A review found that rules/R6's Except, then reading "A family comma is never kept", was contradicted by the shipped code where the part after a family comma holds suffixes alone: `Smith, Jr.` (read with a family comma) renders 'Smith, Jr.', as do `Smith, MD`, `John, Jr.` and `Smith, Jr., John`, which keeps both its commas ('Smith, Jr., John', the parse reading `John` as a second suffix entry). The code stays and the rule is narrowed: the family comma never kept is one between name words, and a comma before a suffix is a suffix comma for the rendering, as the decided text keeps the comma between the family and its suffix. Rejected: dropping the comma by the parse's recorded structure, which `ParsedName` does not carry and which would render `Smith, Jr.` as 'Smith Jr.'. Not a render question: `KIM, AE-RA` keeps its comma ('KIM, AE-RA') because the parse reads `AE-RA` as a credential (suffix 'AE-RA', family 'KIM'), where a reader sees the Korean given name Ae-ra; that is the parse's reading to revisit, and the rendering follows it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# R7 — no initials in a script that writes none

## Standing

- A word whose first letter is in Han, Hangul or kana (`_policy._NO_INITIALS`, the scripts the parse's initial veto reads, #320) contributes nothing to `initials()`, the test asked of the letter's composed spelling — 2026-10-28 #754 (Derek).

Open: none in this file.

## Log

- 2026-10-28 #754 (Derek) — DECIDED, as part of item 6 of decisions/vocabulary-model's #754 output-model entry (the render defaults): `initials()` gives nothing for a word in a script with no initials, where it gave the first character with a period (`'王.'`). Implemented 2026-10-29 (sub-phase 4e) as a rule of its own rather than a clause of rules/R3, since it reads the word's script, a signal R3 does not read. Measured over the committed corpus's distinct entries, each under its declared order, against `9210f221` (the recipe is decisions/R6's 2026-10-29 entry): every `initials()` move is a Han, Hangul or kana initial dropped, and no other initial moves.
2 changes: 2 additions & 0 deletions docs/design/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ Entry conventions:
- [R3 — initials honor the fold](R3-initials-honor-the-fold.md)
- [R4 — case repair reads the unjoined mark](R4-case-repair-reads-the-unjoined-mark.md)
- [R5 — the case-repair gate](R5-case-repair-gate.md)
- [R6 — the default rendering follows the written name](R6-default-rendering-follows-the-written-name.md)
- [R7 — no initials in a script that writes none](R7-no-initials-in-a-script-that-writes-none.md)

## Construction & configuration diagnostics (D)

Expand Down
2 changes: 1 addition & 1 deletion docs/design/decisions/render-default.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Standing

- The default spec is '{title} {given} "{nickname}" {middle} {family} ({maiden}) {suffix}', and a parenthesized maiden re-parsing as a nickname is its accepted cost (`Jane (nee Jones) Smith` renders 'Jane Smith (Jones)', which reads back with nickname 'Jones'); a née template that round-tripped losslessly was declined on presentation grounds — 2026-07-18.
- The given-first layout of the default rendering is '{title} {given} "{nickname}" {middle} {family} ({maiden}) {suffix}' (since 3.0 the default follows the order a name was read in and its written separators, rules/R6 and decisions/R6, and this is the layout of a name read given-first), and a parenthesized maiden re-parsing as a nickname is its accepted cost (`Jane (nee Jones) Smith` renders 'Jane Smith (Jones)', which reads back with nickname 'Jones'); a née template that round-tripped losslessly was declined on presentation grounds — 2026-07-18.

Elsewhere: the v1 facade's own default `string_format`, which omitted the maiden name, so `str()` of a `HumanName` could not show it until #357 removed the facade — decisions/R5's 2026-08-29 WHAT THE OVERRIDE DOES AND DOES NOT PROMISE entry.

Expand Down
2 changes: 1 addition & 1 deletion docs/design/rules/R-rendering-and-views.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Rendering & views (R)

Background: parsing produces words with roles; every string a caller reads is assembled from those words on request. Nothing about rendering changes the parse, and nothing about reading a field mutates anything. The default view renders '{title} {given} "{nickname}" {middle} {family} ({maiden}) {suffix}' — the choice and its declined née-template alternative are decisions/render-default.
Background: parsing produces words with roles; every string a caller reads is assembled from those words on request. Nothing about rendering changes the parse, and nothing about reading a field mutates anything. The default view lays a name out in the order it was read in and keeps its written separators (R6); a name read given-first renders '{title} {given} "{nickname}" {middle} {family} ({maiden}) {suffix}' — that choice and its declined née-template alternative are decisions/render-default.

5 changes: 3 additions & 2 deletions docs/design/rules/R3-initials-honor-the-fold.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
"Jon Dough and" → initials="J. D."
"Juan de y" → initials="J. y."
```
- **The first letter is the one the word's composed spelling begins with**, written in the form the writer used: a letter written as a base letter followed by its combining accent initials whole, and so does a syllable written as its separate parts, wherever the two compose to one character; a mark that never composes with its letter (a nukta after ज, whose precomposed U+095B is excluded from composition) is left out of the initial, while the precomposed letter keeps it.
- **The first letter is the one the word's composed spelling begins with**, written in the form the writer used: a letter written as a base letter followed by its combining accents initials whole, and so does a letter written as its separate parts (a Tamil vowel letter and its length mark), wherever they compose to one character; a mark that never composes with its letter (a nukta after ज, whose precomposed U+095B is excluded from composition) is left out of the initial, while the precomposed letter keeps it.
```
"émile zola" → initials="e\u0301. z."
"マイケル ジャクソン" → initials="マ. シ\u3099."
"Ế Nguyen" → initials="E\u0302\u0301. N."
"ஔவையார் கண்ணன்" → initials="\u0b92\u0bd7. \u0b95."
```
- **Each group initials in the order its field reads**: written order, except folded family words (O3, P6), which initial before the rest of the family exactly as they render before it (R1). Which words a group contributes is this rule's question, and the order they contribute in is the field's; the field and this view never disagree about order, and can differ about membership — the family field of "Dr. Juan Q. Xavier de la Vega III" is de la Vega, where the initials take its base — so the field settles order and nothing else.
```
Expand Down
Loading
Loading