core/generate/rate_comprehension/model.py
Shay 4c271f5e9f feat(rate): R3 RateProblem model + rate gold + setup oracle (R3b)
generate/rate_comprehension/model.py: RateProblem (quantity = rate × time, exactly one unknown == query; rate_unit fixes all three units by construction; structural guard refuses zero/two unknowns). evals/rate_oracle/: span-free signature, a 12-fixture gold (6 solved / 2 solver_refuses / 2... reader_refuses), closed expect+reason taxonomy, gold-validation runner + CLI. No reader yet — proves the ruler (12/12 valid).

Gold shape gives the 6/0/6 target: 6 single-rate solved (forward + both inverses, non-temporal 'per box' included), 2 non-integer-inverse solver refusals, 4 reader refusals (unit mismatch minutes-vs-hour, missing time, combined rates, clock-time temporal). Off-serving. 9 oracle + 10 unit tests; per-branch meaningful-fail.
2026-06-07 23:01:25 -07:00

53 lines
1.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Typed model for an R3 single-rate problem (R3b).
A single-rate problem is the fixed equation ``quantity = rate × time`` with **exactly one**
unknown (the query). ``rate_unit`` (e.g. ``mile/hour``) is the single source of unit truth: the
quantity is measured in ``rate_unit.numerator`` and the time in ``rate_unit.denominator``, so the
three units are consistent by construction. The two non-query slots carry integer values; the
query slot is ``None``.
Pure data with a structural guard: exactly the query slot is unknown (illegal states — zero or two
unknowns — cannot be represented). Off-serving; deterministic.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal
from generate.rate_comprehension.units import RateUnit
RateRole = Literal["rate", "time", "quantity"]
@dataclass(frozen=True, slots=True)
class RateProblem:
"""``quantity = rate × time`` with one unknown. ``rate_unit`` fixes all three units."""
rate_unit: RateUnit
rate: int | None
time: int | None
quantity: int | None
query: RateRole
def __post_init__(self) -> None:
slots: dict[str, int | None] = {"rate": self.rate, "time": self.time, "quantity": self.quantity}
unknown = [role for role, value in slots.items() if value is None]
if unknown != [self.query]:
raise ValueError(
f"exactly the query slot must be the unknown; query={self.query!r}, unknown={unknown}"
)
for role, value in slots.items():
if value is not None and (not isinstance(value, int) or isinstance(value, bool)):
raise ValueError(f"{role} value must be int; got {value!r}")
@property
def quantity_unit(self) -> str:
return self.rate_unit.numerator
@property
def time_unit(self) -> str:
return self.rate_unit.denominator
__all__ = ["RateProblem", "RateRole"]