feat(reader-arc): mass-noun compare frame (0148 core piece) — WIP, verified extraction + fail-closed
Increment 2, target 0148. _COMPARE_MASSNOUN_RE + wiring in _compare_multiplicative_candidates: 'the [total] number of <unit> counted on <A> was twice the total number counted on <B>' -> A = factor x B. Verified: extracts 0148's statement correctly (actor='the first day', ref='the second day', factor=2.0, forward); fail-closed (additive 'N more than' -> 0 cands; off-shape -> 0 cands; possessive frame unaffected). Regression-safe: 76 comparative/compare/q-diff tests pass, tune wrong=0 PARSED=3. NOT yet converting 0148: discovered parse_and_solve parses these day-entity sentences via a DIFFERENT reader path (produces title-case 'First Day'/'Second Day' entities) — both extract_operation_candidates + extract_initial_candidates return [] for the possessive compare + seed, yet the full reader solves rung-1. Next: locate that path, wire the mass-noun frame into it with matching entity canonicalization; then passive-seed + prepositional-lead; then the 3-part proof.
This commit is contained in:
parent
92c811ec39
commit
4fbc992a77
1 changed files with 38 additions and 0 deletions
|
|
@ -1268,6 +1268,23 @@ _ANCHOR_TO_FACTOR: Final[dict[str, tuple[float, str]]] = {
|
|||
"third": (1.0 / 3.0, "fraction"),
|
||||
}
|
||||
|
||||
# ADR-0250 increment 2 (case-first, target 0148) — mass-noun compare frame:
|
||||
# "the [total] number of <unit> <participle> on <A> was <anchor> the [total]
|
||||
# number [<participle>] on <B>" → A = <factor> × B.
|
||||
# The entities are mass-noun/temporal phrases ("the first day" / "the second
|
||||
# day"); the anchor is the SAME closed factor set as the possessive frames, so
|
||||
# no new factor/direction driver. Fail-closed: `$`-anchored, single clause, and
|
||||
# the anchor must be a known multiplier — an additive "N more … than" surface
|
||||
# has no anchor here and cannot match (that hazard belongs to compare_additive,
|
||||
# out of scope). Narrow by design; refuses anything off-shape.
|
||||
_COMPARE_MASSNOUN_RE: Final[re.Pattern[str]] = re.compile(
|
||||
r"^\s*the\s+(?:total\s+)?number\s+of\s+(?P<unit>\w+)\s+\w+\s+on\s+"
|
||||
r"(?P<actor>the\s+\w+(?:\s+\w+)?)\s+was\s+(?:a\s+)?"
|
||||
r"(?P<anchor>twice|thrice|half|quarter|third)\s+the\s+(?:total\s+)?number\s+"
|
||||
r"(?:\w+\s+)?on\s+(?P<reference>the\s+\w+(?:\s+\w+)?)\s*\.?\s*$",
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def _direction_to_anchor(direction_raw: str) -> tuple[str, str]:
|
||||
"""Map surface direction word → (canonical Comparison.direction,
|
||||
|
|
@ -1437,6 +1454,27 @@ def _compare_multiplicative_candidates(sentence: str) -> list[CandidateOperation
|
|||
s = sentence.strip()
|
||||
out: list[CandidateOperation] = []
|
||||
|
||||
# ADR-0250 increment 2 — mass-noun frame ("the number of <unit> counted on
|
||||
# <A> was twice the total number counted on <B>"). Checked first because its
|
||||
# anchor set overlaps the possessive frames; fail-closed by the narrow regex.
|
||||
m = _COMPARE_MASSNOUN_RE.match(s)
|
||||
if m is not None:
|
||||
anchor = m.group("anchor").lower()
|
||||
factor, direction = _ANCHOR_TO_FACTOR[anchor]
|
||||
cand = _build_compare_multiplicative(
|
||||
actor_raw=m.group("actor"),
|
||||
factor=factor,
|
||||
matched_verb=anchor,
|
||||
matched_value_token=anchor,
|
||||
unit_raw=m.group("unit"),
|
||||
reference_raw=m.group("reference"),
|
||||
source=sentence,
|
||||
direction=direction,
|
||||
)
|
||||
if cand is not None:
|
||||
out.append(cand)
|
||||
return out
|
||||
|
||||
m = _COMPARE_MULT_ANCHOR_RE.match(s)
|
||||
if m is not None:
|
||||
anchor = m.group("anchor").lower()
|
||||
|
|
|
|||
Loading…
Reference in a new issue