Skip to content

Case repair: letter masks for the exceptions map (phd → PhD, bsc → BSc), md leaves the map, and roman numerals past iv (john smith xi → Xi) #459

Description

@derek73
HumanName('JOHN SMITH MBA').capitalize()   →  'John Smith Mba'    ← now
HumanName('john smith jd').capitalize()    →  'John Smith Jd'
HumanName('QC MP').capitalize()            →  'Qc Mp'
HumanName('john smith md').capitalize()    →  'John Smith M.D.'   ← correct, and the reason why is the problem

The parser has already decided these are credential acronyms — mba, jd, qc and mp are in suffix_acronyms, and each is assigned the SUFFIX role. Case repair consults neither the role nor the vocabulary. It falls through to str.capitalize() and title-cases an initialism as though it were a name word.

md and phd escape only because they are hand-listed in CAPITALIZATION_EXCEPTIONS, which holds exactly {ii, iii, iv, md, phd} and has been byte-identical since the 2011 svn import (45a1539). Its docstring says "Any pieces that are not capitalized by capitalizing the first letter." 577 of the 579 shipped alphabetic suffix acronyms have no entry (measured 2026-08-29).

Rationale

An acronym is written in capitals because each letter stands for a word. Capitalizing only the first letter discards that, and the parser already knows which words are acronyms — it put them in the suffix role from the acronym vocabulary.

Case repair decides which letters are capital. It should not add or remove characters: what the writer punctuated stays punctuated, and what they left bare stays bare. Two of the five existing map entries break that — md → M.D. and phd → Ph.D. insert periods the writer never typed. That is substitution, not capitalization, and it is also why the map never grew: you cannot hand-write 577 dotted forms.

Statement

Where the parser has assigned the SUFFIX role to a word from suffix_acronyms, case repair capitalizes every letter of it and leaves every other character exactly as written.

'john smith jd'     →  capitalized="John Smith JD"
'john smith j.d.'   →  capitalized="John Smith J.D."
'john smith mba'    →  capitalized="John Smith MBA"
'JOHN SMITH MA'     →  capitalized="John Smith MA"
'john smith md'     →  capitalized="John Smith MD"
'john smith jr'     →  capitalized="John Smith Jr"       · boundary

The boundary is the generational half of the suffix vocabulary: jr, sr and esquire are abbreviations, not initialisms, and title case is right for them.

CAPITALIZATION_EXCEPTIONS values become letter masks

Some acronyms are conventionally written in mixed case, and the existing config surface is the right home for them once its values are read as which letters are capital rather than as replacement strings:

phd → PhD        bsc → BSc        msc → MSc

Applied as a mask over whatever the writer punctuated, this gives phd → PhD and ph.d. → Ph.D. from one entry, with no period added or removed either way. A caller who ships dphil adds dphil → DPhil themselves.

This is not a one-off for phd: bsc and msc are shipped acronyms whose conventional casing is mixed, and Dr. med. univ. Margit Popp, MSc is already a differential corpus name.

md leaves the map — the all-caps default covers it, and there is no reason for it to behave differently from jd or lcpc. ii, iii and iv stay: they are generational, not acronyms, and need the map because str.capitalize() would give Ii.

Two vocabulary entries should go

esq leaves suffix_acronyms. It is the only word in both suffix_acronyms and suffix_words, and it abbreviates "Esquire" rather than initializing anything — without this it would capitalize to ESQ. An acronym reading (Environmental, Safety & Quality) is a special case a caller adds to their own lexicon.

ph leaves suffix_acronyms. It exists to prop up the space-separated Ph. D., and it is what would produce PH. D. under the rule above. Removing it moves 0 of 1094 corpus names across all seven fields — the dotted-abbreviation handling carries that spelling, not this entry. With it gone the fragment capitalizes as an ordinary initial and Ph. D. is left as written.

Accepted consequences

