From 1144b1e7b193f1b58d91a701363a25f343d2c545 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 3 Jul 2026 13:09:17 +0000 Subject: [PATCH] Make nested SapHeating.has_fixed_air_conditioning optional across all RdSAP schemas Some lodged certs omit the sap_heating.has_fixed_air_conditioning key, which the generic from_dict loader treats as a required field and rejects outright. Two prior commits (26651ca71, 8074f4152) fixed the duplicate top-level field of the same name but left this nested copy untouched; a third pass (44991bed0) missed it too. The mapper already reads it defensively (== "true", which is False on None), so this is a safe default. Surfaced by 7 failed modelling_e2e subtasks on portfolio 817. --- datatypes/epc/schema/rdsap_schema_17_0.py | 4 +++- datatypes/epc/schema/rdsap_schema_17_1.py | 6 ++++-- datatypes/epc/schema/rdsap_schema_18_0.py | 6 ++++-- datatypes/epc/schema/rdsap_schema_19_0.py | 6 ++++-- datatypes/epc/schema/rdsap_schema_20_0_0.py | 4 +++- datatypes/epc/schema/rdsap_schema_21_0_0.py | 6 ++++-- datatypes/epc/schema/rdsap_schema_21_0_1.py | 10 +++++++--- 7 files changed, 29 insertions(+), 13 deletions(-) diff --git a/datatypes/epc/schema/rdsap_schema_17_0.py b/datatypes/epc/schema/rdsap_schema_17_0.py index d66f9c699..445d51a77 100644 --- a/datatypes/epc/schema/rdsap_schema_17_0.py +++ b/datatypes/epc/schema/rdsap_schema_17_0.py @@ -44,7 +44,9 @@ class SapHeating: instantaneous_wwhrs: Optional[InstantaneousWwhrs] main_heating_details: List[MainHeatingDetail] immersion_heating_type: Union[int, str] - has_fixed_air_conditioning: str + # Nested duplicate of the outer schema's has_fixed_air_conditioning + # (already Optional below) — some certs omit it here too. + has_fixed_air_conditioning: Optional[str] = None # ADR-0028: cylinder_insulation_type is absent in 308/1000 17.0 certs. cylinder_insulation_type: Optional[int] = None cylinder_thermostat: Optional[str] = None diff --git a/datatypes/epc/schema/rdsap_schema_17_1.py b/datatypes/epc/schema/rdsap_schema_17_1.py index bbd04b791..c0a4c8e62 100644 --- a/datatypes/epc/schema/rdsap_schema_17_1.py +++ b/datatypes/epc/schema/rdsap_schema_17_1.py @@ -44,7 +44,9 @@ class SapHeating: instantaneous_wwhrs: Optional[InstantaneousWwhrs] main_heating_details: List[MainHeatingDetail] immersion_heating_type: Union[int, str] - has_fixed_air_conditioning: str + # Nested duplicate of the outer schema's has_fixed_air_conditioning + # (also made Optional below) — some certs omit it here too. + has_fixed_air_conditioning: Optional[str] = None # ADR-0028: 325/1000 omit cylinder_insulation_type — default it. cylinder_insulation_type: Optional[int] = None cylinder_thermostat: Optional[str] = None @@ -247,7 +249,7 @@ class RdSapSchema17_1: hot_water_cost_potential: CostAmount renewable_heat_incentive: RenewableHeatIncentive energy_consumption_current: int - has_fixed_air_conditioning: str + has_fixed_air_conditioning: Optional[str] = None multiple_glazed_proportion: int calculation_software_version: str energy_consumption_potential: int diff --git a/datatypes/epc/schema/rdsap_schema_18_0.py b/datatypes/epc/schema/rdsap_schema_18_0.py index f35f0733c..a7a9d8702 100644 --- a/datatypes/epc/schema/rdsap_schema_18_0.py +++ b/datatypes/epc/schema/rdsap_schema_18_0.py @@ -44,7 +44,9 @@ class SapHeating: instantaneous_wwhrs: Optional[InstantaneousWwhrs] main_heating_details: List[MainHeatingDetail] immersion_heating_type: Union[int, str] - has_fixed_air_conditioning: str + # Nested duplicate of the outer schema's has_fixed_air_conditioning + # (also made Optional below) — some certs omit it here too. + has_fixed_air_conditioning: Optional[str] = None cylinder_insulation_type: Optional[int] = None cylinder_thermostat: Optional[str] = None secondary_fuel_type: Optional[int] = None @@ -266,7 +268,7 @@ class RdSapSchema18_0: hot_water_cost_potential: CostAmount renewable_heat_incentive: RenewableHeatIncentive energy_consumption_current: int - has_fixed_air_conditioning: str + has_fixed_air_conditioning: Optional[str] = None multiple_glazed_proportion: int calculation_software_version: str energy_consumption_potential: int diff --git a/datatypes/epc/schema/rdsap_schema_19_0.py b/datatypes/epc/schema/rdsap_schema_19_0.py index 2ba0f3cbb..4e0c21414 100644 --- a/datatypes/epc/schema/rdsap_schema_19_0.py +++ b/datatypes/epc/schema/rdsap_schema_19_0.py @@ -44,7 +44,9 @@ class SapHeating: instantaneous_wwhrs: Optional[InstantaneousWwhrs] main_heating_details: List[MainHeatingDetail] immersion_heating_type: Union[int, str] - has_fixed_air_conditioning: str + # Nested duplicate of the outer schema's has_fixed_air_conditioning + # (also made Optional below) — some certs omit it here too. + has_fixed_air_conditioning: Optional[str] = None cylinder_insulation_type: Optional[int] = None cylinder_thermostat: Optional[str] = None secondary_fuel_type: Optional[int] = None @@ -281,7 +283,7 @@ class RdSapSchema19_0: # 19.0-specific block, absent in 713/1000 — Optional + default. windows_transmission_details: Optional[WindowsTransmissionDetails] = None energy_consumption_current: int - has_fixed_air_conditioning: str + has_fixed_air_conditioning: Optional[str] = None multiple_glazed_proportion: int calculation_software_version: str energy_consumption_potential: int diff --git a/datatypes/epc/schema/rdsap_schema_20_0_0.py b/datatypes/epc/schema/rdsap_schema_20_0_0.py index 395fb9b92..8fae99371 100644 --- a/datatypes/epc/schema/rdsap_schema_20_0_0.py +++ b/datatypes/epc/schema/rdsap_schema_20_0_0.py @@ -56,7 +56,9 @@ class SapHeating: instantaneous_wwhrs: Optional[InstantaneousWwhrs] main_heating_details: List[MainHeatingDetail] immersion_heating_type: Union[int, str] - has_fixed_air_conditioning: str + # Nested duplicate of the outer schema's has_fixed_air_conditioning + # (already Optional below) — some certs omit it here too. + has_fixed_air_conditioning: Optional[str] = None cylinder_insulation_type: Optional[int] = None cylinder_thermostat: Optional[str] = None secondary_fuel_type: Optional[int] = None diff --git a/datatypes/epc/schema/rdsap_schema_21_0_0.py b/datatypes/epc/schema/rdsap_schema_21_0_0.py index 2f5c8e69b..4588ee7f6 100644 --- a/datatypes/epc/schema/rdsap_schema_21_0_0.py +++ b/datatypes/epc/schema/rdsap_schema_21_0_0.py @@ -63,7 +63,9 @@ class SapHeating: water_heating_fuel: int main_heating_details: List[MainHeatingDetail] immersion_heating_type: Union[int, str] - has_fixed_air_conditioning: str + # Nested duplicate of the outer schema's has_fixed_air_conditioning + # (also made Optional below) — some certs omit it here too. + has_fixed_air_conditioning: Optional[str] = None instantaneous_wwhrs: Optional[InstantaneousWwhrs] = None # Real-API certs carry shower_outlets as a list, not the synthetic # single-object form; list elements are normalised to the wrapped @@ -459,7 +461,7 @@ class RdSapSchema21_0_0: windows_transmission_details: Optional[WindowsTransmissionDetails] = None cfl_fixed_lighting_bulbs_count: Optional[int] = None energy_consumption_current: int - has_fixed_air_conditioning: str + has_fixed_air_conditioning: Optional[str] = None multiple_glazed_proportion: int calculation_software_version: str energy_consumption_potential: int diff --git a/datatypes/epc/schema/rdsap_schema_21_0_1.py b/datatypes/epc/schema/rdsap_schema_21_0_1.py index 45f1ee465..4b814d7fb 100644 --- a/datatypes/epc/schema/rdsap_schema_21_0_1.py +++ b/datatypes/epc/schema/rdsap_schema_21_0_1.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List, Optional, Union from .common import DescriptionV1, Measurement @@ -64,7 +64,9 @@ class SapHeating: water_heating_fuel: int main_heating_details: List[MainHeatingDetail] immersion_heating_type: Union[int, str] - has_fixed_air_conditioning: str + # Nested duplicate of the outer schema's has_fixed_air_conditioning + # (also made Optional below) — some certs omit it here too. + has_fixed_air_conditioning: Optional[str] = None instantaneous_wwhrs: Optional[InstantaneousWwhrs] = None # Real-API certs carry shower_outlets as a list, not the synthetic single-object form; # accept both shapes so older fixtures keep parsing. List elements @@ -510,7 +512,9 @@ class RdSapSchema21_0_1: renewable_heat_incentive: RenewableHeatIncentive draughtproofed_door_count: int energy_consumption_current: int - has_fixed_air_conditioning: str + # kw_only on just this field: the class isn't kw_only overall, and several + # required fields (calculation_software_version etc.) already follow it. + has_fixed_air_conditioning: Optional[str] = field(default=None, kw_only=True) calculation_software_version: str energy_consumption_potential: int environmental_impact_current: int