mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
slice S-A4: heat-transmission HLC breakdown (SAP 10.3 §3)
Fourth slice of the SAP10 Calculator Session A (ADR-0009). Ports the per-element conduction HLC logic out of domain.ml.envelope into a typed HeatTransmission breakdown under domain.sap.worksheet. Aggregates Σ U×A across walls, roof, floor, party walls, windows, doors, plus thermal- bridging y × total exposed area, summed across every building part. The orchestrator can now read walls_w_per_k / roof_w_per_k / floor_w_per_k etc. directly off the result for audit + monthly-loop wiring, rather than seeing a single envelope_heat_loss scalar. U-value cascade still routes through domain.ml.rdsap_uvalues (migrates to domain.sap.rdsap.cascade_defaults in Session B per ADR-0009 module-layout plan). domain.ml.envelope stays in place to keep the ML transform's physics-feature pipeline running until Session B. 6 AAA cycles cover: per-element breakdown for a baseline age-G cavity mid-terrace, window net-wall subtraction, insulated-door U-value blending, cavity-party-wall contribution per Table 15, thermal-bridging scaling by age band per Table 21, and multi-part (main + extension) aggregation. 192 tests pass across domain.sap + domain.ml — no regressions.
This commit is contained in:
parent
3fcec7ef22
commit
732eef6adb
2 changed files with 484 additions and 0 deletions
229
packages/domain/src/domain/sap/worksheet/heat_transmission.py
Normal file
229
packages/domain/src/domain/sap/worksheet/heat_transmission.py
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
"""SAP 10.3 §3 — heat-transmission Heat Loss Coefficient.
|
||||
|
||||
Conduction HLC summed across every building part: Σ U × A across walls,
|
||||
roof, floor, party walls, windows, doors, plus thermal-bridging factor y
|
||||
multiplied by total exposed envelope area.
|
||||
|
||||
Returns a typed `HeatTransmission` breakdown so the orchestrator can audit
|
||||
each element's contribution.
|
||||
|
||||
This is the calculator-vocabulary sibling of `domain.ml.envelope`. During
|
||||
Session A both modules coexist — the legacy envelope.py continues to feed
|
||||
the ML transform's `envelope_heat_loss_w_per_k` physics-feature. Session B
|
||||
will retire envelope.py in favour of this module (ADR-0009 §"Module
|
||||
layout").
|
||||
|
||||
U-value lookups cascade through `domain.ml.rdsap_uvalues` — migrating to
|
||||
`domain.sap.rdsap.cascade_defaults` in Session B.
|
||||
|
||||
Reference: SAP 10.3 specification §3 (pages 17-22); RdSAP 10 §5.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Final, Optional
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import EpcPropertyData, SapBuildingPart
|
||||
|
||||
from domain.ml.rdsap_uvalues import (
|
||||
Country,
|
||||
WALL_UNKNOWN,
|
||||
thermal_bridging_y,
|
||||
u_door,
|
||||
u_floor,
|
||||
u_party_wall,
|
||||
u_roof,
|
||||
u_wall,
|
||||
u_window,
|
||||
)
|
||||
|
||||
|
||||
_WALL_INSULATION_NONE: Final[int] = 4
|
||||
_DEFAULT_DOOR_AREA_M2: Final[float] = 1.85
|
||||
_DEFAULT_STOREY_HEIGHT_M: Final[float] = 2.5
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HeatTransmission:
|
||||
"""SAP 10.3 §3 conduction HLC broken down per element type, summed
|
||||
across all sap_building_parts (main dwelling + every extension)."""
|
||||
|
||||
walls_w_per_k: float
|
||||
roof_w_per_k: float
|
||||
floor_w_per_k: float
|
||||
party_walls_w_per_k: float
|
||||
windows_w_per_k: float
|
||||
doors_w_per_k: float
|
||||
thermal_bridging_w_per_k: float
|
||||
total_w_per_k: float
|
||||
|
||||
|
||||
def _int_or_none(value: Any) -> Optional[int]:
|
||||
return value if isinstance(value, int) else None
|
||||
|
||||
|
||||
def _parse_thickness_mm(value: Any) -> Optional[int]:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
s = value.strip()
|
||||
if s.upper() == "NI":
|
||||
return 0
|
||||
digits = ""
|
||||
for c in s:
|
||||
if c.isdigit():
|
||||
digits += c
|
||||
else:
|
||||
break
|
||||
return int(digits) if digits else None
|
||||
|
||||
|
||||
def _joined_descriptions(elements: list[Any]) -> Optional[str]:
|
||||
if not elements:
|
||||
return None
|
||||
parts = [getattr(e, "description", "") for e in elements if getattr(e, "description", "")]
|
||||
if not parts:
|
||||
return None
|
||||
return " | ".join(parts)
|
||||
|
||||
|
||||
def _part_geometry(part: SapBuildingPart) -> dict[str, float]:
|
||||
if not part.sap_floor_dimensions:
|
||||
return {
|
||||
"ground_floor_area_m2": 0.0,
|
||||
"ground_perimeter_m": 0.0,
|
||||
"top_floor_area_m2": 0.0,
|
||||
"party_wall_length_m": 0.0,
|
||||
"avg_room_height_m": _DEFAULT_STOREY_HEIGHT_M,
|
||||
"storey_count": 1.0,
|
||||
}
|
||||
fds = list(part.sap_floor_dimensions)
|
||||
ground = next((fd for fd in fds if fd.floor == 0), fds[0])
|
||||
indexed = [(fd.floor if fd.floor is not None else 0, fd) for fd in fds]
|
||||
top = max(indexed, key=lambda kv: kv[0])[1]
|
||||
total_area = sum(fd.total_floor_area_m2 or 0.0 for fd in fds)
|
||||
weighted_height = sum(
|
||||
(fd.total_floor_area_m2 or 0.0) * (fd.room_height_m or _DEFAULT_STOREY_HEIGHT_M)
|
||||
for fd in fds
|
||||
)
|
||||
avg_height = (weighted_height / total_area) if total_area > 0 else _DEFAULT_STOREY_HEIGHT_M
|
||||
return {
|
||||
"ground_floor_area_m2": ground.total_floor_area_m2 or 0.0,
|
||||
"ground_perimeter_m": ground.heat_loss_perimeter_m or 0.0,
|
||||
"top_floor_area_m2": top.total_floor_area_m2 or 0.0,
|
||||
"party_wall_length_m": ground.party_wall_length_m or 0.0,
|
||||
"avg_room_height_m": avg_height,
|
||||
"storey_count": float(len(fds)),
|
||||
}
|
||||
|
||||
|
||||
def heat_transmission_from_cert(
|
||||
epc: EpcPropertyData,
|
||||
*,
|
||||
window_total_area_m2: float = 0.0,
|
||||
window_avg_u_value: Optional[float] = None,
|
||||
door_count: int = 0,
|
||||
insulated_door_count: int = 0,
|
||||
insulated_door_u_value: Optional[float] = None,
|
||||
) -> HeatTransmission:
|
||||
"""Conduction HLC + thermal-bridging contribution, summed across every
|
||||
sap_building_part in the cert. Windows and doors are apportioned to the
|
||||
first part (the main dwelling) per RdSAP convention."""
|
||||
parts = epc.sap_building_parts or []
|
||||
if not parts:
|
||||
return HeatTransmission(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
|
||||
|
||||
country = Country.from_code(epc.country_code)
|
||||
roof_description = _joined_descriptions(epc.roofs)
|
||||
wall_description = _joined_descriptions(epc.walls)
|
||||
|
||||
door_area = max(0, door_count) * _DEFAULT_DOOR_AREA_M2
|
||||
window_u = window_avg_u_value if (window_avg_u_value or 0) > 0 else u_window(
|
||||
installed_year=None, glazing_type=None, frame_type=None
|
||||
)
|
||||
primary_age = parts[0].construction_age_band
|
||||
door_uninsulated_u = u_door(country=country, age_band=primary_age, insulated=False, insulated_u_value=None)
|
||||
door_insulated_u = (
|
||||
insulated_door_u_value
|
||||
if insulated_door_u_value is not None
|
||||
else u_door(country=country, age_band="M", insulated=True, insulated_u_value=None)
|
||||
)
|
||||
insulated_share = (insulated_door_count or 0) / door_count if door_count > 0 else 0.0
|
||||
door_u = (1.0 - insulated_share) * door_uninsulated_u + insulated_share * door_insulated_u
|
||||
|
||||
walls = 0.0
|
||||
roof = 0.0
|
||||
floor = 0.0
|
||||
party = 0.0
|
||||
windows = 0.0
|
||||
doors = 0.0
|
||||
bridging = 0.0
|
||||
for i, part in enumerate(parts):
|
||||
geom = _part_geometry(part)
|
||||
age_band = part.construction_age_band
|
||||
wall_construction = _int_or_none(part.wall_construction)
|
||||
wall_ins_type = _int_or_none(part.wall_insulation_type)
|
||||
wall_ins_thickness = _parse_thickness_mm(part.wall_insulation_thickness)
|
||||
wall_ins_present = wall_ins_type is not None and wall_ins_type != _WALL_INSULATION_NONE
|
||||
party_construction = _int_or_none(part.party_wall_construction)
|
||||
roof_thickness = _parse_thickness_mm(getattr(part, "roof_insulation_thickness", None))
|
||||
floor_ins_thickness = _parse_thickness_mm(getattr(part, "floor_insulation_thickness", None))
|
||||
|
||||
ground_fd = next(
|
||||
(fd for fd in part.sap_floor_dimensions if fd.floor == 0),
|
||||
part.sap_floor_dimensions[0] if part.sap_floor_dimensions else None,
|
||||
)
|
||||
floor_area = ground_fd.total_floor_area_m2 if ground_fd is not None else None
|
||||
floor_perimeter = ground_fd.heat_loss_perimeter_m if ground_fd is not None else None
|
||||
floor_construction = _int_or_none(ground_fd.floor_construction) if ground_fd is not None else None
|
||||
|
||||
uw = u_wall(
|
||||
country=country, age_band=age_band,
|
||||
construction=wall_construction if wall_construction != WALL_UNKNOWN else None,
|
||||
insulation_thickness_mm=wall_ins_thickness,
|
||||
insulation_present=wall_ins_present,
|
||||
description=wall_description,
|
||||
)
|
||||
ur = u_roof(country=country, age_band=age_band, insulation_thickness_mm=roof_thickness, description=roof_description)
|
||||
uf = u_floor(
|
||||
country=country, age_band=age_band, construction=floor_construction,
|
||||
insulation_thickness_mm=floor_ins_thickness,
|
||||
area_m2=floor_area, perimeter_m=floor_perimeter,
|
||||
wall_thickness_mm=part.wall_thickness_mm,
|
||||
)
|
||||
upw = u_party_wall(party_wall_construction=party_construction)
|
||||
y = thermal_bridging_y(age_band=age_band)
|
||||
|
||||
storey_count = geom["storey_count"]
|
||||
storey_height = geom["avg_room_height_m"]
|
||||
gross_wall_area = geom["ground_perimeter_m"] * storey_height * storey_count
|
||||
w_area = window_total_area_m2 if i == 0 else 0.0
|
||||
d_area = door_area if i == 0 else 0.0
|
||||
net_wall_area = max(0.0, gross_wall_area - w_area - d_area)
|
||||
party_area = geom["party_wall_length_m"] * storey_height * storey_count
|
||||
roof_area = geom["top_floor_area_m2"]
|
||||
floor_area_total = geom["ground_floor_area_m2"]
|
||||
|
||||
walls += uw * net_wall_area
|
||||
roof += ur * roof_area
|
||||
floor += uf * floor_area_total
|
||||
party += upw * party_area
|
||||
windows += window_u * w_area
|
||||
doors += door_u * d_area
|
||||
bridging += y * (net_wall_area + party_area + roof_area + floor_area_total + w_area + d_area)
|
||||
|
||||
total = walls + roof + floor + party + windows + doors + bridging
|
||||
return HeatTransmission(
|
||||
walls_w_per_k=walls,
|
||||
roof_w_per_k=roof,
|
||||
floor_w_per_k=floor,
|
||||
party_walls_w_per_k=party,
|
||||
windows_w_per_k=windows,
|
||||
doors_w_per_k=doors,
|
||||
thermal_bridging_w_per_k=bridging,
|
||||
total_w_per_k=total,
|
||||
)
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
"""Tests for SAP 10.3 §3 heat-transmission worksheet.
|
||||
|
||||
Computes the conduction Heat Loss Coefficient (W/K) summed across all
|
||||
building parts: Σ U × A across walls / roof / floor / party walls /
|
||||
windows / doors, plus thermal bridging y × total exposed area. Returns
|
||||
a typed `HeatTransmission` breakdown so callers can audit each
|
||||
worksheet contribution.
|
||||
|
||||
U-values cascade through the RdSAP 10 §5 defaults (currently implemented
|
||||
in `domain.ml.rdsap_uvalues` — migrates to `domain.sap.rdsap.cascade_defaults`
|
||||
in Session B).
|
||||
|
||||
Reference: SAP 10.3 specification §3 (pages 17-22);
|
||||
RdSAP 10 §5 (pages 31-48); test fixtures shared with the legacy
|
||||
envelope.py test pack so cases match production cert shape.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from domain.ml.tests._fixtures import (
|
||||
make_building_part,
|
||||
make_floor_dimension,
|
||||
make_minimal_sap10_epc,
|
||||
)
|
||||
from domain.sap.worksheet.heat_transmission import (
|
||||
HeatTransmission,
|
||||
heat_transmission_from_cert,
|
||||
)
|
||||
|
||||
|
||||
def test_single_storey_age_g_cavity_returns_per_element_breakdown() -> None:
|
||||
# Arrange — Mid-terrace, age G cavity-as-built, 100 m² floor area, 40 m
|
||||
# heat-loss perimeter, 5 m party wall, 2.5 m room height, single storey.
|
||||
# Per RdSAP10 §5 + BS EN ISO 13370 cascade defaults:
|
||||
# u_wall = 0.60 (cavity G uninsulated), u_roof = 0.40, u_floor ≈ 0.60,
|
||||
# u_party = 0.0 (solid masonry), y = 0.15.
|
||||
# Areas: gross_wall 100, net_wall 100, party 12.5, roof 100, floor 100.
|
||||
# walls W/K = 60.0; roof = 40.0; floor ≈ 60.3; party = 0.0;
|
||||
# bridging = 0.15 × (100 + 12.5 + 100 + 100) = 46.9; total ≈ 207.2 W/K.
|
||||
main = make_building_part(
|
||||
identifier="Main Dwelling",
|
||||
construction_age_band="G",
|
||||
wall_construction=4, # cavity
|
||||
wall_insulation_type=4, # none
|
||||
party_wall_construction=1, # solid masonry
|
||||
roof_construction=4,
|
||||
floor_dimensions=[
|
||||
make_floor_dimension(
|
||||
total_floor_area_m2=100.0, room_height_m=2.5,
|
||||
party_wall_length_m=5.0, heat_loss_perimeter_m=40.0, floor=0,
|
||||
),
|
||||
],
|
||||
)
|
||||
epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=100.0,
|
||||
country_code="ENG",
|
||||
sap_building_parts=[main],
|
||||
)
|
||||
|
||||
# Act
|
||||
result = heat_transmission_from_cert(epc)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, HeatTransmission)
|
||||
assert result.walls_w_per_k == pytest.approx(60.0, abs=2.0)
|
||||
assert result.roof_w_per_k == pytest.approx(40.0, abs=2.0)
|
||||
assert result.floor_w_per_k == pytest.approx(60.3, abs=3.0)
|
||||
assert result.party_walls_w_per_k == pytest.approx(0.0, abs=0.5)
|
||||
assert result.windows_w_per_k == pytest.approx(0.0, abs=0.1)
|
||||
assert result.doors_w_per_k == pytest.approx(0.0, abs=0.1)
|
||||
assert result.thermal_bridging_w_per_k == pytest.approx(46.9, abs=3.0)
|
||||
assert result.total_w_per_k == pytest.approx(207.0, abs=8.0)
|
||||
|
||||
|
||||
def test_windows_subtract_from_net_wall_area_so_walls_w_per_k_drops() -> None:
|
||||
# Arrange — Same age G cavity geometry, but with 15 m² of windows. Walls
|
||||
# net area drops from 100 to 85 m²; walls_w_per_k drops from 60 to 51.
|
||||
# windows_w_per_k rises from 0 to ≈ 15 × 2.5 (Table 24 mid-range default).
|
||||
main = make_building_part(
|
||||
identifier="Main Dwelling",
|
||||
construction_age_band="G",
|
||||
wall_construction=4,
|
||||
wall_insulation_type=4,
|
||||
party_wall_construction=1,
|
||||
roof_construction=4,
|
||||
floor_dimensions=[
|
||||
make_floor_dimension(
|
||||
total_floor_area_m2=100.0, room_height_m=2.5,
|
||||
party_wall_length_m=5.0, heat_loss_perimeter_m=40.0, floor=0,
|
||||
),
|
||||
],
|
||||
)
|
||||
epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=100.0, country_code="ENG", sap_building_parts=[main],
|
||||
)
|
||||
|
||||
# Act
|
||||
no_windows = heat_transmission_from_cert(epc)
|
||||
with_windows = heat_transmission_from_cert(
|
||||
epc, window_total_area_m2=15.0, window_avg_u_value=2.8,
|
||||
)
|
||||
|
||||
# Assert — walls fall by U_wall × window_area; windows = U_win × window_area.
|
||||
assert with_windows.walls_w_per_k == pytest.approx(no_windows.walls_w_per_k - 0.60 * 15.0, abs=1.0)
|
||||
assert with_windows.windows_w_per_k == pytest.approx(2.8 * 15.0, abs=0.5)
|
||||
# Total rises because U_window (2.8) > U_wall (0.60), so the net swap adds heat loss.
|
||||
assert with_windows.total_w_per_k > no_windows.total_w_per_k
|
||||
|
||||
|
||||
def test_two_doors_one_insulated_blends_u_values_per_rdsap_share() -> None:
|
||||
# Arrange — Two doors total, one insulated at U=1.0, the other taking the
|
||||
# Table 26 default for age G (3.0). Door area = 2 × 1.85 = 3.70 m². Share
|
||||
# insulated = 0.5, so blended U = 0.5 × 3.0 + 0.5 × 1.0 = 2.0.
|
||||
# Expected doors_w_per_k = 2.0 × 3.70 = 7.40.
|
||||
main = make_building_part(
|
||||
identifier="Main Dwelling",
|
||||
construction_age_band="G",
|
||||
wall_construction=4,
|
||||
wall_insulation_type=4,
|
||||
party_wall_construction=1,
|
||||
roof_construction=4,
|
||||
floor_dimensions=[
|
||||
make_floor_dimension(
|
||||
total_floor_area_m2=100.0, room_height_m=2.5,
|
||||
party_wall_length_m=5.0, heat_loss_perimeter_m=40.0, floor=0,
|
||||
),
|
||||
],
|
||||
)
|
||||
epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=100.0, country_code="ENG",
|
||||
sap_building_parts=[main], door_count=2, insulated_door_count=1,
|
||||
)
|
||||
|
||||
# Act
|
||||
result = heat_transmission_from_cert(
|
||||
epc, door_count=2, insulated_door_count=1, insulated_door_u_value=1.0,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result.doors_w_per_k == pytest.approx(7.40, abs=0.3)
|
||||
|
||||
|
||||
def test_cavity_party_wall_contributes_per_table_15() -> None:
|
||||
# Arrange — Party wall construction = 4 (cavity, unfilled) → U=0.5 per
|
||||
# RdSAP10 §5.10. Party area = 5 × 2.5 × 1 = 12.5 m².
|
||||
# Expected party_walls_w_per_k = 0.5 × 12.5 = 6.25.
|
||||
main = make_building_part(
|
||||
identifier="Main Dwelling",
|
||||
construction_age_band="G",
|
||||
wall_construction=4,
|
||||
wall_insulation_type=4,
|
||||
party_wall_construction=4, # cavity (unfilled) — 0.5 W/m²K per Table 15
|
||||
roof_construction=4,
|
||||
floor_dimensions=[
|
||||
make_floor_dimension(
|
||||
total_floor_area_m2=100.0, room_height_m=2.5,
|
||||
party_wall_length_m=5.0, heat_loss_perimeter_m=40.0, floor=0,
|
||||
),
|
||||
],
|
||||
)
|
||||
epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=100.0, country_code="ENG", sap_building_parts=[main],
|
||||
)
|
||||
|
||||
# Act
|
||||
result = heat_transmission_from_cert(epc)
|
||||
|
||||
# Assert
|
||||
assert result.party_walls_w_per_k == pytest.approx(6.25, abs=0.3)
|
||||
|
||||
|
||||
def test_thermal_bridging_drops_for_newer_age_band_per_table_21() -> None:
|
||||
# Arrange — RdSAP10 §5.15 / Table 21: y = 0.15 for A-I, 0.11 for J,
|
||||
# 0.08 for K-M. Compare age G (0.15) vs age M (0.08) with the same
|
||||
# geometry. Total exposed area is constant; bridging contribution
|
||||
# scales linearly with y.
|
||||
def _make_part(age: str):
|
||||
return make_building_part(
|
||||
identifier="Main Dwelling",
|
||||
construction_age_band=age,
|
||||
wall_construction=4,
|
||||
wall_insulation_type=4,
|
||||
party_wall_construction=1,
|
||||
roof_construction=4,
|
||||
floor_dimensions=[
|
||||
make_floor_dimension(
|
||||
total_floor_area_m2=100.0, room_height_m=2.5,
|
||||
party_wall_length_m=5.0, heat_loss_perimeter_m=40.0, floor=0,
|
||||
),
|
||||
],
|
||||
)
|
||||
epc_g = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=100.0, country_code="ENG",
|
||||
sap_building_parts=[_make_part("G")],
|
||||
)
|
||||
epc_m = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=100.0, country_code="ENG",
|
||||
sap_building_parts=[_make_part("M")],
|
||||
)
|
||||
|
||||
# Act
|
||||
bridging_g = heat_transmission_from_cert(epc_g).thermal_bridging_w_per_k
|
||||
bridging_m = heat_transmission_from_cert(epc_m).thermal_bridging_w_per_k
|
||||
|
||||
# Assert — same geometry × (0.08 / 0.15) ratio = 0.533. Allow loose abs
|
||||
# tolerance because age M defaults a much better wall too.
|
||||
assert bridging_m < bridging_g
|
||||
assert bridging_m == pytest.approx(bridging_g * 0.08 / 0.15, abs=2.0)
|
||||
|
||||
|
||||
def test_main_plus_extension_sums_per_element_contributions() -> None:
|
||||
# Arrange — Main + single-storey age L extension. Each contributes to the
|
||||
# element totals. With_extension > main_only on every populated field
|
||||
# (walls, roof, floor, bridging) and on the total.
|
||||
main = make_building_part(
|
||||
identifier="Main Dwelling",
|
||||
construction_age_band="G",
|
||||
wall_construction=4, wall_insulation_type=4,
|
||||
party_wall_construction=1, roof_construction=4,
|
||||
floor_dimensions=[
|
||||
make_floor_dimension(
|
||||
total_floor_area_m2=100.0, room_height_m=2.5,
|
||||
party_wall_length_m=5.0, heat_loss_perimeter_m=40.0, floor=0,
|
||||
),
|
||||
],
|
||||
)
|
||||
extension = make_building_part(
|
||||
identifier="Extension 1",
|
||||
construction_age_band="L",
|
||||
wall_construction=4, wall_insulation_type=2, # filled cavity
|
||||
party_wall_construction=1, roof_construction=4,
|
||||
floor_dimensions=[
|
||||
make_floor_dimension(
|
||||
total_floor_area_m2=15.0, room_height_m=2.4,
|
||||
party_wall_length_m=0.0, heat_loss_perimeter_m=16.0, floor=0,
|
||||
),
|
||||
],
|
||||
)
|
||||
epc_main_only = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=100.0, country_code="ENG", sap_building_parts=[main],
|
||||
)
|
||||
epc_with_ext = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=115.0, country_code="ENG", sap_building_parts=[main, extension],
|
||||
)
|
||||
|
||||
# Act
|
||||
main_only = heat_transmission_from_cert(epc_main_only)
|
||||
with_ext = heat_transmission_from_cert(epc_with_ext)
|
||||
|
||||
# Assert
|
||||
assert with_ext.walls_w_per_k > main_only.walls_w_per_k
|
||||
assert with_ext.roof_w_per_k > main_only.roof_w_per_k
|
||||
assert with_ext.floor_w_per_k > main_only.floor_w_per_k
|
||||
assert with_ext.thermal_bridging_w_per_k > main_only.thermal_bridging_w_per_k
|
||||
assert with_ext.total_w_per_k > main_only.total_w_per_k
|
||||
Loading…
Add table
Reference in a new issue