mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 31: §11a SAP rating cascade pin (24/24)
Adds `sap_rating_section_from_cert(epc) -> SapRatingSection`. Composes §1 TFA + §10a (255) total fuel cost via `fuel_cost_section_from_cert`, then runs the SAP rating equations (`energy_cost_factor`, `sap_rating`, `sap_rating_integer`). Pins (256) deflator, (257) ECF, SAP continuous, (258) SAP integer for all 6 fixtures — 24/24 PASS. Existing e2e pins on `ecf`, `sap_score_continuous`, `sap_score` already verified these outputs; cascade pins formalise §11a for the worksheet-conformance test surface. Cascade scoreboard: 660/660 → 684/684 (§7..§11a closed). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
74bfac049a
commit
2bfecad272
8 changed files with 129 additions and 0 deletions
|
|
@ -69,6 +69,12 @@ from domain.sap.tables.table_32 import (
|
|||
unit_price_p_per_kwh as table_32_unit_price_p_per_kwh,
|
||||
)
|
||||
from domain.sap.worksheet.fuel_cost import FuelCostResult, fuel_cost
|
||||
from domain.sap.worksheet.rating import (
|
||||
ENERGY_COST_DEFLATOR,
|
||||
energy_cost_factor,
|
||||
sap_rating,
|
||||
sap_rating_integer,
|
||||
)
|
||||
from domain.sap.worksheet.dimensions import dimensions_from_cert
|
||||
from domain.sap.worksheet.internal_gains import (
|
||||
InternalGainsResult,
|
||||
|
|
@ -1042,6 +1048,40 @@ def fabric_energy_efficiency_from_cert(epc: EpcPropertyData) -> Optional[float]:
|
|||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SapRatingSection:
|
||||
"""SAP 10.2 §11a worksheet line refs (256)..(258) — Energy Cost Factor
|
||||
and SAP rating. Returned by `sap_rating_section_from_cert`."""
|
||||
|
||||
energy_cost_deflator: float # (256) — Table 12 constant 0.42
|
||||
energy_cost_factor: float # (257) — (255) × (256) / ((4) + 45)
|
||||
sap_continuous: float # SAP value (un-rounded)
|
||||
sap_integer: int # (258) — round half-up to nearest int
|
||||
|
||||
|
||||
def sap_rating_section_from_cert(
|
||||
epc: EpcPropertyData,
|
||||
) -> Optional[SapRatingSection]:
|
||||
"""SAP 10.2 §11a cert→inputs cascade. Composes §10a (255) + §1 TFA via
|
||||
`_fuel_cost` + `dimensions_from_cert`, then runs the SAP rating equations
|
||||
(`energy_cost_factor`, `sap_rating`, `sap_rating_integer`). Returns the
|
||||
full `SapRatingSection`; None when TFA missing."""
|
||||
if epc.total_floor_area_m2 is None:
|
||||
return None
|
||||
dim = dimensions_from_cert(epc)
|
||||
fc = fuel_cost_section_from_cert(epc)
|
||||
assert fc is not None, "fuel_cost None despite TFA present"
|
||||
ecf = energy_cost_factor(
|
||||
total_cost_gbp=fc.total_cost_gbp, total_floor_area_m2=dim.total_floor_area_m2
|
||||
)
|
||||
return SapRatingSection(
|
||||
energy_cost_deflator=ENERGY_COST_DEFLATOR,
|
||||
energy_cost_factor=ecf,
|
||||
sap_continuous=sap_rating(ecf=ecf),
|
||||
sap_integer=sap_rating_integer(ecf=ecf),
|
||||
)
|
||||
|
||||
|
||||
def fuel_cost_section_from_cert(
|
||||
epc: EpcPropertyData,
|
||||
) -> Optional[FuelCostResult]:
|
||||
|
|
|
|||
|
|
@ -488,3 +488,11 @@ LINE_252_PV_CREDIT: float = 0.0000
|
|||
LINE_253_APPENDIX_Q_SAVED: float = 0.0000
|
||||
LINE_254_APPENDIX_Q_USED: float = 0.0000
|
||||
LINE_255_TOTAL_COST: float = 655.6949
|
||||
|
||||
# ============================================================================
|
||||
# §11a SAP rating — line refs (256)..(258)
|
||||
# ============================================================================
|
||||
LINE_256_ENERGY_COST_DEFLATOR: float = 0.4200
|
||||
LINE_257_ECF: float = 2.7055
|
||||
SAP_VALUE_CONTINUOUS: float = 62.2584
|
||||
LINE_258_SAP_RATING_INTEGER: int = 62
|
||||
|
|
|
|||
|
|
@ -455,3 +455,11 @@ LINE_252_PV_CREDIT: float = 0.0000
|
|||
LINE_253_APPENDIX_Q_SAVED: float = 0.0000
|
||||
LINE_254_APPENDIX_Q_USED: float = 0.0000
|
||||
LINE_255_TOTAL_COST: float = 732.1396
|
||||
|
||||
# ============================================================================
|
||||
# §11a SAP rating — line refs (256)..(258)
|
||||
# ============================================================================
|
||||
LINE_256_ENERGY_COST_DEFLATOR: float = 0.4200
|
||||
LINE_257_ECF: float = 2.5086
|
||||
SAP_VALUE_CONTINUOUS: float = 65.0057
|
||||
LINE_258_SAP_RATING_INTEGER: int = 65
|
||||
|
|
|
|||
|
|
@ -497,3 +497,11 @@ LINE_252_PV_CREDIT: float = 0.0000
|
|||
LINE_253_APPENDIX_Q_SAVED: float = 0.0000
|
||||
LINE_254_APPENDIX_Q_USED: float = 0.0000
|
||||
LINE_255_TOTAL_COST: float = 854.8139
|
||||
|
||||
# ============================================================================
|
||||
# §11a SAP rating — line refs (256)..(258)
|
||||
# ============================================================================
|
||||
LINE_256_ENERGY_COST_DEFLATOR: float = 0.4200
|
||||
LINE_257_ECF: float = 2.7743
|
||||
SAP_VALUE_CONTINUOUS: float = 61.2986
|
||||
LINE_258_SAP_RATING_INTEGER: int = 61
|
||||
|
|
|
|||
|
|
@ -521,3 +521,11 @@ LINE_252_PV_CREDIT: float = 0.0000
|
|||
LINE_253_APPENDIX_Q_SAVED: float = 0.0000
|
||||
LINE_254_APPENDIX_Q_USED: float = 0.0000
|
||||
LINE_255_TOTAL_COST: float = 828.6119
|
||||
|
||||
# ============================================================================
|
||||
# §11a SAP rating — line refs (256)..(258)
|
||||
# ============================================================================
|
||||
LINE_256_ENERGY_COST_DEFLATOR: float = 0.4200
|
||||
LINE_257_ECF: float = 2.7496
|
||||
SAP_VALUE_CONTINUOUS: float = 61.6431
|
||||
LINE_258_SAP_RATING_INTEGER: int = 62
|
||||
|
|
|
|||
|
|
@ -471,3 +471,11 @@ LINE_252_PV_CREDIT: float = 0.0000
|
|||
LINE_253_APPENDIX_Q_SAVED: float = 0.0000
|
||||
LINE_254_APPENDIX_Q_USED: float = 0.0000
|
||||
LINE_255_TOTAL_COST: float = 807.5421
|
||||
|
||||
# ============================================================================
|
||||
# §11a SAP rating — line refs (256)..(258)
|
||||
# ============================================================================
|
||||
LINE_256_ENERGY_COST_DEFLATOR: float = 0.4200
|
||||
LINE_257_ECF: float = 3.0539
|
||||
SAP_VALUE_CONTINUOUS: float = 57.3979
|
||||
LINE_258_SAP_RATING_INTEGER: int = 57
|
||||
|
|
|
|||
|
|
@ -497,3 +497,11 @@ LINE_252_PV_CREDIT: float = 0.0000
|
|||
LINE_253_APPENDIX_Q_SAVED: float = 0.0000
|
||||
LINE_254_APPENDIX_Q_USED: float = 0.0000
|
||||
LINE_255_TOTAL_COST: float = 860.7162
|
||||
|
||||
# ============================================================================
|
||||
# §11a SAP rating — line refs (256)..(258)
|
||||
# ============================================================================
|
||||
LINE_256_ENERGY_COST_DEFLATOR: float = 0.4200
|
||||
LINE_257_ECF: float = 2.6671
|
||||
SAP_VALUE_CONTINUOUS: float = 62.7937
|
||||
LINE_258_SAP_RATING_INTEGER: int = 63
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from domain.sap.rdsap.cert_to_inputs import (
|
|||
heat_transmission_section_from_cert,
|
||||
internal_gains_section_from_cert,
|
||||
mean_internal_temperature_section_from_cert,
|
||||
sap_rating_section_from_cert,
|
||||
solar_gains_section_from_cert,
|
||||
space_cooling_section_from_cert,
|
||||
space_heating_section_from_cert,
|
||||
|
|
@ -850,3 +851,43 @@ def test_section_10a_line_refs_match_pdf(
|
|||
|
||||
# Assert
|
||||
_pin(actual, expected, f"§10a {fixture_attr} {fixture_name}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# §11a SAP rating — LINE_256..LINE_258
|
||||
# ============================================================================
|
||||
|
||||
_SECTION_11A_PINS: Final[tuple[tuple[str, str], ...]] = (
|
||||
("LINE_256_ENERGY_COST_DEFLATOR", "energy_cost_deflator"),
|
||||
("LINE_257_ECF", "energy_cost_factor"),
|
||||
("SAP_VALUE_CONTINUOUS", "sap_continuous"),
|
||||
("LINE_258_SAP_RATING_INTEGER", "sap_integer"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture_name,fixture_attr,result_attr",
|
||||
[
|
||||
(fix, line, attr)
|
||||
for fix in _FIXTURES
|
||||
for line, attr in _SECTION_11A_PINS
|
||||
],
|
||||
ids=lambda x: x if isinstance(x, str) else None,
|
||||
)
|
||||
def test_section_11a_line_refs_match_pdf(
|
||||
fixture_name: str, fixture_attr: str, result_attr: str
|
||||
) -> None:
|
||||
"""§11a pins — (256) deflator, (257) ECF, SAP continuous, (258) SAP
|
||||
integer match the U985 PDF to abs=1e-4."""
|
||||
# Arrange
|
||||
mod = _FIXTURES[fixture_name]
|
||||
epc = mod.build_epc() # type: ignore[attr-defined]
|
||||
expected = getattr(mod, fixture_attr)
|
||||
|
||||
# Act
|
||||
sr = sap_rating_section_from_cert(epc)
|
||||
assert sr is not None, f"{fixture_name}: sap_rating_from_cert returned None"
|
||||
actual = getattr(sr, result_attr)
|
||||
|
||||
# Assert
|
||||
_pin(actual, expected, f"§11a {fixture_attr} {fixture_name}")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue