mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.85: Curtain Wall §5.18 dispatch closes BP[2] Ext2 cascade gap
RdSAP 10 §5.18 (PDF p.48) "Curtain wall - U-value and other parameters":
"If documentary evidence is available, use calculated U-value of the
whole curtain wall. Otherwise for the purpose of RdSAP, U= 2.0 W/m²K
for pre-2023 curtain walls, And for post-2023 (2024 in Scotland)
U-values as for windows given in Notes below Table 24."
Table 24 row "Double or triple glazed England/Wales: 2022 or later"
PVC/wood column = 1.4 W/m²K. Whole-wall curtain walls use Frame
Factor=1 per the §5.18 closer.
Pre-S0380.85 `WALL_CURTAIN=9` was defined at rdsap_uvalues.py:116 but
NOT included in `known_types`, so `u_wall(construction=9)` fell through
to `_DEFAULT_WALL_BY_AGE.get(band, WALL_CAVITY)` → cavity table at age
H = 0.60. Cert 000565 BP[2] Ext2 lodges `Type: CW Curtain Wall` +
`Curtain Wall Age: Post 2023` per Summary PDF §7; worksheet pins U=1.40
(matching the §5.18 Post-2023 PVC/wood row). Cascade under-counted
walls by Δ U=0.80 × area = −112.2 W/K on this BP — 70% of the
post-S0380.84 BP main-wall residual (−161 W/K total).
§5.18 keys the curtain-wall U-value on the per-BP installation age,
NOT on the dwelling-wide `construction_age_band` — cert 000565 is
age H (1991-1995) but the curtain wall itself was installed
Post-2023. Plumb a new optional field through the extractor → datatype
→ mapper → cascade so the §5.18 dispatch sees it.
Files touched (5-layer slice span):
- backend/documents_parser/elmhurst_extractor.py:
`_wall_details_from_lines` reads "Curtain Wall Age" via
`_local_val` so absent lines stay None (not "").
- datatypes/epc/surveys/elmhurst_site_notes.py:WallDetails:
`curtain_wall_age: Optional[str] = None` field added.
- datatypes/epc/domain/epc_property_data.py:SapBuildingPart:
`curtain_wall_age: Optional[str] = None` field added.
- datatypes/epc/domain/mapper.py:_map_elmhurst_building_part:
threads `walls.curtain_wall_age` onto SapBuildingPart.
- domain/sap10_ml/rdsap_uvalues.py:
new `_u_curtain_wall(curtain_wall_age)` helper + WALL_CURTAIN
dispatch in `u_wall` before the `known_types` lookup.
"Post 2023" / "Post-2023" → 1.4; everything else (incl. None)
→ 2.0 per §5.18 fallback.
- domain/sap10_calculator/worksheet/heat_transmission.py:
passes `curtain_wall_age=part.curtain_wall_age` to `u_wall`
on the main-wall path. (Alt-wall path unchanged — cert 000565
lodges CW only as a main wall, never as an alt sub-area; alt
coverage is a follow-up slice if a future cert exercises it.)
Tests (6 new, AAA-structure):
- 3 in domain/sap10_ml/tests/test_rdsap_uvalues.py — `u_wall` direct
unit tests for Post 2023 (1.4), Pre 2023 (2.0), and absent
lodging fallback (2.0).
- 3 in backend/documents_parser/tests/test_summary_pdf_mapper_chain
.py — extractor pin (BP[2] Ext2 surfaces "Post 2023", non-CW BPs
stay None), mapper pin (curtain_wall_age threaded to BP[2]
SapBuildingPart), cascade pin (`heat_transmission_from_cert`
walls subtotal ≥ 540 W/K — pre-S0380.85 was 443).
Cert 000565 cascade walls: 443 → 555.93 W/K (worksheet 604.07; 70%
closer). Test baseline: 558 pass (was 555 + 3 new) + 9 expected
`test_sap_result_pin[000565-*]` fails unchanged.
Per [[feedback-verify-handover-claims]]: the post-S0380.84 handover
predicted SH residual would close +2591 → ~+800 kWh after this slice,
but the cascade is actually OVER-counting SH despite walls being
UNDER-counted. Closing the wall under-count makes the SH residual
*larger* (+2591 → +6348). The wall fix is spec-correct; the SH
over-count is a separate channel that surfaces more sharply now. Per
[[feedback-spec-citation-in-commits]] + [[feedback-spec-floor-skepticism]]
+ the S0380.84 precedent, ship the spec-correct change and document
the surfaced gap for the next slice rather than reverting to the
compensating-bugs state.
Pyright net-zero on every touched file (existing pre-existing errors
unchanged). Cohort + golden + cert 9501 unaffected — curtain_wall_age
defaults to None on those certs and `u_wall` ignores it unless
`construction == WALL_CURTAIN`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
b0fef688da
commit
647c1aad0e
8 changed files with 268 additions and 0 deletions
|
|
@ -268,6 +268,14 @@ class ElmhurstSiteNotesExtractor:
|
|||
thickness_mm=thickness_mm,
|
||||
insulation_thickness_mm=insulation_thickness_mm,
|
||||
alternative_walls=self._alternative_walls_from_lines(lines),
|
||||
# Summary §7 lodges the per-BP "Curtain Wall Age" line only
|
||||
# when `Type: CW Curtain Wall`. Per RdSAP 10 §5.18 (PDF
|
||||
# p.48) this drives the curtain-wall U-value (Post 2023 →
|
||||
# 1.4; Pre 2023 → 2.0) independent of the dwelling-wide
|
||||
# age band. Use `_local_val` (Optional[str]) so absent
|
||||
# lines surface as None, not the empty-string sentinel
|
||||
# `_local_str` returns.
|
||||
curtain_wall_age=self._local_val(lines, "Curtain Wall Age"),
|
||||
)
|
||||
|
||||
def _alternative_walls_from_lines(self, lines: List[str]) -> List[AlternativeWall]:
|
||||
|
|
|
|||
|
|
@ -1340,6 +1340,126 @@ def test_summary_000565_ext3_ext4_wall_constructions_route_to_basement_code_6()
|
|||
assert epc.sap_building_parts[4].main_wall_is_basement is True
|
||||
|
||||
|
||||
def test_summary_000565_extractor_finds_curtain_wall_age_post_2023_on_bp_2_ext2() -> None:
|
||||
"""Summary §7 per-BP Wall block carries a `Curtain Wall Age` line
|
||||
when `Type: CW Curtain Wall` is lodged. Cert 000565 Ext2 (BP[2])
|
||||
is the cohort fixture: it lodges
|
||||
|
||||
Type CW Curtain Wall
|
||||
Curtain Wall Age Post 2023
|
||||
U-value Known No
|
||||
|
||||
Per RdSAP 10 §5.18 (PDF p.48), the U-value of a curtain wall is
|
||||
keyed on the per-BP `Curtain Wall Age` (Post 2023 → Table 24
|
||||
window row; Pre 2023 → 2.0 W/m²K), NOT on the dwelling-wide
|
||||
`construction_age_band`. The extractor must surface this field
|
||||
so the mapper + cascade can dispatch correctly. Pre-S0380.85 the
|
||||
line was silently dropped and `wall_construction=9` fell through
|
||||
to the cavity-default Table 6 row.
|
||||
|
||||
Pure extractor data-completion step — downstream cascade impact
|
||||
lands when the mapper threads the new field through and `u_wall`
|
||||
grows a Curtain Wall branch (follow-up sub-step in the same slice).
|
||||
"""
|
||||
# Arrange
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000565_PDF)
|
||||
|
||||
# Act
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
|
||||
# Assert — BP[2] is Ext2 (index 1 in `extensions`).
|
||||
ext2_walls = site_notes.extensions[1].walls
|
||||
assert ext2_walls.wall_type == "CW Curtain Wall", (
|
||||
f"Ext2 wall_type = {ext2_walls.wall_type!r}; expected 'CW Curtain Wall'"
|
||||
)
|
||||
assert ext2_walls.curtain_wall_age == "Post 2023", (
|
||||
f"Ext2 curtain_wall_age = {ext2_walls.curtain_wall_age!r}; "
|
||||
f"expected 'Post 2023'"
|
||||
)
|
||||
# Negative case — BPs without Curtain Wall don't have a Curtain
|
||||
# Wall Age line; the field must be None (not the empty-string
|
||||
# sentinel `_local_str` returns).
|
||||
main_walls = site_notes.walls
|
||||
assert main_walls.curtain_wall_age is None, (
|
||||
f"Main wall (non-CW) curtain_wall_age = "
|
||||
f"{main_walls.curtain_wall_age!r}; expected None"
|
||||
)
|
||||
|
||||
|
||||
def test_summary_000565_mapper_threads_curtain_wall_age_post_2023_to_bp_2_sap_building_part() -> None:
|
||||
"""The Elmhurst mapper builds a `SapBuildingPart` per BP from the
|
||||
extracted `WallDetails`. `curtain_wall_age` must be threaded
|
||||
through so the heat-transmission cascade can dispatch on it (per
|
||||
[[reference-unmapped-api-code]] strict-plumbing pattern). Cert
|
||||
000565 BP[2] Ext2 is the fixture: `wall_construction=9`
|
||||
(WALL_CURTAIN) + `curtain_wall_age="Post 2023"`.
|
||||
|
||||
Per RdSAP 10 §5.18 + §1.5: a curtain wall can be a main wall, an
|
||||
alt wall, or absorbed into the prevailing wall when <10% area.
|
||||
This slice scopes to the main-wall path (cert 000565 lodges CW
|
||||
only as the BP[2] main wall, never as an alt sub-area).
|
||||
"""
|
||||
# Arrange
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000565_PDF)
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
|
||||
# Act
|
||||
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||
|
||||
# Assert
|
||||
bp_2 = epc.sap_building_parts[2]
|
||||
assert bp_2.wall_construction == 9, (
|
||||
f"BP[2] wall_construction = {bp_2.wall_construction!r}; "
|
||||
f"expected 9 (WALL_CURTAIN)"
|
||||
)
|
||||
assert bp_2.curtain_wall_age == "Post 2023", (
|
||||
f"BP[2] curtain_wall_age = {bp_2.curtain_wall_age!r}; "
|
||||
f"expected 'Post 2023'"
|
||||
)
|
||||
# Non-CW BPs preserve curtain_wall_age=None (no per-BP signal).
|
||||
assert epc.sap_building_parts[0].curtain_wall_age is None
|
||||
assert epc.sap_building_parts[1].curtain_wall_age is None
|
||||
|
||||
|
||||
def test_summary_000565_ext2_curtain_wall_routes_to_u_value_1p4_per_rdsap_10_section_5_18() -> None:
|
||||
"""End-to-end cascade pin: with `curtain_wall_age="Post 2023"` plumbed
|
||||
through extractor + mapper + `u_wall` `WALL_CURTAIN` branch, the
|
||||
`heat_transmission_from_cert` walls subtotal on cert 000565 must
|
||||
reflect the §5.18 Curtain Wall U=1.4 W/m²K on BP[2] Ext2.
|
||||
|
||||
Pre-S0380.85: BP[2] cascade U=0.60 (Cavity default, age H), Δ −0.80
|
||||
W/m²K vs worksheet U=1.40. The BP[2] Ext2 gross wall area on cert
|
||||
000565 multiplied by this U-delta accounts for the documented
|
||||
−112.2 W/K contribution to the walls subtotal residual.
|
||||
|
||||
Asserts the cascade walls subtotal moves materially toward the
|
||||
worksheet target 604.07 W/K (from pre-S0380.85's 443 W/K). The
|
||||
remaining ~50 W/K gap is the BP[0] Main alt1 thin-wall stone
|
||||
granite cascade gap — out of scope for this slice; closes in
|
||||
follow-up S0380.86.
|
||||
"""
|
||||
# Arrange
|
||||
from domain.sap10_calculator.worksheet.heat_transmission import (
|
||||
heat_transmission_from_cert,
|
||||
)
|
||||
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000565_PDF)
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||
|
||||
# Act
|
||||
ht = heat_transmission_from_cert(epc)
|
||||
|
||||
# Assert — pre-S0380.85 cascade had walls 443 W/K. Curtain Wall
|
||||
# closure adds ~112 W/K (worksheet target 604 W/K). Lower-bound
|
||||
# 540 W/K is a robust gate that still leaves headroom for the
|
||||
# remaining BP[0] alt1 thin-wall gap; the cascade reaches ~555.
|
||||
assert ht.walls_w_per_k >= 540.0, (
|
||||
f"walls_w_per_k = {ht.walls_w_per_k:.2f}; expected ≥540 after "
|
||||
f"Curtain Wall §5.18 dispatch (pre-S0380.85 baseline was 443)"
|
||||
)
|
||||
|
||||
|
||||
def test_summary_000565_ext1_party_wall_routes_to_cavity_filled_code_4() -> None:
|
||||
# Arrange — RdSAP 10 Table 15 row 3 "Cavity masonry filled":
|
||||
# cert 000565 Ext1 lodges "CF Cavity masonry filled". Routes
|
||||
|
|
|
|||
|
|
@ -435,6 +435,13 @@ class SapBuildingPart:
|
|||
None # TODO: make enum/mapping?
|
||||
)
|
||||
sap_room_in_roof: Optional[SapRoomInRoof] = None
|
||||
# Per RdSAP 10 §5.18 (PDF p.48), a curtain wall (wall_construction
|
||||
# =WALL_CURTAIN=9) takes its U-value from the per-BP installation
|
||||
# age — "Post 2023" routes to the Table 24 window row (1.4 W/m²K
|
||||
# PVC/wood), anything else (incl. None) defaults to U=2.0 W/m²K.
|
||||
# The dwelling-wide `construction_age_band` does NOT govern curtain
|
||||
# walls; this field decouples them per spec.
|
||||
curtain_wall_age: Optional[str] = None
|
||||
|
||||
@property
|
||||
def main_wall_is_basement(self) -> bool:
|
||||
|
|
|
|||
|
|
@ -3121,6 +3121,12 @@ def _map_elmhurst_building_part(
|
|||
sap_room_in_roof=room_in_roof,
|
||||
sap_alternative_wall_1=alt_walls[0],
|
||||
sap_alternative_wall_2=alt_walls[1],
|
||||
# RdSAP 10 §5.18 (PDF p.48) — curtain-wall U-value is keyed on
|
||||
# the per-BP `Curtain Wall Age` lodging, not on the dwelling-
|
||||
# wide age band. Thread the extractor's optional field through
|
||||
# so heat_transmission's `u_wall(curtain_wall_age=...)` can
|
||||
# dispatch. None for non-curtain-wall BPs.
|
||||
curtain_wall_age=walls.curtain_wall_age,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,13 @@ class WallDetails:
|
|||
# "Insulation Thickness" / "100 mm" line pair when a composite or
|
||||
# retrofit insulation is recorded. None when the PDF omits the line.
|
||||
insulation_thickness_mm: Optional[int] = None
|
||||
# Per-BP curtain-wall installation age, lodged in Summary §7 as
|
||||
# "Curtain Wall Age" when `wall_type` is "CW Curtain Wall". Per
|
||||
# RdSAP 10 §5.18 (PDF p.48) the curtain-wall U-value keys on this
|
||||
# field (Post 2023 → Table 24 window row; Pre 2023 → 2.0 W/m²K),
|
||||
# NOT on the dwelling-wide `construction_age_band`. None when the
|
||||
# BP is not a curtain wall.
|
||||
curtain_wall_age: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -647,6 +647,11 @@ def heat_transmission_from_cert(
|
|||
insulation_present=wall_ins_present,
|
||||
description=wall_description,
|
||||
wall_insulation_type=wall_ins_type,
|
||||
# RdSAP 10 §5.18 (PDF p.48) — curtain walls dispatch
|
||||
# on the per-BP installation age, not the dwelling age
|
||||
# band. None for non-curtain-wall parts (ignored by
|
||||
# `u_wall` unless wall_construction == WALL_CURTAIN).
|
||||
curtain_wall_age=part.curtain_wall_age,
|
||||
)
|
||||
# When the per-bp `roof_insulation_thickness` is explicitly lodged
|
||||
# as 0 (uninsulated — e.g. cert 001479 Ext2 PS sloping ceiling
|
||||
|
|
|
|||
|
|
@ -144,6 +144,39 @@ _WALL_INSULATION_LAMBDA_W_PER_MK: Final[float] = 0.04
|
|||
_DRY_LINING_RESISTANCE_M2K_PER_W: Final[float] = 0.17
|
||||
|
||||
|
||||
# RdSAP 10 §5.18 (PDF p.48) — curtain-wall U-values.
|
||||
#
|
||||
# "If documentary evidence is available, use calculated U-value of the
|
||||
# whole curtain wall. Otherwise for the purpose of RdSAP, U= 2.0 W/m²K
|
||||
# for pre-2023 curtain walls, And for post-2023 (2024 in Scotland)
|
||||
# U-values as for windows given in Notes below Table 24."
|
||||
#
|
||||
# Table 24 row "Double or triple glazed, England/Wales: 2022 or later"
|
||||
# is the matching post-2023 row: U = 1.4 (PVC/wood) / 1.6 (metal). The
|
||||
# Frame Factor for a whole-wall curtain wall is 1 per the §5.18 closer.
|
||||
#
|
||||
# Empirical pin: cert 000565 BP[2] Ext2 lodges `Curtain Wall Age: Post
|
||||
# 2023` and the U985 worksheet uses U=1.40 for this BP — matching the
|
||||
# PVC/wood row (the §5.18 default since curtain-wall frame material is
|
||||
# not separately surfaced on the Elmhurst Summary).
|
||||
_CURTAIN_WALL_U_PRE_2023: Final[float] = 2.0
|
||||
_CURTAIN_WALL_U_POST_2023: Final[float] = 1.4
|
||||
_CURTAIN_WALL_POST_2023_LODGEMENTS: Final[frozenset[str]] = frozenset({
|
||||
"Post 2023",
|
||||
"Post-2023",
|
||||
})
|
||||
|
||||
|
||||
def _u_curtain_wall(curtain_wall_age: Optional[str]) -> float:
|
||||
"""RdSAP 10 §5.18 curtain-wall U-value. Keyed on the per-BP
|
||||
`Curtain Wall Age` lodgement (Summary §7), NOT on the dwelling-wide
|
||||
`construction_age_band`. Unknown / absent → pre-2023 default per
|
||||
the spec's "U= 2.0 W/m²K for pre-2023 curtain walls" sentence."""
|
||||
if curtain_wall_age is not None and curtain_wall_age.strip() in _CURTAIN_WALL_POST_2023_LODGEMENTS:
|
||||
return _CURTAIN_WALL_U_POST_2023
|
||||
return _CURTAIN_WALL_U_PRE_2023
|
||||
|
||||
|
||||
_AGE_BANDS: Final[tuple[str, ...]] = tuple("ABCDEFGHIJKLM")
|
||||
|
||||
|
||||
|
|
@ -336,6 +369,7 @@ def u_wall(
|
|||
description: Optional[str] = None,
|
||||
wall_insulation_type: Optional[int] = None,
|
||||
dry_lined: bool = False,
|
||||
curtain_wall_age: Optional[str] = None,
|
||||
) -> float:
|
||||
"""RdSAP10 wall U-value in W/m^2K, never null.
|
||||
|
||||
|
|
@ -361,12 +395,23 @@ def u_wall(
|
|||
insulation stack. Cohort fixture: cert 7700 Alt 1 cavity-as-built
|
||||
age C with Dry-lining: Yes — base U=1.5 → adjusted U=1.20 (2 d.p.,
|
||||
matching worksheet `CavityWallPlasterOnDabsDenseBlock`).
|
||||
|
||||
`curtain_wall_age` keys the RdSAP 10 §5.18 (PDF p.48) curtain-wall
|
||||
dispatch. Applies only when `construction == WALL_CURTAIN`; ignored
|
||||
for all other constructions. The dwelling-wide `age_band` does NOT
|
||||
govern curtain walls per §5.18 — the installation age does.
|
||||
"""
|
||||
measured = _measured_u_from_description(description)
|
||||
if measured is not None:
|
||||
return measured
|
||||
if country is None and age_band is None and construction is None and insulation_thickness_mm is None and not insulation_present:
|
||||
return 1.5
|
||||
# RdSAP 10 §5.18 (PDF p.48) — curtain walls bypass the Table 6/7/8/9
|
||||
# cascade entirely; their U-value keys solely on the per-BP
|
||||
# `Curtain Wall Age` lodging. Place the dispatch before `known_types`
|
||||
# so an explicit `construction=WALL_CURTAIN` always routes here.
|
||||
if construction == WALL_CURTAIN:
|
||||
return _u_curtain_wall(curtain_wall_age)
|
||||
ctry = country if country is not None else Country.ENG
|
||||
age_idx = _age_index(age_band)
|
||||
band = _AGE_BANDS[age_idx]
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import pytest
|
|||
from domain.sap10_ml.rdsap_uvalues import (
|
||||
Country,
|
||||
WALL_CAVITY,
|
||||
WALL_CURTAIN,
|
||||
WALL_INSULATION_FILLED_CAVITY,
|
||||
WALL_SOLID_BRICK,
|
||||
WALL_STONE_GRANITE,
|
||||
|
|
@ -537,6 +538,75 @@ def test_u_wall_uses_rdsap_unknown_thickness_default_of_50mm_when_insulated_but_
|
|||
assert result == pytest.approx(0.35, abs=0.001)
|
||||
|
||||
|
||||
def test_u_wall_curtain_wall_post_2023_routes_to_window_table_24_u_1p4_per_rdsap_5_18() -> None:
|
||||
# Arrange — RdSAP 10 §5.18 (PDF p.48): "Otherwise for the purpose of
|
||||
# RdSAP, U= 2.0 W/m²K for pre-2023 curtain walls, And for post-2023
|
||||
# (2024 in Scotland) U-values as for windows given in Notes below
|
||||
# Table 24." Table 24 row "Double or triple glazed England/Wales:
|
||||
# 2022 or later" PVC/wood column = 1.4 W/m²K. Cert 000565 BP[2]
|
||||
# Ext2 lodges `Type: CW Curtain Wall` + `Curtain Wall Age: Post 2023`
|
||||
# — worksheet pins U=1.40 for this BP.
|
||||
#
|
||||
# Pre-S0380.85: `WALL_CURTAIN=9` was defined but not in `known_types`
|
||||
# at u_wall:373-376, so the dispatch fell through to
|
||||
# `_DEFAULT_WALL_BY_AGE.get(band, WALL_CAVITY)` → cavity table at age
|
||||
# H = 0.60. Cascade walls subtotal under-counted by ~112 W/K on
|
||||
# this BP.
|
||||
|
||||
# Act
|
||||
result = u_wall(
|
||||
country=Country.ENG,
|
||||
age_band="H",
|
||||
construction=WALL_CURTAIN,
|
||||
insulation_thickness_mm=None,
|
||||
curtain_wall_age="Post 2023",
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert abs(result - 1.4) <= 1e-9
|
||||
|
||||
|
||||
def test_u_wall_curtain_wall_pre_2023_uses_rdsap_5_18_default_u_2p0() -> None:
|
||||
# Arrange — RdSAP 10 §5.18 (PDF p.48) fallback for curtain walls
|
||||
# built before 2023 (or installed-age unknown): U = 2.0 W/m²K.
|
||||
# Independent of construction age band — §5.18 keys solely on the
|
||||
# curtain-wall-age lodging (Post 2023 vs everything else), not on
|
||||
# the dwelling-wide `construction_age_band`.
|
||||
|
||||
# Act
|
||||
result = u_wall(
|
||||
country=Country.ENG,
|
||||
age_band="H",
|
||||
construction=WALL_CURTAIN,
|
||||
insulation_thickness_mm=None,
|
||||
curtain_wall_age="Pre 2023",
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert abs(result - 2.0) <= 1e-9
|
||||
|
||||
|
||||
def test_u_wall_curtain_wall_missing_age_lodgement_defaults_to_pre_2023_u_2p0_per_rdsap_5_18() -> None:
|
||||
# Arrange — when the cert lodges `Type: CW Curtain Wall` but no
|
||||
# `Curtain Wall Age` line (older Elmhurst Summary PDFs, or API EPCs
|
||||
# without the per-BP curtain_wall_age field), apply the §5.18
|
||||
# default. The §5.18 sentence "U= 2.0 W/m²K for pre-2023 curtain
|
||||
# walls" applies as the unknown-age fallback — matches the spec's
|
||||
# "assume as-built" convention elsewhere in the cascade.
|
||||
|
||||
# Act
|
||||
result = u_wall(
|
||||
country=Country.ENG,
|
||||
age_band="H",
|
||||
construction=WALL_CURTAIN,
|
||||
insulation_thickness_mm=None,
|
||||
curtain_wall_age=None,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert abs(result - 2.0) <= 1e-9
|
||||
|
||||
|
||||
# ----- Roofs -----
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue