From fcceeb27360cbce6932158bc8327a76a880fe5e4 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 15 Apr 2026 08:21:14 +0000 Subject: [PATCH] =?UTF-8?q?flatten=20sap=20property=20to=20excel=20row=20o?= =?UTF-8?q?bject=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ecmk_fetcher/tests/test_xml_processor.py | 93 ++++++++++++++++++- backend/ecmk_fetcher/xml_processor.py | 6 +- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/backend/ecmk_fetcher/tests/test_xml_processor.py b/backend/ecmk_fetcher/tests/test_xml_processor.py index 9f28062d..801676ec 100644 --- a/backend/ecmk_fetcher/tests/test_xml_processor.py +++ b/backend/ecmk_fetcher/tests/test_xml_processor.py @@ -1,4 +1,8 @@ -from backend.ecmk_fetcher.xml_processor import SapPropertyDetails, parse_rdsap +from backend.ecmk_fetcher.xml_processor import ( + SapPropertyDetails, + flatten_sap_property, + parse_rdsap, +) SAMPLE_XML = """ @@ -84,6 +88,39 @@ SAMPLE_XML = """ + + +
+ 5 + Somewhere + XY1 2AB +
+
+
+ + + 0 + + + Main Dwelling + + + 10.0 + 2.5 + 50.0 + 0 + 3.0 + + + + + + +
+""" + + def test_parse_rdsap_contract(): # arrange + act result: SapPropertyDetails = parse_rdsap(SAMPLE_XML) @@ -132,3 +169,57 @@ def test_parse_rdsap_contract(): }, ], } + + +def test_flatten_full(): + # Two building parts; Main Dwelling has two floors + full roof, + # Extension has one floor + partial roof (no thickness) + + # arrange + details: SapPropertyDetails = parse_rdsap(SAMPLE_XML) + + # act + result = flatten_sap_property(details) + + # assert + assert result == { + "address": "1, Fake Avenue, Random, AB24 5CD", + "property_type": "House", + "main_dwelling_floor_1_area_m2": 43.61, + "main_dwelling_floor_1_height_m": 2.46, + "main_dwelling_floor_1_heat_loss_perimeter_m": 25.31, + "main_dwelling_floor_1_party_wall_length_m": 0.0, + "main_dwelling_floor_2_area_m2": 42.33, + "main_dwelling_floor_2_height_m": 2.44, + "main_dwelling_floor_2_heat_loss_perimeter_m": 26.16, + "main_dwelling_floor_2_party_wall_length_m": 0.0, + "main_dwelling_roof_construction": 4, + "main_dwelling_roof_insulation_location": 2, + "main_dwelling_roof_insulation_thickness_mm": 100.0, + "extension_floor_1_area_m2": 4.46, + "extension_floor_1_height_m": 2.24, + "extension_floor_1_heat_loss_perimeter_m": 6.85, + "extension_floor_1_party_wall_length_m": 0.0, + "extension_roof_construction": 8, + "extension_roof_insulation_location": 7, + } + + +def test_flatten_no_roof(): + # Single building part with no roof — roof keys must be absent entirely + + # arrange + details: SapPropertyDetails = parse_rdsap(NO_ROOF_XML) + + # act + result = flatten_sap_property(details) + + # assert + assert result == { + "address": "5, Somewhere, XY1 2AB", + "property_type": "House", + "main_dwelling_floor_1_area_m2": 50.0, + "main_dwelling_floor_1_height_m": 2.5, + "main_dwelling_floor_1_heat_loss_perimeter_m": 10.0, + "main_dwelling_floor_1_party_wall_length_m": 3.0, + } diff --git a/backend/ecmk_fetcher/xml_processor.py b/backend/ecmk_fetcher/xml_processor.py index dd999095..38086fe8 100644 --- a/backend/ecmk_fetcher/xml_processor.py +++ b/backend/ecmk_fetcher/xml_processor.py @@ -1,5 +1,5 @@ import xml.etree.ElementTree as ET -from typing import List, Optional, TypedDict +from typing import Any, List, Optional, TypedDict from etl.xml_survey_extraction.XmlParser import PROPERTY_TYPE_LOOKUP @@ -170,3 +170,7 @@ def parse_rdsap(xml_string: str) -> SapPropertyDetails: } return result + + +def flatten_sap_property(details: SapPropertyDetails) -> dict[str, Any]: + raise NotImplementedError