Repository navigation
Carry the reading a report did not take, as Ambiguity.alternative (#752, phase 2m) - #902
Merged
Merged
Conversation
…, phase 2m) Every ambiguity report of a fork now names the reading the parse did not take: a new public Reading of a kind (ReadingKind), the field the word would land in with every other word read as the parse read it, and a source (ReadingSource: vocabulary, declared, shape, position). Each emitter names it at its decision site; classify's connective and group's particle chain defer the field to assign, and a reading no field could hold surfaces as None. The field is compare=False, so names and reports compare as before. The pickle guard admits a field added later (metadata ADDED_IN with a default) missing from an older pickle, and refuses every skew it refused before; a ParsedName pickled by 2.6.0 loads. The snapshot records the alternatives. No reading moves: the snapshot, every agreement record and the local corpora with --base match master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The structure fixes which parts hold name words and the chain fixes the piece its particle heads, so the guards for any other shape never ran; codecov/patch flagged them uncovered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
capitalized() rebuilt every report without its alternative, and a test now compares the alternatives themselves, which equality cannot see. A one-case connective inside a joined piece takes its piece's field, and in a three-word name keeps its own (rules/P3, the three-word carve-out), where it had none or a joined field. The bound join's given-name title licence asks group's predicate of the leading run. _EXPECTED_ALTERNATIVES pins kind, role and source for every branch that chooses them, the credential runs' and connectives' reports the stand-in oracle cannot reach, and the middle_as_family fold; comments, the A1 decisions entry and AGENTS.md are corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The snapshot records each report's alternative in report order, so a report losing one beside an identical one still moves its line, where a set had hidden it (DOE, JOHN MA JD holds two). A snapshot's first line now names the keys its tree could record, and a comparison leaves out a key either side did not record by that header, not by whether a row holds one; a headerless snapshot records no alternatives. compare.py's worker says whether its release has Ambiguity.alternative, and against one that does not the key is left out of both sides and the report says so: --baseline 2.6.0 falls from 1,088 moved entries to 221, the role and ambiguity moves alone; --since v2.6.0 stays at 357. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…guity-alternative-bb6154 # Conflicts: # tools/differential/snapshot.jsonl
…ase 2m) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…guity-alternative-bb6154
derek73
added a commit
that referenced
this pull request
Oct 27, 2026
#752) (#905) * Name an alternative's field through one slot and one name-piece helper (#752) The code naming Ambiguity.alternative's field (phase 2m, #902) grew across three review rounds into five helpers that each re-derived part of one question. _slot_field now answers which field a name slot takes by structure (the family before a family comma, the given part's given or middle name after one, the order's otherwise) for as_a_name, _split_name_alt and _resolve_deferred, and _name_pieces lists a segment's name pieces for those two and post_rules' _unattached. Deferred.BESIDE and PendingReading's beside/unfolded fields, which meant something only under it, become a Beside(index, unfolded) variant of the role. No alternative moves: a record of every report's kind and alternative over 542,555 parses (the case table under three orders, the review shapes, the snapshot corpora under six policies, a generated grid of one- to three-word runs in nine comma shapes) is identical before and after, and a mutation planted in _slot_field moves 198 of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Wrap the lines the alternative-helper refactor left long (#752) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2m of #752: every ambiguity report of a fork now carries the reading the parse did not take, as
Ambiguity.alternative. Targets 2.7.Token.reading, the reading taken, stays for 3.0 as Derek decided on 2026-10-26.What ships
Reading, a new public type (frozen, slotted, picklable, exported fromnameparserwithReadingKindandReadingSource). It has three fields:kind: credential, generation, agnomen, post-nominal, title, particle, conjunction, initial or name.role: the field the word would land in, with every other word of the name read as the parse read it.source:vocabulary,declared(the vocabulary also lists the word as a name),shape(a roman numeral, dotted letters, an initial) orposition(an ordinary word nothing lists).Ambiguity.alternative: Reading | None = None, declaredcompare=Falseand left out ofcomparison_key, so ambiguities and names compare as before. The three shape kinds (comma-structure, unbalanced-delimiter, segmentation) carryNone._state.Deferred) and assign names it. A connective whose join would merge two pieces takes the first's place (JOSE E MARIA SANTOS→ given); one inside its own piece (MARIA DA SILVA E SOUZA, now a case row), or in a three-word name where P3 (connectives) joins nothing (JOHN E SMITH→ middle), keeps its own field. A report assign can't place gets no alternative rather than a guess. That happens in two shapes: a connective letter that opens its part (E SMITH JONES, now a case row), which has no word before it to join, so no field could hold the reading; and the chain inFreiherr von Berg Dr. and Ed. Prof.. rules/A1 and the docs state the first._guarded_setstatenow accepts a missing field only if the field carriesmetadata={ADDED_IN: "2.7"}and has a default. Everything the narrow guard refused is still refused: a missing field without the marker (with or without a default, e.g.Token.tags), an unknown field, a renamed field. Each of those is a row in_REFUSED_SKEWS. I ran all seven rows against the pre-2.7 guard and every one raises there too. The admitted row and a real 2.6.0 pickle (tests/v2/data/parsedname_v26.pickle, made with the released wheel using thePYTHONSAFEPATH=1recipe) fail under the old guard and load under the new one. I also checked by hand that pickles from the 2.0.0 and 2.3.0 wheels load.Decisions for review
position. The RFC's "Readings on the parse" had three sources (vocabulary, name, shape). A word the credential run takes by position alone (Jones) has no declared name and no shape. Its name reading is just the reading any word has, so it needs a source of its own. That is what letsJOHN SMITH MA(declared) be told apart fromJohn Smith PhD Jones(position) without a new ambiguity kind.languagesfield. It would need a per-word reading index compiled into_compiled.py. Measured with bytecode cached: about +0.35 ms on an 18.2 ms import, about 2% (1,790 words, 1,975 readings by word and kind), plus a_normalizecall per report. The bigger problem is that the index describes the default TOML, not theLexiconthe parse used, so a pack's or a caller's words would report wrong languages. It waits for 3.0 alongsideToken.reading, which needs the same index.ReadingKindusespost-nominal(notsuffix) andReadingSourceusesdeclared(notname), so the repo's pinned rule that StrEnum values are disjoint still holds. The one exception is deliberate:ReadingKind.TITLE == Role.TITLE, and the disjointness test now covers all five enums with that overlap allowed.GIVEN_OR_FAMILY: name as family (position)). Without that, a later change that moves an alternative's field would be invisible, and losing one of two identical alternatives still moves the line (DOE, JOHN MA JDholds two).{"recorded": [...]}, names the keys its recording tree could record. A comparison leaves out a key either side did not record, judged by that header rather than by whether any row holds one. A snapshot without the header (a tag's, or a local corpus's from before this change) records no alternatives.compare.py --baselinefollows the same rule. Its worker says whether the wheel hasAmbiguity.alternative. Against a release that doesn't, the key is left out of both sides, and the report says so.post-nominal, sojrandesqlook alike; only H4's input of nothing but post-nominals reaches this. Also, because the field holds every other word fixed, it differs from a re-parse with a plain stand-in word in 14 of the sweep's (row, order) pairs. In 11, the stand-in changes a neighbour's reading (John Smith VI Jones). In 3, it isAndrew Perkins (X.Y.Z.): rules/S1 reads that dotted content as if written bare, so the name reading is the bare word's field (family), while a plain word in brackets stays a nickname.Measurements (2026-10-26, against master 957509d; re-taken against 6ba78d0 after the review fixes)
No reading moves.
alternativeskey to 969 of 6,476 rows and changes nothing else.--basemaster: all seven snapshots (ctgov-officials, fec-individuals, jrc-names, nepal-candidates, tse-donors, tw-donors, ukec-individuals) and the PSC agreement match the base.E SMITH JONES; it reads as 1.4.0 does.Frames. The reference row (
_CALL_BASELINE,tools/perf/call_count.py) keeps 305 andSmith, Johnkeeps 158. Over the snapshot's names, each of the 789 names with one report pays 2 to 13 frames more (JOHN SMITH MA215 → 223). 7 of the 4,840 names with no report pay 2 to 7, for a report that is made and then withdrawn. The serial benchmark band passes.Field test.
test_an_alternative_names_the_field_the_other_reading_givesruns every case row under all three orders. A name reading is checked by re-parsing with a plain stand-in word in its place: 436 reports checked, 299 skipped because the stand-in still lands in the taken field (a decision made by position). Negative controls: 63 failures with the trailing name reading pinned to the family, 16 without the front-piece rule. The reports the oracle can't reach (a credential run's, whose stand-in the same run reads; a one-case connective's, whose stand-in is an initial too) are pinned with their kind, role and source in_EXPECTED_ALTERNATIVES, one row per branch that chooses a kind or source, plus themiddle_as_familyfold.Review shapes. Two code reviews found wrong fields on shapes no case row held. The first found four:
PhD VI,Smith, John van VI,abdul VIandJuan de la Vega van. The second, run on the first round's fixes, found three more:Smith, Abu Mary van(a bound join mistaken for a particle chain),Sir abdul VI(a given-name title also licenses P5's join), andDr. mc VI(P1's fold). All are rows of_REVIEW_SHAPES, which fails 34 of 48 parses on the first draft and 14 on the first fix. A fix in the second round briefly made three scaling rows quadratic; the serial benchmark caught it and it is fixed.PR review (f1f391d). A four-agent review of this PR found:
capitalized()dropped every alternative. It is fixed, and a test now compares the alternatives themselves, since equality can't see them._run_addresses_by_givenover the leading run (abdul VI Sir.,Sir Dr. abdul VI);_REVIEW_SHAPESfails 3 of its 57 parses without that fix.A review of those fixes found the connective's field ignoring P3's three-word carve-out (
JOHN E SMITHgavegivenwhere its twin reads middle). That moved four snapshot alternativesgiven→middle, all intended.Known gaps, recorded in decisions/A1. Under
two_family_names, the field of a chain in front includes O6's move (John Freiherr V Freiherrunder FAMILY_FIRST). A bound given name behind a title-particle joins at group, which the model doesn't see (Freiherr Abu VIunder FAMILY_FIRST). A one-case connective between two particles is taken by the chain in front rather than joined (JOHN VAN E VAN), so itsfamilyalternative is a reading the parse would not give either.Release figures (snapshot-tool fixes, e65feeb9 and its amend).
compare.py --baseline 2.6.0reported 1,088 moved entries with alternatives compared; 867 of those were only the key appearing. With the fix it reports 221, the role and ambiguity moves alone, and says the alternatives were not compared.snapshot.py --since v2.6.0reports 357 both before and after. The new tests (duplicate alternatives, a header with no alternatives in its rows, a headerless snapshot, a baseline without the field) each fail on f1f391d. The review of the fix commit found no defects; I changed one hint so it names only the case views.Merged master with Read King as a given name outside a title phrase (#752 phase 2j, #348) #901 (
king, 2j batch 4). Every newtitle-or-namereport carries atitlealternative. The local corpora match the new master (6ba78d0) as well.Checks.
uv run pytestpasses (31,326). The serial suite passes. Python 3.15, mypy, ruff, the sphinx doctest and HTML builds, and the README doctest are all clean.Docs
usage.rst gains "The reading it did not take", with a recipe doctest. concepts.rst links to it. modules.rst adds autodoc for the three new types. The release log has an Additions bullet with the pickle note. decisions/A1 has the full entry, and decisions/vocabulary-model has a pointer. rules/A1 gains a Then bullet (
rules_budget.jsonre-recorded; the clause reads no new signal). mechanisms.md#AMBIGUITY-AT-THE-DECISION-SITE, the RFC's "Readings on the parse" and AGENTS.md's ambiguity and pickling bullets are updated.Refs #752.
🤖 Generated with Claude Code