Slice 82: bulk-update cohort 000490 hand-built for Cat A diff parity

Closes 31 of 32 mapper-vs-hand-built load-bearing divergences by
populating fields the Elmhurst mapper extracts from Summary_000490.
pdf but the original hand-built left at their `make_minimal_sap10_
epc` / dataclass-default values. Every change is cascade-equivalent —
all 11 `_FIXTURE_PINS["000490"]` SapResult pins remain GREEN against
worksheet `SAP value 57.3979`.

000490-specific deltas vs prior cohort certs:

- `dwelling_type="End-Terrace house"`, `built_form="End-Terrace"` —
  first end-terrace fixture (vs Mid-Terrace / Enclosed Mid-Terrace
  on the other 4 cohort certs); sheltered_sides=1 is already set on
  the existing SapVentilation block.
- `number_of_storeys=2` — 000490 has no room-in-roof (2-storey main
  + 2-storey extension), so dwelling height is 2 (vs 3 for the RR
  cohort certs).
- `number_baths=1` on sap_heating — mapper extracts 1 from Summary
  §16; cascade-equivalent (Appendix J §1a defaults to 1 if absent).
- `wall_thickness_measured=True` on **both** bps (Summary §7 lodges
  measured Wall Thickness 400 mm).

Standard Cat A additions (per Slice 72/75/78 pattern): floor
descriptive fields per bp, roof_insulation_location, 6 ventilation
zero counts, draught_lobby=True, pressure_test="Not available",
top-level descriptive strings + booleans + extensions_count=1,
blocked_chimneys_count=0, shower_outlets=Non-electric shower,
central_heating_pump_age_str="Unknown".

Diff count: 32 → **1**. Remaining diff is `sap_windows: LEN 6 vs 3` —
closes via Slice 83.

Pyright net-zero on the touched fixture.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-25 18:09:45 +00:00
parent 3079153113
commit 3d315a0d90

View file

@ -30,6 +30,8 @@ from datatypes.epc.domain.epc_property_data import (
SapFloorDimension,
SapVentilation,
SapWindow,
ShowerOutlet,
ShowerOutlets,
)
from domain.ml.tests._fixtures import (
make_main_heating_detail,
@ -51,13 +53,23 @@ _WC_CAVITY = 4
def build_epc() -> EpcPropertyData:
"""EpcPropertyData mirroring the Elmhurst 000490 inputs."""
"""EpcPropertyData mirroring the Elmhurst 000490 inputs.
Field-level parity with `from_elmhurst_site_notes(Summary_000490.
pdf)` for the load-bearing field allow-list cascade-equivalent
encoding-only fields (descriptive floor/roof strings, ventilation
zero counts) are populated explicitly to eliminate noise without
altering the SAP cascade output (the 11 1e-4 pins in
`test_e2e_elmhurst_sap_score.py` remain GREEN against the worksheet
PDF's `SAP value 57.3979`).
"""
main = SapBuildingPart(
identifier=BuildingPartIdentifier.MAIN,
construction_age_band="B",
wall_construction=_WC_CAVITY,
wall_insulation_type=4,
wall_thickness_measured=False,
# Summary §7 lodges Wall Thickness 400 mm explicitly; matches mapper.
wall_thickness_measured=True,
party_wall_construction=0, # "U Unable to determine" → U=0.25
roof_insulation_thickness=300, # Table 16 "300 mm joists" → U=0.14
sap_floor_dimensions=[
@ -75,13 +87,20 @@ def build_epc() -> EpcPropertyData:
),
],
wall_thickness_mm=400,
# Mapper-extracted descriptive fields (cascade reads int codes
# on SapFloorDimension; these carry the lodged Summary text).
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,
wall_thickness_measured=True,
party_wall_construction=0,
roof_insulation_thickness=300,
sap_floor_dimensions=[
@ -104,6 +123,11 @@ def build_epc() -> EpcPropertyData:
),
],
wall_thickness_mm=400,
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",
)
# door_count=2 matches the worksheet's 3.70 m² of total door area:
# Elmhurst lodges 1 oversized 3.7 m × 1.0 m door, our cascade uses the
@ -113,7 +137,7 @@ def build_epc() -> EpcPropertyData:
# 28kW, 2004-2012, winter eff 88.2%, summer eff 79.6%). Lodging it on the
# MainHeatingDetail routes `cert_to_inputs` into the PCDB precedence
# cascade rather than the Table 4a category-2 default (80%).
return make_minimal_sap10_epc(
epc = make_minimal_sap10_epc(
total_floor_area_m2=66.06,
country_code="ENG",
postcode="bd19 3TF",
@ -124,6 +148,11 @@ def build_epc() -> EpcPropertyData:
low_energy_fixed_lighting_bulbs_count=8,
sap_windows=list(SECTION_6_VERTICAL_WINDOWS),
percent_draughtproofed=100,
extensions_count=1,
blocked_chimneys_count=0,
dwelling_type="End-Terrace house",
built_form="End-Terrace",
property_type="House",
sap_ventilation=SapVentilation(
extract_fans_count=2,
sheltered_sides=1,
@ -131,6 +160,14 @@ def build_epc() -> EpcPropertyData:
# the worksheet, same pattern as 000480.
has_suspended_timber_floor=False,
has_draught_lobby=False,
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(
main_heating_details=[
@ -140,8 +177,21 @@ def build_epc() -> EpcPropertyData:
),
],
secondary_heating_type=691,
# 000490 cert: number_baths=1 (mapper extracts this). Cascade-
# equivalent to leaving None — Appendix J §1a defaults to 1
# when nothing is lodged.
number_baths=1,
),
)
# Top-level cert-lodgement booleans the mapper surfaces.
epc.has_conservatory = False
epc.any_unheated_rooms = False
epc.number_of_storeys = 2
epc.sap_heating.shower_outlets = ShowerOutlets(
shower_outlet=ShowerOutlet(shower_outlet_type="Non-electric shower"),
)
epc.sap_heating.main_heating_details[0].central_heating_pump_age_str = "Unknown"
return epc
# ============================================================================