Measured over the 1094-name differential corpus, 2026-08-29:

  • On the default path, 4 names change: JOHN SMITH MA, MD - DO - DDS, MD, DO, DDS, QC MP. Every other affected name is mixed case, so the case-repair gate (rules.md#R5) already returns it untouched.
  • Under force=True, 75 more change.
  • All of it is a deliberate deviation from 1.4.0, which produces the same title-cased output as 2.1.0 today — inherited, not a regression.

Three of the changed names make an existing defect louder rather than causing one: Aishwarya Rai → Aishwarya RAI, Donald mc → Donald MC and John Smith Mc V → John Smith MC V are read as suffixes today when they should be name words (#342, #454). Rendering a wrong parse more visibly is arguably a benefit.

Same principle as #458

#458 says the render views should honor what the parser decided instead of re-deciding from the word. This is that principle in a second view: the parse classified the token as a credential acronym, and case repair renders it as though it had not. Keyed on the role and the acronym vocabulary, the fix needs no new wordlist.

Not proposed here

  • Adding periods to suffixes. md → M.D. is a separate concern from capitalization and would belong in a separate method if it is wanted at all; phd would be its exception. This issue removes period insertion from case repair rather than extending it.
  • Normalizing Ph. D. to Ph.D.. Nothing does this today, all 16 spaced spellings in the corpus are mixed case so the gate never reaches them, and the space is not valid grammar for the degree. Left as written.
  • Adding 577 entries to CAPITALIZATION_EXCEPTIONS — that is the wordlist approach, and it would leave every caller-added acronym broken.

Activity

  1. self-assigned this
    on Aug 29, 2026
  2. added this to the 2.4 milestone on Sep 9, 2026
  3. changed the title [-]`HumanName('JOHN SMITH MBA').capitalize()` gives `John Smith Mba` — a credential acronym is title-cased as an ordinary word[/-] [+]Case repair: letter masks for the exceptions map (phd → PhD, bsc → BSc), md leaves the map, and roman numerals past iv (john smith xi → Xi)[/+] on Sep 11, 2026
  4. derek73 commented on Sep 11, 2026

    @derek73
    OwnerAuthor

    Rescoped after #521. PR #521 (IMGillusion) takes the headline half of this issue as a narrow fix: a word the parse put in the suffix role from suffix_acronyms is repaired to all capitals, with the exceptions map consulted first, so JOHN SMITH MBA → John Smith MBA and john smith jd → John Smith JD while md and phd keep M.D. and Ph.D. for now. It also drops ph from the acronym set (measured: 0 corpus names move on any field). Once it merges, what remains of the Statement above is this issue's scope:

    1. CAPITALIZATION_EXCEPTIONS values become letter masks applied over whatever the writer punctuated: phd → PhD and ph.d. → Ph.D. from one entry, no period added or removed. bsc → BSc and msc → MSc join the map (Dr. med. univ. Margit Popp, MSc is a corpus name and reads MSC under force=True after fix(#459): repair one-case suffix acronyms to all-caps in case repair #521); a caller ships dphil → DPhil themselves (the docs/customize.rst example uses dphil for exactly that).
    2. md leaves the map so it reads MD like every other acronym; ii, iii, iv stay.
    3. Roman numerals past iv are the same defect keyed on suffix_words, which fix(#459): repair one-case suffix acronyms to all-caps in case repair #521 does not reach: john smith xi → John Smith Xi (likewise vi, vii, ix). The boundary line in the Statement — generational abbreviations keep title case — is right for jr/sr and wrong for a numeral, so the rule needs to say numeral vs. abbreviation, not acronym vs. suffix word.
    4. The given-role half of QC MP (Qc MP after fix(#459): repair one-case suffix acronyms to all-caps in case repair #521): whether a bare acronym read as a given name should ever repair to caps is left open here; fix(#459): repair one-case suffix acronyms to all-caps in case repair #521's description argues no, and ed/ma/ba are the reason.
    5. Accepted-cost bookkeeping fix(#459): repair one-case suffix acronyms to all-caps in case repair #521's decisions bullet records and this issue inherits: the ambiguous five (ba, do, ed, jd, ma) reaching the suffix role by a non-acronym route (john smith ed → ED), and the within-run coverage inconsistency (John Doe CLU, CFP, Lutc).

    Related, not this issue: #492 (a cased roman-numeral suffix counting as case evidence in R5's already-mixed test, so juan garcia III declines no-force repair).

  5. added a commit that references this issue on Sep 13, 2026
  6. added 4 commits that reference this issue on Sep 22, 2026
  7. derek73 commented on Sep 22, 2026

    @derek73
    OwnerAuthor

    The narrow half shipped. PR #521 (IMGillusion, with a maintainer fix-up) merged 2026-09-22 as 4d0680e: a single-case word the parse put in the suffix role from suffix_acronyms repairs to all-caps, exceptions map first, before the Mac/Mc convention (JOHN SMITH MBA → John Smith MBA, john smith jd → JD, mcse → MCSE), and ph left the acronym set (its cost, a bare ph with no D. behind it losing the suffix reading, is classified on all five ledgers). Record: the R4 entry and the Excluded (SUFFIX_ACRONYMS -- ph) block of docs/design/decisions.md, and the 2.4.0 release log. The rescoped Statement above is unchanged and is what remains here: letter masks, md leaving the map, the given-role half of QC MP, and the numerals past iv.

  8. added 2 commits that reference this issue on Sep 24, 2026
  9. added a commit that references this issue on Sep 25, 2026
  10. derek73 commented on Sep 26, 2026

    @derek73
    OwnerAuthor

    The remainder shipped. PR #539 merged 2026-09-25 as 120033b5, closing this with #492 and #478. The exceptions map now holds letter masks: a value is its key's own letters and digits recased, laid over the word as the writer punctuated it, so one phd → PhD entry gives phd → PhD, ph.d. → Ph.D. and PH.D. → Ph.D., and repair never writes the value's punctuation into the word (john smith iii. keeps its period). The shipped map is phd, bsc, msc plus fifteen masks for listed acronyms whose usual spelling is mixed case (psyd → PsyD, dsc → DSc, pharmd → PharmD, mdiv → MDiv, …); md, ii, iii and iv left it, md now written by the acronym clause and a suffix roman numeral by shape (john smith xi → XI). A word in a name role repairs as a name word (QC MP → Qc MP, iv smith → Iv Smith, Option A). A value that does not spell its key raises ValueError, at construction for a Lexicon and at the first parse for a v1 Constants, the one decided exception to the shim's never-raise rule. edd and meng got no mask because both are names; the vocabulary half of that was #540. Record: rules.md#R4, the 2026-09-23/24 R4 bullets and two Excluded blocks of docs/design/decisions.md, and the 2.4.0 release log.

  11. added 5 commits that reference this issue on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions