Reconciles ChatGPT's four independently-branched extraction PRs (#451/#452/ #453/#454) into one coherent generate/derivation/extract.py. They each rewrote the same file + same new test off main, so they conflicted pairwise and needed integration, not a merge. Integrated (span-tracked, most-specific-pass-first so numbers are never double counted): - EX-1 word-numbers (#452): reuses WORD_NUMBERS; tens-one hyphen compounds; factor-bearing half/third/quarter excluded. - EX-4 list-unit inheritance (#451): bare numeric list with one trailing unit. - EX-5 sentence-final numbers (#454): bare final number with empty unit. Deferred: EX-3 multi-word units (#453). Its greedy lowercase span reads "6 apples and 4 apples" as unit "apples and", regressing GB-2's test_same_unit_list_sums, and still can't recover real multi-word units from 0024-class text ("jumping jacks on"). Needs a tighter rule; see docs/handoff/AUDIT-ADR-0179-EX-RECONCILE.md. Verification (sealed lane only; chat/ does not import this module): - Serving frozen: lane-SHA 8/8 match, generate_claims --check OK -> 3/47/0 byte-identical, wrong=0 held. - Sealed practice improved 4/2/44 -> 4/1/45: case 0025 flips wrong->refused. EX-1 reads "three", so completeness sees a quantity the 6x50 chain omits and refuses the spurious 300 (gold 1200) instead of committing it. - No new test failures (3 pre-existing on main). Also fixes stale test drift from EX-2 (#447): TestDecimalGroundingGapIsDeferred asserted decimals still refuse, but #447 made $0.75-class resolve to 864. Renamed to TestDecimalGroundingResolves and updated to assert the flip. Honest scope note: EX-4 does NOT unblock real case 0024 (its PR test used a fabricated bare-list paraphrase). TestRealCase0024StillBlocked pins the true boundary.
70 lines
3.9 KiB
Markdown
70 lines
3.9 KiB
Markdown
# ADR-0179 EX-1/3/4/5 reconciliation (Claude, at the library)
|
||
|
||
Reconciles the four sealed-lane extraction PRs ChatGPT opened from the remote
|
||
brief (`docs/handoff/CHATGPT-REMOTE-BRIEF.md`) into one coherent
|
||
`generate/derivation/extract.py`. Each PR was branched independently off `main`
|
||
and rewrote the *same* file + the *same* new test, so they conflict pairwise and
|
||
could not be merged as-is — they needed integration, not a fast-forward.
|
||
|
||
## Disposition
|
||
|
||
| PR | Sub-phase | Verdict | Action |
|
||
|----|-----------|---------|--------|
|
||
| #452 | EX-1 word-numbers | **Integrated** | folded in (reuses `WORD_NUMBERS`, factor-words excluded) |
|
||
| #453 | EX-3 multi-word units | **Deferred — regressed GB-2** | not integrated; see below |
|
||
| #451 | EX-4 list-unit inheritance | **Integrated** | folded in (span-tracked; does not flip real 0024) |
|
||
| #454 | EX-5 sentence-final numbers | **Integrated** | folded in (empty unit, span-excluded) |
|
||
| #450 | Stream A lookback audit | **Sound** | merged (read-only, feeds GB-3) |
|
||
|
||
The four EX source PRs are superseded by the integration commit and closed with a
|
||
pointer here; their authored content survives in the merged file/tests.
|
||
|
||
## Why EX-3 (multi-word units) was deferred
|
||
|
||
The brief required "keep units tight" and "don't regress GB-1/GB-2 tests." EX-3's
|
||
greedy lowercase unit span (`[a-z]+(?:\s+[a-z]+)*`) does both wrongs:
|
||
|
||
- **Regresses GB-2.** `compose_sequential("She picked 6 apples and 4 apples.")`
|
||
expects `10.0`. Greedy units read the first unit as `"apples and"` (it swallows
|
||
the connective up to the next digit), so `_same_unit` sees two distinct units
|
||
and the composer refuses. The existing test `test_same_unit_list_sums` flips
|
||
from pass to fail.
|
||
- **Doesn't even recover real multi-word units.** Real gold case 0024 is
|
||
`"20 jumping jacks on Monday, 36 on Tuesday, …"`. Greedy lowercase reads
|
||
`"jumping jacks on"` (stops only at the capital `Monday`), so the intended
|
||
`"jumping jacks"` unit is never produced anyway.
|
||
|
||
EX-3's own tests pass only because they place the unit at a clause end (`"12
|
||
jumping jacks."`) where punctuation halts the greedy run. That is a contrived
|
||
shape, not the GSM8K shape. A correct multi-word-unit extractor needs a tighter,
|
||
non-connective-crossing rule; tracked as future work, not shipped here.
|
||
|
||
## Honest note on EX-4 and case 0024
|
||
|
||
EX-4's PR test asserted it "unblocks 0024" using a *fabricated* input
|
||
(`"20, 36, 40 and 50 jumping-jacks"`). The real case interleaves numbers with
|
||
temporal phrases (`"36 on Tuesday, 40 on Wednesday"`), so the bare-list regex
|
||
never fires and 36/40/50 do not inherit the unit. EX-4 is still a real, safe
|
||
orthographic primitive (some GSM8K problems do state a unit once after a bare
|
||
list), but it does **not** flip 0024. `TestRealCase0024StillBlocked` pins this so
|
||
no future change silently re-claims the unblock without proving 438 end-to-end.
|
||
|
||
## Verification (run at the library)
|
||
|
||
- **Serving frozen:** lane-SHA gate 8/8 match; `scripts/generate_claims.py
|
||
--check` OK → serving `3/47/0` byte-identical. wrong=0 held.
|
||
- **No new test failures:** the 3 failures present (`0163` pronoun, telemetry
|
||
round-trip, and the now-fixed `ms3` decimal-deferred) all pre-existed on `main`.
|
||
- **Sealed practice improved:** `build_search_report` went **4/2/44 → 4/1/45**
|
||
(one wrong eliminated, no correct lost). Case **0025** flipped wrong→refused:
|
||
EX-1 now reads `"three"`, so the completeness check sees a quantity the 6×50
|
||
chain doesn't consume and refuses the spurious `300` (gold is `1200`). Richer
|
||
reading → the gate refuses rather than commits a wrong answer. This is the
|
||
intended direction.
|
||
|
||
## Drift fixed in passing (cleanup-as-you-find)
|
||
|
||
`tests/test_adr_0176_ms3_search.py::TestDecimalGroundingGapIsDeferred` asserted
|
||
decimals were "currently refused." EX-2 (#447) landed decimal grounding and made
|
||
that case resolve to the correct `864`; the test was stale on `main`. Renamed to
|
||
`TestDecimalGroundingResolves` and updated to assert the flip.
|