Skip to content

Let the glued-honorific peel cross a comma and a 间隔号 (#312) - #318

Merged
derek73 merged 14 commits into
masterfrom
fix/honorific-peel-site
Aug 2, 2026
Merged

Let the glued-honorific peel cross a comma and a 间隔号 (#312)#318
derek73 merged 14 commits into
masterfrom
fix/honorific-peel-site

Conversation

@derek73

@derek73 derek73 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

#308's glued-honorific peel sat behind two gates it never asked for — the family comma and the 间隔号 — inherited purely from where it was placed in script_segment. Both gates answer where does a name divide into surname and given. The peel asks something else: does this token end in a word that can never end a name? A comma or a dot elsewhere in the string does not change that answer.

This moves the peel in front of both, and changes its site from segments[0] to the name-bearing runs, so it reaches the given-name side of a family comma:

input before after
김, 민준씨 given 민준씨 family 김, given 민준, suffix 씨
田中, 太郎さん given 太郎さん family 田中, given 太郎, suffix さん
田中さん, PhD family 田中さん family 田中, suffix さん, title PhD
威廉·莎士比亚さん family 莎士比亚さん family 莎士比亚, suffix さん
田中さん, 太郎 family 田中さん unchanged

Each now agrees with its spaced spelling. The surname split still stands down for both marks, unchanged — a comma still means the writer said where the family name ends, and the 间隔号 still marks a transcription. Default-on, reaches HumanName.

The stage is also split into two siblings (_peel_honorific_tail, _split_surname_site) with their own gates, so "these answer different questions" is visible in the code rather than only in prose. That is commit 1, deliberately behavior-free, so commit 2 is only the behavior.

The issue was filed with a wrong example

#312 named 田中さん 太郎 vs 田中さん, 太郎 as the motivating pair. They agree — neither peels, because the site is the last non-post-nominal token, which is 太郎 in the spaced form. The real disagreements were mostly on the other side of the comma, where the honorific is glued to the given name.

That matters because the fix the issue proposed — move the peel above the gates, change nothing else — fixes one case of four, misses two, and makes 田中さん, 太郎 peel, introducing a disagreement where none existed. The last row of the table above is pinned precisely because a naive fix breaks it. Issue body and comments corrected.

A regression caught in review

The first draft flattened all segments. segment() admits a post-comma run on is_suffix_lenient while the peel filters with is_suffix_strict, and the initial-shaped suffix words fall in that gap — V., V, I are lenient-True and strict-False. So:

Dr 김민준씨, V.        peel abandoned, 씨 stranded in the given name
Dr 김민준씨, Jr., 박씨  씨 peeled off the junk tail; the person's own stayed glued

Scoping to segments[:2] under a family comma and segments[0] otherwise fixes both — a FAMILY_COMMA name can never span three runs. Now pinned at stage level and by a case row (ko_honorific_glued_given_suffix_comma_initial), the latter added only after simulating the broken scan against the whole case table to confirm no existing row caught it.

Known limit

A doubled comma (김,, 민준씨) does not peel — segment() puts 민준씨 in a suffix run, so it was never name text. Unchanged from before, and the docs are phrased to stay true of it.

Test Plan

  • uv run --extra ja pytest -q — 2872 passed, 16 skipped, 11 xfailed
  • uv run mypy — clean, 96 files
  • uv run ruff check nameparser/ tests/ — clean
  • sphinx-build -b html / -b doctest / python -m doctest README.rst — clean, 0 doctest failures
  • tools/differential/compare.py vs 1.4.0 — 734 names, 90 intentional, 0 unexplained; each new corpus row's classifying rule verified rather than assumed
  • All 8 acceptance criteria measured, including the two regression guards (Dr 김민준씨, Jr. and 김민준씨 (Jimmy) byte-identical)

Closes #312.

🤖 Generated with Claude Code


Known limit, recorded not fixed

The peel reaches the first two runs under a family comma and nothing beyond. One fact, several faces — all measured, all byte-identical to pre-#312 and to 1.4.0, so none is a regression:

김, 민준 지훈씨   → suffix 씨       peels (two runs)
김, 민준, 지훈씨  → suffix 지훈씨   does not (third run)
김,, 민준씨      → suffix 민준씨   does not (doubled comma → later run)
田中さん, V.     → family 田中さん  does not (suffixy second run — #319)

田中さん, V. is the interesting one and has its own issue: #319. It needs segment()'s suffixy closure extracted into a shared predicate, which is out of scope here — the cheap substitute was measured and leaves PhD and Ph. D. inconsistent instead. Pinned as a parity case row so the limit is recorded rather than latent.

Note for bisecting

Commit 2 (c89536b, the behavior change) is red on its own: it invalidates ja_honorific_glued_family_comma's expectation, and that row is not updated until commit 3 (b54f692). Every other commit is independently green. This was a deliberate choice in the implementation plan — behavior in one commit, its pins in the next — and is left as-is rather than rewriting pushed history. A git bisect for a CJK-comma regression will stop there with two known failures; they are the row and its facade twin, nothing else.

Whole-branch review

All seven original commits had per-task review; a three-agent whole-branch pass then found what slice-level review structurally could not:

  • A coverage hole in the branch's own stated hazard. Every pinned FAMILY_COMMA input had exactly two segments, so segments[:2] was only ever exercised as "more than one run", never as "and not three". A segments[:3] mutant survived the whole suite; its catching input 김, 민준씨, 박씨 peels the junk tail's 씨 and strands the person's own — verbatim the failure the code comment argues against. Now pinned, along with four sibling mutants (extracted content, interpunct-plus-comma, empty leading run, len(segments) == 2).
  • Two measured-false code comments, including a third wrong claim about period_joined_vocab — it does classify Smith.Jr. as a title, and parse("Smith.Jr. Anderson") swallows the surname.
  • Two universal negatives in the docs with counterexamples, replaced by the scoped statement above.

@derek73 derek73 self-assigned this Aug 2, 2026
@derek73 derek73 added this to the v2.1 milestone Aug 2, 2026
@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.46%. Comparing base (6fd116e) to head (3c27dad).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #318   +/-   ##
=======================================
  Coverage   98.46%   98.46%           
=======================================
  Files          41       41           
  Lines        2803     2807    +4     
=======================================
+ Hits         2760     2764    +4     
  Misses         43       43           

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

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

derek73 and others added 7 commits August 1, 2026 21:36
Every family-comma input in the suite has exactly two segments, so
segments[:2] was only ever exercised as "more than one run", never as
"and not three". Five one-line narrowings of the site scan passed the
whole suite:

  segments[:3]                          '김, 민준씨, 박씨'
  ... and len(state.segments) == 2      '김, 민준씨, Jr.'
  ... and nothing was extracted         '김, 민준씨 (Jimmy)'
  ... and not state.interpunct_offsets  '威廉·莎士比亚, 太郎さん'
  ... and state.segments[0]             ', 민준씨'

Each named input now has a pin, and each mutant fails exactly the pin
that names it. The first two are the hazard the site comment already
argues against, unwitnessed on the side #312 opened: 씨 stranded in
the given name, or the junk tail's 씨 peeled instead of the person's
own. The nickname one is a case row rather than a stage test because
extract_delimited runs before this stage in the pipeline and not in
the stage harness; ko_honorific_glued_given_nickname pins only the
NO_COMMA half, where the two runs are one and the choice is invisible.
The empty-run one is separate from ", , 씨" below it, where BOTH runs
are empty and the scan short-circuits before the crossing is reached.

Also rename test_family_comma_is_inert: the stage is no longer inert
under a family comma once the lexicon carries honorific_tails. Its
assertion is sound -- it runs on _LEX, which carries none -- but the
name states a general claim #312 falsified. It pins the surname split;
say so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ja_honorific_glued_family_comma's expectation bakes in two deviations
from 1.4.0 -- the peel, and the comma-family first -> family flip --
and the old note said "the only deviation is first -> family", which
the flip to fix(#312) correctly removed and replaced with nothing. A
reader now has to infer that the classification claims the whole
diff. Name the other half and its Latin witness instead: 1.4.0 reads
'Smith, Dr.' as first Smith where family_comma_lone_title expects
family Smith, so no script-conditional rule is reaching that half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
segments[1] is not always name text. segment picks FAMILY_COMMA
whenever the pre-comma part is a single word, including when the
post-comma part is entirely suffix-shaped, and it admits that run on
is_suffix_lenient where the peel's site scan asks is_suffix_strict.
The initial-shaped suffix words fall in that gap, so the scan lands on
a token that is no name and ends in no tail, and the peel is
abandoned:

  田中さん, PhD     -> family 田中, suffix さん    peels
  田中さん, V.      -> family 田中さん, given V.  does not
  田中さん, Ph. D.  -> family 田中さん, suffix 'Ph. D.'  does not

Same credential, three spellings, two answers. Not a regression: both
of the two that do not peel are parity with 1.4.0 (measured against
1.4.0-on-PyPI, first V. / last 田中さん), and the strict/lenient gap
predates #312. Fixing it wants segment's own suffixy closure extracted
into a predicate the peel can share, which is a bigger change than
this branch; record the behavior with its reason instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both are claims about code outside this stage, and both were written
from reasoning rather than from a run.

The site comment said every non-family-comma structure "puts
post-nominals after" segments[0]. That holds for the SUFFIX comma
only. Under NO_COMMA segment returns exactly one run and a trailing
post-nominal sits inside it -- '김민준씨 Jr.' is NO_COMMA with
segments ((0, 1),) -- which is the fact the scan-back paragraph in
the docstring above already rests on, so the two paragraphs
contradicted each other on the more common structure.

The ASCII bail said a glued Latin post-nominal "gets neither
treatment and simply stays in the name". It gets the period-joined
treatment: period_joined_vocab('Smith.Jr.') is 'title', 'jr' being
title vocabulary as well as suffix vocabulary, and where position
allows the treatment wins -- parse("Smith.Jr. Anderson") gives title
'Smith.Jr.', family 'Anderson'. The example the comment chose
("John Smith.Jr.") only loses to the last-name reservation. The
sentence needs no theory of that function anyway; what it has to say
is that the bail returns above all of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The maiden paragraph was written entirely in NO_COMMA terms ("which
under NO_COMMA reaches into a maiden clause"). #312 extended that
reach to FAMILY_COMMA along with the rest of the crossing:
'김, 민준 née 박씨' gives suffix 씨 / maiden 박 here where master gives
maiden '박씨'. Same behavior, one structure wider.

The "would strand" argument at the site scan reads as a
counterfactual about a mutation nobody would ship.
Policy(lenient_comma_suffixes=False) reaches it without touching the
code: it drops segment's post-comma test to the strict one, which
reads 'Dr 김민준씨, V.' as FAMILY_COMMA, so the scan crosses to 'V.'
through the sanctioned two-run reach and strands 씨 in family
'Dr 김민준씨'.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"No comma structure and no dot gates the peel any more" and "A comma
and a 间隔号 both leave the peel alone" are universal negatives, and
both have counterexamples. Measured:

  김, 민준 지훈씨   -> suffix 씨      peels (two runs)
  김, 민준, 지훈씨  -> suffix 지훈씨  does not (third run)
  김,, 민준씨      -> suffix 민준씨  does not (later run)
  田中さん, V.     -> family 田中さん does not (second run is not name text)

One fact with four faces: the peel reaches the two runs around a
family comma and nothing beyond, presuming the second of them is name
text. Say that instead, in one sentence rather than as a list of
caveats, in the release log, in usage.rst and in AGENTS.md's
architecture paragraph (which named the slice but not its limit).

Also fix the scope of "likewise" in the release log. "gives ... the
same as the spaced 김 민준씨 -- and likewise 田中, 太郎さん,
田中さん, PhD and 威廉·莎士比亚さん" holds for two of the three:
田中さん PhD gives suffix 'さん, PhD' while 田中さん, PhD gives title
PhD. cases.py states that difference; the release log left it
inferable and wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both describe the branch's own mechanism, and both were checked
against origin/master afterwards rather than before.

The maiden clause said 씨 "stayed in maiden 박씨 on the unscoped
segments[0]". It did stay there, but not for that reason: before #312
the family-comma gate sat ABOVE the peel and the stage returned
without running it at all.

The comma-and-dot test comment said the 间隔号 gates the surname split
and the family comma "gates it too". Both stand over the split, but
the family comma returns first, so the dot's gate is never reached on
this input -- which is the only reason the test is a composition and
not two facts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@derek73
derek73 merged commit c8056aa into master Aug 2, 2026
11 checks passed
@derek73
derek73 deleted the fix/honorific-peel-site branch August 2, 2026 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Should a comma turn off the glued-honorific peel? ("田中さん, 太郎" keeps さん in the family name)

1 participant