From 50493f07c6cfb23fc22828298d8de1ea06739e9e Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 23 Jul 2026 09:23:26 +0000 Subject: [PATCH] =?UTF-8?q?Persist=20the=20lodged=20wall=5Fu=5Fvalue=20ove?= =?UTF-8?q?rride=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SapBuildingPart.wall_u_value (the gov-EPC lodged wall U, authoritative over the derived default) had no column and dropped on save, so the reloaded dwelling fell back to the age-band default U — -2.32 SAP on the 2 affected RdSAP corpus certs. Surfaced by the new DB-round-trip gate. The prior _UNPERSISTED_ALLOWLIST justification ("deliberate — full-SAP only; RdSAP re-model uses default U-values") was stale: the cascade DOES honour the lodged U on the API path. Add wall_u_value to EpcBuildingPartModel, write in from_domain, reconstruct in _to_building_part, drop the allowlist entry. Verified: certs 39036600 / 38137278 round-trip 72.43 / 69.11 exactly. Needs an added FE column (epc_building_part.wall_u_value, double precision NULL) on assessment-model#443. Co-Authored-By: Claude Opus 4.8 (1M context) --- infrastructure/postgres/epc_property_table.py | 6 ++++++ repositories/epc/epc_postgres_repository.py | 1 + .../repositories/epc/test_epc_persistence_field_coverage.py | 1 - 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/infrastructure/postgres/epc_property_table.py b/infrastructure/postgres/epc_property_table.py index c08e6c8c5..2cbe8c932 100644 --- a/infrastructure/postgres/epc_property_table.py +++ b/infrastructure/postgres/epc_property_table.py @@ -680,6 +680,11 @@ class EpcBuildingPartModel(SQLModel, table=True): # to the gov-API code-6 heuristic", `False` means "explicitly system-built, # do NOT apply the heuristic". Collapsing False to None inverts the answer. wall_is_basement: Optional[bool] = Field(default=None) + # #1665: lodged wall U (gov-EPC `wall_u_value`), authoritative over the + # derived default when present; dropped it fell back to the age-band default + # (-2.32 SAP). The prior allowlist "RdSAP uses defaults" was stale — the + # cascade does honour the lodged U on the API path. + wall_u_value: Optional[float] = Field(default=None) room_in_roof_floor_area: Optional[float] = Field(default=None) room_in_roof_construction_age_band: Optional[str] = Field(default=None) alt_wall_1_area: Optional[float] = Field(default=None) @@ -740,6 +745,7 @@ class EpcBuildingPartModel(SQLModel, table=True): roof_insulation_thickness=part.roof_insulation_thickness, rafter_insulation_thickness=part.rafter_insulation_thickness, wall_is_basement=part.wall_is_basement, + wall_u_value=part.wall_u_value, room_in_roof_floor_area=float(rir.floor_area) if rir else None, room_in_roof_construction_age_band=( rir.construction_age_band if rir else None diff --git a/repositories/epc/epc_postgres_repository.py b/repositories/epc/epc_postgres_repository.py index e4d16577f..b53fe81a1 100644 --- a/repositories/epc/epc_postgres_repository.py +++ b/repositories/epc/epc_postgres_repository.py @@ -1185,6 +1185,7 @@ class EpcPostgresRepository(EpcRepository): roof_insulation_thickness=bp.roof_insulation_thickness, rafter_insulation_thickness=bp.rafter_insulation_thickness, wall_is_basement=bp.wall_is_basement, + wall_u_value=bp.wall_u_value, # Reconstruct from the epc_room_in_roof child table (full geometry # incl. detailed_surfaces); the flat room_in_roof_* columns on # epc_building_part are superseded and dropped in a follow-up (#1664). diff --git a/tests/repositories/epc/test_epc_persistence_field_coverage.py b/tests/repositories/epc/test_epc_persistence_field_coverage.py index 440179dd6..a43ba20e7 100644 --- a/tests/repositories/epc/test_epc_persistence_field_coverage.py +++ b/tests/repositories/epc/test_epc_persistence_field_coverage.py @@ -59,7 +59,6 @@ _UNPERSISTED_ALLOWLIST: dict[str, str] = { # `wall_is_basement` is deliberately NOT in this list — it is a # disambiguation flag selecting which RdSAP default applies (§5.17/Table 23), # not a measured U-value, and it IS persisted. - "SapBuildingPart.wall_u_value": "deliberate — full-SAP only; RdSAP re-model uses default U-values (#1656)", "SapBuildingPart.roof_u_value": "deliberate — full-SAP only; RdSAP re-model uses default U-values (#1656)", "SapBuildingPart.floor_u_value": "deliberate — full-SAP only; RdSAP re-model uses default U-values (#1656)", }