"""Gate A2t — bounded rate projection ClusterContract. Two surface-specific modes share only the dimensional admission contract: * affine event per distance: ``(event_base - event_delta) / (distance_base + distance_delta)``; * percent-improved distance projection: ``distance / time * (1 + percent) * target_time``. This is not a generic rate, percent, or relation parser. Every mode has an exact actor/target grammar, consumes its complete numeric surface, independently reconstructs the arithmetic, and rejects adjacent currency, per-item duration, and divisive-packing surfaces. """ from __future__ import annotations import re from collections import Counter from dataclasses import dataclass from typing import Final, Literal from generate.derivation.model import GroundedDerivation, Quantity, Step from generate.derivation.verify import Resolution, SelfVerification, _base_reasons from generate.math_roundtrip import WORD_NUMBERS, _tokens _NUMBER: Final[str] = r"(?:\d+(?:\.\d+)?|[A-Za-z]+)" _AFFINE_RATE_RE: Final[re.Pattern[str]] = re.compile( rf"^On\s+(?P[A-Z][A-Za-z'-]+)'s\s+" rf"(?P[A-Za-z]+)\s+trip\s+across\s+town,\s+" rf"(?Phe|she|they)\s+traveled\s+" rf"(?P{_NUMBER})\s+more\s+than\s+" rf"(?P{_NUMBER})\s+" rf"(?Pmiles|kilometers)\s+and\s+encountered\s+" rf"(?P{_NUMBER})\s+less\s+than\s+" rf"(?P{_NUMBER})\s+" rf"(?Pstop\s+signs|traffic\s+lights)\.\s*" rf"How\s+many\s+(?Pstop\s+signs|traffic\s+lights)\s+per\s+" rf"(?Pmile|kilometer)\s+did\s+(?P[A-Z][A-Za-z'-]+)\s+" rf"encounter\s+on\s+(?Phis|her|their)\s+trip\s+across\s+town\?$", re.IGNORECASE, ) _PERCENT_PROJECTION_RE: Final[re.Pattern[str]] = re.compile( rf"^(?P[A-Z][A-Za-z'-]+)\s+is\s+a\s+varsity\s+player\s+on\s+a\s+" rf"football\s+team\.\s+(?PHe|She|They)\s+can\s+run\s+" rf"(?P{_NUMBER})\s+(?Pyards|meters)\s+within\s+" rf"(?P{_NUMBER})\s+seconds\.\s+If\s+(?P=pronoun)\s+can\s+improve\s+" rf"(?Phis|her|their)\s+speed\s+by\s+(?P{_NUMBER})\s+percent,\s+" rf"how\s+many\s+(?Pyards|meters)\s+will\s+(?P=pronoun)\s+be\s+able\s+" rf"to\s+run\s+within\s+(?P{_NUMBER})\s+seconds\?$", re.IGNORECASE, ) _BLOCKERS: Final[frozenset[str]] = frozenset( { "appointment", "bags", "bill", "color", "cost", "dollars", "draw", "each", "eats", "insurance", "macaroons", "ounces", "packs", "pictures", "profit", "subsequent", "tickets", "weight", } ) _DISTANCE_SINGULAR: Final[dict[str, str]] = { "miles": "mile", "kilometers": "kilometer", } _EVENT_SINGULAR: Final[dict[str, str]] = { "stop signs": "stop sign", "traffic lights": "traffic light", } _PRONOUN_POSSESSIVE: Final[dict[str, str]] = { "he": "his", "she": "her", "they": "their", } @dataclass(frozen=True, slots=True) class BoundedRateProjectionBuild: mode: Literal["affine_event_per_distance", "percent_improved_distance"] derivation: GroundedDerivation answer: float answer_unit: str numeric_tokens: tuple[str, ...] def _number(token: str) -> float | None: lowered = token.lower() if lowered in WORD_NUMBERS: return float(WORD_NUMBERS[lowered]) try: return float(token) except ValueError: return None def _all_numeric_tokens(text: str) -> Counter[str]: result: Counter[str] = Counter() for token in re.findall(r"\b\d+(?:\.\d+)?\b|\b[A-Za-z]+\b", text): lowered = token.lower() if re.fullmatch(r"\d+(?:\.\d+)?", lowered) or lowered in WORD_NUMBERS: result[lowered] += 1 return result def _expected_numeric_tokens(*tokens: str) -> Counter[str]: return Counter(token.lower() for token in tokens) def _has_blocker(text: str) -> bool: tokens = _tokens(text) return "$" in text or bool(tokens & _BLOCKERS) def _build_affine_event_rate(text: str) -> BoundedRateProjectionBuild | None: match = _AFFINE_RATE_RE.fullmatch(text.strip()) if match is None: return None groups = {key: value.lower() for key, value in match.groupdict().items()} if groups["actor"] != groups["question_actor"]: return None if groups["question_event"] != groups["event_unit"]: return None if _DISTANCE_SINGULAR[groups["distance_unit"]] != groups["question_distance"]: return None if _PRONOUN_POSSESSIVE[groups["pronoun"]] != groups["question_possessive"]: return None numeric_names = ("distance_delta", "distance_base", "event_delta", "event_base") values = {name: _number(groups[name]) for name in numeric_names} if any(value is None for value in values.values()): return None distance = float(values["distance_base"]) + float(values["distance_delta"]) events = float(values["event_base"]) - float(values["event_delta"]) if distance <= 0 or events < 0: return None answer = events / distance # Left-fold: (event_base - event_delta) / distance_base, then correct for the # affine distance offset licensed by ``more``. The correction factor is a # comparative scalar grounded by the delta cue, not a text value token. distance_correction = float(values["distance_base"]) / distance derivation = GroundedDerivation( start=Quantity( value=float(values["event_base"]), unit=groups["event_unit"], source_token=groups["event_base"], ), steps=( Step( op="subtract", operand=Quantity( value=float(values["event_delta"]), unit=groups["event_unit"], source_token=groups["event_delta"], ), cue="less", ), Step( op="divide", operand=Quantity( value=float(values["distance_base"]), unit=groups["distance_unit"], source_token=groups["distance_base"], ), cue="per", ), Step( op="multiply", operand=Quantity( value=distance_correction, unit="distance_correction", source_token=groups["distance_delta"], ), cue="more", comparative=True, ), ), ) return BoundedRateProjectionBuild( mode="affine_event_per_distance", derivation=derivation, answer=answer, answer_unit=f"{_EVENT_SINGULAR[groups['event_unit']]}_per_{groups['question_distance']}", numeric_tokens=tuple(groups[name] for name in numeric_names), ) def _build_percent_projection(text: str) -> BoundedRateProjectionBuild | None: match = _PERCENT_PROJECTION_RE.fullmatch(text.strip()) if match is None: return None groups = {key: value.lower() for key, value in match.groupdict().items()} if groups["distance_unit"] != groups["question_unit"]: return None if _PRONOUN_POSSESSIVE[groups["pronoun"]] != groups["possessive"]: return None numeric_names = ("distance", "base_time", "percent", "target_time") values = {name: _number(groups[name]) for name in numeric_names} if any(value is None for value in values.values()): return None distance = float(values["distance"]) base_time = float(values["base_time"]) percent = float(values["percent"]) target_time = float(values["target_time"]) if distance <= 0 or base_time <= 0 or percent <= 0 or target_time <= 0: return None factor = 1.0 + percent / 100.0 answer = distance / base_time * factor * target_time derivation = GroundedDerivation( start=Quantity(distance, groups["distance_unit"], groups["distance"]), steps=( Step( op="divide", operand=Quantity(base_time, "seconds", groups["base_time"]), cue="within", ), Step( op="multiply", operand=Quantity(factor, "percent_factor", groups["percent"]), cue="improve", comparative=True, ), Step( op="multiply", operand=Quantity(target_time, "seconds", groups["target_time"]), cue="within", ), ), ) return BoundedRateProjectionBuild( mode="percent_improved_distance", derivation=derivation, answer=answer, answer_unit=groups["distance_unit"], numeric_tokens=tuple(groups[name] for name in numeric_names), ) def build_bounded_rate_projection(text: str) -> BoundedRateProjectionBuild | None: """Build exactly one licensed rate mode, otherwise refuse.""" if not isinstance(text, str) or not text.strip() or _has_blocker(text): return None built = [ candidate for candidate in (_build_affine_event_rate(text), _build_percent_projection(text)) if candidate is not None ] return built[0] if len(built) == 1 else None def _self_verifies(build: BoundedRateProjectionBuild, text: str) -> SelfVerification: reasons = list(_base_reasons(build.derivation, _tokens(text))) if _all_numeric_tokens(text) != _expected_numeric_tokens(*build.numeric_tokens): reasons.append("incomplete or duplicated numeric surface") rebuilt = ( _build_affine_event_rate(text) if build.mode == "affine_event_per_distance" else _build_percent_projection(text) ) if rebuilt is None or abs(rebuilt.answer - build.answer) > 1e-9: reasons.append("independent reconstruction failed") if abs(build.derivation.answer - build.answer) > 1e-9: reasons.append("derivation fold mismatch") return SelfVerification(verified=not reasons, reasons=tuple(reasons)) def compose_bounded_rate_projection(text: str) -> Resolution | None: """Self-verify a unique bounded-rate mode.""" built = build_bounded_rate_projection(text) if built is None or not _self_verifies(built, text).verified: return None return Resolution(built.answer, built.answer_unit, built.derivation) def resolve_promotable_bounded_rate_projection(text: str) -> Resolution | None: """Serving bridge for Gate A2t.""" return compose_bounded_rate_projection(text)