Map full-SAP code-based heating systems via sap_main_heating_code 🟩

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-06-15 14:40:59 +00:00
parent 345154c6b7
commit 140ad39898
4 changed files with 315 additions and 6 deletions

View file

@ -2593,9 +2593,18 @@ def _sap_17_1_heating(schema: SapSchema17_1) -> SapHeating:
main_heating_details=[
MainHeatingDetail(
has_fghrs=False,
main_fuel_type=d.main_fuel_type,
heat_emitter_type=d.heat_emitter_type,
emitter_temperature=d.emitter_temperature,
# D6b: code-based systems (category 6/10) lodge no explicit
# fuel/emitter — the calculator drives them off
# sap_main_heating_code instead. Default the absent fields so
# the cert still maps (0 = unspecified; emitter_temperature "NA").
main_fuel_type=d.main_fuel_type if d.main_fuel_type is not None else 0,
heat_emitter_type=(
d.heat_emitter_type if d.heat_emitter_type is not None else 0
),
emitter_temperature=(
d.emitter_temperature if d.emitter_temperature is not None else "NA"
),
sap_main_heating_code=d.main_heating_code,
main_heating_control=d.main_heating_control,
fan_flue_present=d.is_flue_fan_present == "true",
boiler_flue_type=d.main_heating_flue_type,

View file

@ -254,6 +254,23 @@ class TestFromSapSchema17_1EnergySource:
assert sample.sap_energy_source.wind_turbines_count == 0
class TestFromSapSchema17_1CodeBasedHeating:
"""Slice D6b: ~27% of full-SAP certs use a code-based heating encoding
(main_heating_category 6/10 community heating, heat pumps) lodging
main_heating_code instead of explicit heat_emitter_type/main_fuel_type.
A known variant map main_heating_code sap_main_heating_code rather than
fail loud, so the cert maps for completeness (score is a later concern)."""
def test_code_based_heating_cert_maps(self) -> None:
# Previously raised "missing required field 'heat_emitter_type'".
result = EpcPropertyDataMapper.from_api_response(load("sap_17_1_codeheat.json"))
assert isinstance(result, EpcPropertyData)
def test_main_heating_code_carried(self) -> None:
result = EpcPropertyDataMapper.from_api_response(load("sap_17_1_codeheat.json"))
assert result.sap_heating.main_heating_details[0].sap_main_heating_code == 691
class TestFromSapSchema17_1Ventilation:
"""Slice D5-vent: full-SAP sap_ventilation → measured air permeability (AP4),
ventilation_type MechanicalVentilationKind, sheltered sides, wet rooms and

View file

@ -126,10 +126,13 @@ class SapMainHeatingDetail:
`is_flue_fan_present` vs `fan_flue_present`, `main_heating_flue_type` vs
`boiler_flue_type`); the mapper translates them."""
main_fuel_type: int
heat_emitter_type: int
emitter_temperature: Union[int, str]
main_heating_control: int
# code-based heating (category 6/10 — community heating, heat pumps) lodges
# main_heating_code instead of explicit fuel/emitter; both then absent.
main_fuel_type: Optional[int] = None
heat_emitter_type: Optional[int] = None
emitter_temperature: Optional[Union[int, str]] = None
main_heating_code: Optional[int] = None
main_heating_number: Optional[int] = None
main_heating_category: Optional[int] = None
main_heating_fraction: Optional[Union[int, float]] = None

View file

@ -0,0 +1,280 @@
{
"uprn": 100071290385,
"roofs": [
{
"description": "Average thermal transmittance 0.16 W/m\u00b2K",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"walls": [
{
"description": "Average thermal transmittance 0.63 W/m\u00b2K",
"energy_efficiency_rating": 3,
"environmental_efficiency_rating": 3
}
],
"floors": [
{
"description": "(other premises below)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"status": "entered",
"tenure": 1,
"windows": {
"description": "High performance glazing",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
},
"lighting": {
"description": "Low energy lighting in all fixed outlets",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
},
"postcode": "B7 5SP",
"data_type": 4,
"hot_water": {
"description": "Electric immersion, standard tariff",
"energy_efficiency_rating": 1,
"environmental_efficiency_rating": 2
},
"post_town": "BIRMINGHAM",
"built_form": 2,
"created_at": "2019-05-28 09:11:44",
"living_area": 9.5,
"orientation": 4,
"region_code": 6,
"report_type": 3,
"sap_heating": {
"water_fuel_type": 39,
"water_heating_code": 903,
"hot_water_store_size": 90,
"main_heating_details": [
{
"main_fuel_type": 39,
"main_heating_code": 691,
"emitter_temperature": "NA",
"main_heating_number": 1,
"main_heating_control": 2603,
"main_heating_category": 10,
"main_heating_fraction": 1,
"main_heating_data_source": 3
}
],
"has_hot_water_cylinder": "true",
"immersion_heating_type": 1,
"has_cylinder_thermostat": "true",
"hot_water_store_heat_loss": 1.37,
"has_fixed_air_conditioning": "false",
"secondary_heating_category": 1,
"is_cylinder_in_heated_space": "true",
"hot_water_store_heat_loss_source": 2
},
"sap_version": 9.92,
"schema_type": "SAP-Schema-17.1",
"uprn_source": "Energy Assessor",
"country_code": "ENG",
"main_heating": [
{
"description": "Room heaters, electric",
"energy_efficiency_rating": 1,
"environmental_efficiency_rating": 2
}
],
"air_tightness": {
"description": "(not tested)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
},
"dwelling_type": "Top-floor flat",
"language_code": 1,
"property_type": 2,
"address_line_1": "208, Nechells Park Road",
"assessment_date": "2019-05-24",
"assessment_type": "SAP",
"completion_date": "2019-05-28",
"inspection_date": "2019-05-28",
"sap_ventilation": {
"psv_count": 0,
"pressure_test": 3,
"air_permeability": 15,
"open_flues_count": 0,
"ventilation_type": 1,
"extract_fans_count": 1,
"open_fireplaces_count": 0,
"sheltered_sides_count": 2,
"flueless_gas_fires_count": 0
},
"design_water_use": 1,
"sap_data_version": 9.92,
"sap_flat_details": {
"level": 3
},
"total_floor_area": 13,
"transaction_type": 6,
"conservatory_type": 1,
"registration_date": "2019-05-28",
"sap_energy_source": {
"electricity_tariff": 1,
"wind_turbines_count": 0,
"wind_turbine_terrain_type": 2,
"fixed_lighting_outlets_count": 15,
"low_energy_fixed_lighting_outlets_count": 15,
"low_energy_fixed_lighting_outlets_percentage": 100
},
"sap_opening_types": [
{
"name": "Glazing",
"type": 4,
"u_value": 1.4,
"data_source": 2,
"frame_factor": 0.7,
"glazing_type": 7,
"solar_transmittance": 0.63
},
{
"name": "Solid Door",
"type": 1,
"u_value": 1.8,
"data_source": 2,
"glazing_type": 1
}
],
"secondary_heating": {
"description": "None",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
},
"sap_building_parts": [
{
"sap_roofs": [
{
"name": "Roof 1",
"u_value": 0.16,
"roof_type": 2,
"description": "Insulated Ceiling",
"total_roof_area": 12.62
}
],
"sap_walls": [
{
"name": "External Wall 1",
"u_value": 0.34,
"wall_type": 2,
"description": "Upgraded Ext Walls",
"total_wall_area": 20.54,
"is_curtain_walling": "false"
},
{
"name": "External Wall 2",
"u_value": 1,
"wall_type": 2,
"description": "Wall to Lobby",
"total_wall_area": 16.38,
"is_curtain_walling": "false"
},
{
"name": "Party Wall 0",
"u_value": 0,
"wall_type": 4,
"total_wall_area": 9.88
}
],
"identifier": "Main Dwelling",
"overshading": 2,
"sap_openings": [
{
"name": "W1",
"type": "Glazing",
"width": 1200,
"height": 2000,
"location": "External Wall 1",
"orientation": 8
},
{
"name": "D1",
"type": "Solid Door",
"width": 900,
"height": 2100,
"location": "External Wall 2",
"orientation": 0
}
],
"construction_year": 2019,
"sap_thermal_bridges": {
"thermal_bridge_code": 1
},
"building_part_number": 1,
"sap_floor_dimensions": [
{
"storey": 1,
"u_value": 0,
"floor_type": 4,
"storey_height": 2.6,
"heat_loss_area": 0,
"total_floor_area": 12.62
}
],
"thermal_mass_parameter": 250
}
],
"heating_cost_current": {
"value": 278,
"currency": "GBP"
},
"co2_emissions_current": 1.5,
"energy_rating_average": 60,
"energy_rating_current": 59,
"lighting_cost_current": {
"value": 16,
"currency": "GBP"
},
"main_heating_controls": [
{
"description": "Programmer and appliance thermostats",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"has_hot_water_cylinder": "true",
"heating_cost_potential": {
"value": 278,
"currency": "GBP"
},
"hot_water_cost_current": {
"value": 206,
"currency": "GBP"
},
"co2_emissions_potential": 1.5,
"energy_rating_potential": 59,
"lighting_cost_potential": {
"value": 16,
"currency": "GBP"
},
"schema_version_original": "LIG-17.0",
"hot_water_cost_potential": {
"value": 206,
"currency": "GBP"
},
"is_in_smoke_control_area": "unknown",
"renewable_heat_incentive": {
"rhi_new_dwelling": {
"space_heating": 1637,
"water_heating": 1212
}
},
"seller_commission_report": "Y",
"energy_consumption_current": 716,
"has_fixed_air_conditioning": "false",
"multiple_glazed_percentage": 100,
"calculation_software_version": "4.09r14",
"energy_consumption_potential": 716,
"environmental_impact_current": 63,
"current_energy_efficiency_band": "D",
"environmental_impact_potential": 63,
"has_heated_separate_conservatory": "false",
"potential_energy_efficiency_band": "D",
"co2_emissions_current_per_floor_area": 121
}