From 1fcad454fae2c8fa3ad1890f7d44a21a7ec87b53 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 12 Jun 2026 12:33:26 +0000 Subject: [PATCH] =?UTF-8?q?Synthesise=20reduced-field=20windows=20for=20Rd?= =?UTF-8?q?SAP-Schema-19.0=20certs=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the 19.0 synthesis seam over the shared _synthesise_reduced_field_windows core (inherited 20.0.0 coefficients, ND glazing -> DG-modal default 2, per ADR-0028). 19.0 glazed_type codes (1-4,6,7) are a subset of the verified 1-8 space. The 6 rich certs use lodged window_area directly; the windowless 994 synthesise a 4-way N/E/S/W split. Co-Authored-By: Claude Opus 4.8 (1M context) --- datatypes/epc/domain/mapper.py | 46 ++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 4b854ae9..fc2a9615 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -1109,8 +1109,28 @@ class EpcPropertyDataMapper: secondary_heating_type=schema.sap_heating.secondary_heating_type, cylinder_insulation_thickness_mm=schema.sap_heating.cylinder_insulation_thickness, ), - # 19.0 has no per-window list; individual window fields are at schema root - sap_windows=[], + # ADR-0028: 994/1000 omit sap_windows -> synthesised from the + # glazed_area band + TFA via the shared reduced-field core. The 6 + # rich certs keep their lodged per-window geometry (used directly: + # window_width = area, height = 1.0). + sap_windows=[ + SapWindow( + frame_material=None, + glazing_gap=0, + orientation=w.orientation, + window_type=w.window_type, + glazing_type=w.glazing_type, + window_width=_measurement_value(w.window_area), + window_height=1.0, + draught_proofed=False, + window_location=w.window_location, + window_wall_type=0, + permanent_shutters_present=False, + ) + for w in schema.sap_windows + ] + if schema.sap_windows + else _synthesise_19_0_sap_windows(schema), sap_energy_source=SapEnergySource( mains_gas=es.mains_gas == "Y", meter_type=str(es.meter_type), @@ -3156,6 +3176,28 @@ def _synthesise_18_0_sap_windows(schema: RdSapSchema18_0) -> List[SapWindow]: ) +# ADR-0028: multiple_glazing_type "ND" (Not Defined, 50/1000 19.0 certs) → the +# DG-modal default, as for 18.0/17.1. +_RDSAP19_0_ND_GLAZING_TYPE: int = 2 + + +def _synthesise_19_0_sap_windows(schema: RdSapSchema19_0) -> List[SapWindow]: + """ADR-0028 seam: reuses the inherited 20.0.0 coefficients via the shared + core (976/1000 band-1 with no measured band-1 windows; only 6 rich certs). + 19.0 glazed_type codes (observed 1-4, 6, 7) are a subset of 20.0.0's verified + 1-8 space: route integer codes through the verified cascade; the "ND" string + falls back to the DG-modal default. Own seam so 19.0 can diverge.""" + mgt = schema.multiple_glazing_type + glazing_type = ( + _api_cascade_glazing_type(mgt) + if isinstance(mgt, int) + else _RDSAP19_0_ND_GLAZING_TYPE + ) + return _synthesise_reduced_field_windows( + schema.glazed_area, schema.total_floor_area, glazing_type + ) + + # ADR-0028: multiple_glazing_type "ND" (Not Defined, 56/1000 17.1 certs) → the # DG-modal default, as for 18.0. _RDSAP17_1_ND_GLAZING_TYPE: int = 2