core/tests/test_answer_choices.py
Shay a3a26b8fc5 test(constraint): pin the three Pack-C review hazards as regression tests
Per review: (H1) exactly-two-category binding already has its meaningful-fail twin; (H2) a weighted total whose unit does not match the coefficient unit refuses missing_weighted_total (never a cross-unit sum); (H3) answer-choice ties EXACTLY or refuses no_matching_option (never nearest). All three were already handled by construction; these pin them so they stay handled.
2026-06-07 07:49:10 -07:00

91 lines
4 KiB
Python

"""Tests for the R2 multiple-choice verifier (C4).
Pins the truth-discipline behavior: a proven value ties to exactly one option (else refuse),
and a disagreeing key is flagged as a CONTRADICTION (a confident verdict, not a refusal). Ties
to the C2 gold + C3 solver end-to-end: every solved fixture solves, ties to its labeled answer,
and confirms consistent.
"""
from __future__ import annotations
from evals.constraint_oracle.runner import _load_r2_gold, gold_to_problem
from generate.answer_choices.parse import parse_option_value, parse_options
from generate.answer_choices.verify import ChoiceVerdict, verify_answer_choice
from generate.constraint_comprehension.solver import answer_constraint_problem
from generate.meaning_graph.reader import Refusal
def _solved() -> list[dict]:
return [f for f in _load_r2_gold() if f["expect"] == "solved"]
def test_parse_option_value_int_and_string() -> None:
assert parse_option_value(11) == 11
assert parse_option_value("11") == 11
assert parse_option_value("11 chickens") == 11
assert parse_option_value("$11") == 11
assert parse_option_value("between 5 and 10") is None # two integers -> ambiguous
assert parse_option_value(True) is None # a bool is not a count
def test_parse_options_refuses_empty_and_unparseable() -> None:
assert isinstance(parse_options({}), Refusal)
assert isinstance(parse_options({"A": "lots"}), Refusal)
assert parse_options({"A": 2, "B": "3 buses"}) == {"A": 2, "B": 3}
def test_every_solved_gold_key_is_consistent() -> None:
for fx in _solved():
v = verify_answer_choice(fx["gold"], fx["options"], fx["answer"])
assert isinstance(v, ChoiceVerdict), fx["id"]
assert v.status == "consistent"
assert v.computed_label == fx["answer"]
def test_solve_then_verify_end_to_end() -> None:
# The full off-serving chain that the reader (C5+) will feed: solve -> tie to the option.
for fx in _solved():
computed = answer_constraint_problem(gold_to_problem(fx))
v = verify_answer_choice(computed, fx["options"], fx["answer"], noun=fx["query"]["unit"])
assert isinstance(v, ChoiceVerdict) and v.status == "consistent"
assert v.computed_value == fx["gold"] and v.computed_label == fx["answer"]
def test_disagreeing_key_is_flagged_as_contradiction() -> None:
# chickens: proven 11 == option A; a key of "D" (13) contradicts the equations.
fx = next(f for f in _solved() if f["id"] == "r2-002-chickens")
v = verify_answer_choice(11, fx["options"], "D", noun="animals")
assert isinstance(v, ChoiceVerdict)
assert v.status == "contradiction"
assert v.computed_label == "A" and v.provided_label == "D"
# The message names BOTH the consistent answer and the contradicted key.
assert "A" in v.message and "11" in v.message and "D" in v.message and "13" in v.message
assert "contradicts" in v.message
def test_no_matching_option_refuses() -> None:
out = verify_answer_choice(99, {"A": 2, "B": 3, "C": 4}, "A")
assert isinstance(out, Refusal) and out.reason == "no_matching_option"
def test_exact_match_only_never_nearest() -> None:
# Hazard: a proven value with no exact option REFUSES — it never snaps to the nearest
# (10 or 12). Exact-or-refuse is the wrong=0 boundary for answer-choice tie-in.
out = verify_answer_choice(11, {"A": 10, "B": 12, "C": 13, "D": 14}, "B")
assert isinstance(out, Refusal) and out.reason == "no_matching_option"
def test_ambiguous_duplicate_options_refuse() -> None:
out = verify_answer_choice(4, {"A": 4, "B": 4}, None)
assert isinstance(out, Refusal) and out.reason == "ambiguous_options"
def test_unknown_provided_label_refuses() -> None:
out = verify_answer_choice(4, {"A": 2, "B": 4}, "Z")
assert isinstance(out, Refusal) and out.reason == "unknown_provided_label"
def test_consistent_without_a_provided_key_still_labels() -> None:
v = verify_answer_choice(4, {"A": 2, "B": 4}, None, noun="buses")
assert isinstance(v, ChoiceVerdict) and v.status == "consistent"
assert v.computed_label == "B" and "4 buses" in v.message