mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
heat_transmission: route exposed/semi-exposed floors through Table 20
SapFloorDimension gains an is_exposed_floor flag (default False) signalling that the floor sits over outside air or unheated space rather than soil — typical for an extension that hangs off the main from the first storey upward (Elmhurst 000490 Extension 1 is exactly this shape). heat_transmission_from_cert now consults the flag on the part's ground SapFloorDimension and dispatches to u_exposed_floor (Table 20) instead of the BS EN ISO 13370 / Table 19 cascade. Basement floor still wins priority (Table 23 § 5.17 overrides everything else for that part). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e2c37300ec
commit
6b99ad0a55
3 changed files with 58 additions and 3 deletions
|
|
@ -250,6 +250,12 @@ class SapFloorDimension:
|
|||
floor: Optional[int] = None
|
||||
floor_insulation: Optional[int] = None
|
||||
floor_construction: Optional[int] = None
|
||||
# RdSAP10 §5.13 Table 20: True when this floor is open to outside air
|
||||
# (exposed) or sits over enclosed unheated space (semi-exposed) — e.g.
|
||||
# the lowest floor of an extension that hangs off the main from the
|
||||
# first storey upward. False means a ground floor (on soil), the
|
||||
# default path through the BS EN ISO 13370 / Table 19 cascade.
|
||||
is_exposed_floor: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ from domain.ml.rdsap_uvalues import (
|
|||
u_basement_floor,
|
||||
u_basement_wall,
|
||||
u_door,
|
||||
u_exposed_floor,
|
||||
u_floor,
|
||||
u_party_wall,
|
||||
u_roof,
|
||||
|
|
@ -273,11 +274,20 @@ def heat_transmission_from_cert(
|
|||
wall_insulation_type=wall_ins_type,
|
||||
)
|
||||
ur = u_roof(country=country, age_band=age_band, insulation_thickness_mm=roof_thickness, description=roof_description)
|
||||
# When the part carries a basement, the WHOLE floor=0 is the
|
||||
# basement floor (per user-confirmed convention). Table 23 F-column
|
||||
# overrides the regular floor U-value cascade.
|
||||
# Floor U-value routing (in priority order):
|
||||
# 1. Basement floor — Table 23 F-column override (whole floor=0).
|
||||
# 2. Exposed/semi-exposed upper floor — Table 20 lookup; no
|
||||
# geometry input. Set on the ground SapFloorDimension when
|
||||
# the part hangs off the main from the first storey upward
|
||||
# (e.g. 000490 Extension 1).
|
||||
# 3. Ground floor — BS EN ISO 13370 / Table 19 cascade.
|
||||
is_exposed_floor = bool(ground_fd is not None and ground_fd.is_exposed_floor)
|
||||
if part.has_basement:
|
||||
uf = u_basement_floor(age_band)
|
||||
elif is_exposed_floor:
|
||||
uf = u_exposed_floor(
|
||||
age_band=age_band, insulation_thickness_mm=floor_ins_thickness
|
||||
)
|
||||
else:
|
||||
uf = u_floor(
|
||||
country=country, age_band=age_band, construction=floor_construction,
|
||||
|
|
|
|||
|
|
@ -80,6 +80,45 @@ def test_roof_insulated_assumed_with_ni_thickness_uses_50mm_per_section_5_11_4()
|
|||
assert result.roof_w_per_k == pytest.approx(68.0, abs=2.0)
|
||||
|
||||
|
||||
def test_exposed_timber_floor_age_b_uses_table_20_u_120_not_iso_13370() -> None:
|
||||
# Arrange — RdSAP10 §5.13 Table 20: a part whose lowest floor sits
|
||||
# over outside air (or unheated space) rather than soil takes its
|
||||
# U-value from Table 20, not the BS EN ISO 13370 ground-floor branch.
|
||||
# Elmhurst worksheet 000490 Extension 1 is exactly this shape — the
|
||||
# extension hangs off the main from the first storey upward, so its
|
||||
# floor=0 is "exposed timber" at U=1.20 W/m²K. Without the routing,
|
||||
# the cascade would treat the same dimensions as a small ground-floor
|
||||
# rectangle and return a much lower U via ISO 13370.
|
||||
# Geometry: 18 m² × 8.68 m perimeter at age B, no insulation lodged.
|
||||
# floor_w_per_k expected = 1.20 × 18 = 21.6 W/K.
|
||||
main = make_building_part(
|
||||
identifier="Main Dwelling",
|
||||
construction_age_band="B",
|
||||
wall_construction=4,
|
||||
wall_insulation_type=4,
|
||||
party_wall_construction=1,
|
||||
roof_construction=4,
|
||||
floor_dimensions=[
|
||||
make_floor_dimension(
|
||||
total_floor_area_m2=18.0, room_height_m=2.88,
|
||||
party_wall_length_m=0.0, heat_loss_perimeter_m=8.68, floor=0,
|
||||
),
|
||||
],
|
||||
)
|
||||
main.sap_floor_dimensions[0].is_exposed_floor = True
|
||||
epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=18.0,
|
||||
country_code="ENG",
|
||||
sap_building_parts=[main],
|
||||
)
|
||||
|
||||
# Act
|
||||
result = heat_transmission_from_cert(epc)
|
||||
|
||||
# Assert
|
||||
assert result.floor_w_per_k == pytest.approx(21.6, abs=0.1)
|
||||
|
||||
|
||||
def test_floor_insulated_assumed_with_ni_thickness_uses_50mm_per_table19_footnote() -> None:
|
||||
# Arrange — 2 413 corpus certs lodge floors with thickness="NI" and
|
||||
# description "Solid, insulated (assumed)". The retrofit-insulation
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue