diff --git a/generate/derivation/extract.py b/generate/derivation/extract.py index e2f59953..89871c3d 100644 --- a/generate/derivation/extract.py +++ b/generate/derivation/extract.py @@ -24,11 +24,28 @@ this module, so none of this can move the serving ``3/47/0``): sentence/text or before terminal punctuation) extracts with an empty unit so it stays available to the completeness check without inventing a unit lexeme. -EX-3 (multi-word units) is deliberately **not** integrated: the greedy lowercase -unit span regresses GB-2's same-unit detection (``"6 apples and 4 apples"`` → -unit ``"apples and"``) and does not cleanly recover real multi-word units from -0024-class text (``"20 jumping jacks on Monday"`` → ``"jumping jacks on"``). See -``docs/handoff/AUDIT-ADR-0179-EX-RECONCILE.md``. +EX-3 (multi-word units) is deliberately **not** integrated. Two distinct traps +defeat the tightest lookahead-anchored rule the brief admits: + +1. **Connective-crossing** (in + ``docs/handoff/AUDIT-ADR-0179-EX-RECONCILE.md``). The greedy lowercase unit + span regresses GB-2's same-unit detection (``"6 apples and 4 apples"`` → unit + ``"apples and"``) and does not cleanly recover real multi-word units from + 0024-class text (``"20 jumping jacks on Monday"`` → ``"jumping jacks on"``). +2. **Postmodifier-adjective tails** (discovered during the Track C redo of + ``docs/handoff/PARALLEL-WORK-PLAN-2026-05-29.md``). Even a *tight* + ``digit + lc word + lc word + (?=clause-terminator)`` rule fires on + ``"25 years old?"`` and produces unit ``"years old"`` instead of + ``"years"`` — regressing + ``tests/test_adr_0176_ms1_question_target.py::TestQuestionQuantities::test_extracts_quantity_stated_in_question``. + The pattern is endemic: GSM8K cases 0006/0033 and several MS2 chain tests use + ``"X years old"``. Closing it would need a second closed lexeme set (a stop + list of measurement postmodifiers — ``old``, ``tall``, ``long``, ``wide``, + ``deep``, ``high``, ``away``, ``apart``, ``ago``, …) which the audit did not + anticipate and which the Track C brief judged too open-ended to enumerate + responsibly. ``TestEX3StillDeferred`` in + ``tests/test_adr_0179_extract.py`` pins this second trap so no future redo + silently re-introduces it. """ from __future__ import annotations diff --git a/tests/test_adr_0179_extract.py b/tests/test_adr_0179_extract.py index 689a3952..e7595ce9 100644 --- a/tests/test_adr_0179_extract.py +++ b/tests/test_adr_0179_extract.py @@ -94,6 +94,55 @@ class TestNoRegression: assert _triples("It costs 0.75 dollars.") == [(0.75, "dollars", "0.75")] +class TestEX3StillDeferred: + """Honest pin: EX-3 (multi-word units) remains deferred after the Track C redo. + + The brief in ``docs/handoff/PARALLEL-WORK-PLAN-2026-05-29.md`` Track C asked + for a *tight* rule satisfying: + + * (a) ``"12 jumping jacks."`` → unit ``"jumping jacks"`` only where tight; + * (b) ``"6 apples and 4 apples."`` → two ``apples`` quantities (not + ``"apples and"``); + * (c) all GB-1/GB-2/GB-3 tests stay green; + + plus the plan's global rule that *no currently-green test* be regressed. + + A candidate rule — + ``(? None: + # The trap. Naive tight EX-3 produces "years old"; the correct unit is + # "years" alone. + qs = extract_quantities("How old will the father be when she is 25 years old?") + assert [(q.value, q.unit) for q in qs] == [(25.0, "years")] + + def test_postmodifier_trap_present_for_full_sentence_form_too(self) -> None: + # The same trap with a period instead of a question mark, paralleling + # ``"Rachel is 12 years old."`` from GSM8K case 0033 / the MS2 chain tests. + qs = extract_quantities("Rachel is 12 years old.") + assert qs[0].value == 12.0 + assert qs[0].unit == "years" + + class TestRealCase0024StillBlocked: """Honest pin: the EX-4 unit-list pattern does NOT recover real case 0024.