diff --git a/domain/modelling/scoring/package_scorer.py b/domain/modelling/scoring/package_scorer.py index d9c88cf6..23010572 100644 --- a/domain/modelling/scoring/package_scorer.py +++ b/domain/modelling/scoring/package_scorer.py @@ -8,7 +8,7 @@ user-assembled plan. """ from dataclasses import dataclass -from typing import Sequence +from typing import Optional, Sequence from datatypes.epc.domain.epc_property_data import EpcPropertyData from domain.modelling.scoring.overlay_applicator import apply_simulations @@ -20,11 +20,18 @@ from domain.sap10_calculator.calculator import SapCalculator, SapResult 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.""" + annual totals. + + `sap_result` is the calculator output the headline figures were taken from, + carried so Bill Derivation can price the scored end-state without a second + `calculate` (ADR-0014 amendment). The optimiser never reads it — it works + off `sap_continuous` only — so it stays domain-agnostic and a stub scorer + may leave it `None`.""" sap_continuous: float co2_kg_per_yr: float primary_energy_kwh_per_yr: float + sap_result: Optional[SapResult] = None class PackageScorer: @@ -44,4 +51,5 @@ class PackageScorer: 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, + sap_result=result, ) diff --git a/tests/domain/modelling/test_package_scorer.py b/tests/domain/modelling/test_package_scorer.py index 9310e0e6..e0575ea6 100644 --- a/tests/domain/modelling/test_package_scorer.py +++ b/tests/domain/modelling/test_package_scorer.py @@ -52,3 +52,20 @@ def test_empty_package_scores_the_unmodified_baseline() -> None: abs(score.primary_energy_kwh_per_yr - direct.primary_energy_kwh_per_yr) <= 1e-9 ) + + +def test_score_carries_the_scored_sap_result_for_billing() -> None: + # Arrange — the post-package SapResult must ride on the Score so Bill + # Derivation can price the simulated end-state without a second calculate + # (ADR-0014 amendment). + baseline: EpcPropertyData = build_epc() + scorer = PackageScorer(Sap10Calculator()) + + # Act + filled: Score = scorer.score(baseline, [_CAVITY_FILL]) + + # Assert — the SapResult is the one the Score's headline figures came from. + assert filled.sap_result is not None + assert ( + abs(filled.sap_result.sap_score_continuous - filled.sap_continuous) <= 1e-9 + )