Add SAP-16.3 + SAP-17.0 schema coverage (completes the e2e UPRN set)

- SAP-Schema-16.3: same reduced-field RdSAP shape as 16.2 — generalise the
  normaliser to _normalize_sap_schema_16_x and route both 16.2/16.3 through it.
  uprn_44012843 maps → SAP 79 (lodged 81).
- SAP-Schema-17.0: structurally identical to the full-SAP 17.1 schema (measured
  sap_opening_types), so it parses with the 17.1 dataclass and reuses
  from_sap_schema_17_1 with no normalisation. uprn_10023444324 → 80, uprn_
  10023444320 → 81.
- Regression tests (16.3 dispatch, 17.0 dispatch) + sap_16_3.json / sap_17_0.json
  fixtures; 0 new pyright errors. All 7 e2e UPRNs now map.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-06-16 19:24:50 +00:00
parent 36dc55b499
commit 0b32d9fcee
7 changed files with 1413 additions and 22 deletions

View file

@ -20,9 +20,11 @@ UPRNs needed for end-to-end testing (also tracked in The 100 below).
| 10093116543 | SAP-17.1 | ✅ pinned | 81 (elm 77) | 2017 gas-combi semi |
| 10093116529 | SAP-17.1 | ✅ pinned | 81 (elm 78) | 2017 gas-combi g/f flat; party-wall fix |
| 100020933699 | SAP-16.2 | 🔧 mapper + glazing fix | 71 (lodged 70) | RdSAP-shaped; single-glazing fix; Elmhurst validation pending |
| 44012843 | — | ☐ todo | — | not yet processed |
| 10023444324 | — | ☐ todo | — | not yet processed |
| 10023444320 | — | ☐ todo | — | not yet processed |
| 44012843 | SAP-16.3 | 🔧 mapper added | 79 (lodged 81) | same 16.x reduced-field path; Elmhurst pending |
| 10023444324 | SAP-17.0 | 🔧 mapper added | 80 (lodged 82) | full-SAP shape = 17.1; Elmhurst pending |
| 10023444320 | SAP-17.0 | 🔧 mapper added | 81 (lodged 81) | full-SAP shape = 17.1; Elmhurst pending |
**All 7 e2e UPRNs now map and produce engine SAP scores.** Schema coverage added this session: SAP-16.2, SAP-16.3 (reduced-field → RdSAP-17.1 path), SAP-17.0 (full-SAP → 17.1 path).
## The 100
@ -30,8 +32,8 @@ UPRNs needed for end-to-end testing (also tracked in The 100 below).
- [x] 10093116543 — SAP-17.1 (2017 gas-combi semi) · eng 81 / elm 77 (lodged 82) · PINNED engine 81. +4 = documented full-SAP→RdSAP residual, NOT a mapper bug: ~1.5 floor-U (cert lodges measured 0.11 vs Elmhurst RdSAP solid default 0.23; U-known override disabled) + ~1 boiler-eff (cert PCDB 17644 88.5% vs Elmhurst generic BGW combi 84%; PCDB search disabled, 89% cascade option is a regular boiler needing a cylinder) + ~0.5 roof band-L/infil. Conservatory leftover from prior cert cleared (worksheet 73→77). No mapper change.
- [x] 🔧 10093116529 — SAP-17.1 (2017 gas-combi ground-floor FLAT, TFA 49) · eng 81 / elm 78 (lodged 81) · PINNED engine 81. 🔧 FIXED a real calc bug: full-SAP flats took the 0.25 house party-wall default instead of the RdSAP Table-15 flat 0.0 (flatness is in dwelling_type, not property_type) — heat_transmission._is_flat_or_maisonette_dwelling; +regression test. Cert lodges party u_value 0; Elmhurst worksheet 0.0; fix 80→81. Residual +3 vs Elmhurst = documented full-SAP→RdSAP gap (measured wall 0.184/floor 0.12 + PCDB 88.5% vs generic 84%). Calculator faithful: fed Elmhurst's Us, HTC 93.4 vs ~94. House→Flat Elmhurst switch (storeys→1, roof→another-dwelling-above). No mapper change.
- [ ] 🔧 100020933699 — SAP-16.2 SCHEMA COVERAGE ADDED (end-terrace house, band G). 16.2 is structurally RdSAP-17.1 (reduced fields, glazed_area band, construction-code building parts) under a different name; mapped via `_normalize_sap_schema_16_2` (renames windows→window, main_gas→mains_gas, boiler_index_number→main_heating_index_number, wwhrs→instantaneous_wwhrs + defaults) → reuses from_rdsap_schema_17_1. 🔧 Also fixed: "Single glazed" description honoured when multiple_glazing_type="ND" (was defaulting to double; RdSAP-21 code 5) → eng 72→71. +4 regression tests, sap_16_2.json fixture, 0 new pyright errors. eng 71 / lodged 70. ⚠ Known gap: 16.2 lodges no party_wall_length → end-terrace party wall unmodelled (likely the residual +1). ⏳ Elmhurst build (partial: PropDesc/Dims/Walls/Roofs done) + pin still pending.
- [ ] 44012843
- [ ] 10023444324
- [ ] 🔧 44012843 — SAP-16.3 schema coverage (same _normalize_sap_schema_16_x reduced-field path as 16.2) · eng 79 / lodged 81 · g/f flat band K · Elmhurst pin pending
- [ ] 🔧 10023444324 — SAP-17.0 schema coverage (full-SAP shape ≡ 17.1; dispatched to from_sap_schema_17_1, no normalization) · eng 80 / lodged 82 · Elmhurst pin pending
- [ ] 10092970673
- [ ] 10094601287
- [ ] 10090844932
@ -89,7 +91,7 @@ UPRNs needed for end-to-end testing (also tracked in The 100 below).
- [ ] 10093305101
- [ ] 100020933894
- [ ] 100020937013
- [ ] 10023444320
- [ ] 🔧 10023444320 — SAP-17.0 schema coverage (full-SAP ≡ 17.1) · eng 81 / lodged 81 · Elmhurst pin pending
- [ ] 100062188801
- [ ] 10008048040
- [ ] 10093101966

