S0380.236: extension party-wall type read independently of "As Main Wall"

RdSAP 10 §3.3: "As Main Wall: Yes" makes an extension inherit the main
dwelling's external wall CONSTRUCTION only — the party wall type is
lodged separately per building part in the Summary §7 block and may
differ. `_extract_extensions` was copying `main_walls.party_wall_type`
into the inherited WallDetails, so every extension reused the main's
party wall U.

On the double_glazing fixture (Summary_001431) the Main lodges party
"CU Cavity masonry unfilled" (SAP10 wall_construction 4 → u_party_wall
0.5) but the 1st Extension lodges "U Unable to determine" (→ 0 → RdSAP
default 0.25). Pre-fix both building parts used 0.5, inflating worksheet
(32) party-wall heat loss by 6.56 W/K (Ext1 26.25 m² × 0.25). After the
fix worksheet (32) is exact: ours 32.573 vs worksheet 32.5725.

Now reads the extension's own "Party Wall Type" from its §7 chunk,
falling back to the main's only when the extension lodges none. Adds a
fixture + test asserting Main=4 / Ext=0 with distinct u_party_wall.
Suite 2413 pass; no cohort regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-05 09:19:43 +00:00
parent 3e45b7fa3b
commit ea35bed24c
3 changed files with 44 additions and 1 deletions

View file

@ -670,12 +670,24 @@ class ElmhurstSiteNotesExtractor:
# even when the main wall fields are inherited; merge
# them into the inherited WallDetails so the bp carries
# them through to its SapBuildingPart.
#
# "As Main Wall: Yes" inherits the EXTERNAL wall
# construction only — the PARTY WALL TYPE is lodged
# separately in the extension's §7 block and may differ
# (cert 001431: Main "CU Cavity masonry unfilled" U=0.5,
# 1st Extension "U Unable to determine" → RdSAP default
# U=0.25). Read the extension's own party wall type when
# present; fall back to the main's only when absent.
ext_party_wall_type = (
self._local_str(wall_lines, "Party Wall Type")
or main_walls.party_wall_type
)
walls = WallDetails(
wall_type=main_walls.wall_type,
insulation=main_walls.insulation,
thickness_unknown=main_walls.thickness_unknown,
u_value_known=main_walls.u_value_known,
party_wall_type=main_walls.party_wall_type,
party_wall_type=ext_party_wall_type,
thickness_mm=main_walls.thickness_mm,
insulation_thickness_mm=main_walls.insulation_thickness_mm,
alternative_walls=self._alternative_walls_from_lines(wall_lines),

View file

@ -1502,6 +1502,37 @@ def test_elmhurst_glazing_label_full_coverage_per_sap10_table_6b() -> None:
)
def test_extension_party_wall_type_read_independently_of_as_main_wall() -> None:
# Arrange — RdSAP 10 §3.3: "As Main Wall: Yes" inherits only the
# external wall CONSTRUCTION; the party wall type is lodged
# separately per building part and may differ. The double_glazing
# fixture (Summary_001431) lodges Main party "CU Cavity masonry
# unfilled" (SAP10 wall_construction 4 → u_party_wall 0.5) but the
# 1st Extension party "U Unable to determine" (→ wall_construction 0
# → RdSAP default u_party_wall 0.25), even though the extension is
# "As Main Wall: Yes". Pre-fix the extension inherited the Main's
# party type (both 0.5), inflating worksheet (32) party heat loss.
pages = _summary_pdf_to_textract_style_pages(
_FIXTURES / "Summary_001431_double_glazing.pdf"
)
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
# Act
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
# Assert — Main BP keeps cavity-unfilled (4); the extension BP gets
# the "Unable to determine" sentinel (0), a distinct party wall U.
party_codes = [
bp.party_wall_construction for bp in epc.sap_building_parts
]
assert party_codes == [4, 0], (
f"expected Main=4 (CU, U=0.5) + Ext=0 (Unable, U=0.25), got {party_codes}"
)
# The two map to different SAP party-wall U-values.
assert abs(u_party_wall(4) - 0.5) <= 1e-9
assert abs(u_party_wall(0) - 0.25) <= 1e-9
def test_summary_mapper_raises_on_unmapped_glazing_type_label() -> None:
# Arrange — same strict-coverage gate as the cylinder-size helper
# (Slice S0380.15 + S0380.16): silently routing an unknown glazing