diff --git a/datatypes/epc/domain/epc_property_data.py b/datatypes/epc/domain/epc_property_data.py index d18b2341..c75e730f 100644 --- a/datatypes/epc/domain/epc_property_data.py +++ b/datatypes/epc/domain/epc_property_data.py @@ -427,6 +427,7 @@ class SapBuildingPart: floor_u_value_known: Optional[bool] = None roof_construction: Optional[int] = None + roof_construction_type: Optional[str] = None # str from site notes e.g. "PS Pitched, sloping ceiling" roof_insulation_location: Optional[Union[int, str]] = ( None # TODO: make enum/mapping? ) diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 937100fc..d6d44462 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -2178,6 +2178,7 @@ def _map_elmhurst_building_part( party_wall_construction=_elmhurst_party_wall_construction_int(walls.party_wall_type), sap_floor_dimensions=floor_dims, wall_thickness_mm=walls.thickness_mm, + roof_construction_type=_strip_code(roof.roof_type), roof_insulation_location=_strip_code(roof.insulation), roof_insulation_thickness=_resolve_sloping_ceiling_thickness( roof, age_code, diff --git a/packages/domain/src/domain/sap/worksheet/heat_transmission.py b/packages/domain/src/domain/sap/worksheet/heat_transmission.py index 1c302c61..01d0be61 100644 --- a/packages/domain/src/domain/sap/worksheet/heat_transmission.py +++ b/packages/domain/src/domain/sap/worksheet/heat_transmission.py @@ -68,7 +68,7 @@ from domain.ml.rdsap_uvalues import ( u_wall, u_window, ) -from math import floor, sqrt +from math import cos, floor, radians, sqrt def _round_half_up(value: float, dp: int) -> float: @@ -96,6 +96,10 @@ _WINDOW_CURTAIN_RESISTANCE_M2K_PER_W: Final[float] = 0.04 # rounding policy — applied to gross wall / roof / floor / party / window # / door / alt-wall / RR sub-area inputs to the §3 cascade. _AREA_ROUND_DP: Final[int] = 2 +# RdSAP 10 §3.8 "Roof area" — pitched-sloping-ceiling roofs use the +# inclined surface area (floor area divided by cos(30°)) rather than +# the horizontal projection. +_COS_30_DEG: Final[float] = cos(radians(30.0)) @dataclass(frozen=True) @@ -538,10 +542,15 @@ def heat_transmission_from_cert( rw_area_part = ( _round_half_up(roof_windows_area_total, _AREA_ROUND_DP) if i == 0 else 0.0 ) - gross_roof_area = _round_half_up( - geom["top_floor_area_m2"] if exposure.has_exposed_roof else 0.0, - _AREA_ROUND_DP, - ) + # RdSAP 10 §3.8 "Roof area": roof area is the greatest of the + # floor areas on each level. For a pitched roof with a sloping + # ceiling, divide that area by cos(30°) — the worksheet enters + # the inclined surface area, not the horizontal projection. + top_floor_area = geom["top_floor_area_m2"] if exposure.has_exposed_roof else 0.0 + roof_type = (part.roof_construction_type or "").lower() + if "sloping ceiling" in roof_type: + top_floor_area = top_floor_area / _COS_30_DEG + gross_roof_area = _round_half_up(top_floor_area, _AREA_ROUND_DP) roof_area = max(0.0, gross_roof_area - rw_area_part) floor_area_total = _round_half_up( geom["ground_floor_area_m2"] if exposure.has_exposed_floor else 0.0,