core/tests/test_problem_frame_skeleton.py
Shay a145b7c3d6
feat(kernel): implement ProblemFrame proportional-change closure (#846)
* chore(kernel): update adequacy report, fix lint warnings, and record failure proposal

* chore(ci): refresh PR checks

* chore(ci): remove PR check refresh marker
2026-06-20 19:19:07 -07:00

136 lines
4.2 KiB
Python

"""Tests for generate/problem_frame.py."""
from __future__ import annotations
from fractions import Fraction
import pytest
from generate.problem_frame import BoundQuestionTarget, ProblemFrameBuilder, QuestionTarget
from generate.kernel_facts import (
SourceSpan,
KernelProvenance,
KernelHazard,
GroundedScalar,
GroundedUnit,
RelationRole,
CandidateRelation,
)
from language_packs.scalar_equivalence import ScalarCandidate
from generate.process_frames import frame_by_name
def test_question_target_validation() -> None:
# Valid targets
target = QuestionTarget("how many apples", "count", "apple")
assert target.surface == "how many apples"
assert target.target_type == "count"
assert target.unit == "apple"
# Invalid type
with pytest.raises(ValueError, match="must be one of"):
QuestionTarget("target", "invalid_type")
def test_bound_question_target_rejects_illegal_delta_combinations() -> None:
span = SourceSpan("what will the temperature decrease by?", 0, 37)
with pytest.raises(ValueError, match="difference targets must bind a delta target_state"):
BoundQuestionTarget(
"difference",
"temperature",
"mention-0001",
"delta_quantity",
(span,),
target_operator="difference",
target_state="final",
target_direction="decrease",
)
def test_empty_problem_frame() -> None:
builder = ProblemFrameBuilder()
frame = builder.build()
assert len(frame.quantities) == 0
assert len(frame.scalars) == 0
assert len(frame.units) == 0
assert len(frame.actors) == 0
assert len(frame.objects) == 0
assert len(frame.candidate_relations) == 0
assert len(frame.process_frames) == 0
assert frame.question_target is None
assert len(frame.hazards) == 0
assert len(frame.provenance) == 0
def test_mixed_problem_frame_builder() -> None:
builder = ProblemFrameBuilder()
# Add actors and objects
builder.add_actor("John")
builder.add_object("apple")
# Primitives for scalar
span = SourceSpan("half", 0, 4)
prov_text = KernelProvenance(kind="problem_text", source_spans=(span,))
hazard = KernelHazard("haz-1", "unbound_base_quantity", "half", "unbound")
scalar = GroundedScalar("fact-1", "half", Fraction(1, 2), prov_text, (hazard,))
builder.add_quantity(scalar)
# Scalar facade candidate
scal_cand = ScalarCandidate("0.5", Fraction(1, 2), "decimal", None, ())
builder.add_scalar(scal_cand)
# Grounded unit
prov_unit = KernelProvenance(kind="kernel_unit")
unit = GroundedUnit("fact-2", "dollar", "money", "dollar", prov_unit)
builder.add_unit(unit)
# Process frame
proc_frame = frame_by_name("transfer")
assert proc_frame is not None
builder.add_process_frame(proc_frame)
# Relation
role = RelationRole("agent", True, "The agent")
rel = CandidateRelation("rel-1", "transfer", (role,), prov_text, (hazard,))
builder.add_relation(rel)
# Question target
q_target = QuestionTarget("how many", "quantity", "dollar")
builder.set_question_target(q_target)
# Direct hazard/provenance
prov_spec = KernelProvenance(kind="speculative")
builder.add_provenance(prov_spec)
builder.add_hazard(hazard)
# Build and verify
frame = builder.build()
assert frame.actors == ("John",)
assert frame.objects == ("apple",)
assert frame.quantities == (scalar,)
assert frame.scalars == (scal_cand,)
assert frame.units == (unit,)
assert frame.process_frames == (proc_frame,)
assert frame.candidate_relations == (rel,)
assert frame.question_target == q_target
# Verify hazards collected (scalar.hazards + rel.hazards + direct hazard)
assert hazard in frame.hazards
assert len(frame.hazards) >= 3
# Verify provenance collected (scalar.prov + unit.prov + rel.prov + direct prov_spec + rel.prov)
assert prov_text in frame.provenance
assert prov_unit in frame.provenance
assert prov_spec in frame.provenance
assert len(frame.provenance) >= 4
def test_immutability() -> None:
builder = ProblemFrameBuilder()
frame = builder.build()
with pytest.raises(AttributeError):
frame.quantities = () # type: ignore