mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-08-03 05:18:22 +00:00
Merge pull request #1696 from Hestia-Homes/fix/wall-construction-enum-mapping
Offer EWI/IWI on gov-API system-built walls (wall_construction code 8)
This commit is contained in:
commit
2aba56c28e
3 changed files with 67 additions and 15 deletions
|
|
@ -8,6 +8,8 @@ The solid-wall Recommendation Generator must decide, per Property, which wall-in
|
|||
|
||||
**System-built** is keyed on `wall_construction == 6` (`WALL_SYSTEM_BUILT`; the Elmhurst `SY System build` label). This code is currently *overloaded*: `B Basement wall` also maps to 6 (`BASEMENT_WALL_CONSTRUCTION_CODE`, `mapper.py:2100`), so the generator additionally guards on `main_wall_is_basement` — a basement wall is never solid-wall-insulation-suitable and is excluded regardless of construction. Because `main_wall_is_basement` is presently derived as `wall_construction == 6`, *every* code-6 wall is treated as basement today, so the system-built branch is inert until the calculator disambiguates system-built from basement (target: MAIN `wall_construction == 6` with `main_wall_is_basement` False — tracked in Hestia-Homes/Model#1177). The strict-xfail pin `test_system_built_generator_offers_ewi_and_iwi_each_pinning_its_after` is the tripwire for that fix. Note `wall_construction == 8` is **Park home** (`PH`) on the Elmhurst path, *not* system-built — do not key system-built on 8.
|
||||
|
||||
> **Amendment (2026-07, `fix/wall-construction-enum-mapping`).** The "do not key system-built on 8" clause was wrong for the *gov-EPC API* path — the path that populates every production cert. Per the authoritative GOV.UK RdSAP `WallConstructionCode` enumeration (communitiesuk/epb-data-warehouse `.../RdSAP/UDT/SAP-Domains.xsd`), **8 = "system built"** in every schema version (17.0-21.0.1), and **10 = "park home wall"**; only the Elmhurst summary path reuses 8 for park home. Because the gov→internal translator (`mapper._api_wall_construction_code`) does *not* remap 8→6, a system-built wall reaches the generator as code 8 and was suppressed as a park home — withholding EWI/IWI from ~5.1k certs (~2.1k uninsulated main walls) in a 2026 DB sweep. The generator now keys system-built on **both** code 6 (Elmhurst/internal) and code 8 (gov API), matching `u_wall`'s existing `_GOV_API_WALL_CODE_TO_TYPE[8] = WALL_SYSTEM_BUILT`, and excludes park homes on the gov-canonical code 10. The deeper fix — normalising the whole gov enum into the internal `WALL_*` space at the mapper boundary (so `fabric_description` export, overlays, and the code-6 basement overload stop diverging) — remains open; it is higher-risk because gov 8→internal 6 collides with `BASEMENT_WALL_CONSTRUCTION_CODE` and the Model#1177 basement disambiguation.
|
||||
|
||||
| Construction | Cavity fill | IWI | EWI |
|
||||
|---|---|---|---|
|
||||
| Cavity | ✅ only | ❌ | ❌ |
|
||||
|
|
|
|||
|
|
@ -36,16 +36,35 @@ _INTERNAL_MEASURE_TYPE: Final[MeasureType] = MeasureType.INTERNAL_WALL_INSULATIO
|
|||
# RdSAP `wall_construction` codes (consistent across paths for 1-5).
|
||||
_WALL_SOLID_BRICK: Final[int] = 3
|
||||
_WALL_TIMBER_FRAME: Final[int] = 5
|
||||
# System-built (precast/no-fines concrete): `WALL_SYSTEM_BUILT` in
|
||||
# rdsap_uvalues. NB this is the Elmhurst code (`SY`); the *basement-wall* signal
|
||||
# also lodges as 6 today (`BASEMENT_WALL_CONSTRUCTION_CODE`), so system-built is
|
||||
# disambiguated from basement by `main_wall_is_basement` below — a basement wall
|
||||
# is never solid-wall-insulation-suitable regardless.
|
||||
# System-built (precast/no-fines concrete). TWO codes reach this generator for
|
||||
# the same physical wall, because the `wall_construction` code-space diverges by
|
||||
# ingestion path:
|
||||
# - Code 6 (`WALL_SYSTEM_BUILT`) — the Elmhurst `SY System build` label and the
|
||||
# calculator's internal constant. Code 6 is *overloaded* with the basement-
|
||||
# wall signal (`BASEMENT_WALL_CONSTRUCTION_CODE`), so system-built is
|
||||
# disambiguated from basement by `main_wall_is_basement` below — a basement
|
||||
# wall is never solid-wall-insulation-suitable regardless.
|
||||
# - Code 8 — what the gov-EPC API actually lodges for a system-built wall. Per
|
||||
# the authoritative GOV.UK RdSAP `WallConstructionCode` enumeration
|
||||
# (communitiesuk/epb-data-warehouse `.../SAP-Domains.xsd`), 8 = "system built"
|
||||
# in EVERY schema version 17.0 through 21.0.1. This is the canonical
|
||||
# production signal (a 2026 DB sweep finds 5.1k certs at code 8, overwhelmingly
|
||||
# "System built, ..." wall descriptions; 2.1k are uninsulated main walls).
|
||||
# `u_wall` already resolves code 8 as system-built for the U-value cascade via
|
||||
# `rdsap_uvalues._GOV_API_WALL_CODE_TO_TYPE[8] = WALL_SYSTEM_BUILT`; keying both
|
||||
# codes here brings the recommendation generator into line with that convention.
|
||||
# (Supersedes ADR-0019's "do not key system-built on 8", which read code 8 as the
|
||||
# Elmhurst park-home label — see `_WALL_PARK_HOME` below.)
|
||||
_WALL_SYSTEM_BUILT: Final[int] = 6
|
||||
# Park home (`PH`, the Elmhurst code-8 wall) — NOT system-built (ADR-0019: "do
|
||||
# not key system-built on 8"). A park home's wall is a proprietary panel system
|
||||
# our EWI/IWI model doesn't represent, so it is never solid-wall-suitable.
|
||||
_WALL_PARK_HOME: Final[int] = 8
|
||||
_WALL_SYSTEM_BUILT_GOV_API: Final[int] = 8
|
||||
# Park home — NOT system-built; its proprietary panel is a system our EWI/IWI
|
||||
# model doesn't represent, so it is never solid-wall-suitable. The gov-EPC API
|
||||
# lodges a park home as code 10 per the RdSAP `WallConstructionCode` enumeration
|
||||
# (10 = "park home wall" in every schema version). It is excluded implicitly
|
||||
# (code 10 is not a constructable key) — the earlier code-8 exclusion mistook the
|
||||
# gov "system built" code for a park home and suppressed the whole system-built
|
||||
# cohort's EWI/IWI Recommendation.
|
||||
_WALL_PARK_HOME: Final[int] = 10
|
||||
# `wall_insulation_type`: 4 = as-built / assumed (uninsulated) — the trigger.
|
||||
_WALL_AS_BUILT: Final[int] = 4
|
||||
# `wall_insulation_type` the overlay lodges: 1 = external, 3 = internal.
|
||||
|
|
@ -61,6 +80,7 @@ _SOLID_WALL_INSULATION_MM: Final[int] = 100
|
|||
_CONSTRUCTABLE_OPTIONS: Final[dict[int, tuple[MeasureType, ...]]] = {
|
||||
_WALL_SOLID_BRICK: (_EXTERNAL_MEASURE_TYPE, _INTERNAL_MEASURE_TYPE),
|
||||
_WALL_SYSTEM_BUILT: (_EXTERNAL_MEASURE_TYPE, _INTERNAL_MEASURE_TYPE),
|
||||
_WALL_SYSTEM_BUILT_GOV_API: (_EXTERNAL_MEASURE_TYPE, _INTERNAL_MEASURE_TYPE),
|
||||
_WALL_TIMBER_FRAME: (_INTERNAL_MEASURE_TYPE,),
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +169,7 @@ def recommend_solid_wall(
|
|||
if not isinstance(construction, int):
|
||||
return None # a free-text site-notes construction is not a code we key on
|
||||
if construction == _WALL_PARK_HOME:
|
||||
return None # park home (code 8) — proprietary panel, never EWI/IWI
|
||||
return None # park home (gov code 10) — proprietary panel, never EWI/IWI
|
||||
measure_types = _CONSTRUCTABLE_OPTIONS.get(construction)
|
||||
if not measure_types:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -153,13 +153,43 @@ def test_treatable_old_band_solid_wall_keeps_its_insulation_options() -> None:
|
|||
}
|
||||
|
||||
|
||||
def test_park_home_wall_yields_no_solid_wall_recommendation() -> None:
|
||||
# Arrange — a park home (wall_construction code 8) with an uninsulated
|
||||
# as-built wall. Code 8 is NOT system-built (ADR-0019); a park home's
|
||||
# proprietary panel is never EWI/IWI-suitable, so the generator excludes it.
|
||||
def test_gov_api_system_built_wall_keeps_its_insulation_options() -> None:
|
||||
# Arrange — a SYSTEM-BUILT dwelling as ingested from the gov-EPC API,
|
||||
# where `wall_construction == 8` means "system built" per the
|
||||
# GOV.UK RdSAP `WallConstructionCode` enumeration (8 = system built in
|
||||
# EVERY schema version 17.0-21.0.1). A precast/no-fines-concrete system-
|
||||
# built wall IS solid-wall-insulation-suitable (EWI + IWI), exactly like
|
||||
# `u_wall`'s `_GOV_API_WALL_CODE_TO_TYPE[8] = WALL_SYSTEM_BUILT` already
|
||||
# resolves it for the U-value cascade. Age band B, uninsulated: the
|
||||
# Wall U-Value Gate lets both Options through (0.35 << 2.0).
|
||||
baseline: EpcPropertyData = build_epc() # age band B
|
||||
main: SapBuildingPart = _part(baseline, BuildingPartIdentifier.MAIN)
|
||||
main.wall_construction = 8 # gov-API "system built"
|
||||
main.wall_insulation_type = 4 # as-built / uninsulated — the trigger
|
||||
|
||||
# Act
|
||||
recommendation: Recommendation | None = recommend_solid_wall(
|
||||
baseline, _StubProducts()
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert recommendation is not None
|
||||
assert {option.measure_type for option in recommendation.options} == {
|
||||
"external_wall_insulation",
|
||||
"internal_wall_insulation",
|
||||
}
|
||||
|
||||
|
||||
def test_gov_api_park_home_wall_yields_no_solid_wall_recommendation() -> None:
|
||||
# Arrange — a genuine park home as ingested from the gov-EPC API, where
|
||||
# `wall_construction == 10` means "park home wall" per the GOV.UK RdSAP
|
||||
# `WallConstructionCode` enumeration (10 = park home in every schema
|
||||
# version). A park home's proprietary panel is never EWI/IWI-suitable,
|
||||
# so the generator must still exclude it — the fix for system-built
|
||||
# (code 8) must not start over-offering on real park homes.
|
||||
baseline: EpcPropertyData = build_epc()
|
||||
main: SapBuildingPart = _part(baseline, BuildingPartIdentifier.MAIN)
|
||||
main.wall_construction = 8
|
||||
main.wall_construction = 10 # gov-API "park home wall"
|
||||
main.wall_insulation_type = 4 # as-built / uninsulated — the trigger
|
||||
|
||||
# Act
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue