Persist the measured at-rafters roof insulation thickness 🟥

RdSAP 10 §5.11.2 — a roof insulated at rafters lodges its thickness in
`rafter_insulation_thickness`, not `roof_insulation_thickness`. The field had no
DB column, so it was mapped from the gov-EPC API and silently dropped on save;
every at-rafters roof then fell back to the Table 18 col (2) unknown-thickness
default (2.30 W/m²K for bands A-D — the spec's *uninsulated* value).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-21 17:53:16 +00:00
parent ccba02b51a
commit d2168d518f

View file

@ -240,3 +240,45 @@ def test_building_part_wall_insulation_thickness_preserves_int(
value = reloaded.sap_building_parts[0].wall_insulation_thickness
assert value == 100
assert isinstance(value, int)
def test_rafter_insulation_thickness_round_trips(db_engine: Engine) -> None:
# RdSAP 10 §5.11.2 — a roof insulated AT RAFTERS lodges its insulation
# thickness in `rafter_insulation_thickness`, NOT `roof_insulation_thickness`
# (which stays null for rafter roofs, since rafters aren't loft joists).
# `heat_transmission.py:922` prefers it when `roof_insulation_location == 1`
# so the MEASURED Table 16 column (2) row applies. It had no DB column, so it
# was silently dropped on save and every at-rafters roof fell back to the
# Table 18 column (2) unknown-thickness default (persist != score).
#
# Real impact: property 749719 lodges "250mm" -> Table 16 col (2) U 0.23.
# Dropped, the roof bills at U 2.30 — a 10x error worth -9.8 SAP (lodged 73,
# calculated 63.2) that moved the dwelling a whole band, C -> D.
#
# Note this is an RdSAP INPUT (an insulation thickness feeding the standard
# cascade), not a lodged U-value. The sibling `wall/roof/floor_u_value` and
# `wall_is_basement` fields are full-SAP artefacts and are deliberately NOT
# persisted — see the `_UNPERSISTED_ALLOWLIST` note in
# test_epc_persistence_field_coverage.py.
from dataclasses import replace
# Arrange — a green fixture carrying a measured at-rafters thickness.
original = _load_epc("RdSAP-Schema-21.0.1")
assert original.sap_building_parts, "fixture must have a building part"
bp0 = replace(
original.sap_building_parts[0],
rafter_insulation_thickness="250mm",
)
original = replace(
original, sap_building_parts=[bp0, *original.sap_building_parts[1:]]
)
# Act
with Session(db_engine) as session:
epc_property_id = EpcPostgresRepository(session).save(original)
session.commit()
with Session(db_engine) as session:
reloaded = EpcPostgresRepository(session).get(epc_property_id)
# Assert — the measured rafter thickness survives, deep-equal.
assert reloaded == original