View file

@ -0,0 +1,364 @@
{
"uprn": 10023444320,
"roofs": [
{
"description": "(other premises above)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"walls": [
{
"description": "Average thermal transmittance 0.2 W/m\u00b2K",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
}
],
"floors": [
{
"description": "Average thermal transmittance 0.2 W/m\u00b2K",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
}
],
"status": "entered",
"tenure": "ND",
"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": "DA1 5UU",
"data_type": 2,
"hot_water": {
"description": "From main system",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
},
"post_town": "DARTFORD",
"built_form": 1,
"created_at": "2016-09-06 16:02:50",
"living_area": 21.45,
"orientation": 0,
"region_code": 14,
"report_type": 3,
"sap_heating": {
"thermal_store": 1,
"water_fuel_type": 1,
"water_heating_code": 901,
"main_heating_details": [
{
"main_fuel_type": 1,
"heat_emitter_type": 1,
"emitter_temperature": 1,
"is_flue_fan_present": "true",
"main_heating_number": 1,
"main_heating_control": 2106,
"is_interlocked_system": "true",
"main_heating_category": 2,
"main_heating_fraction": 1,
"main_heating_flue_type": 2,
"central_heating_pump_age": 2,
"main_heating_data_source": 1,
"main_heating_index_number": 16211,
"has_separate_delayed_start": "true",
"load_or_weather_compensation": 0,
"is_central_heating_pump_in_heated_space": "true"
}
],
"has_hot_water_cylinder": "false",
"has_fixed_air_conditioning": "false",
"secondary_heating_category": 1
},
"sap_version": 9.92,
"schema_type": "SAP-Schema-17.0",
"uprn_source": "Energy Assessor",
"country_code": "ENG",
"main_heating": [
{
"description": "Boiler and radiators, mains gas",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"air_tightness": {
"description": "Air permeability 3.1 m\u00b3/h.m\u00b2 (as tested)",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
},
"dwelling_type": "Mid-floor flat",
"language_code": 1,
"property_type": 2,
"address_line_1": "10, Ashmore Close",
"assessment_date": "2016-09-06",
"assessment_type": "SAP",
"completion_date": "2016-09-06",
"inspection_date": "2016-09-06",
"sap_ventilation": {
"psv_count": 0,
"pressure_test": 1,
"wet_rooms_count": 2,
"air_permeability": 3.09,
"open_flues_count": 0,
"ventilation_type": 6,
"extract_fans_count": 0,
"open_fireplaces_count": 0,
"sheltered_sides_count": 2,
"kitchen_duct_fans_count": 0,
"kitchen_room_fans_count": 1,
"kitchen_wall_fans_count": 0,
"flueless_gas_fires_count": 0,
"mechanical_vent_duct_type": 2,
"non_kitchen_duct_fans_count": 0,
"non_kitchen_room_fans_count": 1,
"non_kitchen_wall_fans_count": 0,
"mechanical_ventilation_data_source": 1,
"mechanical_vent_system_index_number": 500416,
"is_mechanical_vent_approved_installer_scheme": "true"
},
"design_water_use": 1,
"sap_data_version": 9.92,
"sap_flat_details": {
"level": 2
},
"total_floor_area": 51,
"transaction_type": 6,
"conservatory_type": 1,
"registration_date": "2016-09-06",
"sap_energy_source": {
"pv_arrays": [
{
"pitch": 2,
"peak_power": 0.48,
"orientation": 5,
"overshading": 1,
"pv_connection": 1
}
],
"electricity_tariff": 1,
"wind_turbines_count": 0,
"wind_turbine_terrain_type": 2,
"fixed_lighting_outlets_count": 6,
"low_energy_fixed_lighting_outlets_count": 6,
"low_energy_fixed_lighting_outlets_percentage": 100
},
"sap_opening_types": [
{
"name": 1,
"type": 1,
"u_value": 0.97,
"data_source": 2,
"description": "Front",
"glazing_type": 1
},
{
"name": 2,
"type": 4,
"u_value": 1.2,
"data_source": 2,
"description": "Window",
"frame_factor": 0.7,
"glazing_type": 6,
"solar_transmittance": 0.63
}
],
"secondary_heating": {
"description": "None",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
},
"lzc_energy_sources": [
11
],
"sap_building_parts": [
{
"sap_walls": [
{
"name": "Wall 1",
"u_value": 0.2,
"wall_type": 2,
"description": "150mm hi-cav 32 AAC",
"total_wall_area": 53.26,
"is_curtain_walling": "false"
},
{
"name": "Wall 2",
"u_value": 0.18,
"wall_type": 3,
"description": "To corridor",
"total_wall_area": 15.18,
"is_curtain_walling": "false"
}
],
"identifier": "Main Dwelling",
"overshading": 2,
"sap_openings": [
{
"name": 1,
"type": 1,
"width": 1,
"height": 2.1,
"location": "Wall 2",
"orientation": 0
},
{
"name": 2,
"type": 2,
"width": 1.17,
"height": 2.29,
"location": "Wall 1",
"orientation": 5
},
{
"name": 3,
"type": 2,
"width": 1.17,
"height": 2.29,
"location": "Wall 1",
"orientation": 5
},
{
"name": 4,
"type": 2,
"width": 2.29,
"height": 2.29,
"location": "Wall 1",
"orientation": 5
},
{
"name": 5,
"type": 2,
"width": 1.18,
"height": 1.38,
"location": "Wall 1",
"orientation": 1
},
{
"name": 6,
"type": 2,
"width": 1.18,
"height": 1.38,
"location": "Wall 1",
"orientation": 1
}
],
"construction_year": 2015,
"sap_thermal_bridges": {
"thermal_bridges": [
{
"length": 7.99,
"psi_value": 0.3,
"psi_value_source": 2,
"thermal_bridge_type": "E2"
},
{
"length": 6.99,
"psi_value": 0.04,
"psi_value_source": 2,
"thermal_bridge_type": "E3"
},
{
"length": 23.46,
"psi_value": 0.05,
"psi_value_source": 2,
"thermal_bridge_type": "E4"
},
{
"length": 28.39,
"psi_value": 0.32,
"psi_value_source": 4,
"thermal_bridge_type": "E20"
},
{
"length": 2.41,
"psi_value": 1,
"psi_value_source": 3,
"thermal_bridge_type": "E9"
},
{
"length": 9.64,
"psi_value": 0.09,
"psi_value_source": 2,
"thermal_bridge_type": "E16"
}
],
"thermal_bridge_code": 5
},
"building_part_number": 1,
"sap_floor_dimensions": [
{
"storey": 1,
"u_value": 0.18,
"floor_type": 4,
"description": "Floor abv Bike Store",
"storey_height": 2.41,
"heat_loss_area": 50.68,
"total_floor_area": 50.68
}
],
"thermal_mass_parameter": 250
}
],
"heating_cost_current": {
"value": 182,
"currency": "GBP"
},
"co2_emissions_current": 0.7,
"energy_rating_average": 60,
"energy_rating_current": 81,
"lighting_cost_current": {
"value": 36,
"currency": "GBP"
},
"main_heating_controls": [
{
"description": "Programmer, room thermostat and TRVs",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"has_hot_water_cylinder": "false",
"heating_cost_potential": {
"value": 182,
"currency": "GBP"
},
"hot_water_cost_current": {
"value": 80,
"currency": "GBP"
},
"co2_emissions_potential": 0.7,
"energy_rating_potential": 81,
"lighting_cost_potential": {
"value": 36,
"currency": "GBP"
},
"schema_version_original": "LIG-17.0",
"hot_water_cost_potential": {
"value": 80,
"currency": "GBP"
},
"is_in_smoke_control_area": "unknown",
"renewable_heat_incentive": {
"rhi_new_dwelling": {
"space_heating": 1525,
"water_heating": 1657
}
},
"seller_commission_report": "Y",
"energy_consumption_current": 80,
"has_fixed_air_conditioning": "false",
"multiple_glazed_percentage": 100,
"calculation_software_version": 6.2,
"energy_consumption_potential": 80,
"environmental_impact_current": 89,
"current_energy_efficiency_band": "B",
"environmental_impact_potential": 89,
"has_heated_separate_conservatory": "false",
"potential_energy_efficiency_band": "B",
"co2_emissions_current_per_floor_area": 14
}

View file

@ -0,0 +1,387 @@
{
"uprn": 10023444324,
"roofs": [
{
"description": "(other premises above)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"walls": [
{
"description": "Average thermal transmittance 0.2 W/m\u00b2K",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
}
],
"floors": [
{
"description": "Average thermal transmittance 0.1 W/m\u00b2K",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
}
],
"status": "entered",
"tenure": "ND",
"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": "DA1 5UU",
"data_type": 2,
"hot_water": {
"description": "From main system",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
},
"post_town": "DARTFORD",
"built_form": 3,
"created_at": "2016-09-06 16:02:00",
"living_area": 25.23,
"orientation": 0,
"region_code": 14,
"report_type": 3,
"sap_heating": {
"thermal_store": 1,
"water_fuel_type": 1,
"water_heating_code": 901,
"main_heating_details": [
{
"main_fuel_type": 1,
"heat_emitter_type": 1,
"emitter_temperature": 1,
"is_flue_fan_present": "true",
"main_heating_number": 1,
"main_heating_control": 2106,
"is_interlocked_system": "true",
"main_heating_category": 2,
"main_heating_fraction": 1,
"main_heating_flue_type": 2,
"central_heating_pump_age": 2,
"main_heating_data_source": 1,
"main_heating_index_number": 16211,
"has_separate_delayed_start": "true",
"load_or_weather_compensation": 0,
"is_central_heating_pump_in_heated_space": "true"
}
],
"has_hot_water_cylinder": "false",
"has_fixed_air_conditioning": "false",
"secondary_heating_category": 1
},
"sap_version": 9.92,
"schema_type": "SAP-Schema-17.0",
"uprn_source": "Energy Assessor",
"country_code": "ENG",
"main_heating": [
{
"description": "Boiler and radiators, mains gas",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"air_tightness": {
"description": "Air permeability 3.2 m\u00b3/h.m\u00b2 (as tested)",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
},
"dwelling_type": "Ground-floor flat",
"language_code": 1,
"property_type": 2,
"address_line_1": "1, Ashmore Close",
"assessment_date": "2016-09-06",
"assessment_type": "SAP",
"completion_date": "2016-09-06",
"inspection_date": "2016-09-06",
"sap_ventilation": {
"psv_count": 0,
"pressure_test": 1,
"wet_rooms_count": 2,
"air_permeability": 3.24,
"open_flues_count": 0,
"ventilation_type": 6,
"extract_fans_count": 0,
"open_fireplaces_count": 0,
"sheltered_sides_count": 2,
"kitchen_duct_fans_count": 0,
"kitchen_room_fans_count": 1,
"kitchen_wall_fans_count": 0,
"flueless_gas_fires_count": 0,
"mechanical_vent_duct_type": 2,
"non_kitchen_duct_fans_count": 0,
"non_kitchen_room_fans_count": 1,
"non_kitchen_wall_fans_count": 0,
"mechanical_ventilation_data_source": 1,
"mechanical_vent_system_index_number": 500416,
"is_mechanical_vent_approved_installer_scheme": "true"
},
"design_water_use": 1,
"sap_data_version": 9.92,
"sap_flat_details": {
"level": 1
},
"total_floor_area": 50,
"transaction_type": 6,
"conservatory_type": 1,
"registration_date": "2016-09-06",
"sap_energy_source": {
"pv_arrays": [
{
"pitch": 2,
"peak_power": 0.48,
"orientation": 5,
"overshading": 1,
"pv_connection": 1
}
],
"electricity_tariff": 1,
"wind_turbines_count": 0,
"wind_turbine_terrain_type": 2,
"fixed_lighting_outlets_count": 6,
"low_energy_fixed_lighting_outlets_count": 6,
"low_energy_fixed_lighting_outlets_percentage": 100
},
"sap_opening_types": [
{
"name": 1,
"type": 1,
"u_value": 0.97,
"data_source": 2,
"description": "Front",
"glazing_type": 1
},
{
"name": 2,
"type": 4,
"u_value": 1.2,
"data_source": 2,
"description": "Window",
"frame_factor": 0.7,
"glazing_type": 6,
"solar_transmittance": 0.63
}
],
"secondary_heating": {
"description": "None",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
},
"lzc_energy_sources": [
11
],
"sap_building_parts": [
{
"sap_walls": [
{
"name": "Wall 1",
"u_value": 0.2,
"wall_type": 2,
"description": "150mm hi-cav 32 AAC",
"total_wall_area": 37.93,
"is_curtain_walling": "false"
},
{
"name": "Wall 2",
"u_value": 0.18,
"wall_type": 3,
"description": "To corridor",
"total_wall_area": 19.28,
"is_curtain_walling": "false"
},
{
"name": "Wall 3",
"u_value": 0,
"wall_type": 4,
"description": "party",
"total_wall_area": 15.5
}
],
"identifier": "Main Dwelling",
"overshading": 2,
"sap_openings": [
{
"name": 1,
"type": 1,
"width": 1,
"height": 2.1,
"location": "Wall 2",
"orientation": 0
},
{
"name": 2,
"type": 2,
"width": 1.16,
"height": 2.28,
"location": "Wall 1",
"orientation": 7
},
{
"name": 3,
"type": 2,
"width": 1.16,
"height": 1.08,
"location": "Wall 1",
"orientation": 7
},
{
"name": 4,
"type": 2,
"width": 2.29,
"height": 2.28,
"location": "Wall 1",
"orientation": 7
},
{
"name": 5,
"type": 2,
"width": 1.17,
"height": 2.23,
"location": "Wall 1",
"orientation": 3
}
],
"construction_year": 2015,
"sap_thermal_bridges": {
"thermal_bridges": [
{
"length": 6.78,
"psi_value": 0.3,
"psi_value_source": 2,
"thermal_bridge_type": "E2"
},
{
"length": 5.78,
"psi_value": 0.04,
"psi_value_source": 2,
"thermal_bridge_type": "E3"
},
{
"length": 19.94,
"psi_value": 0.05,
"psi_value_source": 2,
"thermal_bridge_type": "E4"
},
{
"length": 22.17,
"psi_value": 0.16,
"psi_value_source": 2,
"thermal_bridge_type": "E5"
},
{
"length": 22.17,
"psi_value": 0.07,
"psi_value_source": 2,
"thermal_bridge_type": "E7"
},
{
"length": 2.26,
"psi_value": 1,
"psi_value_source": 3,
"thermal_bridge_type": "E9"
},
{
"length": 4.82,
"psi_value": 0.09,
"psi_value_source": 2,
"thermal_bridge_type": "E16"
},
{
"length": 4.82,
"psi_value": 0.12,
"psi_value_source": 4,
"thermal_bridge_type": "E18"
},
{
"length": 8,
"psi_value": 0.16,
"psi_value_source": 4,
"thermal_bridge_type": "P1"
},
{
"length": 8,
"psi_value": 0,
"psi_value_source": 3,
"thermal_bridge_type": "P3"
}
],
"thermal_bridge_code": 5
},
"building_part_number": 1,
"sap_floor_dimensions": [
{
"storey": 0,
"u_value": 0.1,
"floor_type": 2,
"description": "Floor 1",
"storey_height": 2.41,
"heat_loss_area": 50.17,
"total_floor_area": 50.17
}
],
"thermal_mass_parameter": 250
}
],
"heating_cost_current": {
"value": 171,
"currency": "GBP"
},
"co2_emissions_current": 0.7,
"energy_rating_average": 60,
"energy_rating_current": 82,
"lighting_cost_current": {
"value": 36,
"currency": "GBP"
},
"main_heating_controls": [
{
"description": "Programmer, room thermostat and TRVs",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"has_hot_water_cylinder": "false",
"heating_cost_potential": {
"value": 171,
"currency": "GBP"
},
"hot_water_cost_current": {
"value": 80,
"currency": "GBP"
},
"co2_emissions_potential": 0.7,
"energy_rating_potential": 82,
"lighting_cost_potential": {
"value": 36,
"currency": "GBP"
},
"schema_version_original": "LIG-17.0",
"hot_water_cost_potential": {
"value": 80,
"currency": "GBP"
},
"is_in_smoke_control_area": "unknown",
"renewable_heat_incentive": {
"rhi_new_dwelling": {
"space_heating": 1299,
"water_heating": 1651
}
},
"seller_commission_report": "Y",
"energy_consumption_current": 75,
"has_fixed_air_conditioning": "false",
"multiple_glazed_percentage": 100,
"calculation_software_version": 6.2,
"energy_consumption_potential": 75,
"environmental_impact_current": 89,
"current_energy_efficiency_band": "B",
"environmental_impact_potential": 89,
"has_heated_separate_conservatory": "false",
"potential_energy_efficiency_band": "B",
"co2_emissions_current_per_floor_area": 13
}

View file

@ -2546,24 +2546,27 @@ class EpcPropertyDataMapper:
from_dict(RdSapSchema17_0, data)
)
)
if schema == "SAP-Schema-17.1":
# Full SAP (not RdSAP). D8: _clear_basement_flag_when_system_built is
# an RdSAP code-6 disambiguation; full SAP lodges explicit wall types
# (no code-6 basement ambiguity), so it's a no-op and is skipped.
if schema in ("SAP-Schema-17.1", "SAP-Schema-17.0"):
# Full SAP (not RdSAP). SAP-Schema-17.0 is structurally identical to
# 17.1 (same measured sap_opening_types / building parts), so it
# parses with the 17.1 dataclass and reuses the same mapper. D8:
# _clear_basement_flag_when_system_built is an RdSAP code-6
# disambiguation; full SAP lodges explicit wall types (no code-6
# basement ambiguity), so it's a no-op and is skipped.
return EpcPropertyDataMapper.from_sap_schema_17_1(
from_dict(SapSchema17_1, data)
)
if schema == "SAP-Schema-16.2":
# SAP-Schema-16.2 is structurally an RdSAP-17.1 cert (reduced fields,
# glazed_area band, construction-code building parts) under a different
# name + a handful of renamed/omitted fields — normalise it onto the
# RdSAP-17.1 shape and reuse that tested mapper. See
# `_normalize_sap_schema_16_2`.
if schema in ("SAP-Schema-16.2", "SAP-Schema-16.3"):
# The SAP-Schema-16.x family is structurally RdSAP-17.1 (reduced
# fields, glazed_area band, construction-code building parts) under a
# different name + a handful of renamed/omitted fields — normalise
# onto the RdSAP-17.1 shape and reuse that tested mapper. See
# `_normalize_sap_schema_16_x`.
from datatypes.epc.schema.rdsap_schema_17_1 import RdSapSchema17_1
return _clear_basement_flag_when_system_built(
EpcPropertyDataMapper.from_rdsap_schema_17_1(
from_dict(RdSapSchema17_1, _normalize_sap_schema_16_2(data))
from_dict(RdSapSchema17_1, _normalize_sap_schema_16_x(data))
)
)
@ -3022,11 +3025,11 @@ def _default_missing_post_town(data: Dict[str, Any]) -> Dict[str, Any]:
return {**data, "post_town": ""}
def _normalize_sap_schema_16_2(data: Dict[str, Any]) -> Dict[str, Any]:
"""Rewrite a `SAP-Schema-16.2` API doc onto the `RdSAP-Schema-17.1` shape so
it can reuse the tested `from_rdsap_schema_17_1` mapper.
def _normalize_sap_schema_16_x(data: Dict[str, Any]) -> Dict[str, Any]:
"""Rewrite a `SAP-Schema-16.2`/`16.3` API doc onto the `RdSAP-Schema-17.1`
shape so it can reuse the tested `from_rdsap_schema_17_1` mapper.
Despite the "SAP-Schema" name, 16.2 is structurally an RdSAP cert (reduced
Despite the "SAP-Schema" name, 16.x is structurally an RdSAP cert (reduced
fields: a `glazed_area` *band* not measured openings, construction-code
building parts, `main_gas`/`meter_type` energy source, a PCDB boiler index).
It differs from RdSAP-17.1 only by a handful of field names plus three

View file

@ -381,7 +381,7 @@ class TestFromSapSchema17_1Perimeter:
class TestFromSapSchema16_2:
"""SAP-Schema-16.2 — structurally an RdSAP-17.1 cert under a different name
+ a few renamed/omitted fields. `from_api_response` normalises it onto the
RdSAP-17.1 shape (`_normalize_sap_schema_16_2`) and reuses that mapper.
RdSAP-17.1 shape (`_normalize_sap_schema_16_x`) and reuses that mapper.
Regression for uprn_100020933699 (cert 9548--0950, End-terrace house)."""
def test_16_2_dispatches_and_maps_via_rdsap_17_1(self) -> None:
@ -419,6 +419,32 @@ class TestFromSapSchema16_2:
assert epc.sap_windows, "expected synthesised windows"
assert epc.sap_windows[0].glazing_type == 1 # cascade single-glazing slot
def test_16_3_dispatches_via_same_16_x_path(self) -> None:
# SAP-Schema-16.3 is the same reduced-field RdSAP shape as 16.2 and
# routes through the same _normalize_sap_schema_16_x path. Regression
# for uprn_44012843 (ground-floor flat, age band K).
from domain.sap10_calculator.calculator import Sap10Calculator
epc = EpcPropertyDataMapper.from_api_response(load("sap_16_3.json"))
assert isinstance(epc, EpcPropertyData)
assert epc.uprn == 44012843
assert epc.dwelling_type == "Ground-floor flat"
# lodged 81; engine produces 79.
assert Sap10Calculator().calculate(epc).sap_score == 79
def test_17_0_dispatches_via_full_sap_path(self) -> None:
# SAP-Schema-17.0 is structurally identical to the full-SAP 17.1 schema
# (measured sap_opening_types) — it parses with the 17.1 dataclass and
# reuses from_sap_schema_17_1 with no normalisation. Regression for
# uprn_10023444324 (ground-floor flat).
from domain.sap10_calculator.calculator import Sap10Calculator
epc = EpcPropertyDataMapper.from_api_response(load("sap_17_0.json"))
assert isinstance(epc, EpcPropertyData)
assert epc.uprn == 10023444324
# lodged 82; engine produces 80.
assert Sap10Calculator().calculate(epc).sap_score == 80
def test_16_2_normalizer_does_not_mutate_caller_dict(self) -> None:
# Mirror _normalize_shower_outlets' contract: the caller's dict is
# untouched (deep copy), so a re-dispatch sees the original shape.

View file

@ -0,0 +1,222 @@
{
"uprn": 44012843,
"roofs": [
{
"description": "(another dwelling above)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"walls": [
{
"description": "Cavity wall, as built, insulated (assumed)",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
}
],
"floors": [
{
"description": "Solid, insulated (assumed)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"status": "entered",
"tenure": 2,
"windows": [
{
"description": "Fully double glazed",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"lighting": {
"description": "Low energy lighting in 80% of fixed outlets",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
},
"postcode": "ME2 2BN",
"hot_water": {
"description": "From main system, plus solar",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
},
"post_town": "ROCHESTER",
"built_form": 3,
"created_at": "2014-02-18 23:08:35",
"door_count": 1,
"glazed_area": 1,
"region_code": 14,
"report_type": 2,
"sap_heating": {
"wwhrs": {
"rooms_with_bath_and_or_shower": 1,
"rooms_with_mixer_shower_no_bath": 0,
"rooms_with_bath_and_mixer_shower": 1
},
"cylinder_size": 4,
"water_heating_code": 901,
"water_heating_fuel": 26,
"cylinder_thermostat": "Y",
"main_heating_details": [
{
"has_fghrs": "N",
"main_fuel_type": 26,
"boiler_flue_type": 2,
"fan_flue_present": "Y",
"heat_emitter_type": 1,
"boiler_index_number": 9895,
"main_heating_number": 1,
"main_heating_control": 2106,
"main_heating_category": 2,
"main_heating_fraction": 1,
"main_heating_data_source": 1
}
],
"cylinder_insulation_type": 1,
"has_fixed_air_conditioning": "false",
"cylinder_insulation_thickness": 50
},
"sap_version": 9.91,
"schema_type": "SAP-Schema-16.3",
"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": "Ground-floor flat",
"language_code": 1,
"property_type": 2,
"address_line_1": "1 Alexandra House",
"address_line_2": "Sealand Drive",
"schema_version": "LIG-16.1",
"assessment_type": "RdSAP",
"completion_date": "2014-02-18",
"inspection_date": "2014-02-18",
"extensions_count": 0,
"measurement_type": 1,
"sap_flat_details": {
"level": 1,
"top_storey": "N",
"flat_location": 0,
"heat_loss_corridor": 2,
"unheated_corridor_length": 10.8
},
"total_floor_area": 55,
"transaction_type": 8,
"conservatory_type": 1,
"heated_room_count": 2,
"registration_date": "2014-02-18",
"restricted_access": 0,
"sap_energy_source": {
"main_gas": "Y",
"meter_type": 2,
"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
},
"lzc_energy_sources": [
10
],
"sap_building_parts": [
{
"identifier": "Main Dwelling",
"wall_dry_lined": "N",
"wall_thickness": 340,
"floor_heat_loss": 7,
"roof_construction": 3,
"wall_construction": 4,
"building_part_number": 1,
"sap_floor_dimensions": [
{
"floor": 0,
"room_height": 2.35,
"floor_insulation": 1,
"total_floor_area": 55.26,
"floor_construction": 1,
"heat_loss_perimeter": 29.2
}
],
"wall_insulation_type": 4,
"construction_age_band": "K",
"wall_thickness_measured": "Y",
"roof_insulation_location": "ND",
"roof_insulation_thickness": "ND"
}
],
"low_energy_lighting": 80,
"solar_water_heating": "Y",
"bedf_revision_number": 353,
"habitable_room_count": 2,
"heating_cost_current": {
"value": 231,
"currency": "GBP"
},
"insulated_door_count": 0,
"co2_emissions_current": 1.0,
"energy_rating_average": 60,
"energy_rating_current": 81,
"lighting_cost_current": {
"value": 47,
"currency": "GBP"
},
"main_heating_controls": [
{
"description": "Programmer, room thermostat and TRVs",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"multiple_glazing_type": 2,
"open_fireplaces_count": 0,
"has_hot_water_cylinder": "true",
"heating_cost_potential": {
"value": 231,
"currency": "GBP"
},
"hot_water_cost_current": {
"value": 61,
"currency": "GBP"
},
"mechanical_ventilation": 0,
"percent_draughtproofed": 100,
"co2_emissions_potential": 1.0,
"energy_rating_potential": 81,
"lighting_cost_potential": {
"value": 47,
"currency": "GBP"
},
"hot_water_cost_potential": {
"value": 61,
"currency": "GBP"
},
"renewable_heat_incentive": {
"water_heating": 2177,
"space_heating_existing_dwelling": 2074
},
"seller_commission_report": "Y",
"energy_consumption_current": 99,
"has_fixed_air_conditioning": "false",
"multiple_glazed_proportion": 100,
"calculation_software_version": 8.3,
"energy_consumption_potential": 99,
"environmental_impact_current": 85,
"fixed_lighting_outlets_count": 5,
"current_energy_efficiency_band": "B",
"environmental_impact_potential": 85,
"has_heated_separate_conservatory": "false",
"potential_energy_efficiency_band": "B",
"co2_emissions_current_per_floor_area": 19,
"low_energy_fixed_lighting_outlets_count": 4
}

View file

@ -0,0 +1,387 @@
{
"uprn": 10023444324,
"roofs": [
{
"description": "(other premises above)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"walls": [
{
"description": "Average thermal transmittance 0.2 W/m\u00b2K",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
}
],
"floors": [
{
"description": "Average thermal transmittance 0.1 W/m\u00b2K",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
}
],
"status": "entered",
"tenure": "ND",
"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": "DA1 5UU",
"data_type": 2,
"hot_water": {
"description": "From main system",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
},
"post_town": "DARTFORD",
"built_form": 3,
"created_at": "2016-09-06 16:02:00",
"living_area": 25.23,
"orientation": 0,
"region_code": 14,
"report_type": 3,
"sap_heating": {
"thermal_store": 1,
"water_fuel_type": 1,
"water_heating_code": 901,
"main_heating_details": [
{
"main_fuel_type": 1,
"heat_emitter_type": 1,
"emitter_temperature": 1,
"is_flue_fan_present": "true",
"main_heating_number": 1,
"main_heating_control": 2106,
"is_interlocked_system": "true",
"main_heating_category": 2,
"main_heating_fraction": 1,
"main_heating_flue_type": 2,
"central_heating_pump_age": 2,
"main_heating_data_source": 1,
"main_heating_index_number": 16211,
"has_separate_delayed_start": "true",
"load_or_weather_compensation": 0,
"is_central_heating_pump_in_heated_space": "true"
}
],
"has_hot_water_cylinder": "false",
"has_fixed_air_conditioning": "false",
"secondary_heating_category": 1
},
"sap_version": 9.92,
"schema_type": "SAP-Schema-17.0",
"uprn_source": "Energy Assessor",
"country_code": "ENG",
"main_heating": [
{
"description": "Boiler and radiators, mains gas",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"air_tightness": {
"description": "Air permeability 3.2 m\u00b3/h.m\u00b2 (as tested)",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
},
"dwelling_type": "Ground-floor flat",
"language_code": 1,
"property_type": 2,
"address_line_1": "1, Ashmore Close",
"assessment_date": "2016-09-06",
"assessment_type": "SAP",
"completion_date": "2016-09-06",
"inspection_date": "2016-09-06",
"sap_ventilation": {
"psv_count": 0,
"pressure_test": 1,
"wet_rooms_count": 2,
"air_permeability": 3.24,
"open_flues_count": 0,
"ventilation_type": 6,
"extract_fans_count": 0,
"open_fireplaces_count": 0,
"sheltered_sides_count": 2,
"kitchen_duct_fans_count": 0,
"kitchen_room_fans_count": 1,
"kitchen_wall_fans_count": 0,
"flueless_gas_fires_count": 0,
"mechanical_vent_duct_type": 2,
"non_kitchen_duct_fans_count": 0,
"non_kitchen_room_fans_count": 1,
"non_kitchen_wall_fans_count": 0,
"mechanical_ventilation_data_source": 1,
"mechanical_vent_system_index_number": 500416,
"is_mechanical_vent_approved_installer_scheme": "true"
},
"design_water_use": 1,
"sap_data_version": 9.92,
"sap_flat_details": {
"level": 1
},
"total_floor_area": 50,
"transaction_type": 6,
"conservatory_type": 1,
"registration_date": "2016-09-06",
"sap_energy_source": {
"pv_arrays": [
{
"pitch": 2,
"peak_power": 0.48,
"orientation": 5,
"overshading": 1,
"pv_connection": 1
}
],
"electricity_tariff": 1,
"wind_turbines_count": 0,
"wind_turbine_terrain_type": 2,
"fixed_lighting_outlets_count": 6,
"low_energy_fixed_lighting_outlets_count": 6,
"low_energy_fixed_lighting_outlets_percentage": 100
},
"sap_opening_types": [
{
"name": 1,
"type": 1,
"u_value": 0.97,
"data_source": 2,
"description": "Front",
"glazing_type": 1
},
{
"name": 2,
"type": 4,
"u_value": 1.2,
"data_source": 2,
"description": "Window",
"frame_factor": 0.7,
"glazing_type": 6,
"solar_transmittance": 0.63
}
],
"secondary_heating": {
"description": "None",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
},
"lzc_energy_sources": [
11
],
"sap_building_parts": [
{
"sap_walls": [
{
"name": "Wall 1",
"u_value": 0.2,
"wall_type": 2,
"description": "150mm hi-cav 32 AAC",
"total_wall_area": 37.93,
"is_curtain_walling": "false"
},
{
"name": "Wall 2",
"u_value": 0.18,
"wall_type": 3,
"description": "To corridor",
"total_wall_area": 19.28,
"is_curtain_walling": "false"
},
{
"name": "Wall 3",
"u_value": 0,
"wall_type": 4,
"description": "party",
"total_wall_area": 15.5
}
],
"identifier": "Main Dwelling",
"overshading": 2,
"sap_openings": [
{
"name": 1,
"type": 1,
"width": 1,
"height": 2.1,
"location": "Wall 2",
"orientation": 0
},
{
"name": 2,
"type": 2,
"width": 1.16,
"height": 2.28,
"location": "Wall 1",
"orientation": 7
},
{
"name": 3,
"type": 2,
"width": 1.16,
"height": 1.08,
"location": "Wall 1",
"orientation": 7
},
{
"name": 4,
"type": 2,
"width": 2.29,
"height": 2.28,
"location": "Wall 1",
"orientation": 7
},
{
"name": 5,
"type": 2,
"width": 1.17,
"height": 2.23,
"location": "Wall 1",
"orientation": 3
}
],
"construction_year": 2015,
"sap_thermal_bridges": {
"thermal_bridges": [
{
"length": 6.78,
"psi_value": 0.3,
"psi_value_source": 2,
"thermal_bridge_type": "E2"
},
{
"length": 5.78,
"psi_value": 0.04,
"psi_value_source": 2,
"thermal_bridge_type": "E3"
},
{
"length": 19.94,
"psi_value": 0.05,
"psi_value_source": 2,
"thermal_bridge_type": "E4"
},
{
"length": 22.17,
"psi_value": 0.16,
"psi_value_source": 2,
"thermal_bridge_type": "E5"
},
{
"length": 22.17,
"psi_value": 0.07,
"psi_value_source": 2,
"thermal_bridge_type": "E7"
},
{
"length": 2.26,
"psi_value": 1,
"psi_value_source": 3,
"thermal_bridge_type": "E9"
},
{
"length": 4.82,
"psi_value": 0.09,
"psi_value_source": 2,
"thermal_bridge_type": "E16"
},
{
"length": 4.82,
"psi_value": 0.12,
"psi_value_source": 4,
"thermal_bridge_type": "E18"
},
{
"length": 8,
"psi_value": 0.16,
"psi_value_source": 4,
"thermal_bridge_type": "P1"
},
{
"length": 8,
"psi_value": 0,
"psi_value_source": 3,
"thermal_bridge_type": "P3"
}
],
"thermal_bridge_code": 5
},
"building_part_number": 1,
"sap_floor_dimensions": [
{
"storey": 0,
"u_value": 0.1,
"floor_type": 2,
"description": "Floor 1",
"storey_height": 2.41,
"heat_loss_area": 50.17,
"total_floor_area": 50.17
}
],
"thermal_mass_parameter": 250
}
],
"heating_cost_current": {
"value": 171,
"currency": "GBP"
},
"co2_emissions_current": 0.7,
"energy_rating_average": 60,
"energy_rating_current": 82,
"lighting_cost_current": {
"value": 36,
"currency": "GBP"
},
"main_heating_controls": [
{
"description": "Programmer, room thermostat and TRVs",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"has_hot_water_cylinder": "false",
"heating_cost_potential": {
"value": 171,
"currency": "GBP"
},
"hot_water_cost_current": {
"value": 80,
"currency": "GBP"
},
"co2_emissions_potential": 0.7,
"energy_rating_potential": 82,
"lighting_cost_potential": {
"value": 36,
"currency": "GBP"
},
"schema_version_original": "LIG-17.0",
"hot_water_cost_potential": {
"value": 80,
"currency": "GBP"
},
"is_in_smoke_control_area": "unknown",
"renewable_heat_incentive": {
"rhi_new_dwelling": {
"space_heating": 1299,
"water_heating": 1651
}
},
"seller_commission_report": "Y",
"energy_consumption_current": 75,
"has_fixed_air_conditioning": "false",
"multiple_glazed_percentage": 100,
"calculation_software_version": 6.2,
"energy_consumption_potential": 75,
"environmental_impact_current": 89,
"current_energy_efficiency_band": "B",
"environmental_impact_potential": 89,
"has_heated_separate_conservatory": "false",
"potential_energy_efficiency_band": "B",
"co2_emissions_current_per_floor_area": 13
}