diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 90428bb2e..e3cdde0be 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -2420,7 +2420,7 @@ class EpcPropertyDataMapper: sap_version=schema.sap_version, dwelling_type=schema.dwelling_type, property_type=str(schema.property_type), - built_form=str(schema.built_form), + built_form=None if schema.built_form is None else str(schema.built_form), address_line_1=schema.address_line_1, address_line_2=schema.address_line_2, postcode=schema.postcode, diff --git a/datatypes/epc/schema/rdsap_schema_21_0_1.py b/datatypes/epc/schema/rdsap_schema_21_0_1.py index 4b814d7fb..c0d219568 100644 --- a/datatypes/epc/schema/rdsap_schema_21_0_1.py +++ b/datatypes/epc/schema/rdsap_schema_21_0_1.py @@ -460,7 +460,6 @@ class RdSapSchema21_0_1: postcode: str hot_water: EnergyElement post_town: str - built_form: int door_count: int region_code: int report_type: int @@ -512,9 +511,16 @@ class RdSapSchema21_0_1: renewable_heat_incentive: RenewableHeatIncentive draughtproofed_door_count: int energy_consumption_current: int - # kw_only on just this field: the class isn't kw_only overall, and several - # required fields (calculation_software_version etc.) already follow it. + # kw_only on just these fields: the class isn't kw_only overall, and several + # required fields (calculation_software_version etc.) already follow them. has_fixed_air_conditioning: Optional[str] = field(default=None, kw_only=True) + # Some real-API certs omit `built_form` entirely (e.g. cert + # 2461-5385-9264-6821-5357, property_id=750232, portfolio 824) rather than + # sending a code — `from_dict` otherwise hard-fails the whole cert. The + # mapper/SAP cascade (`_api_sheltered_sides`) already treats a non-int + # built_form as "no lodging" and falls back to the documented default, so + # None here is an already-safe degradation, not a new failure mode. + built_form: Optional[int] = field(default=None, kw_only=True) calculation_software_version: str energy_consumption_potential: int environmental_impact_current: int diff --git a/datatypes/epc/schema/tests/test_schema_loading.py b/datatypes/epc/schema/tests/test_schema_loading.py index 3f2759bf8..ea48b092a 100644 --- a/datatypes/epc/schema/tests/test_schema_loading.py +++ b/datatypes/epc/schema/tests/test_schema_loading.py @@ -400,3 +400,17 @@ class TestRdSapSchema21_0_1AgainstRealApiCert: assert epc.schema_type == "RdSAP-Schema-21.0.1" assert epc.sap_heating is not None assert len(epc.sap_windows) > 0 + + def test_cert_with_no_built_form_key_parses_as_none(self) -> None: + # Arrange — some real gov-API certs omit `built_form` entirely (not + # null: absent). property_id=750232 (portfolio 824) hit exactly this + # on cert 2461-5385-9264-6821-5357 and failed to parse at all. + real_doc = load("21_0_1_real.json") + del real_doc["built_form"] + + # Act + epc = from_dict(RdSapSchema21_0_1, real_doc) + + # Assert — parsing no longer hard-fails; the field degrades to None, + # same as the mapper/SAP-cascade already do for a non-int built_form. + assert epc.built_form is None