Merge pull request #644 from AssetOverflow/feat/r3-e
feat(rate): R3e — R3 ledger + failure-family wiring [merge LAST]
This commit is contained in:
commit
a320c69a3a
3 changed files with 100 additions and 7 deletions
|
|
@ -29,7 +29,7 @@ from typing import Literal
|
|||
|
||||
from core.comprehension_attempt.model import ComprehensionAttempt
|
||||
|
||||
Owner = Literal["r1", "r2", "cross"]
|
||||
Owner = Literal["r1", "r2", "r3", "cross"]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
|
|
@ -54,7 +54,7 @@ REGISTRY: tuple[FailureFamily, ...] = (
|
|||
refusal_reasons=(
|
||||
"empty", "no_quantity_template", "non_digit_quantity", "non_identifier_name",
|
||||
"unreadable_quantity_query", "invalid_binding_graph", "query_target_not_a_category",
|
||||
"unprojectable", "category_pair_not_found",
|
||||
"unprojectable", "category_pair_not_found", "query_target_unrecognized", "no_query",
|
||||
),
|
||||
),
|
||||
FailureFamily(
|
||||
|
|
@ -140,13 +140,25 @@ REGISTRY: tuple[FailureFamily, ...] = (
|
|||
"RESERVED — propose an attribute-coefficient fixture (no emitter yet)",
|
||||
proposal_target="r2_gold_fixture",
|
||||
),
|
||||
# --- R3 single-rate organ (reachable; growth vs boundary by the anti-over-propose rule) -- #
|
||||
FailureFamily(
|
||||
"unsupported_rate_duration", "cross", True, False,
|
||||
"RESERVED — rate/duration frames are R3 (no emitter yet)",
|
||||
"unsupported_rate_duration", "r3", False, True,
|
||||
"propose a rate fixture for a recognized-but-unsupported rate feature (unit conversion / "
|
||||
"multi-rate). GROWTH surface: rate_unit_mismatch + combined_rates are emitted ONLY after a "
|
||||
"rate clause is recognized, so they are always rate-like — never arbitrary text.",
|
||||
proposal_target="r3_gold_fixture",
|
||||
refusal_reasons=("rate_unit_mismatch", "combined_rates"),
|
||||
),
|
||||
FailureFamily(
|
||||
"unsupported_temporal_state", "cross", True, False,
|
||||
"RESERVED — temporal-state frames are R3 (no emitter yet)",
|
||||
"rate_underdetermined", "r3", True, False,
|
||||
"refuse — a single-rate problem missing a needed value (underdetermined), like ungrounded_base",
|
||||
refusal_reasons=("missing_rate", "missing_time", "missing_quantity"),
|
||||
),
|
||||
FailureFamily(
|
||||
"unsupported_temporal_state", "r3", True, False,
|
||||
"refuse — elapsed clock-time is R3.x. NOT a growth surface: the clock-marker detector can "
|
||||
"fire on non-rate text, so temporal_state is not reliably rate-like.",
|
||||
refusal_reasons=("temporal_state",),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
78
docs/analysis/r3-rate-inventory-ledger-2026-06-08.md
Normal file
78
docs/analysis/r3-rate-inventory-ledger-2026-06-08.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# R3 single-rate inventory ledger (v1)
|
||||
|
||||
**As of:** R3 slice-1 (single-rate organ), branch `feat/r3-single-rate` off `main @ a00e87b2`.
|
||||
**Lane state:**
|
||||
- R3 reader (setup): **8 setup_correct / 0 setup_wrong / 0 missed** + **4 correct reader-refusals**
|
||||
- R3 answers: **6 solved / 0 wrong** + **2 solver-refused** + **4 reader-refused** → **6 / 0 / 6**
|
||||
- R3 gold validation: **12 / 12 valid**
|
||||
- R1 unchanged **7/0/3** · R2 unchanged **10/0/3** · serving unchanged
|
||||
|
||||
R3 v1 supports **explicit single-rate integer problems with exact compound-unit composition** —
|
||||
nothing more. It is a fresh off-serving organ (`generate/rate_comprehension/`,
|
||||
`evals/rate_oracle/`) that introduces the genuinely new R3 substrate: **compound units**.
|
||||
|
||||
## Reproduce
|
||||
```bash
|
||||
.venv/bin/python -m evals.rate_oracle # gold validation -> 12/12 valid
|
||||
.venv/bin/python -m evals.rate_oracle reader # reader grading -> setup_wrong 0
|
||||
.venv/bin/python -m pytest tests/test_rate_units.py tests/test_rate_oracle.py \
|
||||
tests/test_rate_solver.py tests/test_rate_reader.py -q
|
||||
```
|
||||
|
||||
## The new substrate: compound-unit algebra (R3a)
|
||||
`mile/hour` is `quantity / time`. The three single-rate operations verify composition, and a
|
||||
non-composing op **refuses** (the wrong=0 dimensional gate):
|
||||
```text
|
||||
rate × time -> quantity (mile/hour × hour = mile) [time must be the rate denominator]
|
||||
quantity ÷ time -> rate (mile ÷ hour = mile/hour)
|
||||
quantity ÷ rate -> time (mile ÷ mile/hour = hour) [quantity must be the rate numerator]
|
||||
```
|
||||
|
||||
## Per-fixture ledger (12 fixtures)
|
||||
|
||||
| Fixture | Family | Reader | Solver | Answer |
|
||||
|---|---|---|---|---|
|
||||
| `r3-01-distance` | quantity = rate×time | ✅ | `180` | C ✅ |
|
||||
| `r3-02-earnings` | quantity (dollars) | ✅ | `120` | A ✅ |
|
||||
| `r3-03-widgets` | quantity (2 sentences) | ✅ | `60` | B ✅ |
|
||||
| `r3-04-items` | quantity (per-box denom) | ✅ | `48` | B ✅ |
|
||||
| `r3-05-speed` | rate = quantity÷time | ✅ | `60` | B ✅ |
|
||||
| `r3-06-runtime` | time = quantity÷rate | ✅ | `5` | A ✅ |
|
||||
| `r3-07-non-integer-rate` | inverse | ✅ | ⛔ `non_integer_solution` | — |
|
||||
| `r3-08-non-integer-time` | inverse | ✅ | ⛔ `non_integer_solution` | — |
|
||||
| `r3-09-unit-mismatch` | minutes vs /hour | ⛔ `rate_unit_mismatch` | — | — |
|
||||
| `r3-10-missing-time` | underdetermined | ⛔ `missing_time` | — | — |
|
||||
| `r3-11-combined-rates` | two rates | ⛔ `combined_rates` | — | — |
|
||||
| `r3-12-temporal-state` | clock time | ⛔ `temporal_state` | — | — |
|
||||
|
||||
The two `solver_refuses` fixtures read **setup_correct** (the setup is valid; the inverse just
|
||||
has no integer solution) — the reader owns the setup, the solver owns solvability, same division
|
||||
of labor as R2.
|
||||
|
||||
## Covered (v1) and deferred (R3.2 / R3.3)
|
||||
**Covered:** distance/speed/time, earnings/wage/hours, widgets/rate/minutes, items/rate/boxes —
|
||||
forward + both inverses, exact integer, exact compound-unit composition.
|
||||
|
||||
**Deferred (refused now, named for later):** combined/multi-rate (`combined_rates`), elapsed
|
||||
clock-time (`temporal_state`), unit conversion (minutes→hours; refused as `rate_unit_mismatch` —
|
||||
v1 never converts), work-rate merging, schedules, relative speed, acceleration. These are R3.2/R3.3.
|
||||
|
||||
## Failure-family wiring (R3e)
|
||||
The N4 registry makes `unsupported_rate_duration` **reachable and precise**, applying the same
|
||||
anti-over-proposing discipline as the N6 fix:
|
||||
|
||||
- **Growth surface (`proposal_allowed = true`) — `unsupported_rate_duration`:** `rate_unit_mismatch`
|
||||
+ `combined_rates`. Both are emitted ONLY after a rate clause is recognized, so they are
|
||||
always rate-like — a proposal targets a genuine future rate feature (unit conversion / multi-rate),
|
||||
never arbitrary text.
|
||||
- **Correct boundaries (`must_remain_refused`, no proposal):** `missing_rate` / `missing_time` /
|
||||
`missing_quantity` (underdetermined → `rate_underdetermined`); `temporal_state` (the clock-marker
|
||||
detector can fire on non-rate text, so it is NOT a safe growth surface →
|
||||
`unsupported_temporal_state`, boundary).
|
||||
- Non-rate text remains `input_shape` (`query_target_unrecognized` / `no_query`).
|
||||
|
||||
## Decision
|
||||
R3 v1 is the off-serving single-rate compound-unit organ, complete on the R3a→R3e ladder, with
|
||||
`setup_wrong = 0` / `answer_wrong = 0`. Claim narrowly: **R3 v1 supports explicit single-rate
|
||||
integer problems with exact compound-unit composition.** Next: R3.2 (combined rates / simple
|
||||
unit conversion) once the dimensional substrate proves stable.
|
||||
|
|
@ -35,6 +35,9 @@ ALL_REASONS = {
|
|||
# R2 answer-choice
|
||||
"no_matching_option", "ambiguous_options", "no_options", "unknown_provided_label",
|
||||
"unparseable_option",
|
||||
# R3 rate reader
|
||||
"rate_unit_mismatch", "combined_rates", "missing_rate", "missing_time", "missing_quantity",
|
||||
"temporal_state", "query_target_unrecognized", "no_query",
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -63,7 +66,7 @@ def test_only_precise_missing_totals_are_reachable_growth_surfaces() -> None:
|
|||
# Only the PRECISE R2 gaps are reachable growth surfaces. category_pair_not_found is too broad
|
||||
# (fires on any non-R2 text), so it maps to input_shape, and missing_category_pair is reserved.
|
||||
growth = {f.name for f in REGISTRY if f.proposal_allowed and f.refusal_reasons}
|
||||
assert growth == {"missing_total_count", "missing_weighted_total"}
|
||||
assert growth == {"missing_total_count", "missing_weighted_total", "unsupported_rate_duration"}
|
||||
assert family_for_reason("category_pair_not_found").name == "input_shape"
|
||||
for f in REGISTRY:
|
||||
if f.proposal_allowed:
|
||||
|
|
|
|||
Loading…
Reference in a new issue