From 0ba057587744d512137f3d62b911159c4659d3b5 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 2 Jun 2026 22:53:12 +0000 Subject: [PATCH] feat(modelling): shared gross heat-loss wall area geometry helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit domain/building_geometry.gross_heat_loss_wall_area(epc, identifier) sums heat_loss_perimeter x room_height across a building part's storeys — the heat-loss wall area (party walls excluded by construction), not total wall area. Lives outside the calculator so Modelling cost quantities can reuse it; the calculator computes the same quantity inline today and should be DRY'd onto this later (coordinated with the calculator branch). Pinned at 45.93 m^2 against the 000490 MAIN part. Toward #1155 cost (behaviour 4). pyright strict clean. Co-Authored-By: Claude Opus 4.8 --- domain/building_geometry.py | 33 ++++++++++++++++++++++++++ tests/domain/test_building_geometry.py | 22 +++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 domain/building_geometry.py create mode 100644 tests/domain/test_building_geometry.py diff --git a/domain/building_geometry.py b/domain/building_geometry.py new file mode 100644 index 00000000..a76e0468 --- /dev/null +++ b/domain/building_geometry.py @@ -0,0 +1,33 @@ +"""Building geometry derived purely from an EpcPropertyData. + +Reusable outside the SAP calculator (e.g. for Modelling cost quantities). +Today this re-derives the heat-loss wall area; the calculator computes the +same quantity inline (`heat_transmission._part_geometry`). A later, calculator- +branch-coordinated refactor should DRY the two onto this module so there is a +single source of truth. See the project memory on calculator geometry. +""" + +from datatypes.epc.domain.epc_property_data import ( + BuildingPartIdentifier, + EpcPropertyData, +) + + +def gross_heat_loss_wall_area( + epc: EpcPropertyData, identifier: BuildingPartIdentifier +) -> float: + """Gross external wall area of one building part, in m^2: the sum over its + storeys of heat-loss perimeter x room height. This is the heat-loss area + (party walls are excluded — they are not on the heat-loss perimeter); it is + not netted of window/door openings. + """ + part = next( + candidate + for candidate in epc.sap_building_parts + if candidate.identifier is identifier + ) + area = sum( + fd.heat_loss_perimeter_m * fd.room_height_m + for fd in part.sap_floor_dimensions + ) + return round(area, 2) diff --git a/tests/domain/test_building_geometry.py b/tests/domain/test_building_geometry.py new file mode 100644 index 00000000..816e334d --- /dev/null +++ b/tests/domain/test_building_geometry.py @@ -0,0 +1,22 @@ +"""Behaviour of shared building geometry derived from EpcPropertyData — +reusable outside the SAP calculator (e.g. for Modelling cost quantities).""" + +from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier +from domain.building_geometry import gross_heat_loss_wall_area +from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( + build_epc, +) + + +def test_gross_heat_loss_wall_area_sums_perimeter_times_height_per_storey() -> None: + # Arrange + # 000490 MAIN: floor 0 (perimeter 7.42 m x height 2.95 m) + floor 1 + # (7.42 m x 3.24 m) = 21.889 + 24.0408 = 45.93 m^2. Party walls are + # excluded by construction (heat-loss perimeter, not total perimeter). + epc = build_epc() + + # Act + area: float = gross_heat_loss_wall_area(epc, BuildingPartIdentifier.MAIN) + + # Assert + assert abs(area - 45.93) <= 0.01