mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
feat: rebaseliner adopts calc output on physical-state change 🟩
CalculatorRebaseliner uses the calculator output as Effective Performance whenever a Rebaselining trigger fired — pre-SAP10 (a) OR overrides/prediction moved the physical state (b)/(c) — tagging pre_sap10 / physical_state_changed / both. Only a pristine lodged >=10.2 cert keeps its accredited figure (the sole case the calculator runs purely to validate). Divergence is logged only in that pristine case. ABC + StubRebaseliner take the new keyword-only flag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1e019ea3b3
commit
2f7cfbf446
2 changed files with 54 additions and 7 deletions
|
|
@ -4,7 +4,11 @@ import logging
|
|||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from domain.property_baseline.performance import Performance
|
||||
from domain.property_baseline.rebaseliner import Rebaseliner, RebaselineResult
|
||||
from domain.property_baseline.rebaseliner import (
|
||||
Rebaseliner,
|
||||
RebaselineReason,
|
||||
RebaselineResult,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
||||
|
|
@ -50,7 +54,12 @@ class CalculatorRebaseliner(Rebaseliner):
|
|||
self._calculator = calculator
|
||||
|
||||
def rebaseline(
|
||||
self, property_id: int, effective_epc: "EpcPropertyData", lodged: Performance
|
||||
self,
|
||||
property_id: int,
|
||||
effective_epc: "EpcPropertyData",
|
||||
lodged: Performance,
|
||||
*,
|
||||
physical_state_changed: bool = False,
|
||||
) -> RebaselineResult:
|
||||
# A raise (UnmappedSapCode, etc.) propagates: the calculator is
|
||||
# load-bearing, so the batch aborts and the cert is fixed at once. The
|
||||
|
|
@ -58,10 +67,24 @@ class CalculatorRebaseliner(Rebaseliner):
|
|||
# regardless of whether lodged or calculated figures win (ADR-0013/0014).
|
||||
result: SapResult = self._calculator.calculate(effective_epc)
|
||||
sap_version: Optional[float] = effective_epc.sap_version
|
||||
if sap_version is not None and sap_version < _MIN_TRUSTED_SAP_VERSION:
|
||||
pre_sap10 = sap_version is not None and sap_version < _MIN_TRUSTED_SAP_VERSION
|
||||
# The calculator output IS Effective Performance whenever a Rebaselining
|
||||
# trigger fired: (a) a superseded methodology (pre-SAP10), or (b)/(c) the
|
||||
# physical state was changed by Landlord Overrides / Prediction. Only a
|
||||
# pristine lodged SAP >= 10.2 cert keeps its accredited figure — that is
|
||||
# the *only* case where the calculator runs purely to validate (and where
|
||||
# its known divergence from the accredited register would mislead).
|
||||
if pre_sap10 or physical_state_changed:
|
||||
reason: RebaselineReason = (
|
||||
"both"
|
||||
if pre_sap10 and physical_state_changed
|
||||
else "pre_sap10"
|
||||
if pre_sap10
|
||||
else "physical_state_changed"
|
||||
)
|
||||
return RebaselineResult(
|
||||
effective=Performance.from_sap_result(result),
|
||||
reason="pre_sap10",
|
||||
reason=reason,
|
||||
sap_result=result,
|
||||
)
|
||||
self._log_divergence(
|
||||
|
|
|
|||
|
|
@ -59,8 +59,20 @@ class Rebaseliner(ABC):
|
|||
|
||||
@abstractmethod
|
||||
def rebaseline(
|
||||
self, property_id: int, effective_epc: EpcPropertyData, lodged: Performance
|
||||
) -> RebaselineResult: ...
|
||||
self,
|
||||
property_id: int,
|
||||
effective_epc: EpcPropertyData,
|
||||
lodged: Performance,
|
||||
*,
|
||||
physical_state_changed: bool = False,
|
||||
) -> RebaselineResult:
|
||||
"""Produce Effective Performance. ``physical_state_changed`` is True when
|
||||
the Effective EPC was assembled from something other than a pristine
|
||||
lodged cert — Landlord Overrides, Site Notes, or EPC Prediction moved the
|
||||
physical picture (Rebaselining trigger (b)/(c)) — so the accredited lodged
|
||||
figure no longer describes the dwelling and the calculator output wins
|
||||
even at SAP >= 10.2."""
|
||||
...
|
||||
|
||||
|
||||
class StubRebaseliner(Rebaseliner):
|
||||
|
|
@ -76,7 +88,12 @@ class StubRebaseliner(Rebaseliner):
|
|||
"""
|
||||
|
||||
def rebaseline(
|
||||
self, property_id: int, effective_epc: EpcPropertyData, lodged: Performance
|
||||
self,
|
||||
property_id: int,
|
||||
effective_epc: EpcPropertyData,
|
||||
lodged: Performance,
|
||||
*,
|
||||
physical_state_changed: bool = False,
|
||||
) -> RebaselineResult:
|
||||
sap_version = effective_epc.sap_version
|
||||
if sap_version is not None and sap_version < _SAP10_FLOOR:
|
||||
|
|
@ -84,4 +101,11 @@ class StubRebaseliner(Rebaseliner):
|
|||
f"Property needs rebaselining (pre-SAP10 cert, sap_version="
|
||||
f"{sap_version}); this stub does not run the calculator"
|
||||
)
|
||||
# A physical-state change needs the calculator this stub does not run;
|
||||
# raise rather than fabricate a "none" that ignores the overrides.
|
||||
if physical_state_changed:
|
||||
raise RebaselineNotImplemented(
|
||||
"Property needs rebaselining (physical state changed by overrides "
|
||||
"/ prediction); this stub does not run the calculator"
|
||||
)
|
||||
return RebaselineResult(effective=lodged, reason="none", sap_result=None)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue