from __future__ import annotations from abc import ABC, abstractmethod from typing import Literal from datatypes.epc.domain.epc_property_data import EpcPropertyData from domain.property_baseline.performance import Performance RebaselineReason = Literal["none", "pre_sap10", "physical_state_changed", "both"] # The SAP spec version below which a cert's recorded scores reflect a superseded # methodology and must be ML-rebaselined (CONTEXT.md: Rebaselining). _SAP10_FLOOR = 10.0 class RebaselineNotImplemented(Exception): """A Property needs Rebaselining, but the ML adapter is not wired yet. Raised rather than silently recording ``reason="none"`` for a property that genuinely needs rebaselining — a plausible-but-wrong baseline is expensive to discover downstream. Surfaces how much of a First Run cohort the pipeline can handle today (#1135). """ class Rebaseliner(ABC): """Produces a Property's Effective Performance from its Effective EPC. Rebaselining (CONTEXT.md) re-predicts the rated quantities via ML when the EPC was lodged pre-SAP10 or its physical state diverged from the lodged EPC; otherwise Effective Performance equals Lodged. Injected into the PropertyBaselineOrchestrator (ADR-0011) so the ML adapter can swap in without touching the orchestrator, and so the single-property re-score-on-override flow reuses the same port. """ @abstractmethod def rebaseline( self, property_id: int, effective_epc: EpcPropertyData, lodged: Performance ) -> tuple[Performance, RebaselineReason]: ... class StubRebaseliner(Rebaseliner): """A no-calculator stub for tests that don't want the real calculator. SAP10 certs pass through untouched — Effective Performance equals Lodged, reason ``"none"``. A pre-SAP10 cert genuinely needs rebaselining, which this stub does not do, so it raises rather than fabricating a "none". Production uses ``CalculatorRebaseliner`` (the calculator is load-bearing — ADR-0013 amendment); this stub stays for orchestrator/repo unit tests. """ def rebaseline( self, property_id: int, effective_epc: EpcPropertyData, lodged: Performance ) -> tuple[Performance, RebaselineReason]: sap_version = effective_epc.sap_version if sap_version is not None and sap_version < _SAP10_FLOOR: raise RebaselineNotImplemented( f"Property needs rebaselining (pre-SAP10 cert, sap_version=" f"{sap_version}); ML rebaselining is not implemented yet" ) return lodged, "none"