Model/domain/property_baseline/performance.py
Khalim Conn-Kowlessar 0fb5da2f79 refactor(baseline): Performance.from_sap_result replaces the loose mapper
PR feedback: the SapResult -> Performance mapping should be a method, not a
free function you must know exists in the rebaseliner. Put the factory on
the target as `Performance.from_sap_result`, beside its sibling
`lodged_performance` and mirroring `Epc.from_sap_score` (the factory this
mapping already calls).

Not a `SapResult.to_performance()`: that would make the SAP calculator
import `Performance` (a property_baseline type), re-introducing the
engine->consumer coupling removed by the SapCalculator ABC. SapResult is a
TYPE_CHECKING-only import in performance.py (the body only reads attributes),
so the calculator module is not pulled in at runtime.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 13:59:25 +00:00

71 lines
2.6 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from typing import Optional, TYPE_CHECKING, TypeVar
from datatypes.epc.domain.epc import Epc
from datatypes.epc.domain.epc_property_data import EpcPropertyData
if TYPE_CHECKING:
from domain.sap10_calculator.calculator import SapResult
_T = TypeVar("_T")
_KG_PER_TONNE = 1000.0
@dataclass(frozen=True)
class Performance:
"""One half of a Baseline Performance — a single set of SAP10 figures.
The four quantities a Property is rated on (CONTEXT.md: Lodged / Effective
Performance): SAP score, EPC Band, carbon emissions, and Primary Energy
Intensity. Used for both the Lodged half (off the gov register) and the
Effective half (what the modelling pipeline scored against).
"""
sap_score: int
epc_band: Epc
co2_emissions: float
primary_energy_intensity: int
@classmethod
def from_sap_result(cls, result: "SapResult") -> "Performance":
"""The four rated quantities, read off a calculator `SapResult`
(ADR-0013): band derived from the score, CO2 converted kg→tonnes, PEUI
rounded to the lodged integer scale. The `from_*` factory mirrors
`Epc.from_sap_score`; living on the target keeps the SAP calculator
free of any `property_baseline` dependency."""
return cls(
sap_score=result.sap_score,
epc_band=Epc.from_sap_score(result.sap_score),
co2_emissions=result.co2_kg_per_yr / _KG_PER_TONNE,
primary_energy_intensity=round(result.primary_energy_kwh_per_m2),
)
def _require(value: Optional[_T], field: str) -> _T:
if value is None:
raise ValueError(
f"EPC is missing recorded performance field {field!r}; "
"cannot establish Lodged Performance"
)
return value
def lodged_performance(epc: EpcPropertyData) -> Performance:
"""The Lodged Performance recorded on an EPC — what the gov register says.
Reads the four rated quantities straight off the EPC's recorded fields
(CONTEXT.md: Primary Energy Intensity is recorded as `energy_consumption_current`).
Unmodified by modelling.
"""
return Performance(
sap_score=_require(epc.energy_rating_current, "energy_rating_current"),
epc_band=_require(
epc.current_energy_efficiency_band, "current_energy_efficiency_band"
),
co2_emissions=_require(epc.co2_emissions_current, "co2_emissions_current"),
primary_energy_intensity=_require(
epc.energy_consumption_current, "energy_consumption_current"
),
)