mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 90: API mapper translates party_wall_construction → SAP10 enum
The GOV.UK API `party_wall_construction` field uses a different enum
from the regular `wall_construction` field — RdSAP 10 Table 15 (p.31
"U-values of party walls") defines 5 categories that the API encodes
as integer codes 0..5 plus a "NA" string for extensions without a
party wall. The cascade's `u_party_wall` consumes the SAP10
`wall_construction` enum directly, so passing the raw API code gave
wildly wrong U-values (API code 2 = "Cavity masonry unfilled" →
should produce U=0.5, but cascade interpreted code 2 as SAP10
WALL_STONE_SANDSTONE → 0.0 W/m²K).
Impact on cert 001479 (the only golden fixture with party=2 lodged):
Before: party_walls = 0.00 W/K (cascade applied U=0.0)
After: party_walls = 16.21 W/K (cascade applies U=0.5)
API mapper → cascade SAP delta:
Before Slice 90: +3.0752
After Slice 90: +1.5298
The remaining party-wall shortfall (16.21 vs target 17.07 W/K, -0.87
W/K) is the room_height_m +0.25 SAP convention not yet applied to
the API path — Slice 92 will close that.
Translation table (per `_API_PARTY_WALL_CONSTRUCTION_TO_SAP10`):
0 → None (no party wall present; party_wall_length=0 anyway)
1 → SAP10 code 3 (Solid Brick) → u_party_wall = 0.0
2 → SAP10 code 4 (Cavity) → u_party_wall = 0.5
3 → SAP10 code 4 (Cavity) → cascade emits 0.5 (TODO: 0.2 for
cavity filled needs cascade extension)
4 → None (Unable, house) → u_party_wall default 0.25
5 → None (Unable, flat) → TODO: spec says 0.0 for flats
Schema change: `SapBuildingPart.party_wall_construction` is now
`Optional[Union[int, str]]` (was `Union[int, str]`) — the "0 sentinel
for Unable" convention was already in cohort hand-builts but the type
forbade the cleaner `None` representation. To preserve the dataclass
"no-default after default" rule, `sap_floor_dimensions` gets a
`field(default_factory=list)`.
Translation applied across all 6 from_rdsap_schema_* mappers + the
flagship `from_rdsap_schema_21_0_1` used by 001479.
Pyright: mapper.py 35 → 33 (cleared 7 cohort party_wall type errors
that were pre-existing, balanced against the schema change). Cohort
cascade pins remain GREEN (66 of 66); no new test regression.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
006e9842c9
commit
fbbdca49ca
2 changed files with 70 additions and 12 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import re
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import date
|
||||
from enum import Enum
|
||||
from typing import Final, List, Optional, Union
|
||||
|
|
@ -399,12 +399,12 @@ class SapBuildingPart:
|
|||
int, str
|
||||
] # int from API, str from site notes TODO: make enum/mapping?
|
||||
wall_thickness_measured: bool
|
||||
party_wall_construction: Union[int, str] # TODO: make enum/mapping?
|
||||
party_wall_construction: Optional[Union[int, str]] = (
|
||||
None # TODO: make enum/mapping?
|
||||
)
|
||||
|
||||
# Floor
|
||||
sap_floor_dimensions: List[
|
||||
SapFloorDimension
|
||||
] # Not included in site notes; should this be optional?
|
||||
sap_floor_dimensions: List[SapFloorDimension] = field(default_factory=list)
|
||||
|
||||
# Optional
|
||||
building_part_number: Optional[int] = (
|
||||
|
|
|
|||
|
|
@ -480,7 +480,9 @@ class EpcPropertyDataMapper:
|
|||
wall_construction=bp.wall_construction,
|
||||
wall_insulation_type=bp.wall_insulation_type,
|
||||
wall_thickness_measured=bp.wall_thickness_measured == "Y",
|
||||
party_wall_construction=bp.party_wall_construction,
|
||||
party_wall_construction=_api_party_wall_construction_int(
|
||||
bp.party_wall_construction
|
||||
),
|
||||
sap_floor_dimensions=[
|
||||
SapFloorDimension(
|
||||
room_height_m=fd.room_height.value,
|
||||
|
|
@ -621,7 +623,9 @@ class EpcPropertyDataMapper:
|
|||
wall_construction=bp.wall_construction,
|
||||
wall_insulation_type=bp.wall_insulation_type,
|
||||
wall_thickness_measured=bp.wall_thickness_measured == "Y",
|
||||
party_wall_construction=bp.party_wall_construction,
|
||||
party_wall_construction=_api_party_wall_construction_int(
|
||||
bp.party_wall_construction
|
||||
),
|
||||
sap_floor_dimensions=[
|
||||
SapFloorDimension(
|
||||
room_height_m=_measurement_value(fd.room_height),
|
||||
|
|
@ -758,7 +762,9 @@ class EpcPropertyDataMapper:
|
|||
wall_construction=bp.wall_construction,
|
||||
wall_insulation_type=bp.wall_insulation_type,
|
||||
wall_thickness_measured=bp.wall_thickness_measured == "Y",
|
||||
party_wall_construction=bp.party_wall_construction,
|
||||
party_wall_construction=_api_party_wall_construction_int(
|
||||
bp.party_wall_construction
|
||||
),
|
||||
sap_floor_dimensions=[
|
||||
SapFloorDimension(
|
||||
room_height_m=_measurement_value(fd.room_height),
|
||||
|
|
@ -904,7 +910,9 @@ class EpcPropertyDataMapper:
|
|||
wall_construction=bp.wall_construction,
|
||||
wall_insulation_type=bp.wall_insulation_type,
|
||||
wall_thickness_measured=bp.wall_thickness_measured == "Y",
|
||||
party_wall_construction=bp.party_wall_construction,
|
||||
party_wall_construction=_api_party_wall_construction_int(
|
||||
bp.party_wall_construction
|
||||
),
|
||||
sap_floor_dimensions=[
|
||||
SapFloorDimension(
|
||||
room_height_m=_measurement_value(fd.room_height),
|
||||
|
|
@ -1067,7 +1075,9 @@ class EpcPropertyDataMapper:
|
|||
wall_construction=bp.wall_construction,
|
||||
wall_insulation_type=bp.wall_insulation_type,
|
||||
wall_thickness_measured=bp.wall_thickness_measured == "Y",
|
||||
party_wall_construction=bp.party_wall_construction,
|
||||
party_wall_construction=_api_party_wall_construction_int(
|
||||
bp.party_wall_construction
|
||||
),
|
||||
sap_floor_dimensions=[
|
||||
SapFloorDimension(
|
||||
room_height_m=_measurement_value(fd.room_height),
|
||||
|
|
@ -1257,7 +1267,9 @@ class EpcPropertyDataMapper:
|
|||
wall_construction=bp.wall_construction,
|
||||
wall_insulation_type=bp.wall_insulation_type,
|
||||
wall_thickness_measured=bp.wall_thickness_measured == "Y",
|
||||
party_wall_construction=bp.party_wall_construction,
|
||||
party_wall_construction=_api_party_wall_construction_int(
|
||||
bp.party_wall_construction
|
||||
),
|
||||
sap_floor_dimensions=[
|
||||
SapFloorDimension(
|
||||
room_height_m=_measurement_value(fd.room_height),
|
||||
|
|
@ -1516,7 +1528,9 @@ class EpcPropertyDataMapper:
|
|||
wall_construction=bp.wall_construction,
|
||||
wall_insulation_type=bp.wall_insulation_type,
|
||||
wall_thickness_measured=bp.wall_thickness_measured == "Y",
|
||||
party_wall_construction=bp.party_wall_construction,
|
||||
party_wall_construction=_api_party_wall_construction_int(
|
||||
bp.party_wall_construction
|
||||
),
|
||||
sap_floor_dimensions=[
|
||||
SapFloorDimension(
|
||||
room_height_m=_measurement_value(fd.room_height),
|
||||
|
|
@ -1889,6 +1903,50 @@ def _elmhurst_party_wall_construction_int(coded: str) -> Optional[int]:
|
|||
return _ELMHURST_PARTY_WALL_CODE_TO_SAP10.get(_leading_code(coded))
|
||||
|
||||
|
||||
# GOV.UK API party_wall_construction enum → SAP10 wall_construction
|
||||
# integer (the domain `u_party_wall` consumes). The API uses a different
|
||||
# enum from the regular wall_construction field — RdSAP 10 Table 15
|
||||
# (p.31 "U-values of party walls") defines 5 categories, mapped to the
|
||||
# nearest SAP10 wall_construction code that `u_party_wall` resolves to
|
||||
# the spec U-value:
|
||||
# 0 = "Not applicable" / no party wall (detached etc.) → cascade
|
||||
# returns 0.25 by default but party_wall_length is 0 so the
|
||||
# contribution is 0 regardless.
|
||||
# 1 = "Solid masonry / timber frame / system built" → SAP10 code 3
|
||||
# (WALL_SOLID_BRICK) → u_party_wall = 0.0 (Table 15 row 1).
|
||||
# 2 = "Cavity masonry unfilled" → SAP10 code 4 (WALL_CAVITY) →
|
||||
# u_party_wall = 0.5 (Table 15 row 2).
|
||||
# 3 = "Cavity masonry filled" → spec U=0.2 (Table 15 row 3) — not
|
||||
# yet representable; the cascade only emits 0.0 / 0.5 / 0.25 from
|
||||
# the current u_party_wall, so this code rounds up to the
|
||||
# conservative 0.5 (matches the cavity-unfilled W/K).
|
||||
# 4 = "Unable to determine, house or bungalow" → None (cascade
|
||||
# default 0.25).
|
||||
# 5 = "Unable to determine, flat or maisonette" → cascade should
|
||||
# return 0.0 per Table 15 footnote * — not yet handled; leave as
|
||||
# None for now and revisit when a flat fixture surfaces.
|
||||
# The 'NA' string (commonly lodged on extensions that don't carry a
|
||||
# party wall) maps to None.
|
||||
_API_PARTY_WALL_CONSTRUCTION_TO_SAP10: Dict[int, Optional[int]] = {
|
||||
0: None,
|
||||
1: 3, # Solid masonry / timber / system → U=0.0
|
||||
2: 4, # Cavity masonry unfilled → U=0.5
|
||||
3: 4, # Cavity masonry filled (cascade falls through to 0.5 — TODO)
|
||||
4: None, # Unable to determine, house — cascade default 0.25
|
||||
5: None, # Unable to determine, flat — TODO: u_party_wall=0.0 path
|
||||
}
|
||||
|
||||
|
||||
def _api_party_wall_construction_int(value: Union[int, str, None]) -> Optional[int]:
|
||||
"""Translate the GOV.UK API `party_wall_construction` integer code
|
||||
(or 'NA' string) to the SAP10 wall_construction integer the cascade
|
||||
consumes. See `_API_PARTY_WALL_CONSTRUCTION_TO_SAP10` for the
|
||||
enum semantics (RdSAP 10 Table 15)."""
|
||||
if value is None or isinstance(value, str):
|
||||
return None
|
||||
return _API_PARTY_WALL_CONSTRUCTION_TO_SAP10.get(value)
|
||||
|
||||
|
||||
def _elmhurst_wall_insulation_int(coded: str) -> Optional[int]:
|
||||
"""Map an Elmhurst wall-insulation-type string ('A As Built') to
|
||||
the SAP10 integer enum (4 = as-built). Returns None on unknown
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue