from __future__ import annotations from collections.abc import Callable from datatypes.epc.domain.epc_property_data import ( EpcPropertyData, RenewableHeatIncentive, ) from domain.property_baseline.property_baseline_performance import PropertyBaselinePerformance from domain.property_baseline.performance import lodged_performance from domain.property_baseline.rebaseliner import Rebaseliner from repositories.unit_of_work import UnitOfWork class PropertyBaselineOrchestrator: """Stage 2: establish each Property's Baseline Performance and persist it. Runs the whole batch in **one** Unit of Work and commits once (ADR-0012): for each property it hydrates the Property via the unit's PropertyRepo, resolves the Effective EPC, reads Lodged Performance off it, runs the Rebaseliner to produce Effective Performance, and persists the pair plus the deterministic kWh. Any property raising aborts the batch — the unit is left uncommitted, so nothing persists and the subtask fails noisily. Reads only from repos — never a Fetcher or HTTP (ADR-0003) — so it is byte-identical whether Ingestion ran milliseconds ago (First Run) or last week. The injected Rebaseliner is the re-score-on-override seam (ADR-0011). """ def __init__( self, *, unit_of_work: Callable[[], UnitOfWork], rebaseliner: Rebaseliner, ) -> None: self._unit_of_work = unit_of_work self._rebaseliner = rebaseliner def run(self, property_ids: list[int]) -> None: with self._unit_of_work() as uow: properties = uow.property.get_many(property_ids) for property_id, prop in zip(property_ids, properties, strict=True): effective_epc = prop.effective_epc lodged = lodged_performance(effective_epc) effective, reason = self._rebaseliner.rebaseline( property_id, effective_epc, lodged ) rhi = _require_rhi(effective_epc) baseline = PropertyBaselinePerformance( lodged=lodged, effective=effective, rebaseline_reason=reason, space_heating_kwh=rhi.space_heating_kwh, water_heating_kwh=rhi.water_heating_kwh, ) uow.property_baseline.save(baseline, property_id) uow.commit() def _require_rhi(epc: EpcPropertyData) -> RenewableHeatIncentive: rhi = epc.renewable_heat_incentive if rhi is None: raise ValueError( "Effective EPC is missing renewable_heat_incentive; cannot read " "baseline space-heating / hot-water kWh" ) return rhi