slice S-B18: SAP rating uses UK-average weather (region 0), not cert region

SAP 10.2 Appendix U explicit rule: "Calculations for fabric energy
efficiency (FEE), regulation compliance (TER and DER, TPER and DPER)
and for ratings (SAP rating and environmental impact rating) are done
with UK average weather. Other calculations (such as for energy use and
costs on EPCs) are done using local weather."

Our calculator was using the cert's region_code for everything. Spec
mandates region 0 (UK average) for rating outputs. Net MAE neutral on
the 100-cert sample (most certs sit close to UK average) and on the
300-cert sample but it's spec-correct, and aligns with what the cert
assessor's SAP rating actually computes.

Found by switching from probe-driven to worksheet-driven iteration —
per user suggestion this is the more efficient mode once the easy
wins from probe-driven have been extracted.

100-cert: MAE 4.39 (unchanged)
300-cert: MAE 5.44

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-18 16:49:11 +00:00
parent 9dc6073bd3
commit 0102ff313a
2 changed files with 22 additions and 17 deletions

View file

@ -214,16 +214,18 @@ def _dwelling_exposure(dwelling_type: Optional[str]) -> DwellingExposure:
def _region_index(region_code: Optional[str]) -> int:
"""Coerce EpcPropertyData.region_code (str) to the integer Appendix U
region index. Out-of-range or unparseable 0 (UK average)."""
if region_code is None:
return 0
try:
idx = int(region_code)
except (TypeError, ValueError):
return 0
if 0 <= idx <= 21:
return idx
"""SAP rating must be computed with UK-average weather per Appendix U:
"Calculations for fabric energy efficiency (FEE), regulation compliance
(TER and DER, TPER and DPER) and for ratings (SAP rating and environmental
impact rating) are done with UK average weather. Other calculations (such
as for energy use and costs on EPCs) are done using local weather."
Since our calculator's primary output is the SAP rating, we always return
region 0 (UK average) regardless of the cert's actual region_code. A
future slice may add a `compute_local_weather` flag to also produce the
energy-use kWh totals at local weather.
"""
_ = region_code
return 0

View file

@ -95,12 +95,15 @@ def test_minimal_cert_round_trips_through_calculator_and_returns_sap_result() ->
assert result.total_fuel_cost_gbp > 0
def test_region_code_string_is_translated_to_appendix_u_region_index() -> None:
# Arrange — `region_code` on EpcPropertyData is a string ("1"="Thames",
# "20"="Shetland"); the mapper must coerce it to the integer index
# Appendix U expects, falling back to 0 (UK average) when missing.
def test_calculator_always_uses_uk_average_weather_for_rating() -> None:
# Arrange — SAP 10.2 Appendix U explicitly states: "Calculations for
# ratings (SAP rating and environmental impact rating) are done with
# UK average weather". The mapper must therefore always set region=0
# regardless of the cert's lodged region_code; a future slice may
# add a separate local-weather path for the energy-use display
# numbers that EPC software shows alongside the rating.
base = _typical_semi_detached_epc()
thames = base
thames = base # region_code="1"
no_region = make_minimal_sap10_epc(
total_floor_area_m2=_TYPICAL_TFA_M2,
habitable_rooms_count=4,
@ -113,8 +116,8 @@ def test_region_code_string_is_translated_to_appendix_u_region_index() -> None:
inputs_thames = cert_to_inputs(thames)
inputs_default = cert_to_inputs(no_region)
# Assert
assert inputs_thames.region == 1
# Assert — both collapse to UK average (region 0) for rating purposes.
assert inputs_thames.region == 0
assert inputs_default.region == 0