Slice 78: bulk-update cohort 000487 hand-built for Cat A diff parity

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 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-25 18:00:14 +00:00
parent 4b74281412
commit b8f35af902

View file

@ -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 <flue>" 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
# ============================================================================