core/tests/test_quantitative_expr.py
Shay 06450928c9 refactor(comprehension): typed expression IR as the source of meaning (PR-4)
Internal hygiene + future-proofing. No serving path, no new capability — the 15-case
reader is structurally cleaner and the projection no longer recovers meaning by
re-parsing a string.

- New generate/quantitative_expr.py: a typed Expr IR (Literal/Symbol/Add/Sub/SumOf) with
  to_canonical_string (BYTE-IDENTICAL to the legacy "ref + delta" / "ref - delta" / "a + b"
  format), dependencies, operation_kind, and to_relation (the structured projection).
- The reader builds the Expr per equation as the SOURCE OF MEANING; rhs_canonical,
  dependencies, and operation_kind are all DERIVED from it (BoundEquation stays a string —
  the binding-graph's deliberate decoupling layer is untouched). QuantComprehension carries
  the IR as equation_exprs.
- to_relational_metric reads the IR via to_relation — the rhs_canonical string-reparse is
  GONE. An unhandled equation shape refuses (None).
- The dead _rhs string builder is removed.

Gates held: relational_metric answer lane 15/15 wrong=0; setup-oracle 15/15 setup_wrong=0;
malformed-target refusals intact; realize-binding-graph + architectural invariants green
(95). rhs_canonical is byte-identical, so the binding-graph + downstream hashes are
unchanged. No model change.

This is the foundation PR-5's R1 frames build on — structured equations, not string parsing
— and only after independent R1 gold is hand-authored.
2026-06-06 16:57:53 -07:00

71 lines
2.9 KiB
Python

"""Typed expression IR (PR-4) — the reader's source of meaning for an equation rhs.
Pins: the canonical serialization is byte-identical to the pre-IR string format (so the
binding-graph + every downstream hash is unchanged), the structured projection reads the
IR (never the string), and dependencies/operation_kind derive from the IR.
"""
from __future__ import annotations
from generate.quantitative_comprehension import comprehend_quantitative
from generate.quantitative_expr import (
Add,
Literal,
Sub,
SumOf,
Symbol,
dependencies,
operation_kind,
to_canonical_string,
to_relation,
)
def test_canonical_string_is_byte_identical_to_legacy_format() -> None:
assert to_canonical_string(Add(Symbol("liam"), Literal(4))) == "liam + 4"
assert to_canonical_string(Sub(Symbol("noah"), Literal(6))) == "noah - 6"
assert to_canonical_string(SumOf((Symbol("dan"), Symbol("eva")))) == "dan + eva"
assert to_canonical_string(Symbol("x")) == "x"
assert to_canonical_string(Literal(7)) == "7"
def test_dependencies_from_structure() -> None:
assert dependencies(Add(Symbol("liam"), Literal(4))) == frozenset({"liam"})
assert dependencies(Sub(Symbol("noah"), Literal(6))) == frozenset({"noah"})
assert dependencies(SumOf((Symbol("dan"), Symbol("eva")))) == frozenset({"dan", "eva"})
assert dependencies(Literal(3)) == frozenset()
def test_operation_kind_from_structure() -> None:
assert operation_kind(Add(Symbol("a"), Literal(1))) == "add"
assert operation_kind(SumOf((Symbol("a"), Symbol("b")))) == "add"
assert operation_kind(Sub(Symbol("a"), Literal(1))) == "subtract"
def test_to_relation_reads_structure_not_string() -> None:
assert to_relation("mia", Add(Symbol("liam"), Literal(4))) == {
"kind": "more_than", "entity": "mia", "ref": "liam", "delta": 4,
}
assert to_relation("olivia", Sub(Symbol("noah"), Literal(6))) == {
"kind": "fewer_than", "entity": "olivia", "ref": "noah", "delta": 6,
}
assert to_relation("total", SumOf((Symbol("dan"), Symbol("eva")))) == {
"kind": "sum_of", "entity": "total", "parts": ["dan", "eva"],
}
def test_to_relation_refuses_unhandled_shape() -> None:
# A literal-only or nested shape the projection doesn't handle returns None (refuse).
assert to_relation("x", Literal(5)) is None
assert to_relation("x", Add(Literal(1), Literal(2))) is None # no symbol ref
def test_reader_carries_ir_consistent_with_rhs_canonical() -> None:
# The IR the reader attaches serializes EXACTLY to the equation's rhs_canonical.
comp = comprehend_quantitative(
"Liam has 6 stickers. Mia has 4 more stickers than Liam. How many stickers does Mia have?"
)
by_lhs = {lhs: expr for lhs, expr in comp.equation_exprs}
for eq in comp.binding_graph.equations:
assert to_canonical_string(by_lhs[eq.lhs_symbol_id]) == eq.rhs_canonical
assert dependencies(by_lhs[eq.lhs_symbol_id]) == eq.dependencies