diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 5c785343f..1971fa79c 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -4029,6 +4029,17 @@ def _normalize_sap_schema_16_x(data: Dict[str, Any]) -> Dict[str, Any]: "true" if cylinder_size != 1 else "false" ) + # The inverse gap: some SAP-Schema-15.0 certs (e.g. cert + # 0685-2881-6867-9029-1761, UPRN 100020699807) lodge + # `has_hot_water_cylinder: "false"` but omit `sap_heating.cylinder_size` + # entirely, rather than lodging it as code 1 ("no cylinder"). Same + # RdSAP 10 §10.5 fact as above, just the other field missing this + # time — derive the code-1 default so `RdSapSchema17_1` (which + # requires `cylinder_size` as an independent field) doesn't fail + # loud on a cert that has already told us there's no cylinder. + if d.get("has_hot_water_cylinder") == "false" and "cylinder_size" not in heating: + heating["cylinder_size"] = 1 + for bp in _dicts(d.get("sap_building_parts")): for fd in _dicts(bp.get("sap_floor_dimensions")): fd.setdefault("party_wall_length", 0) 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 82be846d8..0397644e2 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 @@ -122,3 +122,21 @@ class TestFromSapSchema15_0: epc = EpcPropertyDataMapper.from_api_response(data) assert epc.has_hot_water_cylinder is True + + def test_derives_cylinder_size_when_has_hot_water_cylinder_is_false( + self, + ) -> None: + # Task 9d271e98-96e6-4be6-bb50-e9be6f954003 (portfolio 796 / scenario + # 1268) failed property_id 715358 (UPRN 100020699807) with + # "SapHeating: missing required field 'cylinder_size'": cert + # 0685-2881-6867-9029-1761 lodges `has_hot_water_cylinder: "false"` + # but omits `sap_heating.cylinder_size` entirely, the inverse gap of + # `test_derives_has_hot_water_cylinder_from_cylinder_size_when_absent` + # above. Same RdSAP 10 §10.5 fact, so code 1 ("no cylinder") must be + # derived rather than left to fail loud. + epc = EpcPropertyDataMapper.from_api_response( + load("sap_15_0_uprn_100020699807.json") + ) + + assert epc.has_hot_water_cylinder is False + assert epc.sap_heating.cylinder_size == 1 diff --git a/datatypes/epc/schema/tests/fixtures/sap_15_0_uprn_100020699807.json b/datatypes/epc/schema/tests/fixtures/sap_15_0_uprn_100020699807.json new file mode 100644 index 000000000..f95f33acd --- /dev/null +++ b/datatypes/epc/schema/tests/fixtures/sap_15_0_uprn_100020699807.json @@ -0,0 +1,338 @@ +{ + "uprn": 100020699807, + "roofs": [ + { + "description": "Pitched, 50 mm loft insulation", + "energy_efficiency_rating": 2, + "environmental_efficiency_rating": 2 + }, + { + "description": "Pitched, insulated at rafters", + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "walls": [ + { + "description": "Solid brick, as built, no insulation (assumed)", + "energy_efficiency_rating": 1, + "environmental_efficiency_rating": 1 + }, + { + "description": "Solid brick, as built, insulated (assumed)", + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + { + "description": "Timber frame, as built, insulated (assumed)", + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "floors": [ + { + "description": "Suspended, no insulation (assumed)", + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + { + "description": "Solid, no insulation (assumed)", + "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 38% of fixed outlets", + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "postcode": "CR7 8HW", + "hot_water": { + "description": "From main system", + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "post_town": "THORNTON HEATH", + "built_form": 3, + "created_at": "2011-07-05 13:20:20", + "glazed_area": 1, + "region_code": 17, + "report_type": 2, + "sap_heating": { + "water_heating_code": 901, + "water_heating_fuel": 26, + "main_heating_details": [ + { + "main_fuel_type": 26, + "boiler_flue_type": 2, + "fan_flue_present": "Y", + "heat_emitter_type": 1, + "boiler_index_number": 8829, + "main_heating_number": 1, + "main_heating_control": 2107, + "main_heating_category": 2, + "main_heating_fraction": 1.0, + "main_heating_data_source": 1 + } + ], + "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": "End-terrace house", + "language_code": 1, + "property_type": 0, + "address_line_1": "123, Northwood Road", + "schema_version": "LIG-15.0", + "assessment_type": "RdSAP", + "completion_date": "2011-07-05", + "inspection_date": "2011-06-29", + "extensions_count": 3, + "measurement_type": 1, + "total_floor_area": 126, + "transaction_type": 3, + "conservatory_type": 1, + "heated_room_count": 6, + "registration_date": "2011-07-05", + "restricted_access": 0, + "sap_energy_source": { + "main_gas": "Y", + "meter_type": 1, + "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": 7, + "roof_construction": 7, + "wall_construction": 3, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": 2.71, + "floor_insulation": 0, + "total_floor_area": 34.64, + "floor_construction": 2, + "heat_loss_perimeter": 13.58 + }, + { + "floor": 1, + "room_height": 2.57, + "total_floor_area": 26.11, + "heat_loss_perimeter": 7.0 + } + ], + "wall_insulation_type": 4, + "construction_age_band": "B", + "roof_insulation_location": "ND", + "roof_insulation_thickness": "ND" + }, + { + "identifier": "Extension 1", + "floor_heat_loss": 7, + "roof_construction": 4, + "wall_construction": 3, + "building_part_number": 2, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": 2.55, + "floor_insulation": 0, + "total_floor_area": 16.03, + "floor_construction": 1, + "heat_loss_perimeter": 8.32 + }, + { + "floor": 1, + "room_height": 2.53, + "total_floor_area": 16.03, + "heat_loss_perimeter": 8.32 + } + ], + "wall_insulation_type": 4, + "construction_age_band": "B", + "roof_insulation_location": 2, + "roof_insulation_thickness": "50mm" + }, + { + "identifier": "Extension 2", + "floor_heat_loss": 8, + "roof_construction": 4, + "wall_construction": 3, + "building_part_number": 3, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": 2.57, + "total_floor_area": 7.69, + "heat_loss_perimeter": 6.26 + } + ], + "wall_insulation_type": 4, + "construction_age_band": "B", + "roof_insulation_location": 1, + "roof_insulation_thickness": "NI", + "rafter_insulation_thickness": "150mm" + }, + { + "identifier": "Extension 3", + "floor_heat_loss": 8, + "roof_construction": 4, + "wall_construction": 3, + "building_part_number": 4, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": 1.89, + "total_floor_area": 25.25, + "heat_loss_perimeter": 20.26 + } + ], + "wall_insulation_type": 4, + "construction_age_band": "I", + "roof_insulation_location": 1, + "roof_insulation_thickness": "NI", + "rafter_insulation_thickness": "150mm" + }, + { + "wall_area": 14.25, + "wall_location": 3, + "wall_construction": 5, + "wall_insulation_type": 4 + } + ], + "low_energy_lighting": 37, + "solar_water_heating": "N", + "bedf_revision_number": 310, + "habitable_room_count": 6, + "heating_cost_current": 1001, + "co2_emissions_current": 6.3, + "energy_rating_current": 57, + "lighting_cost_current": 99, + "main_heating_controls": [ + { + "description": "Programmer, TRVs and bypass", + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + } + ], + "multiple_glazing_type": 3, + "open_fireplaces_count": 0, + "has_hot_water_cylinder": "false", + "heating_cost_potential": 862, + "hot_water_cost_current": 100, + "mechanical_ventilation": 0, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": 32, + "indicative_cost": "\u00a325", + "improvement_type": "E", + "improvement_details": { + "improvement_number": 35 + }, + "improvement_category": 1, + "energy_performance_rating": 58, + "environmental_impact_rating": 52 + }, + { + "sequence": 2, + "typical_saving": 59, + "indicative_cost": "\u00a3350 - \u00a3450", + "improvement_type": "G", + "improvement_details": { + "improvement_number": 14 + }, + "improvement_category": 1, + "energy_performance_rating": 60, + "environmental_impact_rating": 54 + }, + { + "sequence": 3, + "typical_saving": 97, + "indicative_cost": "\u00a31,500 - \u00a33,500", + "improvement_type": "I", + "improvement_details": { + "improvement_number": 20 + }, + "improvement_category": 2, + "energy_performance_rating": 63, + "environmental_impact_rating": 59 + }, + { + "sequence": 4, + "typical_saving": 219, + "indicative_cost": "\u00a35,500 - \u00a314,500", + "improvement_type": "Q", + "improvement_details": { + "improvement_number": 7 + }, + "improvement_category": 3, + "energy_performance_rating": 71, + "environmental_impact_rating": 69 + }, + { + "sequence": 5, + "typical_saving": 214, + "indicative_cost": "\u00a311,000 - \u00a320,000", + "improvement_type": "U", + "improvement_details": { + "improvement_number": 34 + }, + "improvement_category": 3, + "energy_performance_rating": 79, + "environmental_impact_rating": 76 + } + ], + "co2_emissions_potential": 5.2, + "energy_rating_potential": 63, + "lighting_cost_potential": 61, + "hot_water_cost_potential": 89, + "renewable_heat_incentive": { + "water_heating": 2302, + "space_heating_existing_dwelling": 18083, + "space_heating_with_loft_insulation": 17854, + "space_heating_with_loft_and_cavity_insulation": 17854 + }, + "seller_commission_report": "Y", + "energy_consumption_current": 259, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "8.0.0", + "energy_consumption_potential": 216, + "environmental_impact_current": 51, + "fixed_lighting_outlets_count": 16, + "current_energy_efficiency_band": "D", + "environmental_impact_potential": 59, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "D", + "co2_emissions_current_per_floor_area": 50, + "low_energy_fixed_lighting_outlets_count": 6 +} \ No newline at end of file