core/tests/test_percent_partition_proposal.py

153 lines
5.3 KiB
Python

"""Percent-partition proposal-first construction seam tests."""
from __future__ import annotations
import dataclasses
import generate.construction_affordances as construction_affordances
import generate.problem_frame_builder as problem_frame_builder
import generate.problem_frame_contracts as problem_frame_contracts
from generate.problem_frame_builder import build_problem_frame
from generate.problem_frame_contracts import assess_contracts
PERCENT_PARTITION_CASE = (
"A school has 100 students. Half of the students are girls, the other half are boys. "
"20% of the girls have dogs at home and 10% of the boys have dogs at home. "
"How many students own dogs?"
)
ONE_SUBGROUP_CONFUSER = (
"There are 100 students. Half are girls. 30% of the girls own pets. "
"How many students own pets?"
)
PERCENT_CHANGE_CONFUSER = "A store spent 20% of its budget. How many dollars remain?"
def _percent_partition_proposal(frame):
return next(
proposal
for proposal in frame.proposals
if proposal.family_id == "partition.percent_partition"
)
def _percent_partition_assessment(frame):
return next(
assessment
for assessment in assess_contracts(frame)
if assessment.candidate_organ == "percent_partition"
)
def test_supported_case_has_diagnostic_catalog_proposal() -> None:
proposal = _percent_partition_proposal(build_problem_frame(PERCENT_PARTITION_CASE))
assert proposal.status == "proposed"
assert proposal.diagnostic_only is True
assert proposal.serving_allowed is False
assert {role.role for role in proposal.role_obligations if role.required} == {
"whole",
"part",
"scale",
}
assert tuple(span.text for span in proposal.evidence_spans) == (
"20% of",
"10% of",
)
assert all(
PERCENT_PARTITION_CASE[span.start : span.end] == span.text
for span in proposal.evidence_spans
)
def test_proposal_precedes_role_binding_and_contract_assessment(monkeypatch) -> None:
events: list[str] = []
original_propose = problem_frame_builder.propose_construction
original_assess = problem_frame_contracts.assess_contracts
def observe_proposal(*args, **kwargs):
proposal = original_propose(*args, **kwargs)
if proposal.family_id == "partition.percent_partition":
events.append("proposal")
return proposal
def observe_assessment(frame):
events.append("assessment")
assert _percent_partition_proposal(frame).status == "proposed"
assert frame.bound_relations
return original_assess(frame)
monkeypatch.setattr(problem_frame_builder, "propose_construction", observe_proposal)
monkeypatch.setattr(problem_frame_contracts, "assess_contracts", observe_assessment)
build_problem_frame(PERCENT_PARTITION_CASE)
assert events == ["proposal", "assessment"]
def test_migrated_family_does_not_use_legacy_assessment_adapter(monkeypatch) -> None:
original_make_proposal = construction_affordances.make_proposal
def reject_migrated_family(*args, **kwargs):
family_id = kwargs.get("family_id", args[0] if args else None)
if family_id == "partition.percent_partition":
raise AssertionError("migrated family reached legacy make_proposal adapter")
return original_make_proposal(*args, **kwargs)
monkeypatch.setattr(
construction_affordances,
"make_proposal",
reject_migrated_family,
)
assert (
_percent_partition_proposal(build_problem_frame(PERCENT_PARTITION_CASE)).status
== "proposed"
)
def test_contract_assessment_remains_runnable_authority() -> None:
frame = build_problem_frame(PERCENT_PARTITION_CASE)
assert _percent_partition_proposal(frame).status == "proposed"
assert _percent_partition_assessment(frame).runnable is True
def test_positive_proposal_does_not_close_missing_subgroup_bindings() -> None:
frame = build_problem_frame(ONE_SUBGROUP_CONFUSER)
assessment = _percent_partition_assessment(frame)
assert _percent_partition_proposal(frame).status == "proposed"
assert assessment.runnable is False
assert "partition_subgroups_not_distinct" in assessment.missing_bindings
assert "percent_subgroup_links_incomplete" in assessment.missing_bindings
def test_percent_partition_dispatch_requires_proposal_first_trace() -> None:
frame = build_problem_frame(PERCENT_PARTITION_CASE)
relation_only_frame = dataclasses.replace(frame, proposals=())
assert {candidate.name for candidate in relation_only_frame.process_frames} & {
"partition",
"consumption",
}
assert any(
relation.relation_type in {"subgroup_partition", "percent_of"}
for relation in relation_only_frame.bound_relations
)
assert not any(
assessment.candidate_organ == "percent_partition"
for assessment in assess_contracts(relation_only_frame)
)
def test_percent_change_confuser_is_proposed_but_not_runnable() -> None:
frame = build_problem_frame(PERCENT_CHANGE_CONFUSER)
assessment = _percent_partition_assessment(frame)
assert _percent_partition_proposal(frame).status == "proposed"
assert assessment.runnable is False
assert "grounded_partition_subgroup" in assessment.missing_bindings
assert "percent_change_vs_percent_of" in assessment.unresolved_hazards