from backend.ecmk_fetcher.xml_processor import (
SapPropertyDetails,
flatten_sap_property,
parse_rdsap,
)
SAMPLE_XML = """
1
Fake Avenue
Random
AB24 5CD
0
1
Main Dwelling
C
7
4
2
100mm
4
4
25.31
2.46
43.61
0
0
26.16
2.44
42.33
1
0
2
Extension
C
8
7
AB
3
4
6.85
2.24
4.46
0
0
"""
NO_ROOF_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)
# assert
assert result == {
"reference": "1AB245CD",
"address": "1, Fake Avenue, Random, AB24 5CD",
"property_type": "House",
"building_parts": [
{
"identifier": "Main Dwelling",
"floors": [
{
"area_m2": 43.61,
"height_m": 2.46,
"heat_loss_perimeter_m": 25.31,
"party_wall_length_m": 0.0,
},
{
"area_m2": 42.33,
"height_m": 2.44,
"heat_loss_perimeter_m": 26.16,
"party_wall_length_m": 0.0,
},
],
"roof": {
"construction": 4,
"insulation_location": 2,
"insulation_thickness_mm": 100.0,
},
},
{
"identifier": "Extension",
"floors": [
{
"area_m2": 4.46,
"height_m": 2.24,
"heat_loss_perimeter_m": 6.85,
"party_wall_length_m": 0.0,
}
],
"roof": {
"construction": 8,
"insulation_location": 7,
},
},
],
}
ND_THICKNESS_XML = """
1
Somewhere
AB1 2CD
0
Main Dwelling
4
2
ND
10.0
2.5
50.0
0
0
"""
ND_INSULATION_LOCATION_XML = """
1
Somewhere
AB1 2CD
0
Main Dwelling
4
ND
250
10.0
2.5
50.0
0
0
"""
def test_parse_rdsap_nd_thickness():
# 'ND' (not determined) is a valid value in the wild for Roof-Insulation-Thickness
# — it should be retained as-is rather than raising
# arrange + act
result: SapPropertyDetails = parse_rdsap(ND_THICKNESS_XML)
# assert
assert result["building_parts"][0]["roof"] == {
"construction": 4,
"insulation_location": 2,
"insulation_thickness_mm": "ND",
}
def test_parse_rdsap_nd_location():
# 'ND' (not determined) is a valid value in the wild for Roof-Insulation-Location
# — it should be retained as-is rather than raising
# arrange + act
result: SapPropertyDetails = parse_rdsap(ND_INSULATION_LOCATION_XML)
# assert
assert result["building_parts"][0]["roof"] == {
"construction": 4,
"insulation_location": "ND",
"insulation_thickness_mm": 250,
}
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 == {
"reference": "1AB245CD",
"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 == {
"reference": "5XY12AB",
"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,
}