mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(elmhurst-mapper): carry §7 alternative-wall "Sheltered Wall" flag
The Elmhurst Summary §7 lodges "Alternative Wall N Sheltered Wall: Yes" for
a sub-area adjacent to an unheated buffer (e.g. a flat's corridor wall),
but the extractor dropped it and _map_elmhurst_alternative_wall never set
SapAlternativeWall.is_sheltered — so the cascade billed the sub-area at its
full exposed U instead of the RdSAP 10 Table 4 (p.22) sheltered U =
1/(1/U + 0.5).
The calculator already applies is_sheltered (_alt_wall_w_per_k) and the
gov-API path already wires sheltered_wall=="Y"; this brings the Elmhurst
front-end to parity. Three-part change: AlternativeWall.sheltered field +
_alternative_walls_from_lines parse ("Alternative Wall N Sheltered Wall") +
_map_elmhurst_alternative_wall is_sheltered=a.sheltered.
Surfaced by simulated case 34 (cert 001431 electric-storage flat): the
6.02 m² corridor wall billed at full U=1.50 (9.03 W/K) instead of the
sheltered 0.86 (5.18 W/K) — +3.85 W/K, -1.61 SAP. Post-fix the alt wall
matches the worksheet's (29a) 5.177 and case 34 closes from -1.61 to -0.30
(remaining residual is a separate window/wall area-allocation thread).
Elmhurst-mapper only: API SAP gauge unchanged (57.6% within 0.5); worksheet
harness 47/47 unaffected; regression gate green (3 pre-existing fails
unrelated); pyright net-zero.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f3dcd7b43e
commit
48b36d3d7e
4 changed files with 47 additions and 0 deletions
|
|
@ -340,6 +340,12 @@ class ElmhurstSiteNotesExtractor:
|
|||
dry_lined=self._local_bool(
|
||||
lines, f"Alternative Wall {n} Dry-lining"
|
||||
),
|
||||
# RdSAP 10 Table 4 (p.22): a sheltered alt sub-area
|
||||
# (adjacent to an unheated buffer, e.g. a flat corridor
|
||||
# wall) adds R=0.5 m²K/W → U = 1/(1/U + 0.5).
|
||||
sheltered=self._local_bool(
|
||||
lines, f"Alternative Wall {n} Sheltered Wall"
|
||||
),
|
||||
))
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -45,8 +45,10 @@ from datatypes.epc.domain.mapper import (
|
|||
_elmhurst_dwelling_type, # pyright: ignore[reportPrivateUsage]
|
||||
_elmhurst_glazing_type_code, # pyright: ignore[reportPrivateUsage]
|
||||
_elmhurst_immersion_type_code, # pyright: ignore[reportPrivateUsage]
|
||||
_map_elmhurst_alternative_wall, # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
from datatypes.epc.surveys.elmhurst_site_notes import (
|
||||
AlternativeWall as ElmhurstAlternativeWall,
|
||||
FloorDetails as ElmhurstFloorDetails,
|
||||
RoofDetails as ElmhurstRoofDetails,
|
||||
)
|
||||
|
|
@ -1545,6 +1547,35 @@ def test_summary_mapper_raises_on_unmapped_cylinder_insulation_label() -> None:
|
|||
assert excinfo.value.value == "Polyester wool"
|
||||
|
||||
|
||||
def test_map_elmhurst_alternative_wall_carries_sheltered_flag() -> None:
|
||||
# Arrange — Elmhurst Summary §7 lodges "Alternative Wall N Sheltered
|
||||
# Wall: Yes" for a sub-area adjacent to an unheated buffer (e.g. a flat's
|
||||
# corridor wall). RdSAP 10 Table 4 (p.22) gives a sheltered wall an added
|
||||
# R=0.5 m²K/W → U=1/(1/U+0.5). The cascade applies this via
|
||||
# SapAlternativeWall.is_sheltered (the API path already wires it); the
|
||||
# Elmhurst path must surface it too. Surfaced by simulated case 34 (cert
|
||||
# 001431 flat: 6.02 m² corridor wall billed at full U=1.50 instead of
|
||||
# the sheltered 0.86 → +3.85 W/K, -1.61 SAP).
|
||||
sheltered = ElmhurstAlternativeWall(
|
||||
area_m2=12.5, wall_type="CA Cavity", insulation="A As Built",
|
||||
thickness_unknown=False, thickness_mm=250, u_value_known=False,
|
||||
dry_lined=False, sheltered=True,
|
||||
)
|
||||
plain = ElmhurstAlternativeWall(
|
||||
area_m2=12.5, wall_type="CA Cavity", insulation="A As Built",
|
||||
thickness_unknown=False, thickness_mm=250, u_value_known=False,
|
||||
dry_lined=False, sheltered=False,
|
||||
)
|
||||
|
||||
# Act
|
||||
mapped_sheltered = _map_elmhurst_alternative_wall(sheltered)
|
||||
mapped_plain = _map_elmhurst_alternative_wall(plain)
|
||||
|
||||
# Assert
|
||||
assert mapped_sheltered.is_sheltered is True
|
||||
assert mapped_plain.is_sheltered is False
|
||||
|
||||
|
||||
def test_elmhurst_dwelling_type_single_storey_flat_with_exposed_roof_is_top_floor() -> None:
|
||||
# Arrange — a single-storey flat exposed BOTH top (external pitched
|
||||
# roof, access to loft) AND bottom (floor over partially-heated space,
|
||||
|
|
|
|||
|
|
@ -3864,6 +3864,11 @@ def _map_elmhurst_alternative_wall(
|
|||
wall_insulation_thickness=None,
|
||||
wall_thickness_mm=measured_thickness_mm,
|
||||
is_basement=_elmhurst_wall_is_basement(a.wall_type),
|
||||
# Summary §7 "Alternative Wall N Sheltered Wall: Yes" → RdSAP 10
|
||||
# Table 4 (p.22) sheltered U = 1/(1/U + 0.5), applied by the
|
||||
# cascade's `_alt_wall_w_per_k`. Mirror of the API path's
|
||||
# `sheltered_wall == "Y"` wiring.
|
||||
is_sheltered=a.sheltered,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,11 @@ class AlternativeWall:
|
|||
thickness_mm: Optional[int]
|
||||
u_value_known: bool
|
||||
dry_lined: bool = False
|
||||
# Summary §7 "Alternative Wall N Sheltered Wall: Yes/No". RdSAP 10
|
||||
# Table 4 (p.22): a sheltered sub-area (adjacent to an unheated buffer
|
||||
# such as a flat's corridor) carries an added R=0.5 m²K/W → U =
|
||||
# 1/(1/U + 0.5). Drives SapAlternativeWall.is_sheltered.
|
||||
sheltered: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue