First rung of the combined-rate ladder, off-serving (imports no generate.derivation / core.reliability_gate). Claims exactly: the combined-rate setup ruler is defined and gold-valid. No reader/solver/wiring yet — no capability claim. - generate/combined_rate_comprehension/: local RateUnit (decoupled from R3), CombinedRateProblem (two-explicit-rates + per-query-slot guard; effective_rate = rate_a +/- rate_b; non-positive net left to the solver, not the model). - evals/combined_rate_oracle/: span-free signature (sum commutative / difference ordered) + a NON-VACUOUS validator. 17 gold fixtures: 6 solved (full combine_mode x query grid), 4 solver_refuses, 7 reader_refuses (the complete refusal taxonomy + the 2x2 domain-entry grid). Adversarial 5-lens verification returned fix_first; the validator now cross-checks every solved gold and solver_refuses reason against the canonical arithmetic (_canonical_outcome) so a mislabelled / arithmetically-impossible fixture is rejected (meaningful-fail per CLAUDE.md), with dedicated tests. Added eff<0 and eff=0/time coverage fixtures; removed a dead determinism guard. gold 17/17 valid; 25 oracle tests; R1/R2/R3 + router-hygiene + serving all unchanged. Doc: docs/analysis/cmb-a-combined-rate-ruler-2026-06-08.md
41 lines
1.7 KiB
Python
41 lines
1.7 KiB
Python
"""Compound-unit value type for the combined-rate organ (CMB-a).
|
|
|
|
A combined-rate problem carries two rates over ONE shared compound unit (``rooms/hour``,
|
|
``liters/minute``): both contributors are measured the same way, so the model holds a single
|
|
``RateUnit``. Two rates with *different* units do not compose — that is a reader refusal
|
|
(``rate_unit_mismatch``), representable only because there is one unit slot, not two.
|
|
|
|
Deliberately a **local** copy of the single-rate organ's ``RateUnit`` rather than an import from
|
|
``generate.rate_comprehension`` — the two rate organs are kept disjoint (CMB-a does not depend on
|
|
R3) until a shared rate algebra is extracted. The duplication is intentional and tiny; if/when the
|
|
rate organs converge (a later slice), promote this to a shared ``rate_algebra`` module and have
|
|
both import it. No unit conversion in CMB v1 (mirrors R3's v1 boundary). Deterministic.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
class UnitError(ValueError):
|
|
"""A malformed or non-composing unit — refuse, never fabricate a unit."""
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RateUnit:
|
|
"""A compound rate unit ``numerator / denominator`` — ``room/hour``, ``liter/minute``."""
|
|
|
|
numerator: str
|
|
denominator: str
|
|
|
|
def __post_init__(self) -> None:
|
|
if not self.numerator or not self.denominator:
|
|
raise UnitError(f"RateUnit needs non-empty units; got {self!r}")
|
|
if self.numerator == self.denominator:
|
|
raise UnitError(f"a rate's numerator and denominator must differ; got {self}")
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.numerator}/{self.denominator}"
|
|
|
|
|
|
__all__ = ["RateUnit", "UnitError"]
|