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.
This commit is contained in:
Jun-te Kim 2026-07-03 13:09:17 +00:00
parent ebca28f704
commit 1144b1e7b1
7 changed files with 29 additions and 13 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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