"""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) def roof_area(epc: EpcPropertyData, identifier: BuildingPartIdentifier) -> float: """Roof area of one building part, in m^2. Per RdSAP10 §3.8 the roof area is the greatest of the part's per-storey floor areas (not the top-floor area, which can be smaller).""" part = next( candidate for candidate in epc.sap_building_parts if candidate.identifier is identifier ) return round( max(fd.total_floor_area_m2 for fd in part.sap_floor_dimensions), 2 ) def ground_floor_area( epc: EpcPropertyData, identifier: BuildingPartIdentifier ) -> float: """Ground-floor area of one building part, in m^2 — the area of its lowest floor (``floor == 0``), the surface a ground-floor insulation measure treats.""" part = next( candidate for candidate in epc.sap_building_parts if candidate.identifier is identifier ) ground = next(fd for fd in part.sap_floor_dimensions if fd.floor == 0) return round(ground.total_floor_area_m2, 2)