mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-19 17:03:02 +00:00
Bill / EnergyBreakdown / BillDerivation / sap_fuel were under domain/property_baseline/ only because Baseline was built first. The Modelling stage now needs them too, so move them (and their tests) to a neutral domain/billing/ — Fuel/FuelRates already live in the shared domain/fuel_rates/. Avoids a modelling -> property_baseline cross-stage import and a package name that wrongly implies ownership (ADR-0011, ADR-0014 amendment). Pure git mv + import rewrite across 10 files; 40 billing/baseline/repo tests pass, pyright strict clean. CONTEXT.md Bill Derivation location updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
3.7 KiB
Python
87 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
|
|
from datatypes.epc.domain.epc_property_data import (
|
|
EpcPropertyData,
|
|
RenewableHeatIncentive,
|
|
)
|
|
from domain.billing.bill import EnergyBreakdown
|
|
from domain.billing.bill_derivation import BillDerivation
|
|
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.fuel_rates.fuel_rates_repository import FuelRatesRepository
|
|
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,
|
|
fuel_rates: FuelRatesRepository,
|
|
) -> None:
|
|
self._unit_of_work = unit_of_work
|
|
self._rebaseliner = rebaseliner
|
|
self._fuel_rates = fuel_rates
|
|
|
|
def run(self, property_ids: list[int]) -> None:
|
|
# The Fuel Rates snapshot is a committed static file (no DB), so read it
|
|
# once before the unit opens and reuse the BillDerivation across the
|
|
# batch — every property prices against the same snapshot.
|
|
fuel_rates = self._fuel_rates.get_current()
|
|
bill_derivation = BillDerivation(fuel_rates)
|
|
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)
|
|
rebaselined = self._rebaseliner.rebaseline(
|
|
property_id, effective_epc, lodged
|
|
)
|
|
# No SapResult (the stub path) means no scored picture to price,
|
|
# so the bill stays None.
|
|
bill = (
|
|
bill_derivation.derive(
|
|
EnergyBreakdown.from_sap_result(rebaselined.sap_result)
|
|
)
|
|
if rebaselined.sap_result is not None
|
|
else None
|
|
)
|
|
rhi = _require_rhi(effective_epc)
|
|
baseline = PropertyBaselinePerformance(
|
|
lodged=lodged,
|
|
effective=rebaselined.effective,
|
|
rebaseline_reason=rebaselined.reason,
|
|
space_heating_kwh=rhi.space_heating_kwh,
|
|
water_heating_kwh=rhi.water_heating_kwh,
|
|
bill=bill,
|
|
)
|
|
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
|