Closes 5 of 8 surface-form gaps Gemini identified in Task 5 on the
99 comparison-bearing sentences my ADR-0123 substrate currently refuses
in the sealed holdout. Pure regex / parser-state work — no graph,
solver, verifier, or pack changes; preserves wrong==0 discipline.
Expansions (in safety order)
- Group 8 (verb): comparison verbs widened from {has} to {has, have,
had, gets, get, got, takes, take, took, buys, buy, bought}.
"lost"/"won" excluded — they semantically invert direction.
- Group 3 (word-form numbers): _WORD_NUMBERS {one..twelve} accepted
wherever digit values are. _parse_compare_number helper centralizes
the dispatch.
- Group 4 (ellipsis / implicit unit): unit slot made optional in
multiplicative patterns (solver already infers unit from reference
state); added "twice|N times as much", "twice|N times the
number/amount of <unit>" variants.
- Group 1 (subjects / references): actor/reference slots widened from
bare proper noun to {proper noun, "the <noun>" collective,
pronoun}. Pronouns resolve via state.last_singular_subject; missing
prior subject raises ParseError (no silent emission with empty
actor). New _resolve_compare_entity helper canonicalizes "The boys"
/ "the boys" to the same entity string.
- Initial-possession + question patterns widened symmetrically so
"the X" subjects round-trip end-to-end:
- _INITIAL_HAS_RE accepts "the <noun>" subject + has/have +
digit-or-word value
- _Q_ENTITY_RE accepts "the <noun>" entity + do/does auxiliary
- _Q_TOTAL_RE now tried first (specificity-ordered) so "do they
have" doesn't get greedily matched as entity="they"
Deferred (per Gemini Task 5c recommendation)
- Group 2 (age): needs new "years_old" attribute model
- Group 5 (nested): needs compound y = mx + c solver operation
- Group 7 (currency): low volume (2 cases), defer
- Group 6 (compound multi-clause "and" split): scoped out of this
PR to keep the wrong==0 risk profile tight; safer to land after
Gemini Task 6 confirms current expansion doesn't introduce
misparses on the sealed set
Test coverage
- 507 existing math + ADR-0122 + ADR-0123 tests pass (no regressions)
- 16 ad-hoc smoke cases pass (3 baseline + 3 Group 8 + 3 Group 3 +
3 Group 4 + 3 Group 1 + 2 refusal guards + 1 rate cross-check)
- smoke suite 67/67, algebra suite 82/82 green
Expected sealed lift
- Gemini Task 5 catalog projected ~65/90 strict-comparison-only
cases unblocked by the 5 included groups (71/99 comparison-bearing
sentences). Empirical sealed measurement pending Gemini Task 6;
PR will be updated with the actual correct/wrong/refused bucket
counts once measured.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Second parser-expansion ADR after ADR-0122 rate/per-unit. Adds the
comparison algebra substrate (Comparison dataclass + compare_additive /
compare_multiplicative operation kinds + parser patterns + solver /
verifier / pack lemmas) mirroring the substrate-only / lift-deferred
pattern ADR-0122 established.
Substrate
- Comparison(reference_actor, delta: Quantity|None, factor: float|None,
direction: Literal[more,fewer,times,fraction]) frozen dataclass with
direction-discriminated delta/factor enforcement and self-reference
refusal at the Operation boundary
- compare_additive + compare_multiplicative operation kinds admitted in
VALID_OPERATION_KINDS; Operation.operand widened to Quantity|Comparison
with kind-discriminated type enforcement; entity-set validation extended
to cover Comparison.reference_actor
- Parser: _COMPARE_ADDITIVE_RE (more/fewer/less), _COMPARE_TWICE_RE,
_COMPARE_N_TIMES_RE, _COMPARE_HALF_RE happy-path patterns + 5
refusal patterns (ambiguous 'N times more', age comparisons,
combined-with-aggregation, nested additive+multiplicative); inserted
before _try_initial so leading 'has <N>' shape is not greedily
consumed as initial possession with unit='more'/'fewer'
- Solver: _apply_compare_additive (refuses on missing reference state,
overwrite, negative result); _apply_compare_multiplicative (refuses
on missing reference, ambiguous multi-unit reference, overwrite);
unit comes from delta.unit (additive) or reference's unique unit
(multiplicative)
- Verifier: _verify_compare_additive_step + _verify_compare_multiplicative_step
byte-equal replay; tamper-detects after_value, direction, factor
- Pack: en-arith-006 compare_additive + en-arith-007 compare_multiplicative
lemmas + glosses; SHA-256 checksums refreshed; manifest 1.0.0 -> 1.1.0;
provenance tagged adr-0123:comparison_extension:2026-05-23
Measurement (honest; from Gemini empirical sealed run on parallel surface
branch with this substrate)
- Sealed GSM8K correct_rate: 0/1319 (substrate matches zero real cases
alone). Validates the ADR-0122 multi-construction barrier prediction:
comparison constructions in GSM8K rarely appear alone — they bind with
rate (ADR-0124), percentage (ADR-0125), aggregation (ADR-0126), or
conditional ('if') clauses. First lift signal requires composition.
- Sealed GSM8K wrong: 0 (load-bearing positive claim; ADR-0114a
Obligation #4 preserved across all 1,319 sealed problems)
- Regression safety: 0 — all 913 non-comparison cases continue to
refuse exactly as before (refused_parser), no greedy consumption by
the new comparison patterns
Surface-form catalog (from Gemini Task 2 survey, see ADR doc) covers
6 primary forms across Groups A/B/C; Groups D (age), E (combined with
aggregation), F (nested additive+multiplicative) refused as out-of-scope
with typed ParseError naming the missing companion ADR.
Branch isolation
- Landed via dedicated worktree (feat/adr-0123-substrate from origin/main)
after a file-race on the shared umbrella branch. Companion surface +
scaffolding (realizer, ADR doc, tests, README) lands separately as
feat/adr-0123-surface; orchestrator merges both into the umbrella
feat/adr-0123-comparison-phrasing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes Phase 1.3 of the ADR-0114 expert-capability roadmap. Turns a
grade-school word problem into a typed MathProblemGraph deterministically
(no LLM, no sampling). Same input string always produces the same
graph; unsupported constructions raise ParseError rather than guessing.
What the parser handles
Initial possession: "<E> has <N> <unit>."
Add verbs: buys, gets, finds, receives, earns, adds
(+ "<N> more" / unit elision via state.last_unit)
Subtract verbs: eats, loses, sells, donates, uses, spends, drops, removes
Transfer verbs: gives, sends, hands, passes, mails (with target)
Multiply (scalar): "X doubles <obj>" / "X triples <obj>"
Divide (split): "X splits {them|his Y|N Y} evenly into M groups [and keeps one]"
Compound sentences: "X buys 5, then donates 3."
Sentence opener: "Then X eats 1." (inherits subject + unit)
Pronoun anaphora: he/she/it → last-introduced singular subject
Object pronoun: them/these/those → state.last_unit
Trailing PP: "finds 7 buttons on the floor" — discarded
Singular→plural: "Iris has 1 coin" → canonical unit "coins"
Questions:
"How many <unit> does <E> have [left|now|in total|altogether]?"
"How many <unit> do they have [in total|altogether|left|now]?"
What it explicitly rejects
- Conditional / time-modal ("If X had ...")
- Compound questions (two unknowns)
- Multiple "?" sentences
- Questions referencing entities never introduced
- Empty / whitespace-only input
Verification
- tests/test_math_parser.py: 20 cases (5 byte-equal parametrized
+ 5 determinism parametrized + 1 exit-criterion gate + 6 typed-
refusal + 2 purity + 1 type check)
- tests/test_math_problem_graph.py: 26 schema cases still green
- On the 5 seed cases: 5/5 = 100% byte-equal
- On Codex's PR #128 50-case dev set (locally tested):
49/50 = 98% byte-equal. Single failure (gpd-021) is a case-
quality issue, not a parser limit; feedback filed on #128 to
rewrite (mixed units + metaphor not in pattern registry).
- Phase 1.3 exit criterion (≥ 0.90): met.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>