From 7f7dcbc05987401cd32220519813e9d1ac751b2e Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Tue, 14 Jul 2026 16:15:35 +0000 Subject: [PATCH] Default description on bare zero-rated party-wall elements in SAP-15.0 certs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 9d271e98-96e6-4be6-bb50-e9be6f954003 (portfolio 796) failed 21 "Top-floor flat" properties with "EnergyElement: missing required field 'description'". Every one lodges a second walls[] entry that is a bare zero-rated party-wall boundary element ({"energy_efficiency_rating": 0, "environmental_efficiency_rating": 0}, no description key at all) — unlike the sibling floors/roofs "(other premises above/below)" sentinel convention, which does carry text. Confirmed schema-wide across all 21 failing certs. Default description to "" rather than fabricate construction text: _joined_descriptions (heat_transmission.py) already filters out falsy descriptions, so this is calc-neutral — it only unblocks the parse. Co-Authored-By: Claude Sonnet 5 --- datatypes/epc/domain/mapper.py | 22 ++ .../domain/tests/test_from_sap_schema_15_0.py | 20 ++ .../fixtures/sap_15_0_uprn_100061740136.json | 225 ++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 datatypes/epc/schema/tests/fixtures/sap_15_0_uprn_100061740136.json diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 1971fa79c..a39776640 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -3913,6 +3913,28 @@ def _normalize_sap_schema_16_x(data: Dict[str, Any]) -> Dict[str, Any]: items: List[Any] = cast(List[Any], value) return [cast(Dict[str, Any], x) for x in items if isinstance(x, dict)] + # Every "Top-floor flat" SAP-Schema-15.0 cert we've seen (confirmed on 21 + # real certs from task 9d271e98-96e6-4be6-bb50-e9be6f954003, portfolio + # 796 — e.g. cert 8204-4998-7729-4026-5693, UPRN 100061740136) lodges a + # second `walls[]` entry that is a bare zero-rated party-wall boundary + # element — `{"energy_efficiency_rating": 0, + # "environmental_efficiency_rating": 0}`, no `description` key at all — + # unlike the sibling `floors`/`roofs` "(other premises above/below)" + # sentinel convention, which DOES carry text. RdSapSchema17_1 requires + # `description`. Default it to "" rather than fabricate construction + # text: `_joined_descriptions` (heat_transmission.py) already filters out + # falsy descriptions, so a rating-0/no-description element contributes + # nothing to the wall U-value derivation either way — this default is + # calc-neutral, purely unblocking the parse. + for elements_key in ("roofs", "walls", "floors", "windows"): + for element in _dicts(d.get(elements_key)): + if ( + "description" not in element + and element.get("energy_efficiency_rating") == 0 + and element.get("environmental_efficiency_rating") == 0 + ): + element["description"] = "" + windows: Any = d.get("windows") if isinstance(windows, list) and windows: window_list: List[Any] = cast(List[Any], windows) diff --git a/datatypes/epc/domain/tests/test_from_sap_schema_15_0.py b/datatypes/epc/domain/tests/test_from_sap_schema_15_0.py index 0397644e2..ef8658ea8 100644 --- a/datatypes/epc/domain/tests/test_from_sap_schema_15_0.py +++ b/datatypes/epc/domain/tests/test_from_sap_schema_15_0.py @@ -140,3 +140,23 @@ class TestFromSapSchema15_0: assert epc.has_hot_water_cylinder is False assert epc.sap_heating.cylinder_size == 1 + + def test_defaults_description_on_a_bare_zero_rated_party_wall(self) -> None: + # Task 9d271e98-96e6-4be6-bb50-e9be6f954003 (portfolio 796 / scenario + # 1268) failed 21 "Top-floor flat" properties with "EnergyElement: + # missing required field 'description'": every one lodges a second + # `walls[]` entry that is a bare zero-rated party-wall boundary + # element (`{"energy_efficiency_rating": 0, + # "environmental_efficiency_rating": 0}`, no description key at all) + # — property_id 735232 / UPRN 100061740136 / cert + # 8204-4998-7729-4026-5693 here. Must default to "" rather than fail + # loud (calc-neutral — `_joined_descriptions` already drops falsy + # descriptions). + epc = EpcPropertyDataMapper.from_api_response( + load("sap_15_0_uprn_100061740136.json") + ) + + assert len(epc.walls) == 2 + assert epc.walls[0].description == "Cavity wall, as built, no insulation (assumed)" + assert epc.walls[1].description == "" + assert epc.walls[1].energy_efficiency_rating == 0 diff --git a/datatypes/epc/schema/tests/fixtures/sap_15_0_uprn_100061740136.json b/datatypes/epc/schema/tests/fixtures/sap_15_0_uprn_100061740136.json new file mode 100644 index 000000000..13ad74d76 --- /dev/null +++ b/datatypes/epc/schema/tests/fixtures/sap_15_0_uprn_100061740136.json @@ -0,0 +1,225 @@ +{ + "uprn": 100061740136, + "roofs": [ + { + "description": "Pitched, no insulation(assumed)", + "energy_efficiency_rating": 1, + "environmental_efficiency_rating": 1 + } + ], + "walls": [ + { + "description": "Cavity wall, as built, no insulation (assumed)", + "energy_efficiency_rating": 2, + "environmental_efficiency_rating": 2 + }, + { + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "floors": [ + { + "description": "(other premises below)", + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "status": "entered", + "windows": [ + { + "description": "Fully double glazed", + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + } + ], + "lighting": { + "description": "Low energy lighting in all fixed outlets", + "energy_efficiency_rating": 5, + "environmental_efficiency_rating": 5 + }, + "postcode": "PO19 3NG", + "hot_water": { + "description": "From main system", + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "post_town": "CHICHESTER", + "built_form": 3, + "created_at": "2011-06-25 16:07:44", + "glazed_area": 1, + "region_code": 14, + "report_type": 2, + "sap_heating": { + "cylinder_size": 1, + "water_heating_code": 901, + "water_heating_fuel": 26, + "main_heating_details": [ + { + "main_fuel_type": 26, + "boiler_flue_type": 2, + "fan_flue_present": "N", + "heat_emitter_type": 1, + "main_heating_number": 1, + "main_heating_control": 2107, + "main_heating_category": 2, + "main_heating_fraction": 1, + "main_heating_data_source": 2 + } + ], + "has_fixed_air_conditioning": "false" + }, + "sap_version": 9.9, + "schema_type": "SAP-Schema-15.0", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + { + "description": "Boiler and radiators, mains gas", + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "dwelling_type": "Top-floor flat", + "language_code": 1, + "property_type": 2, + "address_line_1": "22, Duncan Road", + "schema_version": "LIG-15.0", + "assessment_type": "RdSAP", + "completion_date": "2011-06-25", + "inspection_date": "2011-06-21", + "extensions_count": 0, + "measurement_type": 1, + "sap_flat_details": { + "level": 3, + "top_storey": "Y", + "flat_location": 0, + "heat_loss_corridor": 2, + "unheated_corridor_length": 8.49 + }, + "total_floor_area": 30, + "transaction_type": 3, + "conservatory_type": 1, + "heated_room_count": 1, + "registration_date": "2011-06-25", + "restricted_access": 0, + "sap_energy_source": { + "main_gas": "Y", + "meter_type": 2, + "photovoltaic_supply": { + "percent_roof_area": 0 + }, + "wind_turbines_count": 0, + "wind_turbines_terrain_type": 2 + }, + "secondary_heating": { + "description": "None", + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "floor_heat_loss": 6, + "roof_construction": 5, + "wall_construction": 4, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": 2.28, + "total_floor_area": 29.64, + "heat_loss_perimeter": 18.41 + } + ], + "wall_insulation_type": 4, + "construction_age_band": "D", + "roof_insulation_location": 4, + "roof_insulation_thickness": "NI" + } + ], + "low_energy_lighting": 100, + "solar_water_heating": "N", + "bedf_revision_number": 310, + "habitable_room_count": 1, + "heating_cost_current": 457, + "co2_emissions_current": 2.5, + "energy_rating_current": 55, + "lighting_cost_current": 18, + "main_heating_controls": [ + { + "description": "Programmer, TRVs and bypass", + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + } + ], + "multiple_glazing_type": 1, + "open_fireplaces_count": 0, + "has_hot_water_cylinder": "false", + "heating_cost_potential": 361, + "hot_water_cost_current": 66, + "mechanical_ventilation": 0, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": 32, + "indicative_cost": "?100 - ?300", + "improvement_type": "B", + "improvement_details": { + "improvement_number": 6 + }, + "improvement_category": 1, + "energy_performance_rating": 58, + "environmental_impact_rating": 58 + }, + { + "sequence": 2, + "typical_saving": 24, + "indicative_cost": "?350 - ?450", + "improvement_type": "G", + "improvement_details": { + "improvement_number": 14 + }, + "improvement_category": 1, + "energy_performance_rating": 60, + "environmental_impact_rating": 61 + }, + { + "sequence": 3, + "typical_saving": 52, + "indicative_cost": "?1,500 - ?3,500", + "improvement_type": "I", + "improvement_details": { + "improvement_number": 20 + }, + "improvement_category": 2, + "energy_performance_rating": 64, + "environmental_impact_rating": 67 + } + ], + "co2_emissions_potential": 1.9, + "energy_rating_potential": 64, + "lighting_cost_potential": 18, + "hot_water_cost_potential": 55, + "renewable_heat_incentive": { + "water_heating": 1418, + "space_heating_existing_dwelling": 6225, + "space_heating_with_loft_insulation": 3572, + "space_heating_with_cavity_insulation": 5600, + "space_heating_with_loft_and_cavity_insulation": 2891 + }, + "seller_commission_report": "Y", + "energy_consumption_current": 438, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "1.3.1.0", + "energy_consumption_potential": 326, + "environmental_impact_current": 55, + "fixed_lighting_outlets_count": 4, + "current_energy_efficiency_band": "D", + "environmental_impact_potential": 67, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "D", + "co2_emissions_current_per_floor_area": 85, + "low_energy_fixed_lighting_outlets_count": 4 +} \ No newline at end of file