Option A (text-faithful): RateProblem gains time_unit (the duration's ORIGINAL unit; time stays int; defaults to the rate denominator). The SOLVER converts via convert_time -> exact Fraction -> int-or-refuse(non_integer); Fraction never leaves the solver, no floats. The reader accepts a convertible (minute<->hour) duration mismatch (keeps original time_unit); a non-convertible one still refuses rate_unit_mismatch. Signature includes time_unit ('30 minutes' != '30 hours').
Gold: r3-09 flips reader_refuses -> SOLVED (60 mph for 30 min = 30 mile, exact 1/2 hour; distractor 1800 rejected); new r3-13 non-convertible (3 gallons) stays rate_unit_mismatch. R3 gold 13/13; reader 9/0/0 -> answers 7/0/6 (setup_correct 8->9). Failure-family: rate_unit_mismatch now non-convertible-only -> still unsupported_rate_duration proposal surface; router-hygiene invariant stays green; R1 7/0/3, R2 10/0/0 unchanged. 119-test smoke green incl. invariants. Off-serving, no float path.
39 lines
1.4 KiB
Python
39 lines
1.4 KiB
Python
"""Span-free canonical signature for the R3 rate setup oracle (R3b).
|
|
|
|
A deterministic, order-independent projection of a single-rate setup — the rate unit, the known
|
|
values keyed by role, and the query — used to compare a reader's comprehended ``RateProblem``
|
|
against the independent gold. The R3 twin of ``evals.setup_oracle.signature`` /
|
|
``evals.constraint_oracle.signature``. Pure, deterministic.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from generate.rate_comprehension.model import RateProblem
|
|
|
|
|
|
def rate_setup_signature(problem: RateProblem) -> dict[str, Any]:
|
|
"""Canonical ``(rate_unit, knowns, query)`` signature of a single-rate setup."""
|
|
knowns = tuple(
|
|
sorted(
|
|
(role, value)
|
|
for role, value in (
|
|
("rate", problem.rate),
|
|
("time", problem.time),
|
|
("quantity", problem.quantity),
|
|
)
|
|
if value is not None
|
|
)
|
|
)
|
|
return {
|
|
"rate_unit": (problem.rate_unit.numerator, problem.rate_unit.denominator),
|
|
"knowns": knowns,
|
|
"query": problem.query,
|
|
# The duration's ORIGINAL unit is part of the setup: "30 minutes" and "30 hours" are
|
|
# different problems even at the same rate, so the signature must distinguish them (R3.2).
|
|
"time_unit": problem.time_unit,
|
|
}
|
|
|
|
|
|
__all__ = ["rate_setup_signature"]
|