core/generate/derivation/fraction_decrease.py
Shay 2a3f7ad125
Some checks failed
lane-shas / verify pinned lane SHAs (pull_request) Has been cancelled
smoke / smoke (-m "not quarantine") (pull_request) Has been cancelled
feat: retire fraction_decrease prose regex — bind dilation from frame scale
The post-CGA / ProblemFrame pivot comes to fruition: geometric dilation for
fraction_decrease is built from the bound scale role's GroundedScalar
(exact Fraction from kernel slash_fraction / pack numerics), not from a
local "decrease to N/M of" regex on evidence spans.

- Remove _build_fraction_decrease_payload_and_bind (legacy overfitting path)
- Add _fraction_decrease_scale_binding + _versor_binding_from_scale_value
- assess_fraction_decrease attaches VersorBindings when runnable
- assess_geometric_proposals uses frame scale only for fraction_decrease
- compose fallback prefers obligation-complete contracts with bindings
- Guard test: contracts source must not reintroduce the prose regex

Invariants: construction-boundary CGA only; versor_condition < 1e-6;
no new derivation-organ prose parser.
2026-07-08 20:09:49 -07:00

225 lines
7.8 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.

"""Gate A2k — fraction-decrease delta (decrease-to fraction of base).
Experience Flywheel Sprint 8 / composition-validation **cv-0007** (train_sample
**0005**): forecast clause states a quantity will ``decrease to N/M of`` its
current value; a follow-on clause states the current amount; the question asks
the **decrease by** delta (not the final value).
decrease_by = base × (1 N/M)
Narrow organ — not a generic affine equation parser, not ``N/M more than`` affine
surfaces (0010-class), not percent-decrease. Promotion requires:
- exactly one ``decrease to N/M of forecast`` (no ``more than`` fraction affine);
- question binds ``decrease`` + ``by`` (delta target, not final-value question);
- exactly one explicit current/base quantity with a unit;
- hazard refusal (percent, goal language, multiple decrease-to fractions).
Deterministic; sealed module (no ``chat/`` import).
"""
from __future__ import annotations
import re
from typing import Final
import numpy as np
from algebra.cga import embed_point, read_scalar_e1
from algebra.versor import versor_apply
from generate.derivation.clauses import segment_clauses
from generate.derivation.extract import extract_quantities
from generate.derivation.model import GroundedDerivation, Quantity, Step
from generate.derivation.target import _question_clause
from generate.derivation.verify import Resolution, SelfVerification, select_self_verified
from generate.problem_frame_contracts import ContractAssessment
from generate.math_roundtrip import _tokens
# Slash-fraction surface (allows "1 / 2" spacing). Used for multi-fraction hazard.
_SLASH_FRACTION_RE: Final[re.Pattern[str]] = re.compile(r"\d+\s*/\s*\d+")
_GOAL_INTENT: Final[frozenset[str]] = frozenset(
{"want", "wants", "wanted", "need", "needs", "hoping", "hopes", "plans", "aims", "goal"}
)
_FINAL_VALUE_CUES: Final[frozenset[str]] = frozenset({"be", "will", "what"})
def _asks_decrease_delta(question_clause: str) -> bool:
tokens = _tokens(question_clause)
return "decrease" in tokens and "by" in tokens
def _asks_final_value(question_clause: str) -> bool:
tokens = _tokens(question_clause)
if "by" in tokens:
return False
if "decrease" not in tokens:
return False
return bool(_FINAL_VALUE_CUES & tokens) or "temperature" in tokens
def _has_hazard_surface(problem_text: str, question_clause: str) -> bool:
text_tokens = _tokens(problem_text)
question_tokens = _tokens(question_clause)
if "more" in text_tokens and "than" in text_tokens:
return True
if "%" in problem_text or "percent" in text_tokens or "percentage" in text_tokens:
return True
if text_tokens & _GOAL_INTENT:
return True
if _asks_final_value(question_clause):
return True
# Multiple slash-fractions (e.g. "3/4" and "1 / 2") are multi-forecast
# hazards for this narrow organ — refuse rather than pick one.
if len(_SLASH_FRACTION_RE.findall(problem_text)) > 1:
return True
return False
def _current_base_quantity(
problem_text: str, question_clause: str, contract: ContractAssessment
) -> Quantity | None:
source_text = contract.bindings[0].semantic_identity
for clause in segment_clauses(problem_text):
if source_text in clause:
continue
tokens = _tokens(clause)
if "current" not in tokens and "now" not in tokens:
continue
quantities = [q for q in extract_quantities(clause) if q.unit]
if len(quantities) == 1:
return quantities[0]
for clause in segment_clauses(problem_text):
if clause == question_clause:
continue
if source_text in clause:
continue
quantities = [q for q in extract_quantities(clause) if q.unit]
if len(quantities) == 1:
return quantities[0]
return None
def build_fraction_decrease(
problem_text: str, contract: ContractAssessment
) -> GroundedDerivation | None:
"""Construct mathematical decrease delta, or ``None``."""
if not contract.runnable or not contract.bindings:
return None
question_clause = _question_clause(problem_text)
if not _asks_decrease_delta(question_clause):
return None
if _has_hazard_surface(problem_text, question_clause):
return None
base = _current_base_quantity(problem_text, question_clause, contract)
if base is None:
return None
D = contract.bindings[0].geometric_payload
v = base.value
Q = embed_point([v, 0, 0], dtype=np.float64)
Q_new = versor_apply(D, Q)
new_value = read_scalar_e1(Q_new)
return GroundedDerivation(
start=base,
steps=(
Step(
op="subtract",
operand=Quantity(
value=new_value,
unit=base.unit,
source_token=contract.bindings[0].semantic_identity,
),
cue="decrease",
),
),
)
def _self_verifies_fraction_decrease(
derivation: GroundedDerivation, problem_text: str, contract: ContractAssessment
) -> SelfVerification:
from generate.derivation.verify import _base_reasons
tokens = _tokens(problem_text)
reasons = list(_base_reasons(derivation, tokens))
if not contract.bindings:
reasons.append("missing decrease-to fraction forecast")
else:
token = contract.bindings[0].semantic_identity
if token not in problem_text:
reasons.append(f"fraction token {token!r} not grounded in text")
base = _current_base_quantity(problem_text, _question_clause(problem_text), contract)
if base is None:
reasons.append("missing explicit current/base quantity")
return SelfVerification(verified=not reasons, reasons=tuple(reasons))
def _resolve_fraction_decrease_contract(
problem_text: str,
) -> ContractAssessment | None:
"""Build a ContractAssessment for fraction_decrease when callers omit one.
Prefers ``assess_contracts`` (obligation-complete + frame-grounded scale
VersorBinding). Falls back to ``assess_geometric_proposals`` (same frame
scale binding path). Never re-parses "decrease to N/M of" prose.
"""
from generate.problem_frame_builder import build_problem_frame
from generate.problem_frame_contracts import (
assess_contracts,
assess_geometric_proposals,
)
frame = build_problem_frame(problem_text)
for assessment in assess_contracts(frame):
if (
assessment.candidate_organ == "fraction_decrease"
and assessment.runnable
and assessment.bindings
):
return assessment
return next(
(
a
for a in assess_geometric_proposals(frame)
if a.candidate_organ == "fraction_decrease" and a.bindings
),
None,
)
def compose_fraction_decrease(
problem_text: str, contract: ContractAssessment | None = None
) -> Resolution | None:
"""Gate the typed fraction-decrease chain through self-verification.
``contract`` may be omitted for backward-compatible call sites; when
omitted it is resolved via geometric proposal assessment on a built frame.
"""
if contract is None:
contract = _resolve_fraction_decrease_contract(problem_text)
if contract is None:
return None
derivation = build_fraction_decrease(problem_text, contract)
if derivation is None:
return None
if not _self_verifies_fraction_decrease(derivation, problem_text, contract).verified:
return None
return Resolution(
answer=derivation.answer,
answer_unit=derivation.answer_unit,
derivation=derivation,
)
def resolve_promotable_fraction_decrease(
problem_text: str, contract: ContractAssessment | None = None
) -> Resolution | None:
"""Serving promotion bridge (Gate A2k)."""
return compose_fraction_decrease(problem_text, contract)