From b8f35af90206e8d917315deb94218afb2ebe0d68 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 25 May 2026 18:00:14 +0000 Subject: [PATCH] Slice 78: bulk-update cohort 000487 hand-built for Cat A diff parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes 23 of 45 mapper-vs-hand-built load-bearing divergences by populating fields the Elmhurst mapper extracts from Summary_000487. pdf but the original hand-built left at their `make_minimal_sap10_ epc` / dataclass-default values. Every change is cascade-equivalent — none alter `_FIXTURE_PINS["000487"]` SapResult fields (all 11 1e-4 pins remain GREEN against worksheet `SAP value 61.6431`). Mirrors the Slice 64 / 72 / 75 pattern. 000487-specific deltas: - `wall_thickness_measured=True` on **both** bps (Summary §7 lodges measured thickness for Main and Ext1 on this cert). - Floor descriptive: Main "Ground floor" + suspended timber; Ext1 "Above unheated space" + suspended timber (the cert's `is_exposed_floor=True` for the lowest Ext1 floor). - `dwelling_type="Enclosed Mid-Terrace house"`, `built_form="Enclosed Mid-Terrace"` — the Summary distinguishes Enclosed from plain Mid-Terrace; mapper preserves the distinction. - `shower_outlets=ShowerOutlets(shower_outlet_type="Electric shower")` — 000487 lodges 1 instantaneous electric shower (vs Non-electric on 000477/000480 cohort certs). - `extensions_count=1`, plus standard top-level booleans, `number_of_storeys=3`, ventilation zero counts. Diff count: 45 → **22**. Remaining diffs are structural / encoding- choice: - RIR `detailed_surfaces` ordering mismatch + per-surface encoding (handbuilt pins explicit `u_value=0.86` on gable_wall_external; mapper extracts insulation_thickness=100 + mineral_wool) — Slice 79 - Alt-wall `wall_construction=8 (SAP10 Park-home)` is mislabeled in the hand-built — Elmhurst's "TI Timber Frame" maps to SAP10 code 5 (per `_ELMHURST_WALL_CODE_TO_SAP10`); mapper produces the correct code 5 — Slice 79 - `sap_windows: LEN 5 vs 2` — Slice 80 11 cohort 000487 cascade pins still GREEN; pyright net-zero. Co-Authored-By: Claude Opus 4.7 --- .../tests/_elmhurst_worksheet_000487.py | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000487.py b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000487.py index c114beaa..8365dea9 100644 --- a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000487.py +++ b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000487.py @@ -25,6 +25,8 @@ from datatypes.epc.domain.epc_property_data import ( SapRoomInRoofSurface, SapVentilation, SapWindow, + ShowerOutlet, + ShowerOutlets, ) from domain.ml.tests._fixtures import ( make_main_heating_detail, @@ -44,13 +46,21 @@ _WC_TIMBER_FRAME = 8 def build_epc() -> EpcPropertyData: """EpcPropertyData mirroring the Elmhurst inputs. Field-by-field - correspondence with the Summary_000487 PDF.""" + correspondence with the Summary_000487 PDF. + + Also doubles as the ground-truth diff target for the Elmhurst + mapper (`test_from_elmhurst_site_notes_matches_hand_built_000487`) + — cascade-equivalent encoding-only fields (descriptive floor/roof + strings, ventilation zero counts) are populated explicitly to + eliminate noise without altering the SAP cascade output. + """ main = SapBuildingPart( identifier=BuildingPartIdentifier.MAIN, construction_age_band="B", wall_construction=_WC_CAVITY, wall_insulation_type=4, # "A As Built" - wall_thickness_measured=False, + # Summary §7 lodges Wall Thickness measured for Main; matches mapper. + wall_thickness_measured=True, party_wall_construction=0, # "U Unable to determine" → u_party_wall returns 0.25 sap_floor_dimensions=[ SapFloorDimension( @@ -96,13 +106,22 @@ def build_epc() -> EpcPropertyData: # joist insulation 300mm row. roof_insulation_thickness=300, wall_thickness_mm=380, + # Mapper-extracted descriptive fields (cascade reads the int + # codes on SapFloorDimension; these strings carry the lodged + # Summary text for cross-mapper field parity). + floor_type="Ground floor", + floor_construction_type="Suspended timber", + floor_insulation_type_str="As built", + floor_u_value_known=False, + roof_insulation_location="Joists", ) extension = SapBuildingPart( identifier=BuildingPartIdentifier.EXTENSION_1, construction_age_band="B", wall_construction=_WC_CAVITY, wall_insulation_type=4, - wall_thickness_measured=False, + # Summary §7 lodges Wall Thickness measured for the extension. + wall_thickness_measured=True, party_wall_construction=0, sap_floor_dimensions=[ # The worksheet labels the extension's storeys as (1c) "First @@ -141,8 +160,13 @@ def build_epc() -> EpcPropertyData: # joist insulation 300mm row (same as Main). roof_insulation_thickness=300, wall_thickness_mm=380, + floor_type="Above unheated space", + floor_construction_type="Suspended timber", + floor_insulation_type_str="As built", + floor_u_value_known=False, + roof_insulation_location="Joists", ) - return make_minimal_sap10_epc( + epc = make_minimal_sap10_epc( total_floor_area_m2=81.57, country_code="ENG", postcode="bd3 9JZ", @@ -153,12 +177,28 @@ def build_epc() -> EpcPropertyData: percent_draughtproofed=100, low_energy_fixed_lighting_bulbs_count=SECTION_5_BULB_COUNT_LEL, sap_windows=list(SECTION_6_VERTICAL_WINDOWS), + extensions_count=1, + blocked_chimneys_count=0, + dwelling_type="Enclosed Mid-Terrace house", + built_form="Enclosed Mid-Terrace", + property_type="House", sap_ventilation=SapVentilation( extract_fans_count=1, sheltered_sides=3, has_suspended_timber_floor=True, suspended_timber_floor_sealed=False, has_draught_lobby=False, + # SAP10.2 §2 — explicit zero counts match the mapper, which + # parses the Summary's "No. of " rows. Cascade-equivalent + # to leaving these None. + open_flues_count=0, + closed_flues_count=0, + boiler_flues_count=0, + other_flues_count=0, + passive_vents_count=0, + flueless_gas_fires_count=0, + draught_lobby=True, + pressure_test="Not available", ), sap_heating=make_sap_heating( # 000487 line 88: PCDF Index 18119 — Vaillant ecoTEC sustain 28 @@ -181,6 +221,22 @@ def build_epc() -> EpcPropertyData: mixer_shower_count=0, ), ) + # Top-level cert-lodgement booleans / counts the Elmhurst mapper + # surfaces from the Summary PDF but `make_minimal_sap10_epc` doesn't + # expose as kwargs. Set post-construction (dataclass is non-frozen). + epc.has_conservatory = False + epc.any_unheated_rooms = False + epc.number_of_storeys = 3 + # `make_sap_heating` doesn't expose `shower_outlets` as a kwarg; the + # Elmhurst mapper surfaces it from Summary §16. Cert lodges an + # electric shower for 000487. + epc.sap_heating.shower_outlets = ShowerOutlets( + shower_outlet=ShowerOutlet(shower_outlet_type="Electric shower"), + ) + # Summary §14 "Heat pump age: Unknown" — surfaced by the Elmhurst + # mapper as the str dual-encoding that internal_gains.py reads. + epc.sap_heating.main_heating_details[0].central_heating_pump_age_str = "Unknown" + return epc # ============================================================================