Model/domain/modelling/package_scorer.py
Khalim Conn-Kowlessar 7a478cff6e feat(modelling): Package Scorer — compose overlays + score on the calculator
PackageScorer(calculator: SapCalculator).score(baseline, simulations) folds
the Simulation Overlays onto the baseline via the Overlay Applicator and
scores the throwaway EpcPropertyData on the injected deterministic SAP
calculator, returning Score(sap_continuous, co2_kg_per_yr,
primary_energy_kwh_per_yr). Depends on the SapCalculator abstraction, not a
concrete engine. This is the reusable scoring primitive (ADR-0016) — the
same call serves the optimiser's whole-package re-score and a future live
re-score of a user-assembled plan.

Two behaviour tests against the real Sap10Calculator on a hand-built EPD:
filling the main cavity improves SAP (right-directional through the real
physics); an empty package scores the unmodified baseline (pins the
SapResult->Score mapping). The Elmhurst before/after cascade PIN (#1154's
acceptance) lands once cert 001431 parses (external _extract_windows fix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 08:41:30 +00:00

47 lines
1.8 KiB
Python

"""The Package Scorer — the reusable scoring primitive (ADR-0016).
Composes an ordered set of Simulation Overlays onto a baseline EpcPropertyData
(via the Overlay Applicator) and scores the throwaway result on a deterministic
SAP calculator, returning the headline metrics. The same primitive powers the
optimiser's whole-package re-score and any future live re-score of a
user-assembled plan.
"""
from dataclasses import dataclass
from typing import Sequence
from datatypes.epc.domain.epc_property_data import EpcPropertyData
from domain.modelling.overlay_applicator import apply_simulations
from domain.modelling.simulation import EpcSimulation
from domain.sap10_calculator.calculator import SapCalculator, SapResult
@dataclass(frozen=True)
class Score:
"""The headline metrics of a scored package. `sap_continuous` is the
un-rounded SAP rating (used for deltas); carbon and primary energy are the
annual totals."""
sap_continuous: float
co2_kg_per_yr: float
primary_energy_kwh_per_yr: float
class PackageScorer:
"""Scores a package of Simulation Overlays against a baseline EpcPropertyData
on an injected SAP calculator (depends on the `SapCalculator` abstraction,
not a concrete engine)."""
def __init__(self, calculator: SapCalculator) -> None:
self._calculator = calculator
def score(
self, baseline: EpcPropertyData, simulations: Sequence[EpcSimulation]
) -> Score:
simulated: EpcPropertyData = apply_simulations(baseline, simulations)
result: SapResult = self._calculator.calculate(simulated)
return Score(
sap_continuous=result.sap_score_continuous,
co2_kg_per_yr=result.co2_kg_per_yr,
primary_energy_kwh_per_yr=result.primary_energy_kwh_per_yr,
)