mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Merge remote-tracking branch 'origin/main' into fix/full-sap-export-capable-mapping
This commit is contained in:
commit
325c0ea257
52 changed files with 12569 additions and 65 deletions
|
|
@ -169,6 +169,34 @@ Pattern: `with E.session() as (ctx,page): E.goto(...); E.set_text/set_select(...
|
|||
lines 17/18). This drove the first campaign mapper fix — see Banked findings.
|
||||
|
||||
## Banked findings (fold new ones in here as the corpus grows)
|
||||
- **Register cost-triangulation (no Elmhurst needed):** the register lodges
|
||||
`heating_cost_current` / `hot_water_cost_current` / `lighting_cost_current`
|
||||
at RETAIL prices — absolute £ never match SAP Table-12 spec prices, but for
|
||||
same-fuel end-uses the RATIOS localize a divergence without a worksheet.
|
||||
Normalize by lighting: `(lodged_hw/lodged_light) / (our_hw/our_light)` ≈ 1
|
||||
means our water cascade agrees with the lodging software; »1 flags the
|
||||
divergent end-use. CAVEAT: the register's "hot water cost" INCLUDES the
|
||||
SAP (64a) electric-shower electricity — add `instant_shower` cost to our
|
||||
hw bucket before comparing on any electric-shower cert, else every
|
||||
shower-outlet-type-2 cert false-positives at ratio ~2 (corpus-1000 band
|
||||
triage, 36 certs, mean ratio 2.0 → 1.04 after adding (64a)).
|
||||
- **Lodging-software conventions diverge from Elmhurst on two known axes**
|
||||
(mark such corpus certs ⚠, don't tune): (a) out-of-range-PSR database
|
||||
heat pumps — SAP 10.2 N2 footnotes 44/45 mandate the reciprocal extension
|
||||
toward 100% at 2× the record's largest PSR (Elmhurst does this, golden
|
||||
case 56); at least one software bills 100% direct electric + standard
|
||||
schedule instead (corpus 4510053280, lodged 51 vs engine/Elmhurst-method
|
||||
75). (b) a lodged 0 extract fans — Elmhurst renders 0 as "unknown" →
|
||||
Table 5 age-band default (worksheet case 46); LIG-21.0 lodgements
|
||||
(calculation_software_version 10.2.2.0) honour the 0 literally (corpus
|
||||
quadruplet at Δ −2.56, fans account for 1.42).
|
||||
- **"insulated (assumed)" roof ≠ observed retrofit:** RdSAP renders
|
||||
"(assumed)" when insulation PRESENCE is an age-band assumption — Table 18
|
||||
age default governs, for BOTH the "ND" and "NI" thickness sentinels
|
||||
(which are lodging-software noise). The §5.11.4 50 mm row (U 0.68) is
|
||||
only for the bare "insulated" description (observed retrofit, depth
|
||||
unknown). Fixed in u_roof (corpus 74.2% → 75.5%); unit-pinned in
|
||||
test_rdsap_uvalues.
|
||||
- **MAPPER GAP — cylinder insulation thickness dropped (RdSAP-17.0+):** the mapper
|
||||
carries `cylinder_size` + `cylinder_insulation_type` but NOT
|
||||
`cylinder_insulation_thickness` → `EpcPropertyData.sap_heating.cylinder_
|
||||
|
|
|
|||
|
|
@ -0,0 +1,510 @@
|
|||
{
|
||||
"uprn": 100040550095,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 175 mm loft insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Granite or whin, as built, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
},
|
||||
{
|
||||
"description": "Solid brick, as built, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Solid, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 3,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"addendum": {
|
||||
"stone_walls": "true"
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Excellent lighting efficiency",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "TQ1 2BW",
|
||||
"hot_water": {
|
||||
"description": "Electric immersion, standard tariff",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"post_town": "TORQUAY",
|
||||
"built_form": 4,
|
||||
"created_at": "2026-05-20 13:21:34",
|
||||
"door_count": 2,
|
||||
"region_code": 15,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 2,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 2
|
||||
}
|
||||
],
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 903,
|
||||
"water_heating_fuel": 29,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 29,
|
||||
"heat_emitter_type": 0,
|
||||
"emitter_temperature": "NA",
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2603,
|
||||
"main_heating_category": 10,
|
||||
"main_heating_fraction": 1,
|
||||
"sap_main_heating_code": 691,
|
||||
"main_heating_data_source": 2
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": 2,
|
||||
"cylinder_insulation_type": 2,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"cylinder_insulation_thickness": 38
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 0.91,
|
||||
"window_height": 1.64,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 0.91,
|
||||
"window_height": 1.64,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 0.62,
|
||||
"window_height": 0.91,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.2,
|
||||
"window_height": 0.88,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 1,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.02,
|
||||
"window_height": 0.82,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 1,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 0.6,
|
||||
"window_height": 1.14,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 1,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"uprn_source": "Energy Assessor",
|
||||
"country_code": "ENG",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Room heaters, electric",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"air_tightness": {
|
||||
"description": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Mid-terrace house",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 0,
|
||||
"address_line_1": "11 Meadfoot Lane",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-20",
|
||||
"inspection_date": "2026-05-20",
|
||||
"extensions_count": 1,
|
||||
"measurement_type": 1,
|
||||
"total_floor_area": 59,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 3,
|
||||
"registration_date": "2026-05-20",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "true",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "true"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"extract_fans_count": 1,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 510,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 1,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.31,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 17.29,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 7.27,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 1,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 4.72,
|
||||
"quantity": "metres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": {
|
||||
"value": 2.35,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 17.29,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 7.27,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 4.72,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "A",
|
||||
"party_wall_construction": 1,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "175mm",
|
||||
"wall_insulation_thickness": "NI",
|
||||
"floor_insulation_thickness": "NI"
|
||||
},
|
||||
{
|
||||
"identifier": "Extension 1",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 185,
|
||||
"floor_heat_loss": 7,
|
||||
"sap_room_in_roof": {
|
||||
"floor_area": 12.24,
|
||||
"room_in_roof_type_2": {
|
||||
"gable_wall_type_1": 1,
|
||||
"gable_wall_type_2": 3,
|
||||
"gable_wall_height_1": 2.5,
|
||||
"gable_wall_height_2": 2.5,
|
||||
"gable_wall_length_1": 7.39,
|
||||
"gable_wall_length_2": 7.39,
|
||||
"common_wall_height_1": 1.5,
|
||||
"common_wall_height_2": 1.5,
|
||||
"common_wall_length_1": 1.4,
|
||||
"common_wall_length_2": 1.4
|
||||
},
|
||||
"construction_age_band": "A"
|
||||
},
|
||||
"roof_construction": 5,
|
||||
"wall_construction": 3,
|
||||
"building_part_number": 2,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.27,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 12.24,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 7.39,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 1,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 2.8,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "A",
|
||||
"party_wall_construction": 0,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 4,
|
||||
"roof_insulation_thickness": "ND",
|
||||
"wall_insulation_thickness": "NI",
|
||||
"floor_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 3,
|
||||
"heating_cost_current": {
|
||||
"value": 1266,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 1.1,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 40,
|
||||
"lighting_cost_current": {
|
||||
"value": 43,
|
||||
"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": 366,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 679,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 449,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a37,500 - \u00a311,000",
|
||||
"improvement_type": "Q",
|
||||
"improvement_details": {
|
||||
"improvement_number": 7
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 55,
|
||||
"environmental_impact_rating": 88
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 57,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a35,000 - \u00a310,000",
|
||||
"improvement_type": "W2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 58
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 56,
|
||||
"environmental_impact_rating": 89
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": {
|
||||
"value": 100,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a320 - \u00a340",
|
||||
"improvement_type": "C",
|
||||
"improvement_details": {
|
||||
"improvement_number": 2
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 59,
|
||||
"environmental_impact_rating": 89
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"typical_saving": {
|
||||
"value": 671,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a33,500 - \u00a310,000",
|
||||
"improvement_type": "T",
|
||||
"improvement_details": {
|
||||
"improvement_number": 29
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 76,
|
||||
"environmental_impact_rating": 83
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"typical_saving": {
|
||||
"value": 220,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a38,000 - \u00a310,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 81,
|
||||
"environmental_impact_rating": 84
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.1,
|
||||
"energy_rating_potential": 81,
|
||||
"lighting_cost_potential": {
|
||||
"value": 43,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"alternative_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 822,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"improvement_type": "J2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 54
|
||||
},
|
||||
"improvement_category": 6,
|
||||
"energy_performance_rating": 79,
|
||||
"environmental_impact_rating": 96
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 524,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"improvement_type": "Z3",
|
||||
"improvement_details": {
|
||||
"improvement_number": 53
|
||||
},
|
||||
"improvement_category": 6,
|
||||
"energy_performance_rating": 71,
|
||||
"environmental_impact_rating": 74
|
||||
}
|
||||
],
|
||||
"hot_water_cost_potential": {
|
||||
"value": 301,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2452.35,
|
||||
"space_heating_existing_dwelling": 4576.09
|
||||
},
|
||||
"draughtproofed_door_count": 2,
|
||||
"energy_consumption_current": 189,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "5.02r0344",
|
||||
"energy_consumption_potential": 102,
|
||||
"environmental_impact_current": 84,
|
||||
"current_energy_efficiency_band": "E",
|
||||
"environmental_impact_potential": 84,
|
||||
"led_fixed_lighting_bulbs_count": 7,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 18,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,700 @@
|
|||
{
|
||||
"uprn": 100050355518,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 200 mm loft insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
{
|
||||
"description": "Pitched, limited insulation",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, as built, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Suspended, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 1,
|
||||
"window": {
|
||||
"description": "Partial double glazing",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
},
|
||||
"addendum": {
|
||||
"addendum_numbers": [
|
||||
15
|
||||
],
|
||||
"cavity_fill_recommended": "true"
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Below average lighting efficiency",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"postcode": "BD23 1BG",
|
||||
"hot_water": {
|
||||
"description": "From main system, no cylinder thermostat",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"post_town": "SKIPTON",
|
||||
"psv_count": 0,
|
||||
"built_form": 2,
|
||||
"created_at": "2026-05-18 08:00:25",
|
||||
"door_count": 1,
|
||||
"region_code": 7,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 2,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 2
|
||||
},
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 2
|
||||
}
|
||||
],
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"cylinder_thermostat": "N",
|
||||
"secondary_fuel_type": 5,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "N",
|
||||
"heat_emitter_type": 1,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2102,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"sap_main_heating_code": 117,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 2
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"secondary_heating_type": 631,
|
||||
"cylinder_insulation_type": 2,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"cylinder_insulation_thickness": 80
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 5,
|
||||
"window_width": 0.4,
|
||||
"window_height": 1.18,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 5,
|
||||
"window_width": 5.03,
|
||||
"window_height": 1.72,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.19,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 2,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.74,
|
||||
"window_height": 2.05,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 2,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 2,
|
||||
"glazing_type": 3,
|
||||
"window_width": 0.6,
|
||||
"window_height": 0.9,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 2,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 2,
|
||||
"glazing_type": 3,
|
||||
"window_width": 0.6,
|
||||
"window_height": 0.9,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 2,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 2.34,
|
||||
"window_height": 1.18,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 1,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.79,
|
||||
"window_height": 2.06,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 2,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 5,
|
||||
"window_width": 5.03,
|
||||
"window_height": 1.23,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 5,
|
||||
"window_width": 1.11,
|
||||
"window_height": 1.17,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.19,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.18,
|
||||
"window_height": 1.15,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 1,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.1,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.66,
|
||||
"window_height": 1.17,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 1,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"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": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Semi-detached house",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 0,
|
||||
"address_line_1": "26 Regent Crescent",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-18",
|
||||
"inspection_date": "2026-05-15",
|
||||
"extensions_count": 2,
|
||||
"measurement_type": 1,
|
||||
"open_flues_count": 0,
|
||||
"total_floor_area": 122,
|
||||
"transaction_type": 15,
|
||||
"conservatory_type": 1,
|
||||
"has_draught_lobby": "true",
|
||||
"heated_room_count": 5,
|
||||
"other_flues_count": 0,
|
||||
"registration_date": "2026-05-18",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"pv_diverter": "false",
|
||||
"pv_connection": 0,
|
||||
"pv_battery_count": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "false",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "false",
|
||||
"is_hydro_output_connected_to_dwelling_meter": "false"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "Room heaters, anthracite",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"closed_flues_count": 0,
|
||||
"extract_fans_count": 1,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 230,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.53,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 35.63,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 6.09,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 2,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 7.95,
|
||||
"quantity": "metres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": {
|
||||
"value": 2.38,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 35.63,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 6.09,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 13.37,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "C",
|
||||
"party_wall_construction": 0,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "200mm",
|
||||
"wall_insulation_thickness": "NI"
|
||||
},
|
||||
{
|
||||
"identifier": "Extension 1",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 320,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 2,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.29,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 15.62,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": 0,
|
||||
"floor_construction": 2,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 7.95,
|
||||
"quantity": "metres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": {
|
||||
"value": 2.24,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 15.62,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": 0,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 11.19,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "C",
|
||||
"party_wall_construction": "NA",
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "200mm",
|
||||
"wall_insulation_thickness": "NI"
|
||||
},
|
||||
{
|
||||
"identifier": "Extension 2",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 320,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 8,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 3,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.86,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 19.28,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": 0,
|
||||
"floor_construction": 2,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 12.66,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "C",
|
||||
"party_wall_construction": "NA",
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 7,
|
||||
"wall_insulation_thickness": "NI",
|
||||
"sloping_ceiling_insulation_thickness": "AB"
|
||||
}
|
||||
],
|
||||
"boilers_flues_count": 0,
|
||||
"open_chimneys_count": 0,
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 5,
|
||||
"heating_cost_current": {
|
||||
"value": 3523,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 12,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 36,
|
||||
"lighting_cost_current": {
|
||||
"value": 84,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, no room thermostat",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
}
|
||||
],
|
||||
"blocked_chimneys_count": 1,
|
||||
"has_hot_water_cylinder": "true",
|
||||
"heating_cost_potential": {
|
||||
"value": 1387,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 574,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 0,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": 260,
|
||||
"indicative_cost": "\u00a3900 - \u00a31,200",
|
||||
"improvement_type": "A2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 45
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 39,
|
||||
"environmental_impact_rating": 30
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": 606,
|
||||
"indicative_cost": "\u00a3900 - \u00a31,500",
|
||||
"improvement_type": "B",
|
||||
"improvement_details": {
|
||||
"improvement_number": 6
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 47,
|
||||
"environmental_impact_rating": 38
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": 251,
|
||||
"indicative_cost": "\u00a35,000 - \u00a310,000",
|
||||
"improvement_type": "W1",
|
||||
"improvement_details": {
|
||||
"improvement_number": 57
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 51,
|
||||
"environmental_impact_rating": 42
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"typical_saving": 134,
|
||||
"indicative_cost": "\u00a3150 - \u00a3250",
|
||||
"improvement_type": "D",
|
||||
"improvement_details": {
|
||||
"improvement_number": 10
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 53,
|
||||
"environmental_impact_rating": 44
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"typical_saving": 276,
|
||||
"indicative_cost": "\u00a3220 - \u00a3250",
|
||||
"improvement_type": "G",
|
||||
"improvement_details": {
|
||||
"improvement_number": 12
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 57,
|
||||
"environmental_impact_rating": 49
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"typical_saving": 629,
|
||||
"indicative_cost": "\u00a32,200 - \u00a33,500",
|
||||
"improvement_type": "I",
|
||||
"improvement_details": {
|
||||
"improvement_number": 20
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 67,
|
||||
"environmental_impact_rating": 61
|
||||
},
|
||||
{
|
||||
"sequence": 7,
|
||||
"typical_saving": 197,
|
||||
"indicative_cost": "\u00a34,500 - \u00a36,000",
|
||||
"improvement_type": "O",
|
||||
"improvement_details": {
|
||||
"improvement_number": 8
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 70,
|
||||
"environmental_impact_rating": 66
|
||||
},
|
||||
{
|
||||
"sequence": 8,
|
||||
"typical_saving": 233,
|
||||
"indicative_cost": "\u00a38,000 - \u00a310,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 74,
|
||||
"environmental_impact_rating": 67
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 4.6,
|
||||
"energy_rating_potential": 74,
|
||||
"lighting_cost_potential": {
|
||||
"value": 84,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "LIG-21.0.1",
|
||||
"alternative_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": 172,
|
||||
"improvement_type": "Q2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 55
|
||||
},
|
||||
"improvement_category": 6,
|
||||
"energy_performance_rating": 50,
|
||||
"environmental_impact_rating": 40
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": 774,
|
||||
"improvement_type": "J2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 54
|
||||
},
|
||||
"improvement_category": 6,
|
||||
"energy_performance_rating": 64,
|
||||
"environmental_impact_rating": 82
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": 828,
|
||||
"improvement_type": "Z3",
|
||||
"improvement_details": {
|
||||
"improvement_number": 53
|
||||
},
|
||||
"improvement_category": 6,
|
||||
"energy_performance_rating": 69,
|
||||
"environmental_impact_rating": 66
|
||||
}
|
||||
],
|
||||
"flueless_gas_fires_count": 0,
|
||||
"hot_water_cost_potential": {
|
||||
"value": 358,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 3736.92,
|
||||
"space_heating_existing_dwelling": 24523.93
|
||||
},
|
||||
"draughtproofed_door_count": 0,
|
||||
"energy_consumption_current": 466,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 52,
|
||||
"calculation_software_version": "4.3.74",
|
||||
"energy_consumption_potential": 170,
|
||||
"environmental_impact_current": 27,
|
||||
"current_energy_efficiency_band": "F",
|
||||
"environmental_impact_potential": 67,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "C",
|
||||
"co2_emissions_current_per_floor_area": 98,
|
||||
"low_energy_fixed_lighting_bulbs_count": 17,
|
||||
"incandescent_fixed_lighting_bulbs_count": 4
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,426 @@
|
|||
{
|
||||
"uprn": 100050881708,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 225 mm loft insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, filled cavity",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "To unheated space, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 3,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Below average lighting efficiency",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"postcode": "S65 3RW",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "ROTHERHAM",
|
||||
"built_form": 2,
|
||||
"created_at": "2026-05-27 15:09:39",
|
||||
"door_count": 2,
|
||||
"region_code": 3,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 1,
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 0,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 17741
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.7,
|
||||
"window_height": 1.4,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.2,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.6,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.6,
|
||||
"window_height": 0.7,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.6,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.2,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.2,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.1,
|
||||
"window_height": 0.8,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.6,
|
||||
"window_height": 0.7,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.6,
|
||||
"window_height": 1.1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"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": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Semi-detached house",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 0,
|
||||
"address_line_1": "9 Woodgrove Road",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-27",
|
||||
"inspection_date": "2026-05-27",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"total_floor_area": 63,
|
||||
"transaction_type": 14,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 3,
|
||||
"registration_date": "2026-05-27",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "true",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "true"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"extract_fans_count": 2,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"floor_heat_loss": 2,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.4,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 31.62,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 6.2,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 2,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 16.4,
|
||||
"quantity": "metres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": {
|
||||
"value": 2.41,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 31.62,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 6.2,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 16.4,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 2,
|
||||
"construction_age_band": "B",
|
||||
"party_wall_construction": 1,
|
||||
"wall_thickness_measured": "N",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "225mm",
|
||||
"wall_insulation_thickness": "NI",
|
||||
"floor_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 3,
|
||||
"heating_cost_current": {
|
||||
"value": 777,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 2.3,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 70,
|
||||
"lighting_cost_current": {
|
||||
"value": 79,
|
||||
"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": 671,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 172,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 110,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a35,000 - \u00a310,000",
|
||||
"improvement_type": "W1",
|
||||
"improvement_details": {
|
||||
"improvement_number": 57
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 73,
|
||||
"environmental_impact_rating": 75
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 30,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3120 - \u00a3140",
|
||||
"improvement_type": "E",
|
||||
"improvement_details": {
|
||||
"improvement_number": 35
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 74,
|
||||
"environmental_impact_rating": 75
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": {
|
||||
"value": 184,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a38,000 - \u00a310,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 79,
|
||||
"environmental_impact_rating": 77
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.9,
|
||||
"energy_rating_potential": 79,
|
||||
"lighting_cost_potential": {
|
||||
"value": 45,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 172,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2070.91,
|
||||
"space_heating_existing_dwelling": 7543.77
|
||||
},
|
||||
"draughtproofed_door_count": 2,
|
||||
"energy_consumption_current": 205,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "5.02r0344",
|
||||
"energy_consumption_potential": 161,
|
||||
"environmental_impact_current": 71,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 77,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "C",
|
||||
"co2_emissions_current_per_floor_area": 37,
|
||||
"low_energy_fixed_lighting_bulbs_count": 4,
|
||||
"incandescent_fixed_lighting_bulbs_count": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
{
|
||||
"uprn": 100051051866,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, insulated (assumed)",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, as built, insulated (assumed)",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Suspended, limited insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 1,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"addendum": {
|
||||
"addendum_numbers": [
|
||||
15
|
||||
]
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Excellent lighting efficiency",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "S20 7NQ",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "SHEFFIELD",
|
||||
"psv_count": 0,
|
||||
"built_form": 1,
|
||||
"created_at": "2026-05-18 17:37:45",
|
||||
"door_count": 1,
|
||||
"region_code": 11,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 2,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 4
|
||||
}
|
||||
],
|
||||
"number_baths_wwhrs": 0,
|
||||
"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,
|
||||
"emitter_temperature": 1,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2104,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"sap_main_heating_code": 102,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 2,
|
||||
"main_heating_index_number": 18773
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"cylinder_insulation_type": 1,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"cylinder_insulation_thickness": 50
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1.6,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1.6,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.6,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1.6,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 0.5,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"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": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Detached bungalow",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 1,
|
||||
"address_line_1": "3 Pentland Gardens",
|
||||
"address_line_2": "Waterthorpe",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-18",
|
||||
"inspection_date": "2026-05-18",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"open_flues_count": 0,
|
||||
"total_floor_area": 60,
|
||||
"transaction_type": 1,
|
||||
"conservatory_type": 2,
|
||||
"heated_room_count": 4,
|
||||
"other_flues_count": 0,
|
||||
"registration_date": "2026-05-18",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 3,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "false",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "false"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"closed_flues_count": 0,
|
||||
"extract_fans_count": 0,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 300,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": 2.4,
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": 60,
|
||||
"party_wall_length": 0,
|
||||
"floor_construction": 2,
|
||||
"heat_loss_perimeter": 32
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "I",
|
||||
"party_wall_construction": "NA",
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 4,
|
||||
"roof_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"boilers_flues_count": 0,
|
||||
"open_chimneys_count": 0,
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 4,
|
||||
"heating_cost_current": {
|
||||
"value": 701,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 2.3,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 70,
|
||||
"lighting_cost_current": {
|
||||
"value": 40,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer and room thermostat",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"blocked_chimneys_count": 0,
|
||||
"has_hot_water_cylinder": "true",
|
||||
"heating_cost_potential": {
|
||||
"value": 701,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 235,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"schema_version_current": "LIG-21.0",
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": 238,
|
||||
"indicative_cost": "\u00a38,000 - \u00a310,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 82,
|
||||
"environmental_impact_rating": 73
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 2.1,
|
||||
"energy_rating_potential": 82,
|
||||
"lighting_cost_potential": {
|
||||
"value": 40,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "LIG-21.0",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 235,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2544.67,
|
||||
"space_heating_existing_dwelling": 6292.1
|
||||
},
|
||||
"draughtproofed_door_count": 1,
|
||||
"energy_consumption_current": 209,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "10.2.2.0",
|
||||
"energy_consumption_potential": 185,
|
||||
"environmental_impact_current": 70,
|
||||
"cfl_fixed_lighting_bulbs_count": 0,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 73,
|
||||
"led_fixed_lighting_bulbs_count": 8,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 38,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,410 @@
|
|||
{
|
||||
"uprn": 100061275133,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 300 mm loft insulation",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Solid brick, with external insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Suspended, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 2,
|
||||
"window": {
|
||||
"description": "High performance glazing",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Excellent lighting efficiency",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "ME7 2ES",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "GILLINGHAM",
|
||||
"built_form": 2,
|
||||
"created_at": "2026-05-20 08:39:48",
|
||||
"door_count": 2,
|
||||
"region_code": 14,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 1,
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 0,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 18908
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"orientation": 8,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 1.19,
|
||||
"window_height": 1.13,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 8,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 0.57,
|
||||
"window_height": 0.55,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 8,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 1.19,
|
||||
"window_height": 1.11,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 2,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 1.05,
|
||||
"window_height": 0.85,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 2,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 0.57,
|
||||
"window_height": 0.56,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 4,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 2.11,
|
||||
"window_height": 1.13,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 4,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 1.8,
|
||||
"window_height": 0.97,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 4,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 1.8,
|
||||
"window_height": 0.97,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 4,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 1.2,
|
||||
"window_height": 0.99,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 8,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 1.8,
|
||||
"window_height": 0.97,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 8,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 1.19,
|
||||
"window_height": 0.97,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 8,
|
||||
"window_type": 1,
|
||||
"glazing_type": 13,
|
||||
"window_width": 1.18,
|
||||
"window_height": 0.99,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"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": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Semi-detached house",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 0,
|
||||
"address_line_1": "75 Toronto Road",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-20",
|
||||
"inspection_date": "2026-05-19",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"total_floor_area": 78,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 3,
|
||||
"registration_date": "2026-05-20",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "true",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "true"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 400,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 3,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.4,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 38.99,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 5.09,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 0,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 17.92,
|
||||
"quantity": "metres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": {
|
||||
"value": 2.4,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 38.99,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 5.09,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 17.92,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 1,
|
||||
"construction_age_band": "B",
|
||||
"party_wall_construction": 1,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "300mm",
|
||||
"wall_insulation_thickness": "100mm",
|
||||
"floor_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 3,
|
||||
"heating_cost_current": {
|
||||
"value": 608,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 1.9,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 76,
|
||||
"lighting_cost_current": {
|
||||
"value": 58,
|
||||
"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": 608,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 182,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 216,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a38,000 - \u00a310,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 81,
|
||||
"environmental_impact_rating": 79
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.8,
|
||||
"energy_rating_potential": 81,
|
||||
"lighting_cost_potential": {
|
||||
"value": 58,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 182,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2203.16,
|
||||
"space_heating_existing_dwelling": 5517.85
|
||||
},
|
||||
"draughtproofed_door_count": 2,
|
||||
"energy_consumption_current": 133,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "5.02r0344",
|
||||
"energy_consumption_potential": 118,
|
||||
"environmental_impact_current": 78,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 79,
|
||||
"led_fixed_lighting_bulbs_count": 5,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 24,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
{
|
||||
"uprn": 10012334488,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "(another dwelling above)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "System built, as built, insulated (assumed)",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "(another dwelling below)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 3,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Excellent lighting efficiency",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "SL4 3BP",
|
||||
"hot_water": {
|
||||
"description": "From main system, no cylinder thermostat",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "WINDSOR",
|
||||
"built_form": "NR",
|
||||
"created_at": "2026-05-14 18:33:57",
|
||||
"door_count": 1,
|
||||
"region_code": 17,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 0,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 1
|
||||
}
|
||||
],
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 29,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 29,
|
||||
"heat_emitter_type": 2,
|
||||
"emitter_temperature": "NA",
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2110,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"sap_main_heating_code": 195,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 2
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 2,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.85,
|
||||
"window_height": 1.11,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 2,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.85,
|
||||
"window_height": 1.11,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 2,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.85,
|
||||
"window_height": 1.11,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 2,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.85,
|
||||
"window_height": 1.11,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 2,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.85,
|
||||
"window_height": 1.11,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 2,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.85,
|
||||
"window_height": 1.11,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"uprn_source": "Energy Assessor",
|
||||
"country_code": "ENG",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Boiler and underfloor heating, electric",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"air_tightness": {
|
||||
"description": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Mid-floor flat",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 2,
|
||||
"address_line_1": "Flat 12",
|
||||
"address_line_2": "Alexandra Court 25a",
|
||||
"address_line_3": "St. Leonards Road",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-14",
|
||||
"inspection_date": "2026-05-14",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"sap_flat_details": {
|
||||
"level": 2,
|
||||
"top_storey": "N",
|
||||
"storey_count": 4,
|
||||
"flat_location": 3,
|
||||
"heat_loss_corridor": 1
|
||||
},
|
||||
"total_floor_area": 62,
|
||||
"transaction_type": 14,
|
||||
"conservatory_type": 1,
|
||||
"has_draught_lobby": "true",
|
||||
"heated_room_count": 2,
|
||||
"registration_date": "2026-05-14",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "N",
|
||||
"meter_type": 1,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "false",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 1,
|
||||
"electricity_smart_meter_present": "false"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"extract_fans_count": 2,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 260,
|
||||
"floor_heat_loss": 6,
|
||||
"roof_construction": 3,
|
||||
"wall_construction": 8,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.48,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 62,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 23.99,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 11.89,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "L",
|
||||
"party_wall_construction": 1,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": "ND",
|
||||
"roof_insulation_thickness": "ND",
|
||||
"wall_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 2,
|
||||
"heating_cost_current": {
|
||||
"value": 136,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 0.5,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 72,
|
||||
"lighting_cost_current": {
|
||||
"value": 51,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Time and temperature zone control",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"has_hot_water_cylinder": "true",
|
||||
"heating_cost_potential": {
|
||||
"value": 136,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 893,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 91,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3600 - \u00a31,500",
|
||||
"improvement_type": "Y",
|
||||
"improvement_details": {
|
||||
"improvement_number": 49
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 74,
|
||||
"environmental_impact_rating": 93
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 0.5,
|
||||
"energy_rating_potential": 74,
|
||||
"lighting_cost_potential": {
|
||||
"value": 51,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 802,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2802.07,
|
||||
"space_heating_existing_dwelling": 628.72
|
||||
},
|
||||
"draughtproofed_door_count": 1,
|
||||
"energy_consumption_current": 92,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "5.02r0344",
|
||||
"energy_consumption_potential": 85,
|
||||
"environmental_impact_current": 93,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 93,
|
||||
"led_fixed_lighting_bulbs_count": 23,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "C",
|
||||
"co2_emissions_current_per_floor_area": 9,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,491 @@
|
|||
{
|
||||
"uprn": 10070056075,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, insulated",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, as built, insulated (assumed)",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Suspended, insulated (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 1,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"addendum": {
|
||||
"addendum_numbers": [
|
||||
15
|
||||
]
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Excellent lighting efficiency",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "BD10 9UG",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "BRADFORD",
|
||||
"psv_count": 0,
|
||||
"built_form": 1,
|
||||
"created_at": "2026-05-15 10:22:34",
|
||||
"door_count": 1,
|
||||
"region_code": 11,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 4,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 4
|
||||
}
|
||||
],
|
||||
"number_baths_wwhrs": 0,
|
||||
"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,
|
||||
"emitter_temperature": 1,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 19012
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"cylinder_insulation_type": 1,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"cylinder_insulation_thickness": 80
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.2,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"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": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Detached house",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 0,
|
||||
"address_line_1": "36 Longlands",
|
||||
"address_line_2": "Idle",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-15",
|
||||
"inspection_date": "2026-05-08",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"open_flues_count": 0,
|
||||
"total_floor_area": 139,
|
||||
"transaction_type": 5,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 8,
|
||||
"other_flues_count": 0,
|
||||
"registration_date": "2026-05-15",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 1,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "false",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "false"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"closed_flues_count": 0,
|
||||
"extract_fans_count": 0,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 400,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 8,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": 2.3,
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": 69.6,
|
||||
"party_wall_length": 0,
|
||||
"floor_construction": 2,
|
||||
"heat_loss_perimeter": 37.7
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": 2.3,
|
||||
"total_floor_area": 69.6,
|
||||
"party_wall_length": 0,
|
||||
"heat_loss_perimeter": 37.7
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "K",
|
||||
"party_wall_construction": "NA",
|
||||
"wall_thickness_measured": "N",
|
||||
"roof_insulation_location": 7,
|
||||
"wall_insulation_thickness": "NI",
|
||||
"sloping_ceiling_insulation_thickness": "AB"
|
||||
}
|
||||
],
|
||||
"boilers_flues_count": 0,
|
||||
"open_chimneys_count": 0,
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 8,
|
||||
"heating_cost_current": {
|
||||
"value": 938,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 3.0,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 80,
|
||||
"lighting_cost_current": {
|
||||
"value": 89,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, room thermostat and TRVs",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"blocked_chimneys_count": 0,
|
||||
"has_hot_water_cylinder": "true",
|
||||
"heating_cost_potential": {
|
||||
"value": 938,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 236,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"schema_version_current": "LIG-21.0",
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": 308,
|
||||
"indicative_cost": "\u00a38,000 - \u00a310,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 87,
|
||||
"environmental_impact_rating": 81
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 2.8,
|
||||
"energy_rating_potential": 87,
|
||||
"lighting_cost_potential": {
|
||||
"value": 89,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "LIG-21.0",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 236,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2744.25,
|
||||
"space_heating_existing_dwelling": 9473.16
|
||||
},
|
||||
"draughtproofed_door_count": 1,
|
||||
"energy_consumption_current": 117,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "10.2.2.0",
|
||||
"energy_consumption_potential": 106,
|
||||
"environmental_impact_current": 79,
|
||||
"cfl_fixed_lighting_bulbs_count": 0,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 81,
|
||||
"led_fixed_lighting_bulbs_count": 18,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 21,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
{
|
||||
"uprn": 10091578598,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "(another dwelling above)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, as built, insulated (assumed)",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "(another dwelling below)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 2,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Good lighting efficiency",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"postcode": "RM1 3FB",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"post_town": "ROMFORD",
|
||||
"built_form": "NR",
|
||||
"created_at": "2026-05-16 17:47:19",
|
||||
"door_count": 1,
|
||||
"region_code": 2,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 4,
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 29,
|
||||
"cylinder_thermostat": "Y",
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 29,
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": "NA",
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"sap_main_heating_code": 195,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 2
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"cylinder_insulation_type": 1,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"cylinder_insulation_thickness": 50
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 2,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.15,
|
||||
"window_height": 1.65,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 2,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.15,
|
||||
"window_height": 1.65,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 2,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.15,
|
||||
"window_height": 1.65,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 2,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.15,
|
||||
"window_height": 1.65,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"uprn_source": "Energy Assessor",
|
||||
"country_code": "ENG",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Boiler and radiators, electric",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"air_tightness": {
|
||||
"description": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Mid-floor flat",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 2,
|
||||
"address_line_1": "41 Verve Apartments",
|
||||
"address_line_2": "5 Mercury Gardens",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-16",
|
||||
"inspection_date": "2026-05-16",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"sap_flat_details": {
|
||||
"level": 2,
|
||||
"top_storey": "N",
|
||||
"storey_count": 6,
|
||||
"flat_location": 3,
|
||||
"heat_loss_corridor": 2,
|
||||
"unheated_corridor_length": 6
|
||||
},
|
||||
"total_floor_area": 60,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 2,
|
||||
"registration_date": "2026-05-16",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "N",
|
||||
"meter_type": 4,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "false",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "true"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 300,
|
||||
"floor_heat_loss": 6,
|
||||
"roof_construction": 3,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.5,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 60,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 20,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 12,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "I",
|
||||
"sap_alternative_wall_1": {
|
||||
"wall_area": 15,
|
||||
"sheltered_wall": "Y",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 300,
|
||||
"wall_construction": 4,
|
||||
"wall_insulation_type": 4,
|
||||
"wall_thickness_measured": "Y",
|
||||
"wall_insulation_thickness": "NI"
|
||||
},
|
||||
"party_wall_construction": 0,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": "ND",
|
||||
"roof_insulation_thickness": "ND",
|
||||
"wall_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 2,
|
||||
"heating_cost_current": {
|
||||
"value": 202,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 0.6,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 71,
|
||||
"lighting_cost_current": {
|
||||
"value": 55,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, room thermostat and TRVs",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"has_hot_water_cylinder": "true",
|
||||
"heating_cost_potential": {
|
||||
"value": 370,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 847,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 80,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 76,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3150 - \u00a3250",
|
||||
"improvement_type": "D",
|
||||
"improvement_details": {
|
||||
"improvement_number": 10
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 79,
|
||||
"environmental_impact_rating": 92
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 0.6,
|
||||
"energy_rating_potential": 79,
|
||||
"lighting_cost_potential": {
|
||||
"value": 48,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 611,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2659.46,
|
||||
"space_heating_existing_dwelling": 1061.64
|
||||
},
|
||||
"draughtproofed_door_count": 0,
|
||||
"energy_consumption_current": 102,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "5.02r0344",
|
||||
"energy_consumption_potential": 102,
|
||||
"environmental_impact_current": 92,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 92,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "C",
|
||||
"co2_emissions_current_per_floor_area": 10,
|
||||
"low_energy_fixed_lighting_bulbs_count": 20,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
{
|
||||
"uprn": 10094975827,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, insulated (assumed)",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Solid brick, as built, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "(other premises below)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 3,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Excellent lighting efficiency",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "B8 2NG",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "BIRMINGHAM",
|
||||
"psv_count": 0,
|
||||
"built_form": 3,
|
||||
"created_at": "2026-05-14 13:27:59",
|
||||
"door_count": 1,
|
||||
"region_code": 6,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 1,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 1
|
||||
}
|
||||
],
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"cylinder_thermostat": "N",
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 1,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2107,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 18559
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": {
|
||||
"value": 1.01,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.61,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": {
|
||||
"value": 1.66,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.46,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": {
|
||||
"value": 1.09,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.29,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": {
|
||||
"value": 1.09,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.55,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": {
|
||||
"value": 1.79,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.92,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"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": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Top-floor flat",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 2,
|
||||
"address_line_1": "Flat 2",
|
||||
"address_line_2": "874 Washwood Heath Road",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-14",
|
||||
"inspection_date": "2026-05-13",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"open_flues_count": 0,
|
||||
"sap_flat_details": {
|
||||
"level": 3,
|
||||
"top_storey": "N",
|
||||
"storey_count": 2,
|
||||
"flat_location": 1,
|
||||
"heat_loss_corridor": 0
|
||||
},
|
||||
"total_floor_area": 78,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 3,
|
||||
"other_flues_count": 0,
|
||||
"registration_date": "2026-05-14",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 3,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "false",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "false"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"closed_flues_count": 0,
|
||||
"extract_fans_count": 0,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 340,
|
||||
"floor_heat_loss": 3,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 3,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": 2.44,
|
||||
"total_floor_area": 77.87,
|
||||
"party_wall_length": 11.57,
|
||||
"heat_loss_perimeter": 25.03
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "B",
|
||||
"party_wall_construction": 1,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 4,
|
||||
"roof_insulation_thickness": "NI",
|
||||
"wall_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"boilers_flues_count": 0,
|
||||
"open_chimneys_count": 0,
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 3,
|
||||
"heating_cost_current": {
|
||||
"value": 952,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 3.2,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 66,
|
||||
"lighting_cost_current": {
|
||||
"value": 56,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, TRVs and bypass",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"blocked_chimneys_count": 0,
|
||||
"has_hot_water_cylinder": "false",
|
||||
"heating_cost_potential": {
|
||||
"value": 735,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 197,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"schema_version_current": "LIG-21.0",
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": 171,
|
||||
"indicative_cost": "\u00a37,500 - \u00a311,000",
|
||||
"improvement_type": "Q",
|
||||
"improvement_details": {
|
||||
"improvement_number": 7
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 70,
|
||||
"environmental_impact_rating": 70
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": 45,
|
||||
"indicative_cost": "\u00a3220 - \u00a3250",
|
||||
"improvement_type": "G",
|
||||
"improvement_details": {
|
||||
"improvement_number": 14
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 72,
|
||||
"environmental_impact_rating": 71
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 2.6,
|
||||
"energy_rating_potential": 72,
|
||||
"lighting_cost_potential": {
|
||||
"value": 56,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "LIG-21.0",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 198,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2548.95,
|
||||
"space_heating_existing_dwelling": 10093.12
|
||||
},
|
||||
"draughtproofed_door_count": 1,
|
||||
"energy_consumption_current": 227,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "10.2.2.0",
|
||||
"energy_consumption_potential": 181,
|
||||
"environmental_impact_current": 64,
|
||||
"cfl_fixed_lighting_bulbs_count": 0,
|
||||
"current_energy_efficiency_band": "D",
|
||||
"environmental_impact_potential": 71,
|
||||
"led_fixed_lighting_bulbs_count": 8,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "C",
|
||||
"co2_emissions_current_per_floor_area": 41,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,321 @@
|
|||
{
|
||||
"uprn": 103001004,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, Unknown loft insulation",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, filled cavity",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Solid, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 2,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Good lighting efficiency",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"postcode": "NE33 1TN",
|
||||
"hot_water": {
|
||||
"description": "Electric immersion, off-peak",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "SOUTH SHIELDS",
|
||||
"built_form": "NR",
|
||||
"created_at": "2026-04-16 10:25:37",
|
||||
"door_count": 0,
|
||||
"region_code": 1,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 0,
|
||||
"cylinder_size": 3,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_outlet": {
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"cylinder_heat_loss": 1.8,
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 903,
|
||||
"water_heating_fuel": 29,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 20,
|
||||
"heat_emitter_type": 0,
|
||||
"emitter_temperature": "NA",
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2307,
|
||||
"main_heating_category": 6,
|
||||
"main_heating_fraction": 1,
|
||||
"sap_main_heating_code": 301,
|
||||
"main_heating_data_source": 2,
|
||||
"community_heat_distribution_type": 6
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": 2,
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": 12,
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.21,
|
||||
"window_height": 1.44,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": 12,
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.77,
|
||||
"window_height": 1.14,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"uprn_source": "Energy Assessor",
|
||||
"country_code": "ENG",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Community scheme",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"air_tightness": {
|
||||
"description": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Ground-floor flat",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 2,
|
||||
"address_line_1": "3 Julius Court",
|
||||
"address_line_2": "Mile End Road",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-04-16",
|
||||
"inspection_date": "2026-04-16",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"sap_flat_details": {
|
||||
"level": 1,
|
||||
"top_storey": "N",
|
||||
"storey_count": 1,
|
||||
"flat_location": 0,
|
||||
"heat_loss_corridor": 1
|
||||
},
|
||||
"total_floor_area": 31,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 1,
|
||||
"has_draught_lobby": "true",
|
||||
"heated_room_count": 2,
|
||||
"registration_date": "2026-04-16",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 1,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "false",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "false"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"extract_fans_count": 2,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.37,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 30.55,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 16.75,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 1,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 5.37,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 2,
|
||||
"construction_age_band": "F",
|
||||
"party_wall_construction": 0,
|
||||
"wall_thickness_measured": "N",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "NI",
|
||||
"wall_insulation_thickness": "NI",
|
||||
"floor_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 2,
|
||||
"heating_cost_current": {
|
||||
"value": 251,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 1.2,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 50,
|
||||
"lighting_cost_current": {
|
||||
"value": 29,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Flat rate charging, TRVs",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"has_hot_water_cylinder": "true",
|
||||
"heating_cost_potential": {
|
||||
"value": 313,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 1054,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 35,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3900 - \u00a31,200",
|
||||
"improvement_type": "A",
|
||||
"improvement_details": {
|
||||
"improvement_number": 5
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 52,
|
||||
"environmental_impact_rating": 81
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 598,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a320 - \u00a340",
|
||||
"improvement_type": "C",
|
||||
"improvement_details": {
|
||||
"improvement_number": 1
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 68,
|
||||
"environmental_impact_rating": 83
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": {
|
||||
"value": 51,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3600 - \u00a31,500",
|
||||
"improvement_type": "Y",
|
||||
"improvement_details": {
|
||||
"improvement_number": 49
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 70,
|
||||
"environmental_impact_rating": 83
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.0,
|
||||
"energy_rating_potential": 70,
|
||||
"lighting_cost_potential": {
|
||||
"value": 29,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 308,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 5719.21,
|
||||
"space_heating_existing_dwelling": 1270.63
|
||||
},
|
||||
"draughtproofed_door_count": 0,
|
||||
"energy_consumption_current": 367,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "5.02r0342",
|
||||
"energy_consumption_potential": 214,
|
||||
"environmental_impact_current": 78,
|
||||
"current_energy_efficiency_band": "E",
|
||||
"environmental_impact_potential": 83,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "C",
|
||||
"co2_emissions_current_per_floor_area": 41,
|
||||
"low_energy_fixed_lighting_bulbs_count": 5,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,372 @@
|
|||
{
|
||||
"uprn": 200004017091,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 100 mm loft insulation",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Timber frame, as built, insulated (assumed)",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Solid, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 1,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"addendum": {
|
||||
"addendum_numbers": [
|
||||
15
|
||||
]
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Good lighting efficiency",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"postcode": "BN14 9EL",
|
||||
"hot_water": {
|
||||
"description": "From main system, no cylinder thermostat",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"post_town": "WORTHING",
|
||||
"built_form": 3,
|
||||
"created_at": "2026-04-21 13:18:06",
|
||||
"door_count": 2,
|
||||
"region_code": 14,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 0,
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 0,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 17803
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.1,
|
||||
"window_height": 1.2,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.1,
|
||||
"window_height": 1.2,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.1,
|
||||
"window_height": 1.2,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 0.8,
|
||||
"window_height": 0.8,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"glazing_gap": "16+",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 3,
|
||||
"window_width": 1.5,
|
||||
"window_height": 0.8,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"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": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "End-terrace house",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 0,
|
||||
"address_line_1": "3 Radbone Close",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-04-21",
|
||||
"inspection_date": "2026-04-20",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"total_floor_area": 65,
|
||||
"transaction_type": 1,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 4,
|
||||
"registration_date": "2026-04-21",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "false",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "false"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"extract_fans_count": 1,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 280,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 5,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.37,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 32.38,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 7.42,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 1,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 16.15,
|
||||
"quantity": "metres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": {
|
||||
"value": 2.35,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 32.37,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 7.42,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 16.15,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "G",
|
||||
"party_wall_construction": 0,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "100mm",
|
||||
"wall_insulation_thickness": "NI",
|
||||
"floor_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 4,
|
||||
"heating_cost_current": {
|
||||
"value": 556,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 2.2,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 71,
|
||||
"lighting_cost_current": {
|
||||
"value": 51,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, room thermostat and TRVs",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"has_hot_water_cylinder": "true",
|
||||
"heating_cost_potential": {
|
||||
"value": 516,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 333,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 71,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 44,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a35,000 - \u00a310,000",
|
||||
"improvement_type": "W2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 58
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 73,
|
||||
"environmental_impact_rating": 74
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 143,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a34,000 - \u00a37,000",
|
||||
"improvement_type": "N",
|
||||
"improvement_details": {
|
||||
"improvement_number": 19
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 76,
|
||||
"environmental_impact_rating": 79
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": {
|
||||
"value": 212,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a38,000 - \u00a310,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 81,
|
||||
"environmental_impact_rating": 80
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.5,
|
||||
"energy_rating_potential": 81,
|
||||
"lighting_cost_potential": {
|
||||
"value": 51,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 186,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 3562.39,
|
||||
"space_heating_existing_dwelling": 4593.09
|
||||
},
|
||||
"draughtproofed_door_count": 0,
|
||||
"energy_consumption_current": 184,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "5.02r0342",
|
||||
"energy_consumption_potential": 118,
|
||||
"environmental_impact_current": 72,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 80,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 33,
|
||||
"low_energy_fixed_lighting_bulbs_count": 12,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# Elmhurst validation — UPRN 200004296092 (corpus, slice-5 wall fix)
|
||||
|
||||
Built in accredited Elmhurst RdSAP10-Online on the lodged register inputs
|
||||
(end-terrace house, solid brick + **external insulation, thickness "NI"**,
|
||||
pitched 150 mm loft, solid ground floor, mains-gas combi, TFA 62).
|
||||
Artefacts: `elmhurst_summary.pdf` (Input Summary), `elmhurst_worksheet.pdf`
|
||||
(SAP Worksheets).
|
||||
|
||||
## Headline
|
||||
- **Lodged SAP 71**; **engine 71.74** (post slice-5 §5.4 100 mm fix, was 64.35);
|
||||
**accredited Elmhurst current SAP 66** (band D), potential 75.
|
||||
- Slice 5 is **directionally validated**: the externally-insulated wall must be
|
||||
billed insulated, not at the uninsulated 1.40 the §5.8 gate produced. Both
|
||||
Elmhurst (0.48) and the engine (0.29) are far below 1.40.
|
||||
|
||||
## Fabric line-by-line (engine dive vs Elmhurst worksheet)
|
||||
| Element | Engine | Elmhurst | Note |
|
||||
|---|---|---|---|
|
||||
| External wall | 21.1 W/K (U 0.29) | 30.7 W/K (U **0.48**, "SolidWallDensePlaster**200**") | see WALL below |
|
||||
| Roof | 9.35 W/K (U 0.30) | 9.35 W/K (U 0.30) | **exact match** |
|
||||
| Ground floor | 21.2 W/K | 22.7 W/K (U 0.73) | close |
|
||||
| Party wall | **8.57 W/K** | **0.00 W/K** | see PARTY below |
|
||||
| Windows | 7.96 W/K | ~same area (4.31 + 4×2.89 split) | ~ |
|
||||
|
||||
The wall (engine −9.6) and party wall (engine +8.6) differences nearly cancel,
|
||||
so the two HTCs are close; the residual SAP gap (71.7 vs 66) is dominated by the
|
||||
heating system — this build used a GENERIC Post-98 condensing combi, whereas the
|
||||
engine uses the lodged PCDB 16137 (Ideal Logic combi 30). Not a clean 1e-4
|
||||
comparison for that reason.
|
||||
|
||||
## Leads (both partly confounded by an imperfect build — see caveats)
|
||||
1. **WALL — base thickness on an unmeasured wall.** The cert lodges
|
||||
`wall_thickness 360` but `wall_thickness_measured = N`. Elmhurst modelled a
|
||||
**~200 mm** base wall ("SolidWallDensePlaster200") — but note my build did
|
||||
NOT enter a wall thickness, so Elmhurst used its own default; had I keyed
|
||||
360 mm it might match the engine. The open question is genuine though:
|
||||
RdSAP §5.3 uses the documentary/measured thickness, and when NOT measured
|
||||
the engine perhaps should fall to the Table-3 default rather than trust the
|
||||
unmeasured lodged 360 mm. WORTH A DEDICATED CHECK (build with the thickness
|
||||
keyed both ways).
|
||||
2. **PARTY WALL — NOT an engine gap.** Elmhurst rendered the party wall as
|
||||
"Solid, U 0.00", but the engine's **0.25** is SPEC-CORRECT: RdSAP §5.10
|
||||
Table 15 row 4 ("unable to determine, house") = 0.25. The cert lodges
|
||||
`party_wall_construction = 0` (unable to determine), so 0.25 is right; the
|
||||
Elmhurst 0.00 reflects how my build's party-wall dropdown resolved (a
|
||||
"Solid" masonry party wall, Table 15 row 1 = 0), not the cert's "unknown".
|
||||
Build artifact, not a bug.
|
||||
|
||||
## Build caveats (why the 71.7-vs-66 residual isn't cleanly attributable)
|
||||
- Heating: built a GENERIC Post-98 condensing combi; the engine uses the lodged
|
||||
PCDB 16137 (Ideal Logic combi 30) — different efficiency.
|
||||
- Wall thickness: not keyed into Elmhurst (defaulted 200 mm) vs engine's 360 mm.
|
||||
- Party wall: entered as a resolved type (0.00) vs the cert's "unable to
|
||||
determine" (engine 0.25).
|
||||
So this build's clean, confirmed results are: (a) the ROOF matches exactly
|
||||
(U 0.30, 9.35 W/K), (b) slice 5's DIRECTION is validated (insulated wall, not
|
||||
the uninsulated 1.40). The wall-thickness question (lead 1) is the one genuine
|
||||
follow-up.
|
||||
|
||||
## Slice-5 calibration note (banked)
|
||||
The accredited "External insulation, **Unknown** thickness" selection in Elmhurst
|
||||
produced U 0.48 (≈ a 50 mm equivalent on its 200 mm base), NOT the 0.29 the
|
||||
§5.4-literal "assume 100 mm" gives on the engine's 360 mm base. The 100 mm fix
|
||||
nevertheless matches the LODGED corpus better (MAE 0.658 → 0.647, within held) —
|
||||
lodged assessors typically knew the real (~100 mm) thickness the register then
|
||||
flattens to "NI". Per the campaign rule (engine = lodged is the goal; Elmhurst
|
||||
arbitrates only when lodged can't be explained from register data), the 100 mm
|
||||
fix STANDS; the lodged-vs-Elmhurst gap here is explained by the register's "NI"
|
||||
discarding the assessor's thickness knowledge. But the base-thickness and
|
||||
party-wall leads above are genuine and independent of the insulation default.
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,361 @@
|
|||
{
|
||||
"uprn": 200004296092,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 150 mm loft insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Solid brick, with external insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Solid, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 1,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Excellent lighting efficiency",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "LL16 3RL",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "DENBIGH",
|
||||
"psv_count": 0,
|
||||
"built_form": 3,
|
||||
"created_at": "2026-05-14 14:33:05",
|
||||
"door_count": 2,
|
||||
"region_code": 7,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 0,
|
||||
"cylinder_size": 1,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 2
|
||||
}
|
||||
],
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 1,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 16137
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1.07,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.34,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1.07,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.34,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 0.47,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 0.85,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 0.47,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 0.85,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 0.76,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 0.84,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"uprn_source": "Energy Assessor",
|
||||
"country_code": "WLS",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Boiler and radiators, mains gas",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"air_tightness": {
|
||||
"description": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "End-terrace house",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 0,
|
||||
"address_line_1": "54 Myddleton Avenue",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-14",
|
||||
"inspection_date": "2026-05-14",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"open_flues_count": 0,
|
||||
"total_floor_area": 62,
|
||||
"transaction_type": 1,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 3,
|
||||
"other_flues_count": 0,
|
||||
"registration_date": "2026-05-14",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "true",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "true"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"closed_flues_count": 0,
|
||||
"extract_fans_count": 0,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 360,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 3,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": 2.4,
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": 31.16,
|
||||
"party_wall_length": 6.79,
|
||||
"floor_construction": 1,
|
||||
"heat_loss_perimeter": 15.97
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": 2.4,
|
||||
"total_floor_area": 31.16,
|
||||
"party_wall_length": 6.79,
|
||||
"heat_loss_perimeter": 15.97
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 1,
|
||||
"construction_age_band": "B",
|
||||
"party_wall_construction": 0,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "150mm",
|
||||
"wall_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"boilers_flues_count": 0,
|
||||
"open_chimneys_count": 0,
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 3,
|
||||
"heating_cost_current": {
|
||||
"value": 589,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 1.8,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 71,
|
||||
"lighting_cost_current": {
|
||||
"value": 50,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, room thermostat and TRVs",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"blocked_chimneys_count": 0,
|
||||
"has_hot_water_cylinder": "false",
|
||||
"heating_cost_potential": {
|
||||
"value": 543,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 275,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"schema_version_current": "LIG-21.0",
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": 47,
|
||||
"indicative_cost": "\u00a35,000 - \u00a310,000",
|
||||
"improvement_type": "W2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 58
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 72,
|
||||
"environmental_impact_rating": 79
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": 275,
|
||||
"indicative_cost": "\u00a38,000 - \u00a310,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 84,
|
||||
"environmental_impact_rating": 81
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.4,
|
||||
"energy_rating_potential": 84,
|
||||
"lighting_cost_potential": {
|
||||
"value": 50,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "LIG-21.0",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 275,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 1768.31,
|
||||
"space_heating_existing_dwelling": 5933.75
|
||||
},
|
||||
"draughtproofed_door_count": 2,
|
||||
"energy_consumption_current": 169,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "10.2.2.0",
|
||||
"energy_consumption_potential": 130,
|
||||
"environmental_impact_current": 77,
|
||||
"cfl_fixed_lighting_bulbs_count": 0,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 81,
|
||||
"led_fixed_lighting_bulbs_count": 12,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 29,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
Summary Information
|
||||
Surveyor: P960-0001
|
||||
Name: Richard Matthew Ratcliff Title: Mr. Tel Number: 07760 443 469
|
||||
Survey Reference: 001431 My Reference: Khalim-test
|
||||
Current SAP rating: D 66 Potential SAP rating: C 75 Emissions (t/year): 1.927 tonnes
|
||||
Current EI rating: C 72 Potential EI rating: C 76 Fuel Bill: £1026
|
||||
Property Details:
|
||||
RdSAP version: RdSAP10
|
||||
Reference Number: P960-0001-001431
|
||||
My Reference: Khalim-test
|
||||
Lodgement Required: No
|
||||
Regs Region: England
|
||||
EPC Language: English
|
||||
UPRN:
|
||||
Postcode: W6 9BF
|
||||
Region: Thames Valley
|
||||
House Name: 28 Distillery Wharf
|
||||
House No: 28
|
||||
Street: Distillery Wharf
|
||||
Locality:
|
||||
Town: London
|
||||
County:
|
||||
Property Tenure: Owner-occupied
|
||||
Transaction Type: Marketed sale
|
||||
Inspection Date: 25/06/2025
|
||||
Process date: 02/07/2026
|
||||
Check for the existence of No
|
||||
an EPC:
|
||||
Does an EPC exist at the No
|
||||
point of carrying out this
|
||||
energy assessment:
|
||||
Reason why another energy
|
||||
assessment needs to be
|
||||
undertaken:
|
||||
|
||||
RdSAP Inputs
|
||||
|
||||
Property Description:
|
||||
1.0 Property type: H House
|
||||
E End-Terrace
|
||||
2.0 Number of Storeys: 2
|
||||
Habitable Rooms: 3
|
||||
Heated Habitable Rooms: 3
|
||||
3.0 Date Built: Main Property B 1900-1929
|
||||
|
||||
|
||||
4.0 Dimensions:
|
||||
Dimension type: Internal
|
||||
Main Property
|
||||
Floor Room Heat Loss Party Wall
|
||||
Area Height Wall Perimeter Length
|
||||
[m2] [m] [m] [m]
|
||||
|
||||
1st Floor: 31.16 2.40 15.97 6.79
|
||||
Lowest Floor: 31.16 2.40 15.97 6.79 No
|
||||
|
||||
5.0 Conservatory:
|
||||
Is there a conservatory? No
|
||||
7.0 Walls:
|
||||
|
||||
|
||||
|
||||
|
||||
© Elmhurst Energy Systems Limited Registered Office Unit 16, St Johns Business Park, Lutterworth, Leicestershire LE17 4HB
|
||||
Summary Information
|
||||
Main Property
|
||||
|
||||
Type SO Solid Brick
|
||||
Insulation E External
|
||||
Insulation Thickness Unknown
|
||||
Conductivity Known No
|
||||
Thermal Conductivity Lambda = 0.04 W/m*K
|
||||
Wall Thickness Unknown No
|
||||
Wall Thickness 260 mm
|
||||
U-value Known No
|
||||
Party Wall Type S Solid masonry / timber / system build
|
||||
8.0 Roofs:
|
||||
Main Property
|
||||
|
||||
Type PA Pitched (slates/tiles), access to loft
|
||||
Insulation J Joists
|
||||
Insulation Thickness 150 mm
|
||||
U-value Known No
|
||||
8.1 Rooms in Roof:
|
||||
9.0 Floors:
|
||||
Main Property
|
||||
|
||||
Location G Ground floor
|
||||
Type S Solid
|
||||
Insulation A As built
|
||||
Default U-value 0.73
|
||||
U-value Known No
|
||||
10.0 Doors:
|
||||
Total Number of Doors 2
|
||||
Number of Insulated Doors 0
|
||||
11.0 Windows:
|
||||
Frame Frame Glazing Building U g Draught Permanent
|
||||
W H Area Glazing Type Location Orient. Data-Source
|
||||
Type Factor Gap Part value value Proofed Shutters
|
||||
Double with unknown
|
||||
4.31 1.00 4.31 PVC 0.70 12 mm Main External wall North Manufacturer 2.80 0.76 Yes None
|
||||
install date
|
||||
Double with unknown
|
||||
2.89 1.00 2.89 PVC 0.70 12 mm Main External wall West Manufacturer 2.80 0.76 Yes None
|
||||
install date
|
||||
Double with unknown
|
||||
2.89 1.00 2.89 PVC 0.70 12 mm Main External wall North Manufacturer 2.80 0.76 Yes None
|
||||
install date
|
||||
Double with unknown
|
||||
2.89 1.00 2.89 PVC 0.70 12 mm Main External wall East Manufacturer 2.80 0.76 Yes None
|
||||
install date
|
||||
|
||||
Draught Proofing 67 %
|
||||
12.0 Ventilation & Cooling
|
||||
No. of open chimneys 0
|
||||
No. of open flues 0
|
||||
No. of open chimneys/open flues attached to closed fire 0
|
||||
No. of flues attached to solid fuel boiler 0
|
||||
No. of open flues attached to other heater 0
|
||||
No. of blocked chimneys 0
|
||||
No. of intermittent extract fans 0
|
||||
No. of passive vents 0
|
||||
No. of flueless gas fires 0
|
||||
Fixed Space Cooling No
|
||||
Draught Lobby Unable to determine
|
||||
|
||||
|
||||
|
||||
|
||||
© Elmhurst Energy Systems Limited Registered Office Unit 16, St Johns Business Park, Lutterworth, Leicestershire LE17 4HB
|
||||
Summary Information
|
||||
12.1 Mechanical Ventilation
|
||||
Mechanical Ventilation No
|
||||
12.2 Air Pressure Test
|
||||
Test Method Not available
|
||||
13.0 Lighting
|
||||
Total number of bulbs 6
|
||||
Number of LED and CFL Known Yes
|
||||
Number of LED lights 0
|
||||
Number of CFL lights 0
|
||||
Total number of Low Energy 0
|
||||
Total number of incandescents 6
|
||||
14.0 Main Heating1
|
||||
PCDF boiler Reference 0
|
||||
Main Heating EES Code BGW
|
||||
Main Heating SAP Code 104
|
||||
Heat Emitter Radiators
|
||||
Heat pump age 2013 or later
|
||||
Flue Type Balanced
|
||||
Fan Assisted Flue Yes
|
||||
Design flow temperature <= 35°C
|
||||
PCDF Heating Controls 0
|
||||
Main Heating Controls EES CBE
|
||||
Main Heating Controls Sap SAP code 2106, Programmer, room thermostat and TRVs
|
||||
PCDF Compensator 0
|
||||
Percentage of Heat 100 %
|
||||
14.1 Main Heating2
|
||||
PCDF boiler Reference 0
|
||||
Main Heating EES Code
|
||||
Main Heating SAP Code 0
|
||||
Percentage of Heat 0%
|
||||
14.1 Community Heating/Heat Network
|
||||
Heating Type None
|
||||
14.2 Meters
|
||||
Electricity meter type Single
|
||||
Main gas Yes
|
||||
Electricity Smart Meter Present No
|
||||
Gas Smart Meter Present No
|
||||
15.0 Water Heating
|
||||
Water Heating Code HWP
|
||||
Water Heating SapCode 901
|
||||
Water Heating Fuel Type Mains gas
|
||||
15.1 Hot Water Cylinder
|
||||
Hot Water Cylinder Present No
|
||||
15.2 Community Hot Water
|
||||
PCDF boiler Reference 0
|
||||
16.0 Solar water heating
|
||||
Solar Water Heating No
|
||||
17.0 Waste Water Heat Recovery System
|
||||
Is WWHRS present in the property? No / Unknown
|
||||
1x.0 Baths and Showers
|
||||
Total Number of Baths 2
|
||||
Number of Baths Connected 0
|
||||
|
||||
Description Type Connected
|
||||
|
||||
1 Non-electric shower None
|
||||
|
||||
|
||||
|
||||
|
||||
© Elmhurst Energy Systems Limited Registered Office Unit 16, St Johns Business Park, Lutterworth, Leicestershire LE17 4HB
|
||||
Summary Information
|
||||
18.0 Flue Gas Heat Recovery System
|
||||
Present No
|
||||
19.0 Photovoltaic Panel
|
||||
Photovoltaic Panel None
|
||||
Export capable meter No
|
||||
20.0 Wind Turbine
|
||||
Terrain Type Urban
|
||||
Wind turbine present? No
|
||||
22.0 Special Features
|
||||
21.0 Small-Scale Hydro
|
||||
Electricity generated [kWh/year] 0.00
|
||||
|
||||
|
||||
|
||||
|
||||
© Elmhurst Energy Systems Limited Registered Office Unit 16, St Johns Business Park, Lutterworth, Leicestershire LE17 4HB
|
||||
Summary Information
|
||||
|
||||
Recommendations
|
||||
|
||||
Loft insulation (SAP increase too small)
|
||||
Flat roof insulation (Not applicable)
|
||||
Room-in-roof insulation (Not applicable)
|
||||
Cavity wall insulation (Not applicable)
|
||||
Solid wall insulation (SAP increase too small)
|
||||
Floor insulation (solid floor) (Recommended)
|
||||
Hot water cylinder insulation (Not applicable)
|
||||
Draught proofing (SAP increase too small)
|
||||
Low energy lighting (Recommended)
|
||||
Cylinder thermostat (Not applicable)
|
||||
Heating controls for wet central heating system (Already installed)
|
||||
Upgrade boiler, same fuel (Already installed)
|
||||
Change heating to condensing gas condensing boiler (fuel switch) (Not applicable)
|
||||
Flue gas heat recovery in conjunction with new boiler (Not applicable)
|
||||
Solar water heating (SAP increase too small)
|
||||
Heat recovery system for mixer showers (SAP increase too small)
|
||||
Double glazed windows (Already installed)
|
||||
Insulated doors (SAP increase too small)
|
||||
Solar photovoltaic panels (Recommended)
|
||||
Wind turbine (Not applicable)
|
||||
PV diverter (Not applicable)
|
||||
PV battery (Not applicable)
|
||||
Water heating controls (Not applicable)
|
||||
|
||||
Alternative Recommendations
|
||||
External wall insulation with cavity insulation (Not applicable)
|
||||
Biomass boiler (alternative) (Not applicable)
|
||||
Micro CHP (alternative) (Not applicable)
|
||||
|
||||
Related Party Disclosure
|
||||
|
||||
No related party
|
||||
|
||||
Addenda
|
||||
When considering the PV installation consider installing PV battery and a PV diverter for water heating
|
||||
|
||||
|
||||
|
||||
|
||||
© Elmhurst Energy Systems Limited Registered Office Unit 16, St Johns Business Park, Lutterworth, Leicestershire LE17 4HB
|
||||
|
||||
File diff suppressed because it is too large
Load diff
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,384 @@
|
|||
{
|
||||
"uprn": 217091901,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Flat, no insulation",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Solid brick, as built, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "(another dwelling below)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 3,
|
||||
"window": {
|
||||
"description": "Single glazed",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Good lighting efficiency",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"postcode": "W11 1EP",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "LONDON",
|
||||
"built_form": "NR",
|
||||
"created_at": "2026-05-16 15:45:17",
|
||||
"door_count": 0,
|
||||
"region_code": 17,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 1,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 1
|
||||
}
|
||||
],
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 0,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2107,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 18658
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 5,
|
||||
"window_width": 1.2,
|
||||
"window_height": 1.8,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 5,
|
||||
"window_width": 1.2,
|
||||
"window_height": 1.8,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 9,
|
||||
"window_type": 2,
|
||||
"glazing_type": 5,
|
||||
"window_width": 0.5,
|
||||
"window_height": 0.5,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 9,
|
||||
"window_type": 2,
|
||||
"glazing_type": 5,
|
||||
"window_width": 0.7,
|
||||
"window_height": 0.5,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 5,
|
||||
"window_width": 1.2,
|
||||
"window_height": 1.8,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 5,
|
||||
"window_width": 1.1,
|
||||
"window_height": 1.4,
|
||||
"draught_proofed": "false",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"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": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Top-floor flat",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 2,
|
||||
"address_line_1": "Second Floor Flat",
|
||||
"address_line_2": "216 Westbourne Park Road",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-16",
|
||||
"inspection_date": "2026-05-16",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"sap_flat_details": {
|
||||
"level": 3,
|
||||
"top_storey": "Y",
|
||||
"storey_count": 3,
|
||||
"flat_location": 2,
|
||||
"heat_loss_corridor": 0
|
||||
},
|
||||
"total_floor_area": 63,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 1,
|
||||
"has_draught_lobby": "true",
|
||||
"heated_room_count": 3,
|
||||
"registration_date": "2026-05-16",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "true",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "true"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 280,
|
||||
"floor_heat_loss": 6,
|
||||
"roof_construction": 1,
|
||||
"wall_construction": 3,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.85,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 63.29,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 22.8,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 14,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "A",
|
||||
"party_wall_construction": 0,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 6,
|
||||
"wall_insulation_thickness": "NI",
|
||||
"flat_roof_insulation_thickness": "AB"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 3,
|
||||
"heating_cost_current": {
|
||||
"value": 967,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 2.8,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 62,
|
||||
"lighting_cost_current": {
|
||||
"value": 43,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, TRVs and bypass",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"blocked_chimneys_count": 1,
|
||||
"has_hot_water_cylinder": "false",
|
||||
"heating_cost_potential": {
|
||||
"value": 330,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 167,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 0,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 411,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3900 - \u00a31,200",
|
||||
"improvement_type": "A2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 45
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 74,
|
||||
"environmental_impact_rating": 76
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 129,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a37,500 - \u00a311,000",
|
||||
"improvement_type": "Q",
|
||||
"improvement_details": {
|
||||
"improvement_number": 7
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 78,
|
||||
"environmental_impact_rating": 81
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": {
|
||||
"value": 18,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3150 - \u00a3250",
|
||||
"improvement_type": "D",
|
||||
"improvement_details": {
|
||||
"improvement_number": 10
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 79,
|
||||
"environmental_impact_rating": 82
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"typical_saving": {
|
||||
"value": 78,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a34,500 - \u00a36,000",
|
||||
"improvement_type": "O",
|
||||
"improvement_details": {
|
||||
"improvement_number": 8
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 81,
|
||||
"environmental_impact_rating": 85
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.0,
|
||||
"energy_rating_potential": 81,
|
||||
"lighting_cost_potential": {
|
||||
"value": 45,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 167,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2022.69,
|
||||
"space_heating_existing_dwelling": 9246.82
|
||||
},
|
||||
"draughtproofed_door_count": 0,
|
||||
"energy_consumption_current": 245,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 0,
|
||||
"calculation_software_version": "5.02r0344",
|
||||
"energy_consumption_potential": 90,
|
||||
"environmental_impact_current": 59,
|
||||
"cfl_fixed_lighting_bulbs_count": 2,
|
||||
"current_energy_efficiency_band": "D",
|
||||
"environmental_impact_potential": 85,
|
||||
"led_fixed_lighting_bulbs_count": 9,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 45,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
{
|
||||
"uprn": 4510053280,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "(another dwelling above)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Solid brick, as built, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
{
|
||||
"description": "Solid brick, as built, partial insulation (assumed)",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Solid, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 2,
|
||||
"window": {
|
||||
"description": "Full secondary glazing",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Good lighting efficiency",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"postcode": "NE1 4DG",
|
||||
"hot_water": {
|
||||
"description": "From main system, no cylinder thermostat",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "NEWCASTLE UPON TYNE",
|
||||
"built_form": "NR",
|
||||
"created_at": "2026-05-24 12:41:32",
|
||||
"door_count": 0,
|
||||
"region_code": 1,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 1,
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 29,
|
||||
"secondary_fuel_type": 29,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 29,
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 0,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2203,
|
||||
"main_heating_category": 4,
|
||||
"main_heating_fraction": 1,
|
||||
"mcs_installed_heat_pump": "false",
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 100053
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"secondary_heating_type": 691,
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"orientation": 4,
|
||||
"window_type": 1,
|
||||
"glazing_type": 4,
|
||||
"window_width": 1.3,
|
||||
"window_height": 1.46,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"orientation": 4,
|
||||
"window_type": 1,
|
||||
"glazing_type": 4,
|
||||
"window_width": 1.3,
|
||||
"window_height": 1.46,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"uprn_source": "Energy Assessor",
|
||||
"country_code": "ENG",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Air source heat pump, radiators, electric",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"air_tightness": {
|
||||
"description": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Ground-floor flat",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 2,
|
||||
"address_line_1": "64 Waterloo Street",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-24",
|
||||
"inspection_date": "2026-05-22",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"sap_flat_details": {
|
||||
"level": 1,
|
||||
"top_storey": "N",
|
||||
"storey_count": 3,
|
||||
"flat_location": 0,
|
||||
"heat_loss_corridor": 2,
|
||||
"unheated_corridor_length": 3.5
|
||||
},
|
||||
"total_floor_area": 47,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 1,
|
||||
"has_draught_lobby": "true",
|
||||
"heated_room_count": 2,
|
||||
"registration_date": "2026-05-24",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "N",
|
||||
"meter_type": 2,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "false",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 1,
|
||||
"electricity_smart_meter_present": "false"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "Room heaters, electric",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"lzc_energy_sources": [
|
||||
9
|
||||
],
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 520,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 3,
|
||||
"wall_construction": 3,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.32,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 47,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 8,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 1,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 14.15,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "B",
|
||||
"sap_alternative_wall_1": {
|
||||
"wall_area": 8.12,
|
||||
"sheltered_wall": "Y",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 520,
|
||||
"wall_construction": 3,
|
||||
"wall_insulation_type": 4,
|
||||
"wall_thickness_measured": "Y",
|
||||
"wall_insulation_thickness": "NI"
|
||||
},
|
||||
"party_wall_construction": 0,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": "ND",
|
||||
"roof_insulation_thickness": "ND",
|
||||
"wall_insulation_thickness": "NI",
|
||||
"floor_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 2,
|
||||
"heating_cost_current": {
|
||||
"value": 278,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 0.8,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 51,
|
||||
"lighting_cost_current": {
|
||||
"value": 39,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Room thermostat only",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
}
|
||||
],
|
||||
"has_hot_water_cylinder": "false",
|
||||
"heating_cost_potential": {
|
||||
"value": 272,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 1295,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 68,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3220 - \u00a3250",
|
||||
"improvement_type": "G",
|
||||
"improvement_details": {
|
||||
"improvement_number": 16
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 53,
|
||||
"environmental_impact_rating": 88
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 0.8,
|
||||
"energy_rating_potential": 53,
|
||||
"lighting_cost_potential": {
|
||||
"value": 39,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 1233,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 5072.68,
|
||||
"space_heating_existing_dwelling": 2884.0
|
||||
},
|
||||
"draughtproofed_door_count": 0,
|
||||
"energy_consumption_current": 190,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "5.02r0344",
|
||||
"energy_consumption_potential": 182,
|
||||
"environmental_impact_current": 88,
|
||||
"current_energy_efficiency_band": "E",
|
||||
"environmental_impact_potential": 88,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "E",
|
||||
"co2_emissions_current_per_floor_area": 18,
|
||||
"low_energy_fixed_lighting_bulbs_count": 6,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,324 @@
|
|||
{
|
||||
"uprn": 47084930,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Flat, insulated",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, with internal insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "(other premises below)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 3,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Excellent lighting efficiency",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "NE12 8AQ",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "NEWCASTLE UPON TYNE",
|
||||
"built_form": "NR",
|
||||
"created_at": "2026-05-06 10:59:38",
|
||||
"door_count": 0,
|
||||
"region_code": 1,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 1,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 1
|
||||
}
|
||||
],
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 0,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 17815
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.23,
|
||||
"window_height": 1.61,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.23,
|
||||
"window_height": 1.61,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.23,
|
||||
"window_height": 1.61,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.23,
|
||||
"window_height": 1.61,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.23,
|
||||
"window_height": 1.61,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"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": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Top-floor flat",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 2,
|
||||
"address_line_1": "83 Station Road",
|
||||
"address_line_2": "Forest Hall",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-06",
|
||||
"inspection_date": "2026-05-05",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"sap_flat_details": {
|
||||
"level": 3,
|
||||
"top_storey": "Y",
|
||||
"storey_count": 2,
|
||||
"flat_location": 1,
|
||||
"heat_loss_corridor": 1
|
||||
},
|
||||
"total_floor_area": 64,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 1,
|
||||
"has_draught_lobby": "true",
|
||||
"heated_room_count": 3,
|
||||
"registration_date": "2026-05-06",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "true",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "true"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 380,
|
||||
"floor_heat_loss": 3,
|
||||
"roof_construction": 1,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.32,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 64.26,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 18.15,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 18.15,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 3,
|
||||
"construction_age_band": "E",
|
||||
"party_wall_construction": 0,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 6,
|
||||
"wall_insulation_thickness": "100mm",
|
||||
"flat_roof_insulation_thickness": "75mm"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 3,
|
||||
"heating_cost_current": {
|
||||
"value": 623,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 2.0,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 74,
|
||||
"lighting_cost_current": {
|
||||
"value": 42,
|
||||
"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": 553,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 207,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 69,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3900 - \u00a31,200",
|
||||
"improvement_type": "A2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 45
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 76,
|
||||
"environmental_impact_rating": 78
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.8,
|
||||
"energy_rating_potential": 76,
|
||||
"lighting_cost_potential": {
|
||||
"value": 42,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 208,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2384.75,
|
||||
"space_heating_existing_dwelling": 5681.26
|
||||
},
|
||||
"draughtproofed_door_count": 0,
|
||||
"energy_consumption_current": 170,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "5.02r0344",
|
||||
"energy_consumption_potential": 154,
|
||||
"environmental_impact_current": 76,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 78,
|
||||
"led_fixed_lighting_bulbs_count": 9,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "C",
|
||||
"co2_emissions_current_per_floor_area": 31,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
{
|
||||
"uprn": 74061136,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "(another dwelling above)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Solid brick, with internal insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
{
|
||||
"description": "Timber frame, with additional insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "(other premises below)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 1,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Excellent lighting efficiency",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "S41 7TP",
|
||||
"hot_water": {
|
||||
"description": "Electric instantaneous at point of use",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"post_town": "Chesterfield,",
|
||||
"psv_count": 0,
|
||||
"built_form": 4,
|
||||
"created_at": "2026-05-20 13:45:13",
|
||||
"door_count": 1,
|
||||
"region_code": 6,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 1,
|
||||
"shower_outlets": [
|
||||
{
|
||||
"shower_wwhrs": 1,
|
||||
"shower_outlet_type": 1
|
||||
}
|
||||
],
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 909,
|
||||
"water_heating_fuel": 29,
|
||||
"cylinder_thermostat": "N",
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 29,
|
||||
"storage_heaters": [
|
||||
{
|
||||
"index_number": 230002,
|
||||
"number_of_heaters": 1,
|
||||
"high_heat_retention": "true"
|
||||
}
|
||||
],
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 0,
|
||||
"emitter_temperature": "NA",
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2404,
|
||||
"main_heating_category": 7,
|
||||
"main_heating_fraction": 50,
|
||||
"sap_main_heating_code": 409,
|
||||
"main_heating_data_source": 2
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.6,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 5,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": {
|
||||
"value": 1,
|
||||
"quantity": "m"
|
||||
},
|
||||
"window_height": {
|
||||
"value": 1.6,
|
||||
"quantity": "m"
|
||||
},
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"uprn_source": "Address Matched",
|
||||
"country_code": "ENG",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Electric storage heaters",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"air_tightness": {
|
||||
"description": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Mid-floor flat",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 2,
|
||||
"address_line_1": "Flat 3,",
|
||||
"address_line_2": "14-20 Corporation Street,",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-20",
|
||||
"inspection_date": "2026-05-20",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"open_flues_count": 0,
|
||||
"sap_flat_details": {
|
||||
"level": 2,
|
||||
"top_storey": "N",
|
||||
"storey_count": 3,
|
||||
"flat_location": 1,
|
||||
"heat_loss_corridor": 2,
|
||||
"unheated_corridor_length": 6
|
||||
},
|
||||
"total_floor_area": 24,
|
||||
"transaction_type": 1,
|
||||
"conservatory_type": 2,
|
||||
"heated_room_count": 1,
|
||||
"other_flues_count": 0,
|
||||
"registration_date": "2026-05-20",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "N",
|
||||
"meter_type": 3,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "false",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "false"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "Portable electric heaters (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"closed_flues_count": 0,
|
||||
"extract_fans_count": 0,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 300,
|
||||
"floor_heat_loss": 3,
|
||||
"roof_construction": 3,
|
||||
"wall_construction": 3,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": 2.4,
|
||||
"total_floor_area": 24,
|
||||
"party_wall_length": 8,
|
||||
"heat_loss_perimeter": 12
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 3,
|
||||
"construction_age_band": "B",
|
||||
"sap_alternative_wall_1": {
|
||||
"wall_area": 14.4,
|
||||
"sheltered_wall": "Y",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 150,
|
||||
"wall_construction": 5,
|
||||
"wall_insulation_type": 3,
|
||||
"wall_thickness_measured": "Y",
|
||||
"wall_insulation_thickness": "NI"
|
||||
},
|
||||
"party_wall_construction": 0,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": "ND",
|
||||
"roof_insulation_thickness": "ND",
|
||||
"wall_insulation_thickness": "measured",
|
||||
"wall_insulation_thickness_measured": 25,
|
||||
"wall_insulation_thermal_conductivity": 3
|
||||
}
|
||||
],
|
||||
"boilers_flues_count": 0,
|
||||
"open_chimneys_count": 0,
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 1,
|
||||
"heating_cost_current": {
|
||||
"value": 348,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 0.4,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 73,
|
||||
"lighting_cost_current": {
|
||||
"value": 23,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Controls for high heat retention storage heaters",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"blocked_chimneys_count": 0,
|
||||
"has_hot_water_cylinder": "false",
|
||||
"heating_cost_potential": {
|
||||
"value": 348,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 344,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"schema_version_current": "LIG-21.0",
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": 66,
|
||||
"indicative_cost": "\u00a3600 - \u00a31,500",
|
||||
"improvement_type": "Y",
|
||||
"improvement_details": {
|
||||
"improvement_number": 49
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 76,
|
||||
"environmental_impact_rating": 92
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 0.4,
|
||||
"energy_rating_potential": 76,
|
||||
"lighting_cost_potential": {
|
||||
"value": 23,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "LIG-21.0",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 278,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 1079.39,
|
||||
"space_heating_existing_dwelling": 2013.05
|
||||
},
|
||||
"draughtproofed_door_count": 1,
|
||||
"energy_consumption_current": 200,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "10.2.2.0",
|
||||
"energy_consumption_potential": 187,
|
||||
"environmental_impact_current": 91,
|
||||
"cfl_fixed_lighting_bulbs_count": 0,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 92,
|
||||
"led_fixed_lighting_bulbs_count": 6,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "C",
|
||||
"co2_emissions_current_per_floor_area": 19,
|
||||
"incandescent_fixed_lighting_bulbs_count": 0
|
||||
}
|
||||
|
|
@ -2888,6 +2888,15 @@ def _hot_water_fuel_cost_gbp_per_kwh(
|
|||
)
|
||||
blended = high_frac * high_rate + (1.0 - high_frac) * low_rate
|
||||
return blended * _PENCE_TO_GBP
|
||||
if water_heating_code in _INSTANTANEOUS_WATER_CODES:
|
||||
# SAP 10.2 §12 (PDF line 2680): the Table-13 off-peak on-peak HW
|
||||
# split is keyed on the CYLINDER size — it presumes stored water
|
||||
# charged overnight. An instantaneous point-of-use heater (WHC
|
||||
# 907/909, no cylinder) heats on demand and cannot shift to the
|
||||
# off-peak window, so 100% bills at the HIGH rate (Table 12a WH
|
||||
# high-rate fraction 1.0), not the off-peak low rate.
|
||||
high_rate, _low = _tariff_high_low_rates_p_per_kwh(tariff)
|
||||
return high_rate * _PENCE_TO_GBP
|
||||
return _off_peak_low_rate_gbp_per_kwh(tariff)
|
||||
if water_heating_fuel is not None:
|
||||
return prices.unit_price_p_per_kwh(water_heating_fuel) * _PENCE_TO_GBP
|
||||
|
|
@ -2942,6 +2951,11 @@ def _hot_water_high_rate_fraction(
|
|||
single_immersion=effective_single,
|
||||
tariff=tariff,
|
||||
)
|
||||
if water_heating_code in _INSTANTANEOUS_WATER_CODES:
|
||||
# Instantaneous point-of-use electric DHW (WHC 907/909) has no
|
||||
# cylinder to charge overnight — it heats on demand, wholly at the
|
||||
# high/day rate (see `_hot_water_fuel_cost_gbp_per_kwh`).
|
||||
return 1.0
|
||||
return 0.0
|
||||
|
||||
|
||||
|
|
@ -5307,6 +5321,35 @@ _AGE_BANDS_A_TO_E: Final[frozenset[str]] = frozenset({"A", "B", "C", "D", "E"})
|
|||
_SUSPENDED_TIMBER_FLOOR_TYPE: Final[str] = "Suspended timber"
|
||||
_GROUND_FLOOR_TYPE: Final[str] = "Ground floor"
|
||||
_FLOOR_U_SEALED_THRESHOLD: Final[float] = 0.5
|
||||
# Table 19 footnote (1): age bands whose default floor_construction is
|
||||
# suspended timber when the construction is unknown. Mirrors
|
||||
# `_SUSPENDED_TIMBER_DEFAULT_BANDS` in rdsap_uvalues.py — duplicated
|
||||
# locally rather than cross-imported (private symbol) per this module's
|
||||
# existing convention (see `_main_floor_u_value`).
|
||||
_TABLE_19_FOOTNOTE_1_SUSPENDED_TIMBER_BANDS: Final[frozenset[str]] = frozenset({"A", "B"})
|
||||
|
||||
|
||||
def _effective_floor_description(
|
||||
epc: EpcPropertyData, main: SapBuildingPart,
|
||||
) -> Optional[str]:
|
||||
"""Mirror `heat_transmission_section_from_cert`'s
|
||||
`effective_floor_description` rule: the per-bp `floor_construction_type`
|
||||
lodgement ("Suspended timber" / "Solid") takes precedence over the
|
||||
global `epc.floors[].description` since it's the explicit per-part
|
||||
Elmhurst Summary §3/§9 lodgement. Falls back to the joined global
|
||||
description when the per-bp field is unlodged (the gov-API mapper
|
||||
often leaves it `None`). Inlined (vs importing from
|
||||
heat_transmission) to keep cert_to_inputs free of cross-module
|
||||
private symbol imports.
|
||||
"""
|
||||
if main.floor_construction_type:
|
||||
return main.floor_construction_type
|
||||
descs = [
|
||||
d for d in
|
||||
(getattr(f, "description", None) for f in (epc.floors or []))
|
||||
if d
|
||||
]
|
||||
return " | ".join(descs) if descs else None
|
||||
|
||||
|
||||
def _main_floor_u_value(epc: EpcPropertyData) -> Optional[float]:
|
||||
|
|
@ -5343,21 +5386,6 @@ def _main_floor_u_value(epc: EpcPropertyData) -> Optional[float]:
|
|||
int(raw_floor_ins) if isinstance(raw_floor_ins, (int, float))
|
||||
else (0 if raw_floor_ins == "NI" else None)
|
||||
)
|
||||
# Mirror heat_transmission's `effective_floor_description`: the per-bp
|
||||
# `floor_construction_type` takes precedence over a joined
|
||||
# `epc.floors[].description` since the per-part lodgement is the
|
||||
# explicit Elmhurst Summary §3/§9 surface. Inline the join (vs
|
||||
# importing from heat_transmission) to keep cert_to_inputs free of
|
||||
# cross-module private symbol imports.
|
||||
if main.floor_construction_type:
|
||||
effective_floor_description = main.floor_construction_type
|
||||
else:
|
||||
descs = [
|
||||
d for d in
|
||||
(getattr(f, "description", None) for f in (epc.floors or []))
|
||||
if d
|
||||
]
|
||||
effective_floor_description = " | ".join(descs) if descs else None
|
||||
return u_floor(
|
||||
country=Country.from_code(epc.country_code) if epc.country_code else None,
|
||||
age_band=main.construction_age_band,
|
||||
|
|
@ -5366,10 +5394,49 @@ def _main_floor_u_value(epc: EpcPropertyData) -> Optional[float]:
|
|||
area_m2=ground_fd.total_floor_area_m2,
|
||||
perimeter_m=ground_fd.heat_loss_perimeter_m,
|
||||
wall_thickness_mm=main.wall_thickness_mm,
|
||||
description=effective_floor_description,
|
||||
description=_effective_floor_description(epc, main),
|
||||
)
|
||||
|
||||
|
||||
def _floor_construction_is_suspended_timber(
|
||||
epc: EpcPropertyData, main: SapBuildingPart,
|
||||
) -> bool:
|
||||
"""RdSAP 10 Table 19 footnote (1) construction resolution for the §5
|
||||
(12) floor-infiltration rule. Tri-state, mirroring `u_floor`'s
|
||||
`_floor_is_suspended_from_description` / `_SUSPENDED_TIMBER_DEFAULT_
|
||||
BANDS` cascade (rdsap_uvalues.py) so the (12) infiltration rule and
|
||||
the floor U-value calc agree on which floors are suspended timber:
|
||||
|
||||
1. An explicit construction lodgement (`_effective_floor_description`
|
||||
— per-bp `floor_construction_type` else the joined
|
||||
`epc.floors[].description`) decides: "Suspended, ..." is timber
|
||||
UNLESS it's "Suspended, not timber" (suspended concrete etc. has
|
||||
no floorboard-gap infiltration); "Solid, ..." is not suspended.
|
||||
2. No description signal AND no lodged `floor_construction` code at
|
||||
all → Table 19 footnote (1): unknown construction defaults to
|
||||
suspended timber only for age bands A/B.
|
||||
"""
|
||||
desc = _effective_floor_description(epc, main)
|
||||
if desc is not None:
|
||||
d = desc.strip().lower()
|
||||
if d.startswith("suspended"):
|
||||
return "not timber" not in d
|
||||
if d.startswith("solid"):
|
||||
return False
|
||||
ground_fd = next(
|
||||
(fd for fd in main.sap_floor_dimensions if fd.floor == 0),
|
||||
main.sap_floor_dimensions[0] if main.sap_floor_dimensions else None,
|
||||
)
|
||||
construction_known = (
|
||||
ground_fd is not None
|
||||
and _int_or_none(ground_fd.floor_construction) is not None
|
||||
)
|
||||
if construction_known:
|
||||
return False
|
||||
age = (main.construction_age_band or "").strip().upper()
|
||||
return age in _TABLE_19_FOOTNOTE_1_SUSPENDED_TIMBER_BANDS
|
||||
|
||||
|
||||
def _has_suspended_timber_floor_per_spec(
|
||||
epc: EpcPropertyData,
|
||||
) -> tuple[bool, bool]:
|
||||
|
|
@ -5395,9 +5462,13 @@ def _has_suspended_timber_floor_per_spec(
|
|||
infiltration 0.2.
|
||||
|
||||
The rule only applies when the Main bp's lowest floor is a
|
||||
"Ground floor" with "Suspended timber" construction. All other
|
||||
combinations fall through to `(False, False)` and the cascade
|
||||
enters 0 for (12).
|
||||
"Ground floor" with "Suspended timber" construction. Construction is
|
||||
resolved the same tri-state way `u_floor` resolves it (RdSAP 10
|
||||
Table 19 footnote 1): the explicit per-bp `floor_construction_type` /
|
||||
joined `epc.floors[].description` lodgement, else — when neither
|
||||
lodges a construction signal — the footnote-1 age-band default
|
||||
(suspended timber for bands A/B only). All other combinations fall
|
||||
through to `(False, False)` and the cascade enters 0 for (12).
|
||||
"""
|
||||
if not epc.sap_building_parts:
|
||||
return False, False
|
||||
|
|
@ -5407,7 +5478,7 @@ def _has_suspended_timber_floor_per_spec(
|
|||
return True, False
|
||||
if main.floor_type != _GROUND_FLOOR_TYPE:
|
||||
return False, False
|
||||
if main.floor_construction_type != _SUSPENDED_TIMBER_FLOOR_TYPE:
|
||||
if not _floor_construction_is_suspended_timber(epc, main):
|
||||
return False, False
|
||||
age = (main.construction_age_band or "").strip().upper()
|
||||
if age in _AGE_BANDS_F_TO_M:
|
||||
|
|
@ -7090,6 +7161,18 @@ def _cylinder_storage_loss_override(
|
|||
# 550 kWh/yr). The Table-2b temperature factor still applies (49)→(50).
|
||||
declared_loss = _float_or_none(getattr(sh, "cylinder_heat_loss", None))
|
||||
volume_l = _cylinder_volume_l_from_code(epc)
|
||||
if volume_l is None and _int_or_none(sh.cylinder_size) == 0:
|
||||
# gov-API cylinder_size=0 = "size not determined" for a present
|
||||
# cylinder (has_hot_water_cylinder=true). RdSAP 10 §10.5 Table 28:
|
||||
# "If the actual size is not determined, the size ... is taken as
|
||||
# according to Table 28" → the "Normal" 110 L baseline, which STILL
|
||||
# incurs a storage loss. Without this the code lookup returned None
|
||||
# and the `if volume_l is None: return None` below dropped the loss,
|
||||
# under-costing the DHW (7 corpus certs; e.g. 200004017091 +1.75 ->
|
||||
# +0.39). Gated on the EXPLICIT 0 so a full-SAP cert whose RdSAP
|
||||
# cylinder_size is simply unlodged (None, e.g. 10091568921) keeps its
|
||||
# own cylinder handling rather than a forced 110 L RdSAP default.
|
||||
volume_l = _CYLINDER_SIZE_NOT_DETERMINED_L
|
||||
if declared_loss is not None:
|
||||
storage_56m = cylinder_storage_loss_monthly_kwh(
|
||||
volume_l=volume_l or 0.0,
|
||||
|
|
@ -7517,8 +7600,17 @@ def _fuel_cost(
|
|||
secondary_fuel = (
|
||||
epc.sap_heating.secondary_fuel_type if epc.sap_heating else None
|
||||
)
|
||||
# Canonicalise the colliding gov-API secondary fuel enums (5 anthracite /
|
||||
# 9 dual fuel / 33 coal — `_GOV_API_COLLISION_FUELS`) BEFORE the price
|
||||
# lookup, exactly as the main-fuel boundary and the CO2/display
|
||||
# `_secondary_fuel_cost_gbp_per_kwh` already do. Without this the raw
|
||||
# same-value Table-32 lookup mis-priced e.g. anthracite (enum 5) at the
|
||||
# bulk-LPG code-5 rate (12.19 vs 3.64 p/kWh) — a 3× over-cost that sank
|
||||
# cert 100050355518 (lodged 36, engine 20.8). This inline cost path had
|
||||
# diverged from the already-canonicalised display helper.
|
||||
secondary_high_rate_gbp_per_kwh = (
|
||||
table_32_unit_price_p_per_kwh(secondary_fuel) * _PENCE_TO_GBP
|
||||
table_32_unit_price_p_per_kwh(canonical_fuel_code(secondary_fuel))
|
||||
* _PENCE_TO_GBP
|
||||
if secondary_fuel is not None
|
||||
else other_uses_gbp_per_kwh
|
||||
)
|
||||
|
|
|
|||
|
|
@ -922,6 +922,22 @@ def heat_transmission_from_cert(
|
|||
raw_rafter_thickness = getattr(part, "rafter_insulation_thickness", None)
|
||||
if raw_rafter_thickness is not None:
|
||||
raw_roof_thickness = raw_rafter_thickness
|
||||
elif "flat" in (part.roof_construction_type or "").lower():
|
||||
# RdSAP 10 §5.11 — a FLAT roof lodges its insulation thickness in
|
||||
# the dedicated gov-API `flat_roof_insulation_thickness` field
|
||||
# (e.g. "75mm"), NOT `roof_insulation_thickness` (which stays None
|
||||
# for flat roofs). Prefer it so a measured flat-roof thickness
|
||||
# bills at its Table-16 U instead of the uninsulated age-band flat
|
||||
# default. Mirrors the rafter branch above. Cert 47084930: flat
|
||||
# roof "75mm" -> U 0.5 (accredited Elmhurst worksheet, "insulated
|
||||
# flat roof") vs the age-E default 1.5 (roof 96.4 -> 32.1 W/K). An
|
||||
# "AB"/"NI" (as-built/unknown) value parses to None and keeps the
|
||||
# age-band default, unchanged.
|
||||
raw_flat_thickness = getattr(
|
||||
part, "flat_roof_insulation_thickness", None
|
||||
)
|
||||
if raw_flat_thickness is not None:
|
||||
raw_roof_thickness = raw_flat_thickness
|
||||
roof_thickness = _parse_thickness_mm(raw_roof_thickness)
|
||||
floor_ins_thickness = _parse_thickness_mm(getattr(part, "floor_insulation_thickness", None))
|
||||
|
||||
|
|
|
|||
|
|
@ -157,6 +157,11 @@ _WALL_INSULATION_INTERNAL: Final[int] = 3
|
|||
WALL_INSULATION_CAVITY_PLUS_EXTERNAL: Final[int] = 6
|
||||
WALL_INSULATION_CAVITY_PLUS_INTERNAL: Final[int] = 7
|
||||
|
||||
# RdSAP 10 §5.4 (PDF p.32): "assume 100 mm if thickness is unknown" — the
|
||||
# default added-insulation thickness for an External / Internal wall
|
||||
# insulation lodgement (type 1/3) with no measured thickness.
|
||||
_WALL_ADDED_INSULATION_UNKNOWN_THICKNESS_MM: Final[int] = 100
|
||||
|
||||
# RdSAP 10 §4-6 (page 73): default thermal conductivity of insulation when
|
||||
# no documentary evidence is available. Used to convert the lodged
|
||||
# `wall_insulation_thickness` (mm) into thermal resistance (m²K/W) via
|
||||
|
|
@ -348,7 +353,13 @@ def _insulation_bucket(thickness_mm: Optional[int], insulation_present: bool) ->
|
|||
to the 50 mm bucket when `insulation_present=True`; when not
|
||||
present, the as-built (bucket 0) row applies regardless.
|
||||
"""
|
||||
if insulation_present and (thickness_mm is None or thickness_mm == 0):
|
||||
# Insulation known to be present must never fall to the uninsulated
|
||||
# (bucket 0) row. RdSAP 10 Table 6 footnote routes present-but-unknown
|
||||
# thickness to the 50 mm row; a KNOWN thin lodgement (5-24 mm, below the
|
||||
# smallest tabulated 50 mm column) must not read as WORSE (higher U) than
|
||||
# the unknown case — so it too rounds up to the 50 mm row rather than to
|
||||
# bucket 0. Only genuinely absent insulation (present=False) maps to 0.
|
||||
if insulation_present and (thickness_mm is None or thickness_mm < 25):
|
||||
return 50
|
||||
if thickness_mm is None:
|
||||
return 0
|
||||
|
|
@ -589,6 +600,28 @@ def u_wall(
|
|||
ctry = country if country is not None else Country.ENG
|
||||
age_idx = _age_index(age_band)
|
||||
band = _AGE_BANDS[age_idx]
|
||||
# RdSAP 10 §5.4 (PDF p.32): "the U-value with additional insulation is
|
||||
# based on the thickness of insulation of mineral wool type (assume
|
||||
# 100 mm if thickness is unknown)". A SOLID-BRICK or STONE wall lodging
|
||||
# External or Internal insulation (wall_insulation_type 1/3) but no
|
||||
# measured insulation thickness (gov-API "NI" → None) takes the 100 mm
|
||||
# default so the §5.8 Table-14 R-value adjustment (brick/stone branches
|
||||
# below) fires instead of billing the wall at its uninsulated
|
||||
# §5.6/Table-6 U (over-counting fabric loss → under-rate; corpus cert
|
||||
# 200004296092, solid brick 360 mm, walls 101.7 W/K → PE +59.6). Scoped
|
||||
# to brick/stone because only those constructions have a §5.8 added-
|
||||
# insulation R-value path here — cavity uses the separate
|
||||
# `WALL_INSULATION_CAVITY_PLUS_*` composite, and timber/system-built
|
||||
# carry no §5.8 branch, so a thickness default there would be inert or
|
||||
# mis-routed.
|
||||
if (
|
||||
construction
|
||||
in (WALL_STONE_GRANITE, WALL_STONE_SANDSTONE, WALL_SOLID_BRICK)
|
||||
and wall_insulation_type
|
||||
in (_WALL_INSULATION_EXTERNAL, _WALL_INSULATION_INTERNAL)
|
||||
and (insulation_thickness_mm is None or insulation_thickness_mm == 0)
|
||||
):
|
||||
insulation_thickness_mm = _WALL_ADDED_INSULATION_UNKNOWN_THICKNESS_MM
|
||||
# RdSAP 10 Tables 6-10 stone rows + footnote (a) (PDF p.33-39), §5.6
|
||||
# formula (PDF p.40), §5.8 + Table 14 (PDF p.41-42). A documentary wall
|
||||
# thickness (per §5.3) routes stone in age bands A-E off the §5.6 formula,
|
||||
|
|
@ -998,13 +1031,30 @@ def u_roof(
|
|||
# (band J = 0.16), matching the cohort's "ND" (thickness None)
|
||||
# vaulted roofs which already reach col (1) by falling through.
|
||||
return _ROOF_BY_AGE.get(age_band.upper(), 0.4)
|
||||
if (
|
||||
age_band is not None
|
||||
and insulation_thickness_mm == 0
|
||||
and _described_as_insulated(description)
|
||||
and description is not None
|
||||
and "(assumed)" in description.lower()
|
||||
):
|
||||
# RdSAP renders "insulated (assumed)" when insulation PRESENCE is
|
||||
# itself assumed (age-band / as-built assumption), not an observed
|
||||
# retrofit — so the §5.11.4 50 mm footnote (below) does not apply;
|
||||
# the thickness "cannot be determined" and the Table 18 age-band
|
||||
# default governs (§5.11.4 opening clause). Identical
|
||||
# "Pitched, insulated (assumed)" corpus dwellings lodge thickness
|
||||
# "ND" (None → Table 18 by fallthrough) or "NI" (0 → previously the
|
||||
# 50 mm row) purely per lodging software; both take Table 18.
|
||||
table_18 = _FLAT_ROOF_BY_AGE if is_flat_roof else roof_by_age
|
||||
return table_18.get(age_band.upper(), 0.4)
|
||||
if insulation_thickness_mm == 0 and _described_as_insulated(description):
|
||||
# Spec §5.11.4 (page 44 footnote): "If retrofit insulation
|
||||
# present of unknown thickness use 50 mm". The cert encodes
|
||||
# "thickness unknown but retrofit insulation present" as the
|
||||
# "NI" sentinel which `_parse_thickness_mm` parses to 0. Without
|
||||
# this override the Table 16 row-0 lookup below returns the
|
||||
# uninsulated 2.30 W/m²K.
|
||||
# "thickness unknown but OBSERVED retrofit insulation" as the
|
||||
# "NI" sentinel (parsed to 0) + an unqualified "insulated"
|
||||
# description (no "(assumed)"). Without this override the Table
|
||||
# 16 row-0 lookup below returns the uninsulated 2.30 W/m²K.
|
||||
return 0.68 # Table 16 row 50, "Insulation at joists at ceiling level"
|
||||
if insulation_thickness_mm is not None:
|
||||
# nearest tabulated thickness <= supplied (Table 16 column (1)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ from domain.sap10_ml.rdsap_uvalues import (
|
|||
WALL_STONE_SANDSTONE,
|
||||
WALL_SYSTEM_BUILT,
|
||||
WALL_TIMBER_FRAME,
|
||||
_insulation_bucket,
|
||||
thermal_bridging_y,
|
||||
u_door,
|
||||
u_exposed_floor,
|
||||
|
|
@ -114,6 +115,43 @@ def test_u_wall_solid_brick_with_ni_thickness_uses_50mm_row_per_table6_footnote(
|
|||
assert result == pytest.approx(0.55, abs=0.001)
|
||||
|
||||
|
||||
def test_u_wall_solid_brick_present_insulation_thin_known_thickness_not_uninsulated() -> None:
|
||||
# Arrange — a solid-brick wall lodging internal insulation (type 3)
|
||||
# with a KNOWN thin thickness (10 mm) but no documentary wall
|
||||
# thickness (so the §5.8 formula branch, which needs wall_thickness,
|
||||
# cannot fire) falls to the Table-6 bucket. A known 10 mm value sits
|
||||
# below the smallest tabulated 50 mm column, but insulation IS present
|
||||
# — it must NOT read as WORSE (higher U) than the present-but-unknown
|
||||
# case, which the footnote routes to the 50 mm row. Pre-fix,
|
||||
# `_insulation_bucket(10, present=True)` returned 0 (uninsulated,
|
||||
# U=1.7), under-crediting the wall (corpus cert 5117280: Δ −4.44).
|
||||
|
||||
# Act
|
||||
result = u_wall(
|
||||
country=Country.ENG,
|
||||
age_band="B",
|
||||
construction=WALL_SOLID_BRICK,
|
||||
insulation_thickness_mm=10,
|
||||
insulation_present=True,
|
||||
wall_insulation_type=3,
|
||||
)
|
||||
|
||||
# Assert — present insulation → 50 mm row (U=0.55), never uninsulated 1.7.
|
||||
assert result == pytest.approx(0.55, abs=0.001)
|
||||
|
||||
|
||||
def test_insulation_bucket_present_thin_thickness_rounds_up_to_50_not_zero() -> None:
|
||||
# Direct guard on the bucket: present insulation never maps to bucket 0.
|
||||
assert _insulation_bucket(10, True) == 50
|
||||
assert _insulation_bucket(24, True) == 50
|
||||
assert _insulation_bucket(None, True) == 50
|
||||
assert _insulation_bucket(50, True) == 50
|
||||
assert _insulation_bucket(100, True) == 100
|
||||
# Genuinely absent insulation still routes thin/zero to bucket 0.
|
||||
assert _insulation_bucket(10, False) == 0
|
||||
assert _insulation_bucket(None, False) == 0
|
||||
|
||||
|
||||
def test_u_wall_cavity_as_built_insulated_assumed_routes_to_as_built_row() -> None:
|
||||
# Arrange — a cavity lodged "Cavity wall, as built, insulated (assumed)"
|
||||
# with wall_insulation_type=4 is in its AS-BUILT state, NOT a retrofit
|
||||
|
|
@ -835,6 +873,34 @@ def test_u_wall_stone_granite_with_external_insulation_applies_5_8_table_14_r_va
|
|||
assert abs(result - 0.57) <= 1e-4
|
||||
|
||||
|
||||
def test_u_wall_solid_brick_external_insulation_unknown_thickness_assumes_100mm() -> None:
|
||||
# Arrange — RdSAP 10 §5.4 (PDF p.32): "the U-value with additional
|
||||
# insulation is based on the thickness of insulation of mineral wool
|
||||
# type (assume 100 mm if thickness is unknown)". A solid brick / stone
|
||||
# wall lodging External or Internal insulation (wall_insulation_type
|
||||
# 1/3) but NO measured insulation thickness (gov-API "NI" → parsed to
|
||||
# None) must take the 100 mm §5.4 default so the §5.8 Table-14 R-value
|
||||
# fires — NOT fall through to the uninsulated §5.6 U. Corpus cert
|
||||
# 200004296092 (age-B solid brick 360 mm, "with external insulation",
|
||||
# thickness NI) was billed at the uninsulated 1.40 (walls 101.7 W/K,
|
||||
# HLP 3.46) → PE +59.6, SAP −6.65. With 100 mm: U₀(§5.6, 360 mm brick)
|
||||
# + R(100 mm, λ=0.04 → 2.5) → 0.29.
|
||||
|
||||
# Act
|
||||
result = u_wall(
|
||||
country=Country.ENG,
|
||||
age_band="B",
|
||||
construction=WALL_SOLID_BRICK,
|
||||
insulation_thickness_mm=None, # gov-API "NI" — thickness unknown
|
||||
insulation_present=True,
|
||||
wall_insulation_type=1, # External
|
||||
wall_thickness_mm=360,
|
||||
)
|
||||
|
||||
# Assert — 100 mm default applied, not the uninsulated 1.40.
|
||||
assert abs(result - 0.29) <= 1e-4
|
||||
|
||||
|
||||
def test_u_wall_stone_granite_age_g_with_wall_thickness_ignores_5_6_formula_per_age_a_to_e_gate() -> None:
|
||||
# Arrange — §5.6 (PDF p.40) heading explicitly scopes the formula
|
||||
# to "age bands A to E". For age F onwards Table 6 gives literal
|
||||
|
|
@ -1059,16 +1125,41 @@ def test_u_roof_description_with_measured_transmittance_returns_parsed_value() -
|
|||
assert result == pytest.approx(0.11, abs=0.001)
|
||||
|
||||
|
||||
def test_u_roof_ni_thickness_with_insulated_description_applies_50mm_per_section_5_11_4() -> None:
|
||||
# Arrange — 346 corpus certs lodge roof_insulation_thickness="NI"
|
||||
# (Not Indicated, parsed to 0 by _parse_thickness_mm). When the
|
||||
# description also signals retrofit insulation ("Pitched, insulated
|
||||
# (assumed)" / "Flat, insulated" / "Roof room(s), insulated
|
||||
# (assumed)"), RdSAP 10 §5.11.4 (page 44) footnote applies:
|
||||
def test_u_roof_ni_thickness_with_observed_insulated_description_applies_50mm_per_section_5_11_4() -> None:
|
||||
# Arrange — a roof lodging thickness "NI" (parsed to 0) whose
|
||||
# description signals OBSERVED insulation without the "(assumed)"
|
||||
# qualifier ("Pitched, insulated" — RdSAP renders the bare form when
|
||||
# the assessor saw the insulation but not its depth). RdSAP 10
|
||||
# §5.11.4 (page 44) footnote applies:
|
||||
# "If retrofit insulation present of unknown thickness use 50 mm".
|
||||
# That maps to Table 16 row "50 mm at joists at ceiling level" = 0.68
|
||||
# W/m²K — vs the current 2.30 we return when thickness=0 hits the
|
||||
# Table 16 row-0 lookup.
|
||||
# W/m²K — vs the 2.30 the Table 16 row-0 lookup would give.
|
||||
|
||||
# Act
|
||||
result = u_roof(
|
||||
country=Country.ENG,
|
||||
age_band="C",
|
||||
insulation_thickness_mm=0, # parsed from "NI"
|
||||
description="Pitched, insulated",
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result == pytest.approx(0.68, abs=0.01)
|
||||
|
||||
|
||||
def test_u_roof_ni_thickness_with_assumed_insulated_description_uses_table18_default() -> None:
|
||||
# Arrange — RdSAP renders "insulated (assumed)" when insulation
|
||||
# presence is ASSUMED (age-band / as-built assumption), not an
|
||||
# observed retrofit — so the §5.11.4 "retrofit insulation present of
|
||||
# unknown thickness use 50 mm" footnote does NOT apply; the thickness
|
||||
# "cannot be determined" and Table 18's age-band default governs
|
||||
# (§5.11.4 opening clause). The corpus lodges the SAME description
|
||||
# with thickness "ND" (102 certs → already Table 18 via the
|
||||
# thickness-None fallthrough) or "NI" (27 certs → previously took the
|
||||
# 50 mm row 0.68), purely per lodging software; both must land on the
|
||||
# same Table 18 value (band C pitched → 0.40). The loc-4 NI cohort
|
||||
# was systematically under-rated by ~1 SAP before this (e.g. corpus
|
||||
# cert 10094975827 band B: Δ −1.87).
|
||||
|
||||
# Act
|
||||
result = u_roof(
|
||||
|
|
@ -1078,8 +1169,8 @@ def test_u_roof_ni_thickness_with_insulated_description_applies_50mm_per_section
|
|||
description="Pitched, insulated (assumed)",
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result == pytest.approx(0.68, abs=0.01)
|
||||
# Assert — Table 18 column (1) band C.
|
||||
assert result == pytest.approx(0.40, abs=0.01)
|
||||
|
||||
|
||||
def test_u_roof_ni_thickness_with_no_insulation_description_stays_at_2_30() -> None:
|
||||
|
|
@ -1324,18 +1415,20 @@ def test_u_roof_vaulted_ni_unknown_band_j_uses_col1_age_band_not_50mm() -> None:
|
|||
assert abs(result - 0.16) <= 1e-4
|
||||
|
||||
|
||||
def test_u_roof_normal_pitched_ni_insulated_still_returns_50mm_per_5_11_4() -> None:
|
||||
def test_u_roof_normal_pitched_ni_observed_insulated_still_returns_50mm_per_5_11_4() -> None:
|
||||
# Arrange — regression guard: the is_sloping_ceiling flag defaults
|
||||
# False, so a NORMAL pitched roof (with loft) lodged NI + "insulated
|
||||
# (assumed)" must STILL hit the §5.11.4 retrofit-50 mm row (U=0.68).
|
||||
# Same inputs as the sloping test above minus is_sloping_ceiling.
|
||||
# False, so a NORMAL pitched roof (with loft) lodged NI + an
|
||||
# unqualified "insulated" description (observed retrofit, no
|
||||
# "(assumed)") must STILL hit the §5.11.4 retrofit-50 mm row
|
||||
# (U=0.68). The "(assumed)" form routes to Table 18 instead — see
|
||||
# test_u_roof_ni_thickness_with_assumed_insulated_description_uses_table18_default.
|
||||
|
||||
# Act
|
||||
result = u_roof(
|
||||
country=Country.ENG,
|
||||
age_band="J",
|
||||
insulation_thickness_mm=0,
|
||||
description="Pitched, insulated (assumed)",
|
||||
description="Pitched, insulated",
|
||||
)
|
||||
|
||||
# Assert
|
||||
|
|
|
|||
243
scripts/corpus_1000/build_worklist.py
Normal file
243
scripts/corpus_1000/build_worklist.py
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
"""Build the RdSAP-21.0.1 corpus campaign ledger (scripts/corpus_1000/worklist.md).
|
||||
|
||||
Runs all 1000 corpus certs through the gauge's own path
|
||||
(from_api_response -> Sap10Calculator.calculate, mirroring
|
||||
tests/infrastructure/epc_client/test_sap_accuracy_corpus.py), ranks by
|
||||
|engine - lodged| descending, attaches a short dwelling signature per cert,
|
||||
and groups same-signature certs into named clusters.
|
||||
|
||||
USAGE
|
||||
-----
|
||||
PYTHONPATH=/workspaces/model python scripts/corpus_1000/build_worklist.py
|
||||
|
||||
Rewrites worklist.md in place, PRESERVING existing per-cert statuses
|
||||
([x] / 🔧 / ⚠ / ⛔ and trailing `<-` notes) keyed by uprn, so it doubles as
|
||||
the re-rank step after each fix lands.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
from domain.sap10_calculator.calculator import Sap10Calculator
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
CORPUS = ROOT / "backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl"
|
||||
WORKLIST = ROOT / "scripts/corpus_1000/worklist.md"
|
||||
|
||||
# Table 12 fuel names (abbreviated) for the signature column.
|
||||
_FUEL = {
|
||||
1: "gas", 2: "gas-bulk", 3: "biogas", 4: "biogas", 7: "bLPG", 9: "LPG",
|
||||
17: "LPG-sc", 10: "oil", 15: "bioliquid", 16: "B30K", 18: "rapeseed",
|
||||
58: "biodiesel", 51: "housecoal", 20: "coal-anth", 21: "smokeless",
|
||||
22: "anthracite", 23: "manuf-smokeless", 12: "wood-log", 13: "wood-pellet",
|
||||
75: "wood-pellet-bag", 71: "wood-chip", 48: "dual-mineral-wood",
|
||||
26: "gas", 25: "elec", 29: "elec", 30: "elec7hr", 31: "elec10hr",
|
||||
32: "elec18hr", 33: "elec24hr", 34: "elec-heatnet", 35: "elec",
|
||||
36: "elec", 37: "elec", 47: "heatnet", 6: "heatnet",
|
||||
}
|
||||
|
||||
|
||||
def _fuel_name(code: Optional[int]) -> str:
|
||||
if code is None:
|
||||
return "?"
|
||||
return _FUEL.get(code, f"fuel{code}")
|
||||
|
||||
|
||||
def _lead(desc: Optional[str], n: int = 3) -> str:
|
||||
"""First n comma/word tokens of a register description, lowercased."""
|
||||
if not desc:
|
||||
return "?"
|
||||
return " ".join(desc.lower().replace(",", " ").split()[:n])
|
||||
|
||||
|
||||
def signature(doc: dict[str, Any]) -> tuple[str, str]:
|
||||
"""(cluster_key, human signature) for one cert."""
|
||||
h = doc.get("sap_heating") or {}
|
||||
mhd = (h.get("main_heating_details") or [{}])
|
||||
m0 = mhd[0] if mhd else {}
|
||||
code = m0.get("sap_main_heating_code")
|
||||
idx = m0.get("main_heating_index_number")
|
||||
fuel = _fuel_name(m0.get("main_fuel_type"))
|
||||
heat = f"h{code or ('pcdb' + str(idx) if idx else '?')}/{fuel}"
|
||||
if len(mhd) > 1:
|
||||
heat += "+2mains"
|
||||
|
||||
walls = doc.get("walls") or []
|
||||
wall = _lead(walls[0].get("description") if walls else None, 2)
|
||||
roofs = doc.get("roofs") or []
|
||||
roof = _lead(roofs[0].get("description") if roofs else None, 2)
|
||||
|
||||
es = doc.get("sap_energy_source") or {}
|
||||
sec = (doc.get("secondary_heating") or {}).get("description") or "None"
|
||||
feats: list[str] = []
|
||||
pv = es.get("photovoltaic_supply") or {}
|
||||
if es.get("pv_connection") not in (0, None) or not pv.get("none_or_no_details"):
|
||||
feats.append("PV")
|
||||
if (es.get("wind_turbines_count") or 0) > 0:
|
||||
feats.append("wind")
|
||||
if sec != "None":
|
||||
feats.append("sec:" + _lead(sec, 2))
|
||||
if (doc.get("extensions_count") or 0) > 0:
|
||||
feats.append(f"ext{doc.get('extensions_count')}")
|
||||
if (doc.get("conservatory_type") or 1) != 1:
|
||||
feats.append(f"consv{doc.get('conservatory_type')}")
|
||||
for bp in doc.get("sap_building_parts") or []:
|
||||
if bp.get("sap_room_in_roof") is not None:
|
||||
feats.append("RR")
|
||||
break
|
||||
mv = doc.get("mechanical_ventilation")
|
||||
if mv not in (0, None):
|
||||
feats.append(f"mv{mv}")
|
||||
if (h.get("water_heating_code") or 901) != 901:
|
||||
feats.append(f"whw{h.get('water_heating_code')}")
|
||||
|
||||
key = f"{heat} | {wall} | {roof}" + (" | " + ",".join(sorted(set(feats))) if feats else "")
|
||||
sig = key
|
||||
return key, sig
|
||||
|
||||
|
||||
_LINE_RE = re.compile(
|
||||
r"^- \[(?P<st>[ x])\] |^- (?P<emoji>🔧|⚠|⛔) "
|
||||
)
|
||||
|
||||
|
||||
def load_existing_statuses() -> dict[str, tuple[str, str]]:
|
||||
"""uprn -> (status token, trailing note) from the current worklist."""
|
||||
out: dict[str, tuple[str, str]] = {}
|
||||
if not WORKLIST.exists():
|
||||
return out
|
||||
for line in WORKLIST.read_text().splitlines():
|
||||
m = re.match(r"^- (?:\[(?P<st>[ x])\]|(?P<emoji>🔧|⚠|⛔)) `(?P<uprn>\w+)`", line)
|
||||
if not m:
|
||||
continue
|
||||
status = f"[{m.group('st')}]" if m.group("st") is not None else m.group("emoji")
|
||||
note = ""
|
||||
if "<-" in line:
|
||||
note = "<-" + line.split("<-", 1)[1]
|
||||
out[m.group("uprn")] = (status, note)
|
||||
return out
|
||||
|
||||
|
||||
def main() -> None:
|
||||
docs = [json.loads(l) for l in CORPUS.read_text().splitlines() if l.strip()]
|
||||
calc = Sap10Calculator()
|
||||
rows: list[dict[str, Any]] = []
|
||||
skipped: list[tuple[str, str]] = []
|
||||
sap_abs: list[float] = []
|
||||
|
||||
for idx, doc in enumerate(docs, 1):
|
||||
# Some corpus docs lodge no top-level uprn; fall back to the corpus
|
||||
# line number so ledger statuses don't collide on "None".
|
||||
uprn = str(doc.get("uprn") or f"line{idx}")
|
||||
lodged = doc.get("energy_rating_current")
|
||||
if lodged is None:
|
||||
skipped.append((uprn, "no lodged SAP"))
|
||||
continue
|
||||
try:
|
||||
epc = EpcPropertyDataMapper.from_api_response(doc)
|
||||
result = calc.calculate(epc)
|
||||
except Exception as e:
|
||||
skipped.append((uprn, f"{type(e).__name__}: {str(e)[:70]}"))
|
||||
continue
|
||||
eng = result.sap_score_continuous
|
||||
delta = eng - lodged
|
||||
sap_abs.append(abs(delta))
|
||||
key, sig = signature(doc)
|
||||
rows.append({
|
||||
"uprn": uprn, "lodged": lodged, "eng": eng, "delta": delta,
|
||||
"key": key, "sig": sig,
|
||||
})
|
||||
|
||||
n = len(sap_abs)
|
||||
within = sum(1 for e in sap_abs if e < 0.5) / n
|
||||
mae = sum(sap_abs) / n
|
||||
|
||||
# Cluster: same signature key, only certs still outside 0.5.
|
||||
clusters: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||
for r in rows:
|
||||
if abs(r["delta"]) >= 0.5:
|
||||
clusters[r["key"]].append(r)
|
||||
ranked = sorted(
|
||||
clusters.items(),
|
||||
key=lambda kv: -sum(abs(r["delta"]) for r in kv[1]),
|
||||
)
|
||||
|
||||
prev = load_existing_statuses()
|
||||
|
||||
lines: list[str] = []
|
||||
lines.append("# RdSAP-21.0.1 corpus campaign ledger")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"**Gauge:** {n} computed / {len(skipped)} skipped · "
|
||||
f"SAP within-0.5 = **{within:.1%}** · MAE = **{mae:.3f}** "
|
||||
f"(floors in test_sap_accuracy_corpus.py)"
|
||||
)
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · "
|
||||
"⚠ xfail engine bug · ⛔ unbuildable. "
|
||||
"Line: `uprn · lodged L / eng E / Δ · signature`. "
|
||||
"Rebuild/re-rank: `PYTHONPATH=. python scripts/corpus_1000/build_worklist.py` "
|
||||
"(preserves statuses & `<-` notes)."
|
||||
)
|
||||
lines.append("")
|
||||
lines.append(f"## Clusters (certs with |Δ| ≥ 0.5, ranked by summed |Δ|) — {sum(len(v) for v in clusters.values())} certs in {len(ranked)} clusters")
|
||||
lines.append("")
|
||||
|
||||
for i, (key, members) in enumerate(ranked, 1):
|
||||
members.sort(key=lambda r: -abs(r["delta"]))
|
||||
tot = sum(abs(r["delta"]) for r in members)
|
||||
lines.append(
|
||||
f"### C{i:03d} · Σ|Δ| {tot:.1f} · {len(members)} certs · `{key}`"
|
||||
)
|
||||
for r in members:
|
||||
st, note = prev.get(r["uprn"], ("[ ]", ""))
|
||||
frag = (
|
||||
f"- {st} `{r['uprn']}` · L {r['lodged']} / E {r['eng']:.2f} / "
|
||||
f"Δ {r['delta']:+.2f}"
|
||||
)
|
||||
if note:
|
||||
frag += f" {note}"
|
||||
lines.append(frag)
|
||||
lines.append("")
|
||||
|
||||
if skipped:
|
||||
lines.append(f"## Skipped ({len(skipped)})")
|
||||
for uprn, why in skipped:
|
||||
st, note = prev.get(uprn, ("⛔", ""))
|
||||
lines.append(f"- {st} `{uprn}` · {why} {note}".rstrip())
|
||||
lines.append("")
|
||||
|
||||
resolved = [r for r in rows if abs(r["delta"]) < 0.5]
|
||||
lines.append(f"## Resolved ≤0.5 ({len(resolved)} certs — no action)")
|
||||
lines.append("")
|
||||
lines.append("<details><summary>expand</summary>")
|
||||
lines.append("")
|
||||
for r in sorted(resolved, key=lambda r: -abs(r["delta"])):
|
||||
st, note = prev.get(r["uprn"], ("[x]", ""))
|
||||
if st == "[ ]":
|
||||
st = "[x]"
|
||||
lines.append(
|
||||
f"- {st} `{r['uprn']}` · L {r['lodged']} / E {r['eng']:.2f} / "
|
||||
f"Δ {r['delta']:+.2f} · `{r['key']}` {note}".rstrip()
|
||||
)
|
||||
lines.append("")
|
||||
lines.append("</details>")
|
||||
lines.append("")
|
||||
|
||||
WORKLIST.write_text("\n".join(lines))
|
||||
print(f"wrote {WORKLIST}")
|
||||
print(f"gauge: {n} computed / {len(skipped)} skipped, within-0.5 {within:.1%}, MAE {mae:.3f}")
|
||||
print(f"clusters: {len(ranked)}, worst 5:")
|
||||
for key, members in ranked[:5]:
|
||||
tot = sum(abs(r["delta"]) for r in members)
|
||||
print(f" Σ{tot:7.1f} n={len(members):3d} {key}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
58
scripts/corpus_1000/demand_cohort.py
Normal file
58
scripts/corpus_1000/demand_cohort.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"""Demand-driven divergence analyzer. Flags corpus certs where the SAP miss is
|
||||
driven by FABRIC/DEMAND (PE & CO2 move with SAP), not cost — the fixable class.
|
||||
Tags nbp (building parts) and room-in-roof so multi-part / RR bugs surface."""
|
||||
from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
|
||||
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||
SAP_10_2_SPEC_PRICES, cert_to_demand_inputs, cert_to_inputs)
|
||||
|
||||
CORPUS = Path("backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl")
|
||||
|
||||
def has_rr(doc):
|
||||
for bp in doc.get("sap_building_parts") or []:
|
||||
r = bp.get("room_in_roof") or bp.get("rooms_in_roof")
|
||||
if r: return True
|
||||
for r in doc.get("roofs") or []:
|
||||
if "room in roof" in (r.get("description") or "").lower(): return True
|
||||
return False
|
||||
|
||||
rows = []
|
||||
for line in CORPUS.read_text().splitlines():
|
||||
doc = json.loads(line)
|
||||
L = doc.get("energy_rating_current"); peL = doc.get("energy_consumption_current")
|
||||
co2L = doc.get("co2_emissions_current")
|
||||
if L is None: continue
|
||||
try:
|
||||
epc = EpcPropertyDataMapper.from_api_response(doc)
|
||||
r = calculate_sap_from_inputs(cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES))
|
||||
d = calculate_sap_from_inputs(cert_to_demand_inputs(epc, prices=SAP_10_2_SPEC_PRICES))
|
||||
except Exception:
|
||||
continue
|
||||
dsap = r.sap_score_continuous - L
|
||||
if abs(dsap) < 0.5: continue
|
||||
peE = getattr(d, "primary_energy_kwh_per_m2", None)
|
||||
dpe = (peE - peL) if (peE is not None and peL is not None) else None
|
||||
co2E = d.co2_kg_per_yr/1000 if d.co2_kg_per_yr is not None else None
|
||||
dco2 = (co2E - co2L) if (co2E is not None and co2L is not None) else None
|
||||
nbp = len(doc.get("sap_building_parts") or [])
|
||||
rr = has_rr(doc)
|
||||
# demand-driven: PE moves same direction as SAP-miss inverse (PE up -> SAP down)
|
||||
# i.e. dpe and dsap have OPPOSITE signs, and |dpe| is material
|
||||
demand = (dpe is not None and abs(dpe) >= 4 and (dpe > 0) == (dsap < 0))
|
||||
rows.append((doc.get("uprn"), L, round(r.sap_score_continuous,1), round(dsap,2),
|
||||
round(dpe,1) if dpe is not None else None,
|
||||
round(dco2,3) if dco2 is not None else None, nbp, rr, demand))
|
||||
|
||||
demand_rows = [r for r in rows if r[8]]
|
||||
demand_rows.sort(key=lambda x:-abs(x[3]))
|
||||
print(f"diverging certs: {len(rows)} | demand-driven: {len(demand_rows)}")
|
||||
mp = [r for r in demand_rows if r[6] > 1]
|
||||
rr = [r for r in demand_rows if r[7]]
|
||||
print(f" demand+multipart(nbp>1): {len(mp)} | demand+RR: {len(rr)}")
|
||||
print(f"\n=== top demand-driven divergences ===")
|
||||
print(f"{'uprn':13s} {'L':>3} {'E':>5} {'dSAP':>6} {'dPE':>6} {'dCO2':>6} {'nbp':>3} {'RR':>3}")
|
||||
for r in demand_rows[:32]:
|
||||
print(f"{str(r[0]):13s} {r[1]:3d} {r[2]:5.1f} {r[3]:+6.2f} {str(r[4]):>6} {str(r[5]):>6} {r[6]:3d} {'Y' if r[7] else '':>3}")
|
||||
61
scripts/corpus_1000/elec_cohort.py
Normal file
61
scripts/corpus_1000/elec_cohort.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"""Electric-main cohort analysis: for every corpus cert whose main heating is
|
||||
electric, dump heating SAP code / tariff (meter_type) / lodged vs engine SAP /
|
||||
cost-vs-demand signature. Used to steer a spec-grounded Table-12a tariff fix
|
||||
without regressing in-band certs."""
|
||||
from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
|
||||
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||
SAP_10_2_SPEC_PRICES, cert_to_demand_inputs, cert_to_inputs, _is_electric_main,
|
||||
_first_main_heating,
|
||||
)
|
||||
|
||||
CORPUS = Path("backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl")
|
||||
|
||||
def hcode(doc):
|
||||
sh = doc.get("sap_heating") or {}
|
||||
ds = sh.get("main_heating_details") or [{}]
|
||||
return ds[0].get("main_heating_category"), ds[0].get("main_fuel_type")
|
||||
|
||||
rows = []
|
||||
for line in CORPUS.read_text().splitlines():
|
||||
doc = json.loads(line)
|
||||
try:
|
||||
epc = EpcPropertyDataMapper.from_api_response(doc)
|
||||
except Exception:
|
||||
continue
|
||||
main = _first_main_heating(epc)
|
||||
if not _is_electric_main(main):
|
||||
continue
|
||||
try:
|
||||
r = calculate_sap_from_inputs(cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES))
|
||||
d = calculate_sap_from_inputs(cert_to_demand_inputs(epc, prices=SAP_10_2_SPEC_PRICES))
|
||||
except Exception as e:
|
||||
continue
|
||||
L = doc.get("energy_rating_current"); pe_l = doc.get("energy_consumption_current")
|
||||
if L is None: continue
|
||||
dsap = r.sap_score_continuous - L
|
||||
dpe = (d.pe_per_m2 - pe_l) if (pe_l is not None and hasattr(d,'pe_per_m2')) else None
|
||||
cat, fuel = hcode(doc)
|
||||
mt = (doc.get("sap_energy_source") or {}).get("meter_type")
|
||||
sh = doc.get("sap_heating") or {}
|
||||
code = (sh.get("main_heating_details") or [{}])[0].get("main_heating_code") \
|
||||
or (sh.get("main_heating_details") or [{}])[0].get("main_heating_index_number")
|
||||
rows.append((doc.get("uprn"), code, cat, mt, sh.get("water_heating_code"),
|
||||
L, round(r.sap_score_continuous,1), round(dsap,2)))
|
||||
|
||||
rows.sort(key=lambda x:-abs(x[7]))
|
||||
print(f"electric-main certs: {len(rows)}")
|
||||
print(f"{'uprn':14s} {'hcode':>6} {'cat':>4} {'mtr':>4} {'whc':>5} {'L':>3} {'E':>6} {'dSAP':>6}")
|
||||
for r in rows:
|
||||
print(f"{str(r[0]):14s} {str(r[1]):>6} {str(r[2]):>4} {str(r[3]):>4} {str(r[4]):>5} {r[5]:3d} {r[6]:6.1f} {r[7]:+6.2f}")
|
||||
# summary by (hcode, meter)
|
||||
from collections import defaultdict
|
||||
agg = defaultdict(lambda:[0,0.0,0,0])
|
||||
for r in rows:
|
||||
k=(r[1], r[3]); a=agg[k]; a[0]+=1; a[1]+=r[7]; a[2]+=(abs(r[7])>0.5); a[3]+=(abs(r[7])<=0.5)
|
||||
print("\n=== by (hcode, meter): n, meanΔ, #div>0.5, #inband ===")
|
||||
for k,a in sorted(agg.items(), key=lambda x:-abs(x[1][1])):
|
||||
print(f" code {str(k[0]):>6} meter {str(k[1]):>4}: n={a[0]:3d} meanΔ={a[1]/a[0]:+6.2f} div={a[2]:3d} inband={a[3]:3d}")
|
||||
46
scripts/corpus_1000/field_bias.py
Normal file
46
scripts/corpus_1000/field_bias.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"""Field-level systematic-bias scan: mean Δ(SAP) grouped by each mapped
|
||||
attribute. A subgroup with large |meanΔ| AND enough n is a systematic mapping
|
||||
error for that attribute (would cancel in the overall ~0 bias)."""
|
||||
from __future__ import annotations
|
||||
import json, statistics
|
||||
from collections import defaultdict
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
|
||||
from domain.sap10_calculator.rdsap.cert_to_inputs import SAP_10_2_SPEC_PRICES, cert_to_inputs
|
||||
CORPUS="backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl"
|
||||
G=defaultdict(lambda: defaultdict(list))
|
||||
def gv(x): return x.get('value') if isinstance(x,dict) else x
|
||||
for line in open(CORPUS):
|
||||
d=json.loads(line); L=d.get('energy_rating_current')
|
||||
if L is None: continue
|
||||
try:
|
||||
epc=EpcPropertyDataMapper.from_api_response(d)
|
||||
r=calculate_sap_from_inputs(cert_to_inputs(epc,prices=SAP_10_2_SPEC_PRICES))
|
||||
except Exception: continue
|
||||
dsap=r.sap_score_continuous-L
|
||||
bp=(d.get('sap_building_parts') or [{}])[0]
|
||||
sh=d.get('sap_heating') or {}; md=(sh.get('main_heating_details') or [{}])[0]
|
||||
G['wall_con'][bp.get('wall_construction')].append(dsap)
|
||||
G['wall_ins'][bp.get('wall_insulation_type')].append(dsap)
|
||||
G['wall_con+ins'][(bp.get('wall_construction'),bp.get('wall_insulation_type'))].append(dsap)
|
||||
G['roof_con'][bp.get('roof_construction')].append(dsap)
|
||||
G['floor_hl'][bp.get('floor_heat_loss')].append(dsap)
|
||||
G['age'][bp.get('construction_age_band')].append(dsap)
|
||||
G['main_cat'][md.get('main_heating_category')].append(dsap)
|
||||
G['whc'][sh.get('water_heating_code')].append(dsap)
|
||||
G['meter'][(d.get('sap_energy_source') or {}).get('meter_type')].append(dsap)
|
||||
G['country'][d.get('country_code')].append(dsap)
|
||||
G['dwelling'][d.get('dwelling_type')].append(dsap)
|
||||
G['nbp'][len(d.get('sap_building_parts') or [])].append(dsap)
|
||||
G['glz'][tuple(sorted(set(w.get('glazing_type') for w in (d.get('sap_windows') or []))))[:1]].append(dsap)
|
||||
print("=== subgroups with |meanΔ| >= 0.5 and n >= 8 (systematic-bias candidates) ===")
|
||||
hits=[]
|
||||
for field, groups in G.items():
|
||||
for key, vals in groups.items():
|
||||
if len(vals) >= 8:
|
||||
m=statistics.mean(vals)
|
||||
if abs(m) >= 0.5:
|
||||
hits.append((abs(m), field, key, len(vals), m, statistics.median(vals)))
|
||||
for am,field,key,n,m,med in sorted(hits, reverse=True):
|
||||
print(f" {field:12s} = {str(key):20s} n={n:4d} meanΔ={m:+.2f} medianΔ={med:+.2f}")
|
||||
if not hits: print(" (none — no systematic subgroup bias)")
|
||||
56
scripts/corpus_1000/mapper_audit.py
Normal file
56
scripts/corpus_1000/mapper_audit.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"""Mapper faithfulness audit: for identity pass-through fields (dims, counts,
|
||||
thicknesses) compare the RAW cert value to what EpcPropertyDataMapper produced.
|
||||
Any mismatch is a mapper bug silently feeding the SAP engine wrong inputs."""
|
||||
from __future__ import annotations
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
CORPUS="backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl"
|
||||
def gv(x): return x.get('value') if isinstance(x,dict) else x
|
||||
def num(x):
|
||||
x=gv(x)
|
||||
if x is None: return None
|
||||
s=str(x).lower().replace('mm','').replace('m2','').strip()
|
||||
try: return float(s)
|
||||
except: return s # keep strings like 'measured'/'NI' for equality
|
||||
mism=defaultdict(list)
|
||||
n=0
|
||||
for line in open(CORPUS):
|
||||
d=json.loads(line)
|
||||
try: epc=EpcPropertyDataMapper.from_api_response(d)
|
||||
except Exception: continue
|
||||
n+=1; u=d.get('uprn')
|
||||
# top-level counts
|
||||
checks=[('door_count', d.get('door_count'), epc.door_count),
|
||||
('habitable', d.get('habitable_room_count'), getattr(epc,'habitable_room_count',None)),
|
||||
('led', d.get('led_fixed_lighting_bulbs_count'), epc.led_fixed_lighting_bulbs_count),
|
||||
('cfl', d.get('cfl_fixed_lighting_bulbs_count'), epc.cfl_fixed_lighting_bulbs_count),
|
||||
('incand', d.get('incandescent_fixed_lighting_bulbs_count'), epc.incandescent_fixed_lighting_bulbs_count)]
|
||||
for name,rv,ev in checks:
|
||||
if rv is not None and ev is not None and num(rv)!=num(ev):
|
||||
mism[name].append((u,rv,ev))
|
||||
# per-building-part fabric
|
||||
rbps=d.get('sap_building_parts') or []; ebps=epc.sap_building_parts or []
|
||||
for i,rbp in enumerate(rbps):
|
||||
if i>=len(ebps):
|
||||
mism['DROPPED_building_part'].append((u,len(rbps),len(ebps))); break
|
||||
ebp=ebps[i]
|
||||
for name,rv,ev in [('wall_thick',rbp.get('wall_thickness'),getattr(ebp,'wall_thickness_mm',None)),
|
||||
('wall_ins_thick',rbp.get('wall_insulation_thickness'),getattr(ebp,'wall_insulation_thickness',None)),
|
||||
('roof_ins_thick',rbp.get('roof_insulation_thickness'),getattr(ebp,'roof_insulation_thickness',None)),
|
||||
('wall_con',rbp.get('wall_construction'),getattr(ebp,'wall_construction',None)),
|
||||
('wall_ins_type',rbp.get('wall_insulation_type'),getattr(ebp,'wall_insulation_type',None))]:
|
||||
if rv is not None and ev is not None and num(rv)!=num(ev):
|
||||
mism[name].append((u,rv,ev))
|
||||
# windows: count + total area
|
||||
rw=d.get('sap_windows') or []; ew=epc.sap_windows or []
|
||||
if len(rw)!=len(ew): mism['window_count'].append((u,len(rw),len(ew)))
|
||||
ra=sum((gv(w.get('window_width')) or 0)*(gv(w.get('window_height')) or 0) for w in rw)
|
||||
ea=sum((float(w.window_width) if w.window_width else 0)*(float(w.window_height) if w.window_height else 0) for w in ew)
|
||||
if abs(ra-ea)>0.02: mism['window_area'].append((u,round(ra,2),round(ea,2)))
|
||||
print(f"audited {n} certs")
|
||||
print("=== field mismatches (raw cert vs EpcPropertyData) ===")
|
||||
for k,v in sorted(mism.items(), key=lambda x:-len(x[1])):
|
||||
print(f"\n[{len(v):4d}] {k}")
|
||||
for u,rv,ev in v[:5]: print(f" {u}: raw={rv} epc={ev}")
|
||||
if not mism: print(" (no mismatches — mapper is identity-faithful on all checked fields)")
|
||||
76
scripts/corpus_1000/why_diverge.py
Normal file
76
scripts/corpus_1000/why_diverge.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
"""Categorize every corpus cert outside +-0.5 SAP by the REASON it diverges,
|
||||
combining the computed cost-vs-demand signature, the electric heating code /
|
||||
meter, and the ledger's RR/multipart tokens."""
|
||||
from __future__ import annotations
|
||||
import json, re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
|
||||
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||
SAP_10_2_SPEC_PRICES, cert_to_demand_inputs, cert_to_inputs, _is_electric_main,
|
||||
_first_main_heating)
|
||||
|
||||
CORPUS = Path("backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl")
|
||||
# ledger signatures (for RR / multipart tokens)
|
||||
sig_of={}; cur=""
|
||||
for l in Path("scripts/corpus_1000/worklist.md").read_text().splitlines():
|
||||
if l.startswith('### C'):
|
||||
m=re.search(r'`([^`]*)`',l); cur=m.group(1) if m else ""
|
||||
m=re.match(r'^- .*?`([^`]+)`',l)
|
||||
if m: sig_of[m.group(1)]=cur
|
||||
|
||||
def bucket(doc, dsap, dpe, dco2, code, mt, sig):
|
||||
rr = 'RR' in (sig or '')
|
||||
elec = code in range(191,600) if code else False
|
||||
demand = dpe is not None and abs(dpe) >= 4 and (dpe>0)==(dsap<0)
|
||||
cost = dpe is not None and abs(dpe) < 4
|
||||
# 1. electric storage-boiler / CPSU on off-peak (spec §12 Rule 2/1)
|
||||
if code in (192,193,194,195,196) and dsap>0:
|
||||
return "elec storage/CPSU boiler → §12 off-peak (spec-faithful; lodged over-billed)"
|
||||
# 2. electric direct + storage heaters, tariff/cost
|
||||
if elec and cost:
|
||||
return "electric tariff/cost (meter-column & high-rate fraction; spec-faithful vs lodged)"
|
||||
if elec and demand:
|
||||
return "electric + fabric/demand mismatch"
|
||||
if elec:
|
||||
return "electric other"
|
||||
# 3. RR
|
||||
if rr:
|
||||
return "room-in-roof surface geometry (spec-mandated, worksheet-validated; high variance)"
|
||||
# 4. non-elec demand/fabric
|
||||
if demand:
|
||||
return "non-electric fabric/demand (per-cert: glazing area, lodged geometry, mixed walls)"
|
||||
if cost:
|
||||
return "non-electric cost (secondary fuel / cylinder / tariff)"
|
||||
return "unclassified"
|
||||
|
||||
cnt=defaultdict(list)
|
||||
band=defaultdict(int)
|
||||
for line in CORPUS.read_text().splitlines():
|
||||
doc=json.loads(line); L=doc.get('energy_rating_current')
|
||||
if L is None: continue
|
||||
try:
|
||||
epc=EpcPropertyDataMapper.from_api_response(doc)
|
||||
r=calculate_sap_from_inputs(cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES))
|
||||
d=calculate_sap_from_inputs(cert_to_demand_inputs(epc, prices=SAP_10_2_SPEC_PRICES))
|
||||
except Exception: continue
|
||||
dsap=r.sap_score_continuous-L
|
||||
if abs(dsap)<0.5: continue
|
||||
peL=doc.get('energy_consumption_current'); co2L=doc.get('co2_emissions_current')
|
||||
dpe=(d.primary_energy_kwh_per_m2-peL) if peL is not None else None
|
||||
dco2=(d.co2_kg_per_yr/1000-co2L) if co2L is not None else None
|
||||
sh=doc.get('sap_heating') or {}; det=(sh.get('main_heating_details') or [{}])[0]
|
||||
code=det.get('sap_main_heating_code'); mt=(doc.get('sap_energy_source') or {}).get('meter_type')
|
||||
b=bucket(doc,dsap,dpe,dco2,code,mt,sig_of.get(str(doc.get('uprn')),''))
|
||||
cnt[b].append((doc.get('uprn'),L,round(r.sap_score_continuous,1),round(dsap,1)))
|
||||
ad=abs(dsap); band['0.5-1' if ad<1 else '1-2' if ad<2 else '2-4' if ad<4 else '4+']+=1
|
||||
|
||||
total=sum(len(v) for v in cnt.values())
|
||||
print(f"TOTAL diverging (|Δ|≥0.5): {total}\n")
|
||||
print("by |Δ| band:", dict(band), "\n")
|
||||
print("=== by REASON (count, mean|Δ|, examples) ===")
|
||||
for b,rows in sorted(cnt.items(), key=lambda x:-len(x[1])):
|
||||
mabs=sum(abs(x[3]) for x in rows)/len(rows)
|
||||
ex=", ".join(f"{x[0]}({x[3]:+.1f})" for x in sorted(rows,key=lambda x:-abs(x[3]))[:3])
|
||||
print(f"\n[{len(rows):3d}] mean|Δ|={mabs:.1f} {b}\n e.g. {ex}")
|
||||
1446
scripts/corpus_1000/worklist.md
Normal file
1446
scripts/corpus_1000/worklist.md
Normal file
File diff suppressed because it is too large
Load diff
231
scripts/hyde/build_100021924710.py
Normal file
231
scripts/hyde/build_100021924710.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
"""Elmhurst build for UPRN 100021924710 (RdSAP-21.0.1, MID-TERRACE HOUSE, band B,
|
||||
SOLID BRICK 250 mm uninsulated, pitched insulated (assumed) loft, floor to
|
||||
UNHEATED space, mains-gas COMBI (PCDB 19008) + gas room-heater SECONDARY, DUAL
|
||||
meter, 11 DG windows ~18.32 m², TFA 116. Lodged SAP 63; engine 65.0 (Δ +2.0).
|
||||
|
||||
Per-cert Elmhurst validation. Uses set_single_window (deletes the shared
|
||||
assessment's carried-over window rows) + reset_transient_state at the end so the
|
||||
assessment is left neutral. Run:
|
||||
DISPLAY=:99 python scripts/hyde/build_100021924710.py <page>
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import elmhurst_lib as E
|
||||
|
||||
DIM = "TabContainer_TabPanelMain_WebUserControlDimensionsMain_"
|
||||
WALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelExternalWallMain_WebUserControlWallMain_")
|
||||
PWALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelPartyWallMain_WebUserControlPartyWallMain_")
|
||||
ROOF = "TabContainer_TabPanelMain_WebUserControlRoofMain_"
|
||||
FLOOR = "TabContainer_TabPanelMain_WebUserControlFloorsMain_"
|
||||
DP = "TabContainer_TabPanelDoorsPanel_"
|
||||
VP = "TabContainer_TabPanelVentilationPanel_"
|
||||
APT = "TabContainer_TabPanelAirPressureTest_"
|
||||
LP = "TabContainer_TabPanelLighting_"
|
||||
MV = "TabContainer_TabPanelMechVent_"
|
||||
WH = "TabContainer_TabPanelWaterHeating_"
|
||||
MH1B = "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_"
|
||||
_SAMPLE_DIR = (Path(__file__).parent.parent.parent
|
||||
/ "backend/epc_api/json_samples/real_life_examples"
|
||||
/ "RdSAP-Schema-21.0.1/uprn_100021924710")
|
||||
|
||||
|
||||
def _pick(page, suffix, contains):
|
||||
val = page.evaluate(
|
||||
"""(a)=>{const s=document.getElementById(a[0]);if(!s)return null;
|
||||
for(const o of s.options){if(o.text.toLowerCase().includes(a[1].toLowerCase()))return o.value;}return null;}""",
|
||||
[f"{E.FP}{suffix}", contains])
|
||||
if val is not None:
|
||||
E.set_select(page, suffix, val)
|
||||
return val
|
||||
|
||||
|
||||
def property_description(page):
|
||||
E.goto(page, "PropertyDescription", "WebFormPropertyDescription.aspx")
|
||||
E.set_select(page, "DropDownListPropertyType1", "H House")
|
||||
_pick(page, "DropDownListPropertyType2", "mid-terrace") or \
|
||||
_pick(page, "DropDownListPropertyType2", "mid terrace")
|
||||
E.set_text(page, "TextBoxStoreys", "2")
|
||||
E.set_text(page, "TextBoxHabitableRooms", "5")
|
||||
E.set_text(page, "TextBoxHeatedHabitableRooms", "5")
|
||||
print("date ->", _pick(page, "DropDownListDateBuiltMain", "1900-1929"))
|
||||
E.set_select(page, "DropDownListDateBuiltFirst", "")
|
||||
E.set_select(page, "DropDownListRoomInRoofMain", "")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def dimensions(page):
|
||||
E.goto(page, "Dimensions", "WebFormDimensions.aspx")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorAreaLowestFloor", "59.20")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.74")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "16.0")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "21.1")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorArea1stFloor", "57.10")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeight1stFloor", "2.74")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeter1stFloor", "15.7")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLength1stFloor", "21.7")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def walls(page):
|
||||
E.goto(page, "Walls", "WebFormWalls.aspx")
|
||||
_pick(page, f"{WALL}DropDownListType", "solid brick")
|
||||
page.wait_for_timeout(500)
|
||||
print("insulation ->", _pick(page, f"{WALL}DropDownListInsulation", "as built"))
|
||||
print("party ->", _pick(page, f"{PWALL}DropDownListPartyWallType", "determine")
|
||||
or _pick(page, f"{PWALL}DropDownListPartyWallType", "masonry"))
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def roofs(page):
|
||||
E.goto(page, "Roofs", "WebFormRoofs.aspx")
|
||||
_pick(page, f"{ROOF}DropDownListType", "access to loft") or \
|
||||
_pick(page, f"{ROOF}DropDownListType", "loft")
|
||||
_pick(page, f"{ROOF}DropDownListInsulationAt", "joists")
|
||||
_pick(page, f"{ROOF}DropDownListThickness", "unknown") or \
|
||||
_pick(page, f"{ROOF}DropDownListThickness", "assumed")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def floors(page):
|
||||
E.goto(page, "Floors", "WebFormFloors.aspx")
|
||||
_pick(page, f"{FLOOR}DropDownListLocation", "unheated")
|
||||
page.wait_for_timeout(400)
|
||||
_pick(page, f"{FLOOR}DropDownListType", "solid")
|
||||
ins = page.locator(f"#{E.FP}{FLOOR}DropDownListInsulation")
|
||||
if ins.count():
|
||||
E.set_select(page, f"{FLOOR}DropDownListInsulation", "A As built")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def openings(page):
|
||||
E.goto(page, "Openings", "WebFormOpenings.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWindowsPanel")
|
||||
page.wait_for_timeout(500)
|
||||
E.set_single_window(page, 18.32, "North",
|
||||
_glazing(page) if False else "Double post or during 2022")
|
||||
E.click_tab(page, "TabContainer_TabPanelDoorsPanel")
|
||||
E.set_text(page, f"{DP}TextBoxDoors", "1")
|
||||
E.set_text(page, f"{DP}TextBoxDoorsInsulated", "0")
|
||||
E.set_text(page, f"{DP}TextBoxDraughtProofedDoors", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def _glazing(page):
|
||||
return "Double with unknown install date"
|
||||
|
||||
|
||||
def ventilation(page):
|
||||
E.goto(page, "VentilationAndCooling", "WebFormVentilationAndCooling.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelVentilationPanel")
|
||||
E.set_text(page, f"{VP}TextBoxIntermittentFans", "1")
|
||||
cool = page.locator(f"#{E.FP}{VP}CheckBoxFixedSpaceCooling")
|
||||
if cool.count() and cool.is_checked():
|
||||
E.commit(page, cool.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelMechVent")
|
||||
mv = page.locator(f"#{E.FP}{MV}CheckBoxMechanicalVentilation")
|
||||
if mv.count() and mv.is_checked():
|
||||
E.commit(page, mv.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelAirPressureTest")
|
||||
E.set_select(page, f"{APT}DropDownListTestMethod", "Not available")
|
||||
E.click_tab(page, "TabContainer_TabPanelLighting")
|
||||
E.set_text(page, f"{LP}TextBoxLightsTotal", "8")
|
||||
E.set_text(page, f"{LP}TextBoxLedLightsTotal", "0")
|
||||
E.set_text(page, f"{LP}TextBoxCflLightsTotal", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def space_heating(page):
|
||||
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(1000)
|
||||
mhc = page.locator(f"#{E.MH1}TextBoxMainHeatingCode")
|
||||
code = mhc.input_value() if mhc.count() else ""
|
||||
if code and code not in ("0",):
|
||||
print(f"clearing leftover MainHeatingCode {code}")
|
||||
page.evaluate("""(id)=>{const e=document.getElementById(id);if(e){e.value='';
|
||||
e.dispatchEvent(new Event('change',{bubbles:true}));}}""", f"{E.MH1}TextBoxMainHeatingCode")
|
||||
page.wait_for_timeout(400)
|
||||
E.save_close(page)
|
||||
return
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingCode",
|
||||
"^Gas", "Mains gas", "Boilers", "Post 1998", "Condensing",
|
||||
"Combi condens")
|
||||
print("code:", page.locator(f"#{E.MH1}TextBoxMainHeatingCode").input_value())
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingControls",
|
||||
"^Boilers", "^Standard", "CBE Programmer, room thermostat and TRVs")
|
||||
print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value())
|
||||
# dual meter (cert meter_type 1)
|
||||
E.click_tab(page, "TabContainer_TabPanelMeters")
|
||||
try:
|
||||
E.set_select(page, "TabContainer_TabPanelMeters_RadioButtonListElectricityType", "Dual")
|
||||
except Exception:
|
||||
pass
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def secondary(page):
|
||||
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(600)
|
||||
E.set_select(page, "DropDownListSecondaryHeatingPresent", "Yes")
|
||||
page.wait_for_timeout(900)
|
||||
E.set_heating_dialog(page, "ButtonSecondaryHeatingCode",
|
||||
"^Gas", "Mains gas", "Room Heater")
|
||||
tb = page.locator(f"#{E.FP}TextBoxSecondaryHeatingCode")
|
||||
print("secondary code:", tb.input_value() if tb.count() else "?")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def water_heating(page):
|
||||
E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWaterHeating")
|
||||
page.wait_for_timeout(400)
|
||||
E.clear_hot_water_cylinder(page)
|
||||
E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode",
|
||||
"From Space Heating", "From the primary heating system")
|
||||
print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value())
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def download(page):
|
||||
_SAMPLE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
E.goto(page, "Recommendations", "WebFormRecommendations.aspx")
|
||||
page.wait_for_timeout(1500)
|
||||
with page.expect_navigation(wait_until="networkidle", timeout=40000):
|
||||
page.click("#ContentBody_buttonActionSummary_Link", timeout=15000)
|
||||
page.wait_for_timeout(2000)
|
||||
for btn, fname in (("ContentBody_ContentPlaceHolder1_LinkButtonSummary", "elmhurst_summary.pdf"),
|
||||
("ContentBody_ContentPlaceHolder1_ButtonDebugInforPdf", "elmhurst_worksheet.pdf")):
|
||||
try:
|
||||
with page.expect_download(timeout=60000) as dl:
|
||||
page.click(f"#{btn}", timeout=15000)
|
||||
dl.value.save_as(str(_SAMPLE_DIR / fname))
|
||||
print("saved", fname)
|
||||
except Exception as e:
|
||||
print("!! download failed", fname, type(e).__name__, str(e)[:120])
|
||||
|
||||
|
||||
def cleanup(page):
|
||||
"""Leave the shared assessment neutral for the next build."""
|
||||
E.reset_transient_state(page)
|
||||
print("reset transient state (RR/secondary/meter)")
|
||||
|
||||
|
||||
_ORDER = ["property_description", "dimensions", "walls", "roofs", "floors",
|
||||
"openings", "ventilation", "space_heating", "secondary",
|
||||
"water_heating", "download", "cleanup"]
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in _ORDER:
|
||||
print("usage: build_100021924710.py <" + "|".join(_ORDER) + ">")
|
||||
return 2
|
||||
with E.session() as (ctx, page):
|
||||
globals()[sys.argv[1]](page)
|
||||
print("done:", sys.argv[1], "->", page.url)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
223
scripts/hyde/build_100050881708.py
Normal file
223
scripts/hyde/build_100050881708.py
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
"""Elmhurst build for UPRN 100050881708 (RdSAP-21.0.1, MID-TERRACE HOUSE, band B,
|
||||
SOLID BRICK 250 mm uninsulated, pitched insulated (assumed) loft, floor to
|
||||
UNHEATED space, mains-gas COMBI (PCDB 19008) + gas room-heater SECONDARY, DUAL
|
||||
meter, 11 DG windows ~18.32 m², TFA 116. Lodged SAP 63; engine 65.0 (Δ +2.0).
|
||||
|
||||
Per-cert Elmhurst validation. Uses set_single_window (deletes the shared
|
||||
assessment's carried-over window rows) + reset_transient_state at the end so the
|
||||
assessment is left neutral. Run:
|
||||
DISPLAY=:99 python scripts/hyde/build_100050881708.py <page>
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import elmhurst_lib as E
|
||||
|
||||
DIM = "TabContainer_TabPanelMain_WebUserControlDimensionsMain_"
|
||||
WALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelExternalWallMain_WebUserControlWallMain_")
|
||||
PWALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelPartyWallMain_WebUserControlPartyWallMain_")
|
||||
ROOF = "TabContainer_TabPanelMain_WebUserControlRoofMain_"
|
||||
FLOOR = "TabContainer_TabPanelMain_WebUserControlFloorsMain_"
|
||||
DP = "TabContainer_TabPanelDoorsPanel_"
|
||||
VP = "TabContainer_TabPanelVentilationPanel_"
|
||||
APT = "TabContainer_TabPanelAirPressureTest_"
|
||||
LP = "TabContainer_TabPanelLighting_"
|
||||
MV = "TabContainer_TabPanelMechVent_"
|
||||
WH = "TabContainer_TabPanelWaterHeating_"
|
||||
MH1B = "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_"
|
||||
_SAMPLE_DIR = (Path(__file__).parent.parent.parent
|
||||
/ "backend/epc_api/json_samples/real_life_examples"
|
||||
/ "RdSAP-Schema-21.0.1/uprn_100050881708")
|
||||
|
||||
|
||||
def _pick(page, suffix, contains):
|
||||
val = page.evaluate(
|
||||
"""(a)=>{const s=document.getElementById(a[0]);if(!s)return null;
|
||||
for(const o of s.options){if(o.text.toLowerCase().includes(a[1].toLowerCase()))return o.value;}return null;}""",
|
||||
[f"{E.FP}{suffix}", contains])
|
||||
if val is not None:
|
||||
E.set_select(page, suffix, val)
|
||||
return val
|
||||
|
||||
|
||||
def property_description(page):
|
||||
E.goto(page, "PropertyDescription", "WebFormPropertyDescription.aspx")
|
||||
E.set_select(page, "DropDownListPropertyType1", "H House")
|
||||
_pick(page, "DropDownListPropertyType2", "semi")
|
||||
E.set_text(page, "TextBoxStoreys", "2")
|
||||
E.set_text(page, "TextBoxHabitableRooms", "3")
|
||||
E.set_text(page, "TextBoxHeatedHabitableRooms", "3")
|
||||
print("date ->", _pick(page, "DropDownListDateBuiltMain", "1900-1929"))
|
||||
E.set_select(page, "DropDownListDateBuiltFirst", "")
|
||||
E.set_select(page, "DropDownListRoomInRoofMain", "")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def dimensions(page):
|
||||
E.goto(page, "Dimensions", "WebFormDimensions.aspx")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorAreaLowestFloor", "31.62")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.40")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "16.4")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "6.2")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorArea1stFloor", "31.62")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeight1stFloor", "2.40")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeter1stFloor", "16.4")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLength1stFloor", "6.2")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def walls(page):
|
||||
E.goto(page, "Walls", "WebFormWalls.aspx")
|
||||
_pick(page, f"{WALL}DropDownListType", "cavity")
|
||||
page.wait_for_timeout(500)
|
||||
print("insulation ->", _pick(page, f"{WALL}DropDownListInsulation", "filled"))
|
||||
print("party ->", _pick(page, f"{PWALL}DropDownListPartyWallType", "determine")
|
||||
or _pick(page, f"{PWALL}DropDownListPartyWallType", "masonry"))
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def roofs(page):
|
||||
E.goto(page, "Roofs", "WebFormRoofs.aspx")
|
||||
_pick(page, f"{ROOF}DropDownListType", "access to loft") or \
|
||||
_pick(page, f"{ROOF}DropDownListType", "loft")
|
||||
_pick(page, f"{ROOF}DropDownListInsulationAt", "joists")
|
||||
_pick(page, f"{ROOF}DropDownListThickness", "225")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def floors(page):
|
||||
E.goto(page, "Floors", "WebFormFloors.aspx")
|
||||
_pick(page, f"{FLOOR}DropDownListLocation", "unheated")
|
||||
page.wait_for_timeout(400)
|
||||
_pick(page, f"{FLOOR}DropDownListType", "solid")
|
||||
ins = page.locator(f"#{E.FP}{FLOOR}DropDownListInsulation")
|
||||
if ins.count():
|
||||
E.set_select(page, f"{FLOOR}DropDownListInsulation", "A As built")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def openings(page):
|
||||
E.goto(page, "Openings", "WebFormOpenings.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWindowsPanel")
|
||||
page.wait_for_timeout(500)
|
||||
E.set_single_window(page, 9.56, "North",
|
||||
_glazing(page) if False else "Double post or during 2022")
|
||||
E.click_tab(page, "TabContainer_TabPanelDoorsPanel")
|
||||
E.set_text(page, f"{DP}TextBoxDoors", "2")
|
||||
E.set_text(page, f"{DP}TextBoxDoorsInsulated", "0")
|
||||
E.set_text(page, f"{DP}TextBoxDraughtProofedDoors", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def _glazing(page):
|
||||
return "Double with unknown install date"
|
||||
|
||||
|
||||
def ventilation(page):
|
||||
E.goto(page, "VentilationAndCooling", "WebFormVentilationAndCooling.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelVentilationPanel")
|
||||
E.set_text(page, f"{VP}TextBoxIntermittentFans", "2")
|
||||
cool = page.locator(f"#{E.FP}{VP}CheckBoxFixedSpaceCooling")
|
||||
if cool.count() and cool.is_checked():
|
||||
E.commit(page, cool.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelMechVent")
|
||||
mv = page.locator(f"#{E.FP}{MV}CheckBoxMechanicalVentilation")
|
||||
if mv.count() and mv.is_checked():
|
||||
E.commit(page, mv.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelAirPressureTest")
|
||||
E.set_select(page, f"{APT}DropDownListTestMethod", "Not available")
|
||||
E.click_tab(page, "TabContainer_TabPanelLighting")
|
||||
E.set_text(page, f"{LP}TextBoxLightsTotal", "8")
|
||||
E.set_text(page, f"{LP}TextBoxLedLightsTotal", "0")
|
||||
E.set_text(page, f"{LP}TextBoxCflLightsTotal", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def space_heating(page):
|
||||
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(1000)
|
||||
mhc = page.locator(f"#{E.MH1}TextBoxMainHeatingCode")
|
||||
code = mhc.input_value() if mhc.count() else ""
|
||||
if code and code not in ("0",):
|
||||
print(f"clearing leftover MainHeatingCode {code}")
|
||||
page.evaluate("""(id)=>{const e=document.getElementById(id);if(e){e.value='';
|
||||
e.dispatchEvent(new Event('change',{bubbles:true}));}}""", f"{E.MH1}TextBoxMainHeatingCode")
|
||||
page.wait_for_timeout(400)
|
||||
E.save_close(page)
|
||||
return
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingCode",
|
||||
"^Gas", "Mains gas", "Boilers", "Post 1998", "Condensing",
|
||||
"Combi condens")
|
||||
print("code:", page.locator(f"#{E.MH1}TextBoxMainHeatingCode").input_value())
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingControls",
|
||||
"^Boilers", "^Standard", "CBE Programmer, room thermostat and TRVs")
|
||||
print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value())
|
||||
E.set_select(page, "DropDownListSecondaryHeatingPresent", "No")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def secondary(page):
|
||||
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(600)
|
||||
E.set_select(page, "DropDownListSecondaryHeatingPresent", "Yes")
|
||||
page.wait_for_timeout(900)
|
||||
E.set_heating_dialog(page, "ButtonSecondaryHeatingCode",
|
||||
"^Gas", "Mains gas", "Room Heater")
|
||||
tb = page.locator(f"#{E.FP}TextBoxSecondaryHeatingCode")
|
||||
print("secondary code:", tb.input_value() if tb.count() else "?")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def water_heating(page):
|
||||
E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWaterHeating")
|
||||
page.wait_for_timeout(400)
|
||||
E.clear_hot_water_cylinder(page)
|
||||
E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode",
|
||||
"From Space Heating", "From the primary heating system")
|
||||
print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value())
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def download(page):
|
||||
_SAMPLE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
E.goto(page, "Recommendations", "WebFormRecommendations.aspx")
|
||||
page.wait_for_timeout(1500)
|
||||
with page.expect_navigation(wait_until="networkidle", timeout=40000):
|
||||
page.click("#ContentBody_buttonActionSummary_Link", timeout=15000)
|
||||
page.wait_for_timeout(2000)
|
||||
for btn, fname in (("ContentBody_ContentPlaceHolder1_LinkButtonSummary", "elmhurst_summary.pdf"),
|
||||
("ContentBody_ContentPlaceHolder1_ButtonDebugInforPdf", "elmhurst_worksheet.pdf")):
|
||||
try:
|
||||
with page.expect_download(timeout=60000) as dl:
|
||||
page.click(f"#{btn}", timeout=15000)
|
||||
dl.value.save_as(str(_SAMPLE_DIR / fname))
|
||||
print("saved", fname)
|
||||
except Exception as e:
|
||||
print("!! download failed", fname, type(e).__name__, str(e)[:120])
|
||||
|
||||
|
||||
def cleanup(page):
|
||||
"""Leave the shared assessment neutral for the next build."""
|
||||
E.reset_transient_state(page)
|
||||
print("reset transient state (RR/secondary/meter)")
|
||||
|
||||
|
||||
_ORDER = ["property_description", "dimensions", "walls", "roofs", "floors",
|
||||
"openings", "ventilation", "space_heating", "water_heating", "download", "cleanup"]
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in _ORDER:
|
||||
print("usage: build_100050881708.py <" + "|".join(_ORDER) + ">")
|
||||
return 2
|
||||
with E.session() as (ctx, page):
|
||||
globals()[sys.argv[1]](page)
|
||||
print("done:", sys.argv[1], "->", page.url)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
230
scripts/hyde/build_100061275133.py
Normal file
230
scripts/hyde/build_100061275133.py
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
"""Elmhurst build for UPRN 100061275133 (RdSAP-21.0.1, SEMI-DETACHED HOUSE, band B,
|
||||
SOLID BRICK 400 mm with EXTERNAL insulation, pitched 300 mm loft, ground floor
|
||||
SUSPENDED, no insulation (assumed) — no per-bp floor_construction_type lodged,
|
||||
mains-gas COMBI (PCDB 18908, Worcester Greenstar 4000 88.70%), no secondary
|
||||
heating, single meter, 12 DG windows ~15.32 m², TFA 78. Lodged SAP 76; engine
|
||||
76 (76.33, exact match after the §5 (12) floor-infiltration fix).
|
||||
|
||||
Per-cert Elmhurst validation. Uses set_single_window (deletes the shared
|
||||
assessment's carried-over window rows) + reset_transient_state at the end so the
|
||||
assessment is left neutral. Run:
|
||||
DISPLAY=:99 python scripts/hyde/build_100061275133.py <page>
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import elmhurst_lib as E
|
||||
|
||||
DIM = "TabContainer_TabPanelMain_WebUserControlDimensionsMain_"
|
||||
WALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelExternalWallMain_WebUserControlWallMain_")
|
||||
PWALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelPartyWallMain_WebUserControlPartyWallMain_")
|
||||
ROOF = "TabContainer_TabPanelMain_WebUserControlRoofMain_"
|
||||
FLOOR = "TabContainer_TabPanelMain_WebUserControlFloorsMain_"
|
||||
DP = "TabContainer_TabPanelDoorsPanel_"
|
||||
VP = "TabContainer_TabPanelVentilationPanel_"
|
||||
APT = "TabContainer_TabPanelAirPressureTest_"
|
||||
LP = "TabContainer_TabPanelLighting_"
|
||||
MV = "TabContainer_TabPanelMechVent_"
|
||||
WH = "TabContainer_TabPanelWaterHeating_"
|
||||
MH1B = "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_"
|
||||
_SAMPLE_DIR = (Path(__file__).parent.parent.parent
|
||||
/ "backend/epc_api/json_samples/real_life_examples"
|
||||
/ "RdSAP-Schema-21.0.1/uprn_100061275133")
|
||||
|
||||
|
||||
def _pick(page, suffix, contains):
|
||||
val = page.evaluate(
|
||||
"""(a)=>{const s=document.getElementById(a[0]);if(!s)return null;
|
||||
for(const o of s.options){if(o.text.toLowerCase().includes(a[1].toLowerCase()))return o.value;}return null;}""",
|
||||
[f"{E.FP}{suffix}", contains])
|
||||
if val is not None:
|
||||
E.set_select(page, suffix, val)
|
||||
return val
|
||||
|
||||
|
||||
def property_description(page):
|
||||
E.goto(page, "PropertyDescription", "WebFormPropertyDescription.aspx")
|
||||
E.set_select(page, "DropDownListPropertyType1", "H House")
|
||||
_pick(page, "DropDownListPropertyType2", "semi")
|
||||
E.set_text(page, "TextBoxStoreys", "2")
|
||||
E.set_text(page, "TextBoxHabitableRooms", "3")
|
||||
E.set_text(page, "TextBoxHeatedHabitableRooms", "3")
|
||||
print("date ->", _pick(page, "DropDownListDateBuiltMain", "1900-1929"))
|
||||
E.set_select(page, "DropDownListDateBuiltFirst", "")
|
||||
E.set_select(page, "DropDownListRoomInRoofMain", "")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def dimensions(page):
|
||||
E.goto(page, "Dimensions", "WebFormDimensions.aspx")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorAreaLowestFloor", "38.99")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.40")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "17.92")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "5.09")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorArea1stFloor", "38.99")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeight1stFloor", "2.40")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeter1stFloor", "17.92")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLength1stFloor", "5.09")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def walls(page):
|
||||
E.goto(page, "Walls", "WebFormWalls.aspx")
|
||||
_pick(page, f"{WALL}DropDownListType", "solid brick")
|
||||
page.wait_for_timeout(500)
|
||||
print("insulation ->", _pick(page, f"{WALL}DropDownListInsulation", "external"))
|
||||
print("party ->", _pick(page, f"{PWALL}DropDownListPartyWallType", "determine")
|
||||
or _pick(page, f"{PWALL}DropDownListPartyWallType", "masonry"))
|
||||
# wall thickness (cert lodges 400 mm; Elmhurst else defaults 260)
|
||||
for suf in (f"{WALL}TextBoxThickness", f"{WALL}TextBoxWallThickness"):
|
||||
if page.locator(f"#{E.FP}{suf}").count():
|
||||
E.set_text(page, suf, "400"); print("wall thickness set 400 via", suf); break
|
||||
else:
|
||||
print("thickness field not found; wall fields:", [i for i in page.evaluate("()=>Array.from(document.querySelectorAll('[id*=Wall]')).map(e=>e.id)")][:20])
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def roofs(page):
|
||||
E.goto(page, "Roofs", "WebFormRoofs.aspx")
|
||||
_pick(page, f"{ROOF}DropDownListType", "access to loft") or \
|
||||
_pick(page, f"{ROOF}DropDownListType", "loft")
|
||||
_pick(page, f"{ROOF}DropDownListInsulationAt", "joists")
|
||||
_pick(page, f"{ROOF}DropDownListThickness", "300")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def floors(page):
|
||||
E.goto(page, "Floors", "WebFormFloors.aspx")
|
||||
_pick(page, f"{FLOOR}DropDownListLocation", "ground floor")
|
||||
page.wait_for_timeout(400)
|
||||
_pick(page, f"{FLOOR}DropDownListType", "suspended")
|
||||
ins = page.locator(f"#{E.FP}{FLOOR}DropDownListInsulation")
|
||||
if ins.count():
|
||||
E.set_select(page, f"{FLOOR}DropDownListInsulation", "A As built")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def openings(page):
|
||||
E.goto(page, "Openings", "WebFormOpenings.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWindowsPanel")
|
||||
page.wait_for_timeout(500)
|
||||
E.set_single_window(page, 15.32, "North", _glazing(page))
|
||||
E.click_tab(page, "TabContainer_TabPanelDoorsPanel")
|
||||
E.set_text(page, f"{DP}TextBoxDoors", "2")
|
||||
E.set_text(page, f"{DP}TextBoxDoorsInsulated", "0")
|
||||
E.set_text(page, f"{DP}TextBoxDraughtProofedDoors", "2") # cert: percent_draughtproofed=100
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def _glazing(page):
|
||||
# glazed_type 13 -> double glazed, 2022 or later (U 1.4)
|
||||
return "Double post or during 2022"
|
||||
|
||||
|
||||
def ventilation(page):
|
||||
E.goto(page, "VentilationAndCooling", "WebFormVentilationAndCooling.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelVentilationPanel")
|
||||
# Cert lodges no extract-fan count; RdSAP 10 Table 5 age-band default for
|
||||
# band B (A-E) is 0, and our engine's ventilation_from_cert computes 0 —
|
||||
# was wrongly entered as 1 here, inflating Elmhurst's (8)/(21) ACH vs ours.
|
||||
E.set_text(page, f"{VP}TextBoxIntermittentFans", "0")
|
||||
cool = page.locator(f"#{E.FP}{VP}CheckBoxFixedSpaceCooling")
|
||||
if cool.count() and cool.is_checked():
|
||||
E.commit(page, cool.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelMechVent")
|
||||
mv = page.locator(f"#{E.FP}{MV}CheckBoxMechanicalVentilation")
|
||||
if mv.count() and mv.is_checked():
|
||||
E.commit(page, mv.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelAirPressureTest")
|
||||
E.set_select(page, f"{APT}DropDownListTestMethod", "Not available")
|
||||
E.click_tab(page, "TabContainer_TabPanelLighting")
|
||||
E.set_text(page, f"{LP}TextBoxLightsTotal", "5")
|
||||
E.set_text(page, f"{LP}TextBoxLedLightsTotal", "5")
|
||||
E.set_text(page, f"{LP}TextBoxCflLightsTotal", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def space_heating(page):
|
||||
# PCDB exact-boiler entry (elmhurst_lib.set_pcdb_boiler): the cert's
|
||||
# main_heating_index_number 18908 resolves to Worcester Greenstar 4000
|
||||
# GR4700iW 25 C NG, 88.70% winter efficiency — matching the SAP engine's
|
||||
# PCDB lookup exactly, vs the generic "Combi condens" SAP-Table code
|
||||
# (84%) the old dialog-driven entry carried a keep-hot artifact from.
|
||||
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(1000)
|
||||
E.clear_main_heating_code(page)
|
||||
desc = E.set_pcdb_boiler(page, 18908)
|
||||
print("PCDB boiler ->", desc)
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingControls",
|
||||
"^Boilers", "^Standard", "CBE Programmer, room thermostat and TRVs")
|
||||
print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value())
|
||||
E.set_select(page, "DropDownListSecondaryHeatingPresent", "No")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def _unused_secondary(page):
|
||||
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(600)
|
||||
E.set_select(page, "DropDownListSecondaryHeatingPresent", "Yes")
|
||||
page.wait_for_timeout(900)
|
||||
E.set_heating_dialog(page, "ButtonSecondaryHeatingCode",
|
||||
"^Gas", "Mains gas", "Room Heater")
|
||||
tb = page.locator(f"#{E.FP}TextBoxSecondaryHeatingCode")
|
||||
print("secondary code:", tb.input_value() if tb.count() else "?")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def water_heating(page):
|
||||
E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWaterHeating")
|
||||
page.wait_for_timeout(400)
|
||||
E.clear_hot_water_cylinder(page)
|
||||
E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode",
|
||||
"From Space Heating", "From the primary heating system")
|
||||
print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value())
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def download(page):
|
||||
_SAMPLE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
E.goto(page, "Recommendations", "WebFormRecommendations.aspx")
|
||||
page.wait_for_timeout(1500)
|
||||
with page.expect_navigation(wait_until="networkidle", timeout=40000):
|
||||
page.click("#ContentBody_buttonActionSummary_Link", timeout=15000)
|
||||
page.wait_for_timeout(2000)
|
||||
for btn, fname in (("ContentBody_ContentPlaceHolder1_LinkButtonSummary", "elmhurst_summary.pdf"),
|
||||
("ContentBody_ContentPlaceHolder1_ButtonDebugInforPdf", "elmhurst_worksheet.pdf")):
|
||||
try:
|
||||
with page.expect_download(timeout=60000) as dl:
|
||||
page.click(f"#{btn}", timeout=15000)
|
||||
dl.value.save_as(str(_SAMPLE_DIR / fname))
|
||||
print("saved", fname)
|
||||
except Exception as e:
|
||||
print("!! download failed", fname, type(e).__name__, str(e)[:120])
|
||||
|
||||
|
||||
def cleanup(page):
|
||||
"""Leave the shared assessment neutral for the next build."""
|
||||
E.reset_transient_state(page)
|
||||
print("reset transient state (RR/secondary/meter)")
|
||||
|
||||
|
||||
_ORDER = ["property_description", "dimensions", "walls", "roofs", "floors",
|
||||
"openings", "ventilation", "space_heating",
|
||||
"water_heating", "download", "cleanup"]
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in _ORDER:
|
||||
print("usage: build_100061275133.py <" + "|".join(_ORDER) + ">")
|
||||
return 2
|
||||
with E.session() as (ctx, page):
|
||||
globals()[sys.argv[1]](page)
|
||||
print("done:", sys.argv[1], "->", page.url)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
297
scripts/hyde/build_200004296092.py
Normal file
297
scripts/hyde/build_200004296092.py
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
"""Elmhurst build for UPRN 200004296092 (RdSAP-Schema-21.0.1, END-TERRACE HOUSE,
|
||||
2-storey, band B, SOLID BRICK 360 mm WITH EXTERNAL INSULATION (thickness lodged
|
||||
"NI" = unknown), pitched 150 mm loft, solid uninsulated ground floor, mains-gas
|
||||
COMBI (PCDB 16137 Ideal Logic combi 30 ~ generic Post-98 condensing combi),
|
||||
control 2106 (CBE), double glazed ~4.31 m², 2 doors, TFA ~62, party wall 6.79 m.
|
||||
Engine 71.74 = lodged 71 AFTER the §5.4 "assume 100 mm if unknown" wall fix
|
||||
(slice 5); this build validates that against the accredited Elmhurst worksheet.
|
||||
|
||||
The KEY page is `walls`: it prints Elmhurst's insulation dropdown so we can see
|
||||
how the accredited tool handles External insulation of unknown thickness. Run:
|
||||
DISPLAY=:99 python scripts/hyde/build_200004296092.py <page>
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import elmhurst_lib as E
|
||||
|
||||
DIM = "TabContainer_TabPanelMain_WebUserControlDimensionsMain_"
|
||||
WALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelExternalWallMain_WebUserControlWallMain_")
|
||||
PWALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelPartyWallMain_WebUserControlPartyWallMain_")
|
||||
ROOF = "TabContainer_TabPanelMain_WebUserControlRoofMain_"
|
||||
FLOOR = "TabContainer_TabPanelMain_WebUserControlFloorsMain_"
|
||||
WP = "TabContainer_TabPanelWindowsPanel_"
|
||||
DP = "TabContainer_TabPanelDoorsPanel_"
|
||||
VP = "TabContainer_TabPanelVentilationPanel_"
|
||||
APT = "TabContainer_TabPanelAirPressureTest_"
|
||||
LP = "TabContainer_TabPanelLighting_"
|
||||
MV = "TabContainer_TabPanelMechVent_"
|
||||
WH = "TabContainer_TabPanelWaterHeating_"
|
||||
MH1B = "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_"
|
||||
|
||||
_SAMPLE_DIR = (Path(__file__).parent.parent.parent
|
||||
/ "backend/epc_api/json_samples/real_life_examples"
|
||||
/ "RdSAP-Schema-21.0.1/uprn_200004296092")
|
||||
|
||||
|
||||
def _pick(page, suffix, contains):
|
||||
val = page.evaluate(
|
||||
"""(a)=>{const s=document.getElementById(a[0]);if(!s)return null;
|
||||
for(const o of s.options){if(o.text.toLowerCase().includes(a[1].toLowerCase()))return o.value;}return null;}""",
|
||||
[f"{E.FP}{suffix}", contains])
|
||||
if val is not None:
|
||||
E.set_select(page, suffix, val)
|
||||
return val
|
||||
|
||||
|
||||
def _options(page, suffix):
|
||||
return page.evaluate(
|
||||
"""(id)=>{const s=document.getElementById(id);if(!s)return [];
|
||||
return Array.from(s.options).map(o=>o.text);}""", f"{E.FP}{suffix}")
|
||||
|
||||
|
||||
def property_description(page):
|
||||
E.goto(page, "PropertyDescription", "WebFormPropertyDescription.aspx")
|
||||
E.set_select(page, "DropDownListPropertyType1", "H House")
|
||||
print("built_form ->", _pick(page, "DropDownListPropertyType2", "end-terrace")
|
||||
or _pick(page, "DropDownListPropertyType2", "end terrace"))
|
||||
E.set_text(page, "TextBoxStoreys", "2")
|
||||
E.set_text(page, "TextBoxHabitableRooms", "3")
|
||||
E.set_text(page, "TextBoxHeatedHabitableRooms", "3")
|
||||
print("date ->", _pick(page, "DropDownListDateBuiltMain", "1900-1929")
|
||||
or _pick(page, "DropDownListDateBuiltMain", "1900"))
|
||||
E.set_select(page, "DropDownListDateBuiltFirst", "")
|
||||
E.set_select(page, "DropDownListRoomInRoofMain", "")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def dimensions(page):
|
||||
E.goto(page, "Dimensions", "WebFormDimensions.aspx")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorAreaLowestFloor", "31.16")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.40")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "15.97")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "6.79")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorArea1stFloor", "31.16")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeight1stFloor", "2.40")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeter1stFloor", "15.97")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLength1stFloor", "6.79")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def walls(page):
|
||||
E.goto(page, "Walls", "WebFormWalls.aspx")
|
||||
print("WALL TYPE options:", _options(page, f"{WALL}DropDownListType"))
|
||||
_pick(page, f"{WALL}DropDownListType", "solid brick") \
|
||||
or _pick(page, f"{WALL}DropDownListType", "brick")
|
||||
page.wait_for_timeout(600)
|
||||
print("INSULATION options:", _options(page, f"{WALL}DropDownListInsulation"))
|
||||
ins = (_pick(page, f"{WALL}DropDownListInsulation", "external")
|
||||
or _pick(page, f"{WALL}DropDownListInsulation", "ext"))
|
||||
print("insulation ->", ins)
|
||||
page.wait_for_timeout(600)
|
||||
# If a thickness dropdown appears for external insulation, show it (this is
|
||||
# the slice-5 question: what does Elmhurst do with UNKNOWN thickness?).
|
||||
for suf in (f"{WALL}DropDownListInsulationThickness",
|
||||
f"{WALL}DropDownListExternalInsulationThickness"):
|
||||
if page.locator(f"#{E.FP}{suf}").count():
|
||||
print("INSULATION THICKNESS options:", _options(page, suf),
|
||||
"current:", page.locator(f"#{E.FP}{suf}").input_value())
|
||||
print("party wall ->",
|
||||
_pick(page, f"{PWALL}DropDownListPartyWallType", "determine")
|
||||
or _pick(page, f"{PWALL}DropDownListPartyWallType", "masonry"))
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def roofs(page):
|
||||
E.goto(page, "Roofs", "WebFormRoofs.aspx")
|
||||
_pick(page, f"{ROOF}DropDownListType", "access to loft") \
|
||||
or _pick(page, f"{ROOF}DropDownListType", "loft")
|
||||
_pick(page, f"{ROOF}DropDownListInsulationAt", "joists")
|
||||
E.set_select(page, f"{ROOF}DropDownListThickness", "150 mm")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def floors(page):
|
||||
E.goto(page, "Floors", "WebFormFloors.aspx")
|
||||
E.set_select(page, f"{FLOOR}DropDownListLocation", "G Ground floor")
|
||||
_pick(page, f"{FLOOR}DropDownListType", "solid")
|
||||
E.set_select(page, f"{FLOOR}DropDownListInsulation", "A As built")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def openings(page):
|
||||
E.goto(page, "Openings", "WebFormOpenings.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWindowsPanel")
|
||||
_add_window(page, 4.31, "North", _glazing(page))
|
||||
_delete_zero_rows(page)
|
||||
E.click_tab(page, "TabContainer_TabPanelDoorsPanel")
|
||||
E.set_text(page, f"{DP}TextBoxDoors", "2")
|
||||
E.set_text(page, f"{DP}TextBoxDoorsInsulated", "0")
|
||||
E.set_text(page, f"{DP}TextBoxDraughtProofedDoors", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def _glazing(page):
|
||||
for needle in ("unknown install date", "before 2002", "pre 2002"):
|
||||
for opt in _options(page, f"{WP}DropDownListExtGlazing"):
|
||||
low = opt.lower()
|
||||
if needle in low and "triple" not in low and "single" not in low and "known data" not in low:
|
||||
return opt
|
||||
return "Double post or during 2022"
|
||||
|
||||
|
||||
def _add_window(page, area, orientation, glazing):
|
||||
print("glazing ->", glazing)
|
||||
E.set_select(page, f"{WP}DropDownListExtGlazing", glazing)
|
||||
page.wait_for_timeout(400)
|
||||
ft = page.locator(f"#{E.FP}{WP}DropDownListExtFrameType")
|
||||
if ft.count():
|
||||
ft.select_option("PVC")
|
||||
gg = page.locator(f"#{E.FP}{WP}DropDownListExtGlazingGap")
|
||||
if gg.count():
|
||||
gg.select_option("12 mm")
|
||||
wid = f"{E.FP}{WP}TextBoxExtWidth"
|
||||
page.evaluate(
|
||||
"""(a)=>{const e=document.getElementById(a[0]);if(e){e.value=a[1];
|
||||
e.dispatchEvent(new Event('input',{bubbles:true}));
|
||||
e.dispatchEvent(new Event('change',{bubbles:true}));
|
||||
e.dispatchEvent(new Event('blur',{bubbles:true}));}}""", [wid, str(area)])
|
||||
page.locator(f"#{E.FP}{WP}TextBoxExtHeight").fill("1.00")
|
||||
page.locator(f"#{E.FP}{WP}DropDownListExtOrientation").select_option(orientation)
|
||||
page.locator(f"#{E.FP}{WP}DropDownListExtBuildingPartId").select_option("Main")
|
||||
page.locator(f"#{E.FP}{WP}DropDownListExtLocation").select_option("External wall")
|
||||
page.wait_for_timeout(300)
|
||||
before = E.window_row_count(page)
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", f"{E.FP}{WP}ButtonAddWindow")
|
||||
for _ in range(25):
|
||||
page.wait_for_timeout(200)
|
||||
if E.window_row_count(page) > before:
|
||||
break
|
||||
|
||||
|
||||
def _grid_rows(page):
|
||||
return page.evaluate(
|
||||
"""()=>{const t=document.querySelector("[id*=GridViewExtendedWidows]");
|
||||
if(!t)return[];return Array.from(t.querySelectorAll('tr')).slice(1)
|
||||
.map(r=>Array.from(r.querySelectorAll('td')).map(c=>c.innerText.trim()));}""")
|
||||
|
||||
|
||||
def _delete_zero_rows(page):
|
||||
g = 0
|
||||
while g < 6 and E.window_row_count(page) > 1:
|
||||
g += 1
|
||||
rows = _grid_rows(page)
|
||||
bad = next((i for i, c in enumerate(rows) if len(c) > 1 and c[1] in ("0.00", "0", "0.0")), None)
|
||||
if bad is None:
|
||||
break
|
||||
_delete_row(page, bad)
|
||||
page.wait_for_timeout(400)
|
||||
|
||||
|
||||
def _delete_row(page, idx):
|
||||
before = E.window_row_count(page)
|
||||
btn = page.evaluate(
|
||||
"""(i)=>{const b=document.querySelectorAll("[id*='GridViewExtendedWidows_DeleteButton_']");return b[i]?b[i].id:null;}""", idx)
|
||||
if not btn:
|
||||
return
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", btn)
|
||||
page.wait_for_selector(f"#{E.FP}DeleteWindowDialog_LinkButtonYes", state="visible", timeout=5000)
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", f"{E.FP}DeleteWindowDialog_LinkButtonYes")
|
||||
for _ in range(20):
|
||||
page.wait_for_timeout(200)
|
||||
if E.window_row_count(page) < before:
|
||||
break
|
||||
|
||||
|
||||
def ventilation(page):
|
||||
E.goto(page, "VentilationAndCooling", "WebFormVentilationAndCooling.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelVentilationPanel")
|
||||
E.set_text(page, f"{VP}TextBoxIntermittentFans", "0")
|
||||
cool = page.locator(f"#{E.FP}{VP}CheckBoxFixedSpaceCooling")
|
||||
if cool.count() and cool.is_checked():
|
||||
E.commit(page, cool.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelMechVent")
|
||||
mv = page.locator(f"#{E.FP}{MV}CheckBoxMechanicalVentilation")
|
||||
if mv.count() and mv.is_checked():
|
||||
E.commit(page, mv.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelAirPressureTest")
|
||||
E.set_select(page, f"{APT}DropDownListTestMethod", "Not available")
|
||||
E.click_tab(page, "TabContainer_TabPanelLighting")
|
||||
E.set_text(page, f"{LP}TextBoxLightsTotal", "6")
|
||||
E.set_text(page, f"{LP}TextBoxLedLightsTotal", "0")
|
||||
E.set_text(page, f"{LP}TextBoxCflLightsTotal", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def space_heating(page):
|
||||
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(1000)
|
||||
mhc = page.locator(f"#{E.MH1}TextBoxMainHeatingCode")
|
||||
code = mhc.input_value() if mhc.count() else ""
|
||||
if code and code not in ("0",):
|
||||
print(f"clearing leftover MainHeatingCode {code}")
|
||||
page.evaluate("""(id)=>{const e=document.getElementById(id);if(e){e.value='';
|
||||
e.dispatchEvent(new Event('change',{bubbles:true}));}}""", f"{E.MH1}TextBoxMainHeatingCode")
|
||||
page.wait_for_timeout(400)
|
||||
E.save_close(page)
|
||||
return
|
||||
# Generic Post-1998 condensing combi (~89% SAP 4b, ≈ PCDB 16137 Ideal Logic
|
||||
# combi 30). combi vs regular is set on the water page (from primary + no
|
||||
# cylinder). Control 2106 = CBE (programmer, room thermostat and TRVs).
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingCode",
|
||||
"^Gas", "Mains gas", "Boilers", "Post 1998", "Condensing",
|
||||
"Combi condens")
|
||||
print("code:", page.locator(f"#{E.MH1}TextBoxMainHeatingCode").input_value())
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingControls",
|
||||
"^Boilers", "^Standard", "CBE Programmer, room thermostat and TRVs")
|
||||
print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value())
|
||||
E.set_select(page, "DropDownListSecondaryHeatingPresent", "No")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def water_heating(page):
|
||||
E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWaterHeating")
|
||||
page.wait_for_timeout(400)
|
||||
E.clear_hot_water_cylinder(page)
|
||||
E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode",
|
||||
"From Space Heating", "From the primary heating system")
|
||||
print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value())
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def download(page):
|
||||
_SAMPLE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
print("navigating via Recommendations ...")
|
||||
with page.expect_navigation(wait_until="networkidle", timeout=60_000):
|
||||
page.click("#ContentBody_buttonActionRecommendations_Link", timeout=15_000)
|
||||
page.wait_for_timeout(1500)
|
||||
for btn, fname in (("ContentBody_ContentPlaceHolder1_LinkButtonSummary", "elmhurst_summary.pdf"),
|
||||
("ContentBody_ContentPlaceHolder1_ButtonDebugInforPdf", "elmhurst_worksheet.pdf")):
|
||||
try:
|
||||
with page.expect_download(timeout=60_000) as dl:
|
||||
page.click(f"#{btn}", timeout=15_000)
|
||||
dl.value.save_as(str(_SAMPLE_DIR / fname))
|
||||
print("saved", fname)
|
||||
except Exception as e:
|
||||
print("!! download failed", fname, type(e).__name__, str(e)[:120])
|
||||
|
||||
|
||||
_ORDER = ["property_description", "dimensions", "walls", "roofs", "floors",
|
||||
"openings", "ventilation", "space_heating", "water_heating", "download"]
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in _ORDER:
|
||||
print("usage: build_200004296092.py <" + "|".join(_ORDER) + ">")
|
||||
return 2
|
||||
with E.session() as (ctx, page):
|
||||
globals()[sys.argv[1]](page)
|
||||
print("done:", sys.argv[1], "->", page.url)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
287
scripts/hyde/build_217091901.py
Normal file
287
scripts/hyde/build_217091901.py
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
"""Elmhurst build for UPRN 217091901 (RdSAP-21.0.1, TOP-FLOOR FLAT, band A,
|
||||
SOLID BRICK 280 mm UNINSULATED, FLAT ROOF no insulation (As Built), floor over
|
||||
ANOTHER DWELLING below (zero loss), mains-gas COMBI (PCDB 18658), 6 DG windows
|
||||
~8.62 m², no doors, TFA 63. Lodged SAP 62; engine 60.82 (Δ −1.18, PE +8.7).
|
||||
|
||||
Per-cert worksheet validation. The roof dominates loss (145.6 W/K at flat-default
|
||||
U 2.30); this build confirms the accredited uninsulated flat-roof + solid-brick
|
||||
wall U against the engine. Run:
|
||||
DISPLAY=:99 python scripts/hyde/build_217091901.py <page>
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import elmhurst_lib as E
|
||||
|
||||
DIM = "TabContainer_TabPanelMain_WebUserControlDimensionsMain_"
|
||||
WALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelExternalWallMain_WebUserControlWallMain_")
|
||||
ROOF = "TabContainer_TabPanelMain_WebUserControlRoofMain_"
|
||||
FLOOR = "TabContainer_TabPanelMain_WebUserControlFloorsMain_"
|
||||
WP = "TabContainer_TabPanelWindowsPanel_"
|
||||
DP = "TabContainer_TabPanelDoorsPanel_"
|
||||
VP = "TabContainer_TabPanelVentilationPanel_"
|
||||
APT = "TabContainer_TabPanelAirPressureTest_"
|
||||
LP = "TabContainer_TabPanelLighting_"
|
||||
MV = "TabContainer_TabPanelMechVent_"
|
||||
WH = "TabContainer_TabPanelWaterHeating_"
|
||||
MH1B = "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_"
|
||||
_SAMPLE_DIR = (Path(__file__).parent.parent.parent
|
||||
/ "backend/epc_api/json_samples/real_life_examples"
|
||||
/ "RdSAP-Schema-21.0.1/uprn_217091901")
|
||||
|
||||
|
||||
def _pick(page, suffix, contains):
|
||||
val = page.evaluate(
|
||||
"""(a)=>{const s=document.getElementById(a[0]);if(!s)return null;
|
||||
for(const o of s.options){if(o.text.toLowerCase().includes(a[1].toLowerCase()))return o.value;}return null;}""",
|
||||
[f"{E.FP}{suffix}", contains])
|
||||
if val is not None:
|
||||
E.set_select(page, suffix, val)
|
||||
return val
|
||||
|
||||
|
||||
def _options(page, suffix):
|
||||
return page.evaluate(
|
||||
"""(id)=>{const s=document.getElementById(id);if(!s)return [];
|
||||
return Array.from(s.options).map(o=>o.text);}""", f"{E.FP}{suffix}")
|
||||
|
||||
|
||||
def property_description(page):
|
||||
E.goto(page, "PropertyDescription", "WebFormPropertyDescription.aspx")
|
||||
_pick(page, "DropDownListPropertyType1", "flat") or \
|
||||
E.set_select(page, "DropDownListPropertyType1", "F Flat")
|
||||
E.set_text(page, "TextBoxStoreys", "1")
|
||||
E.set_text(page, "TextBoxHabitableRooms", "3")
|
||||
E.set_text(page, "TextBoxHeatedHabitableRooms", "3")
|
||||
print("date ->", _pick(page, "DropDownListDateBuiltMain", "before 1900"))
|
||||
E.set_select(page, "DropDownListDateBuiltFirst", "")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def flats(page):
|
||||
E.goto(page, "Flats", "WebFormFlats.aspx")
|
||||
print("flat positions:", _options(page, "" if False else "DropDownListPositionOfFlat"))
|
||||
_pick(page, "DropDownListPositionOfFlat", "top") or \
|
||||
E.set_select(page, "DropDownListPositionOfFlat", "Top Floor")
|
||||
E.set_text(page, "TextBoxFloor", "3")
|
||||
E.set_select(page, "RadioButtonListFlatCoridor", "None")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def dimensions(page):
|
||||
E.goto(page, "Dimensions", "WebFormDimensions.aspx")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorAreaLowestFloor", "63.29")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.85")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "14.0")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "22.8")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def walls(page):
|
||||
E.goto(page, "Walls", "WebFormWalls.aspx")
|
||||
_pick(page, f"{WALL}DropDownListType", "solid brick")
|
||||
page.wait_for_timeout(500)
|
||||
print("insulation opts:", _options(page, f"{WALL}DropDownListInsulation"))
|
||||
_pick(page, f"{WALL}DropDownListInsulation", "as built") or \
|
||||
_pick(page, f"{WALL}DropDownListInsulation", "none")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def roofs(page):
|
||||
E.goto(page, "Roofs", "WebFormRoofs.aspx")
|
||||
print("ROOF TYPE opts:", _options(page, f"{ROOF}DropDownListType"))
|
||||
_pick(page, f"{ROOF}DropDownListType", "flat")
|
||||
page.wait_for_timeout(600)
|
||||
_pick(page, f"{ROOF}DropDownListInsulationAt", "flat roof insulation")
|
||||
page.wait_for_timeout(600)
|
||||
# discover the thickness/additional field that appears for flat-roof insulation
|
||||
for suf in (f"{ROOF}DropDownListThickness", f"{ROOF}DropDownListInsulation",
|
||||
f"{ROOF}DropDownListAdditionalInsulation"):
|
||||
if page.locator(f"#{E.FP}{suf}").count():
|
||||
print(f" {suf.split('_')[-1]} opts:", _options(page, suf))
|
||||
_pick(page, suf, "as built") or _pick(page, suf, "unknown") or \
|
||||
_pick(page, suf, "none") or _pick(page, suf, "nil")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def floors(page):
|
||||
E.goto(page, "Floors", "WebFormFloors.aspx")
|
||||
print("FLOOR LOCATION opts:", _options(page, f"{FLOOR}DropDownListLocation"))
|
||||
# "(other premises below)" = above a partially-heated space
|
||||
_pick(page, f"{FLOOR}DropDownListLocation", "another dwelling below")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def openings(page):
|
||||
E.goto(page, "Openings", "WebFormOpenings.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWindowsPanel")
|
||||
E.set_single_window(page, 8.62, "North", _glazing(page))
|
||||
_delete_zero_rows(page)
|
||||
E.click_tab(page, "TabContainer_TabPanelDoorsPanel")
|
||||
E.set_text(page, f"{DP}TextBoxDoors", "0")
|
||||
E.set_text(page, f"{DP}TextBoxDoorsInsulated", "0")
|
||||
E.set_text(page, f"{DP}TextBoxDraughtProofedDoors", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def _glazing(page):
|
||||
# Cert glazed_type=5 → SINGLE glazing (epc_codes.csv, RdSAP-Schema-21).
|
||||
for opt in _options(page, f"{WP}DropDownListExtGlazing"):
|
||||
if "single" in opt.lower():
|
||||
return opt
|
||||
return "Single glazed"
|
||||
|
||||
|
||||
def _add_window(page, area, orientation, glazing):
|
||||
print("glazing ->", glazing)
|
||||
E.set_select(page, f"{WP}DropDownListExtGlazing", glazing)
|
||||
page.wait_for_timeout(400)
|
||||
ft = page.locator(f"#{E.FP}{WP}DropDownListExtFrameType")
|
||||
if ft.count():
|
||||
ft.select_option("PVC")
|
||||
gg = page.locator(f"#{E.FP}{WP}DropDownListExtGlazingGap")
|
||||
if gg.count():
|
||||
gg.select_option("12 mm")
|
||||
wid = f"{E.FP}{WP}TextBoxExtWidth"
|
||||
page.evaluate(
|
||||
"""(a)=>{const e=document.getElementById(a[0]);if(e){e.value=a[1];
|
||||
e.dispatchEvent(new Event('input',{bubbles:true}));
|
||||
e.dispatchEvent(new Event('change',{bubbles:true}));
|
||||
e.dispatchEvent(new Event('blur',{bubbles:true}));}}""", [wid, str(area)])
|
||||
page.locator(f"#{E.FP}{WP}TextBoxExtHeight").fill("1.00")
|
||||
page.locator(f"#{E.FP}{WP}DropDownListExtOrientation").select_option(orientation)
|
||||
page.locator(f"#{E.FP}{WP}DropDownListExtBuildingPartId").select_option("Main")
|
||||
page.locator(f"#{E.FP}{WP}DropDownListExtLocation").select_option("External wall")
|
||||
page.wait_for_timeout(300)
|
||||
before = E.window_row_count(page)
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", f"{E.FP}{WP}ButtonAddWindow")
|
||||
for _ in range(25):
|
||||
page.wait_for_timeout(200)
|
||||
if E.window_row_count(page) > before:
|
||||
break
|
||||
|
||||
|
||||
def _grid_rows(page):
|
||||
return page.evaluate(
|
||||
"""()=>{const t=document.querySelector("[id*=GridViewExtendedWidows]");
|
||||
if(!t)return[];return Array.from(t.querySelectorAll('tr')).slice(1)
|
||||
.map(r=>Array.from(r.querySelectorAll('td')).map(c=>c.innerText.trim()));}""")
|
||||
|
||||
|
||||
def _delete_zero_rows(page):
|
||||
g = 0
|
||||
while g < 6 and E.window_row_count(page) > 1:
|
||||
g += 1
|
||||
rows = _grid_rows(page)
|
||||
bad = next((i for i, c in enumerate(rows) if len(c) > 1 and c[1] in ("0.00", "0", "0.0")), None)
|
||||
if bad is None:
|
||||
break
|
||||
_delete_row(page, bad)
|
||||
page.wait_for_timeout(400)
|
||||
|
||||
|
||||
def _delete_row(page, idx):
|
||||
before = E.window_row_count(page)
|
||||
btn = page.evaluate(
|
||||
"""(i)=>{const b=document.querySelectorAll("[id*='GridViewExtendedWidows_DeleteButton_']");return b[i]?b[i].id:null;}""", idx)
|
||||
if not btn:
|
||||
return
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", btn)
|
||||
page.wait_for_selector(f"#{E.FP}DeleteWindowDialog_LinkButtonYes", state="visible", timeout=5000)
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", f"{E.FP}DeleteWindowDialog_LinkButtonYes")
|
||||
for _ in range(20):
|
||||
page.wait_for_timeout(200)
|
||||
if E.window_row_count(page) < before:
|
||||
break
|
||||
|
||||
|
||||
def ventilation(page):
|
||||
E.goto(page, "VentilationAndCooling", "WebFormVentilationAndCooling.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelVentilationPanel")
|
||||
E.set_text(page, f"{VP}TextBoxIntermittentFans", "0")
|
||||
cool = page.locator(f"#{E.FP}{VP}CheckBoxFixedSpaceCooling")
|
||||
if cool.count() and cool.is_checked():
|
||||
E.commit(page, cool.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelMechVent")
|
||||
mv = page.locator(f"#{E.FP}{MV}CheckBoxMechanicalVentilation")
|
||||
if mv.count() and mv.is_checked():
|
||||
E.commit(page, mv.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelAirPressureTest")
|
||||
E.set_select(page, f"{APT}DropDownListTestMethod", "Not available")
|
||||
E.click_tab(page, "TabContainer_TabPanelLighting")
|
||||
E.set_text(page, f"{LP}TextBoxLightsTotal", "6")
|
||||
E.set_text(page, f"{LP}TextBoxLedLightsTotal", "0")
|
||||
E.set_text(page, f"{LP}TextBoxCflLightsTotal", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def space_heating(page):
|
||||
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(1000)
|
||||
mhc = page.locator(f"#{E.MH1}TextBoxMainHeatingCode")
|
||||
code = mhc.input_value() if mhc.count() else ""
|
||||
if code and code not in ("0",):
|
||||
print(f"clearing leftover MainHeatingCode {code}")
|
||||
page.evaluate("""(id)=>{const e=document.getElementById(id);if(e){e.value='';
|
||||
e.dispatchEvent(new Event('change',{bubbles:true}));}}""", f"{E.MH1}TextBoxMainHeatingCode")
|
||||
page.wait_for_timeout(400)
|
||||
E.save_close(page)
|
||||
return
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingCode",
|
||||
"^Gas", "Mains gas", "Boilers", "Post 1998", "Condensing",
|
||||
"Combi condens")
|
||||
print("code:", page.locator(f"#{E.MH1}TextBoxMainHeatingCode").input_value())
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingControls",
|
||||
"^Boilers", "^Standard", "CBE Programmer, room thermostat and TRVs")
|
||||
print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value())
|
||||
E.set_select(page, "DropDownListSecondaryHeatingPresent", "No")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def water_heating(page):
|
||||
E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWaterHeating")
|
||||
page.wait_for_timeout(400)
|
||||
E.clear_hot_water_cylinder(page)
|
||||
E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode",
|
||||
"From Space Heating", "From the primary heating system")
|
||||
print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value())
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def download(page):
|
||||
_SAMPLE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
E.goto(page, "Recommendations", "WebFormRecommendations.aspx")
|
||||
page.wait_for_timeout(1500)
|
||||
with page.expect_navigation(wait_until="networkidle", timeout=40000):
|
||||
page.click("#ContentBody_buttonActionSummary_Link", timeout=15000)
|
||||
page.wait_for_timeout(2000)
|
||||
for btn, fname in (("ContentBody_ContentPlaceHolder1_LinkButtonSummary", "elmhurst_summary.pdf"),
|
||||
("ContentBody_ContentPlaceHolder1_ButtonDebugInforPdf", "elmhurst_worksheet.pdf")):
|
||||
try:
|
||||
with page.expect_download(timeout=60000) as dl:
|
||||
page.click(f"#{btn}", timeout=15000)
|
||||
dl.value.save_as(str(_SAMPLE_DIR / fname))
|
||||
print("saved", fname)
|
||||
except Exception as e:
|
||||
print("!! download failed", fname, type(e).__name__, str(e)[:120])
|
||||
|
||||
|
||||
_ORDER = ["property_description", "flats", "dimensions", "walls", "roofs",
|
||||
"floors", "openings", "ventilation", "space_heating", "water_heating",
|
||||
"download"]
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in _ORDER:
|
||||
print("usage: build_217091901.py <" + "|".join(_ORDER) + ">")
|
||||
return 2
|
||||
with E.session() as (ctx, page):
|
||||
globals()[sys.argv[1]](page)
|
||||
print("done:", sys.argv[1], "->", page.url)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
299
scripts/hyde/build_47084930.py
Normal file
299
scripts/hyde/build_47084930.py
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
"""Elmhurst build for UPRN 47084930 (RdSAP-21.0.1, TOP-FLOOR FLAT, band E,
|
||||
CAVITY wall with INTERNAL insulation (100 mm), FLAT ROOF insulated 75 mm, floor
|
||||
over OTHER PREMISES below, mains-gas COMBI (PCDB 17815), 5 DG windows ~9.9 m²,
|
||||
no doors, TFA 64. Lodged SAP 74; engine 69.51 (Δ −4.49, PE +47 over-count).
|
||||
|
||||
Per-cert worksheet validation. Suspected engine gap: the flat-roof insulation
|
||||
thickness is lodged in `flat_roof_insulation_thickness` (75 mm) but the U-value
|
||||
calc reads `roof_insulation_thickness` (None) → bills the roof at the age-E flat
|
||||
default 1.5 (96 W/K) instead of the 75 mm insulated ~0.5 (~30 W/K). This build
|
||||
gets the accredited flat-roof + floor U. Run:
|
||||
DISPLAY=:99 python scripts/hyde/build_47084930.py <page>
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import elmhurst_lib as E
|
||||
|
||||
DIM = "TabContainer_TabPanelMain_WebUserControlDimensionsMain_"
|
||||
WALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelExternalWallMain_WebUserControlWallMain_")
|
||||
ROOF = "TabContainer_TabPanelMain_WebUserControlRoofMain_"
|
||||
FLOOR = "TabContainer_TabPanelMain_WebUserControlFloorsMain_"
|
||||
WP = "TabContainer_TabPanelWindowsPanel_"
|
||||
DP = "TabContainer_TabPanelDoorsPanel_"
|
||||
VP = "TabContainer_TabPanelVentilationPanel_"
|
||||
APT = "TabContainer_TabPanelAirPressureTest_"
|
||||
LP = "TabContainer_TabPanelLighting_"
|
||||
MV = "TabContainer_TabPanelMechVent_"
|
||||
WH = "TabContainer_TabPanelWaterHeating_"
|
||||
MH1B = "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_"
|
||||
_SAMPLE_DIR = (Path(__file__).parent.parent.parent
|
||||
/ "backend/epc_api/json_samples/real_life_examples"
|
||||
/ "RdSAP-Schema-21.0.1/uprn_47084930")
|
||||
|
||||
|
||||
def _pick(page, suffix, contains):
|
||||
val = page.evaluate(
|
||||
"""(a)=>{const s=document.getElementById(a[0]);if(!s)return null;
|
||||
for(const o of s.options){if(o.text.toLowerCase().includes(a[1].toLowerCase()))return o.value;}return null;}""",
|
||||
[f"{E.FP}{suffix}", contains])
|
||||
if val is not None:
|
||||
E.set_select(page, suffix, val)
|
||||
return val
|
||||
|
||||
|
||||
def _options(page, suffix):
|
||||
return page.evaluate(
|
||||
"""(id)=>{const s=document.getElementById(id);if(!s)return [];
|
||||
return Array.from(s.options).map(o=>o.text);}""", f"{E.FP}{suffix}")
|
||||
|
||||
|
||||
def property_description(page):
|
||||
E.goto(page, "PropertyDescription", "WebFormPropertyDescription.aspx")
|
||||
_pick(page, "DropDownListPropertyType1", "flat") or \
|
||||
E.set_select(page, "DropDownListPropertyType1", "F Flat")
|
||||
E.set_text(page, "TextBoxStoreys", "1")
|
||||
E.set_text(page, "TextBoxHabitableRooms", "3")
|
||||
E.set_text(page, "TextBoxHeatedHabitableRooms", "3")
|
||||
print("date ->", _pick(page, "DropDownListDateBuiltMain", "1967-1975"))
|
||||
E.set_select(page, "DropDownListDateBuiltFirst", "")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def flats(page):
|
||||
E.goto(page, "Flats", "WebFormFlats.aspx")
|
||||
print("flat positions:", _options(page, "" if False else "DropDownListPositionOfFlat"))
|
||||
_pick(page, "DropDownListPositionOfFlat", "top") or \
|
||||
E.set_select(page, "DropDownListPositionOfFlat", "Top Floor")
|
||||
E.set_text(page, "TextBoxFloor", "3")
|
||||
E.set_select(page, "RadioButtonListFlatCoridor", "None")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def dimensions(page):
|
||||
E.goto(page, "Dimensions", "WebFormDimensions.aspx")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorAreaLowestFloor", "64.26")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.32")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "18.15")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "18.15")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def walls(page):
|
||||
E.goto(page, "Walls", "WebFormWalls.aspx")
|
||||
E.set_select(page, f"{WALL}DropDownListType", "CA Cavity")
|
||||
page.wait_for_timeout(500)
|
||||
print("insulation opts:", _options(page, f"{WALL}DropDownListInsulation"))
|
||||
_pick(page, f"{WALL}DropDownListInsulation", "internal")
|
||||
page.wait_for_timeout(500)
|
||||
for suf in (f"{WALL}DropDownListInsulationThickness",):
|
||||
if page.locator(f"#{E.FP}{suf}").count():
|
||||
print("wall ins thickness opts:", _options(page, suf))
|
||||
_pick(page, suf, "100")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def roofs(page):
|
||||
E.goto(page, "Roofs", "WebFormRoofs.aspx")
|
||||
print("ROOF TYPE opts:", _options(page, f"{ROOF}DropDownListType"))
|
||||
_pick(page, f"{ROOF}DropDownListType", "flat")
|
||||
page.wait_for_timeout(600)
|
||||
# discover flat-roof insulation fields
|
||||
for suf in (f"{ROOF}DropDownListInsulationAt", f"{ROOF}DropDownListThickness",
|
||||
f"{ROOF}DropDownListInsulation"):
|
||||
if page.locator(f"#{E.FP}{suf}").count():
|
||||
print(f" {suf.split('_')[-1]} opts:", _options(page, suf))
|
||||
# flat roof insulated 75 mm
|
||||
if page.locator(f"#{E.FP}{ROOF}DropDownListThickness").count():
|
||||
_pick(page, f"{ROOF}DropDownListThickness", "75")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def floors(page):
|
||||
E.goto(page, "Floors", "WebFormFloors.aspx")
|
||||
print("FLOOR LOCATION opts:", _options(page, f"{FLOOR}DropDownListLocation"))
|
||||
# "(other premises below)" = above a partially-heated space
|
||||
(_pick(page, f"{FLOOR}DropDownListLocation", "partially heated")
|
||||
or _pick(page, f"{FLOOR}DropDownListLocation", "other premises")
|
||||
or _pick(page, f"{FLOOR}DropDownListLocation", "unheated"))
|
||||
page.wait_for_timeout(400)
|
||||
tsel = page.locator(f"#{E.FP}{FLOOR}DropDownListType")
|
||||
if tsel.count():
|
||||
_pick(page, f"{FLOOR}DropDownListType", "solid")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def openings(page):
|
||||
E.goto(page, "Openings", "WebFormOpenings.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWindowsPanel")
|
||||
_add_window(page, 9.9, "North", _glazing(page))
|
||||
_delete_zero_rows(page)
|
||||
E.click_tab(page, "TabContainer_TabPanelDoorsPanel")
|
||||
E.set_text(page, f"{DP}TextBoxDoors", "0")
|
||||
E.set_text(page, f"{DP}TextBoxDoorsInsulated", "0")
|
||||
E.set_text(page, f"{DP}TextBoxDraughtProofedDoors", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def _glazing(page):
|
||||
for needle in ("unknown install date", "before 2002", "pre 2002"):
|
||||
for opt in _options(page, f"{WP}DropDownListExtGlazing"):
|
||||
low = opt.lower()
|
||||
if needle in low and "triple" not in low and "single" not in low and "known data" not in low:
|
||||
return opt
|
||||
return "Double post or during 2022"
|
||||
|
||||
|
||||
def _add_window(page, area, orientation, glazing):
|
||||
print("glazing ->", glazing)
|
||||
E.set_select(page, f"{WP}DropDownListExtGlazing", glazing)
|
||||
page.wait_for_timeout(400)
|
||||
ft = page.locator(f"#{E.FP}{WP}DropDownListExtFrameType")
|
||||
if ft.count():
|
||||
ft.select_option("PVC")
|
||||
gg = page.locator(f"#{E.FP}{WP}DropDownListExtGlazingGap")
|
||||
if gg.count():
|
||||
gg.select_option("12 mm")
|
||||
wid = f"{E.FP}{WP}TextBoxExtWidth"
|
||||
page.evaluate(
|
||||
"""(a)=>{const e=document.getElementById(a[0]);if(e){e.value=a[1];
|
||||
e.dispatchEvent(new Event('input',{bubbles:true}));
|
||||
e.dispatchEvent(new Event('change',{bubbles:true}));
|
||||
e.dispatchEvent(new Event('blur',{bubbles:true}));}}""", [wid, str(area)])
|
||||
page.locator(f"#{E.FP}{WP}TextBoxExtHeight").fill("1.00")
|
||||
page.locator(f"#{E.FP}{WP}DropDownListExtOrientation").select_option(orientation)
|
||||
page.locator(f"#{E.FP}{WP}DropDownListExtBuildingPartId").select_option("Main")
|
||||
page.locator(f"#{E.FP}{WP}DropDownListExtLocation").select_option("External wall")
|
||||
page.wait_for_timeout(300)
|
||||
before = E.window_row_count(page)
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", f"{E.FP}{WP}ButtonAddWindow")
|
||||
for _ in range(25):
|
||||
page.wait_for_timeout(200)
|
||||
if E.window_row_count(page) > before:
|
||||
break
|
||||
|
||||
|
||||
def _grid_rows(page):
|
||||
return page.evaluate(
|
||||
"""()=>{const t=document.querySelector("[id*=GridViewExtendedWidows]");
|
||||
if(!t)return[];return Array.from(t.querySelectorAll('tr')).slice(1)
|
||||
.map(r=>Array.from(r.querySelectorAll('td')).map(c=>c.innerText.trim()));}""")
|
||||
|
||||
|
||||
def _delete_zero_rows(page):
|
||||
g = 0
|
||||
while g < 6 and E.window_row_count(page) > 1:
|
||||
g += 1
|
||||
rows = _grid_rows(page)
|
||||
bad = next((i for i, c in enumerate(rows) if len(c) > 1 and c[1] in ("0.00", "0", "0.0")), None)
|
||||
if bad is None:
|
||||
break
|
||||
_delete_row(page, bad)
|
||||
page.wait_for_timeout(400)
|
||||
|
||||
|
||||
def _delete_row(page, idx):
|
||||
before = E.window_row_count(page)
|
||||
btn = page.evaluate(
|
||||
"""(i)=>{const b=document.querySelectorAll("[id*='GridViewExtendedWidows_DeleteButton_']");return b[i]?b[i].id:null;}""", idx)
|
||||
if not btn:
|
||||
return
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", btn)
|
||||
page.wait_for_selector(f"#{E.FP}DeleteWindowDialog_LinkButtonYes", state="visible", timeout=5000)
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", f"{E.FP}DeleteWindowDialog_LinkButtonYes")
|
||||
for _ in range(20):
|
||||
page.wait_for_timeout(200)
|
||||
if E.window_row_count(page) < before:
|
||||
break
|
||||
|
||||
|
||||
def ventilation(page):
|
||||
E.goto(page, "VentilationAndCooling", "WebFormVentilationAndCooling.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelVentilationPanel")
|
||||
E.set_text(page, f"{VP}TextBoxIntermittentFans", "0")
|
||||
cool = page.locator(f"#{E.FP}{VP}CheckBoxFixedSpaceCooling")
|
||||
if cool.count() and cool.is_checked():
|
||||
E.commit(page, cool.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelMechVent")
|
||||
mv = page.locator(f"#{E.FP}{MV}CheckBoxMechanicalVentilation")
|
||||
if mv.count() and mv.is_checked():
|
||||
E.commit(page, mv.uncheck)
|
||||
E.click_tab(page, "TabContainer_TabPanelAirPressureTest")
|
||||
E.set_select(page, f"{APT}DropDownListTestMethod", "Not available")
|
||||
E.click_tab(page, "TabContainer_TabPanelLighting")
|
||||
E.set_text(page, f"{LP}TextBoxLightsTotal", "6")
|
||||
E.set_text(page, f"{LP}TextBoxLedLightsTotal", "0")
|
||||
E.set_text(page, f"{LP}TextBoxCflLightsTotal", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def space_heating(page):
|
||||
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(1000)
|
||||
mhc = page.locator(f"#{E.MH1}TextBoxMainHeatingCode")
|
||||
code = mhc.input_value() if mhc.count() else ""
|
||||
if code and code not in ("0",):
|
||||
print(f"clearing leftover MainHeatingCode {code}")
|
||||
page.evaluate("""(id)=>{const e=document.getElementById(id);if(e){e.value='';
|
||||
e.dispatchEvent(new Event('change',{bubbles:true}));}}""", f"{E.MH1}TextBoxMainHeatingCode")
|
||||
page.wait_for_timeout(400)
|
||||
E.save_close(page)
|
||||
return
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingCode",
|
||||
"^Gas", "Mains gas", "Boilers", "Post 1998", "Condensing",
|
||||
"Combi condens")
|
||||
print("code:", page.locator(f"#{E.MH1}TextBoxMainHeatingCode").input_value())
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingControls",
|
||||
"^Boilers", "^Standard", "CBE Programmer, room thermostat and TRVs")
|
||||
print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value())
|
||||
E.set_select(page, "DropDownListSecondaryHeatingPresent", "No")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def water_heating(page):
|
||||
E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWaterHeating")
|
||||
page.wait_for_timeout(400)
|
||||
E.clear_hot_water_cylinder(page)
|
||||
E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode",
|
||||
"From Space Heating", "From the primary heating system")
|
||||
print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value())
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def download(page):
|
||||
_SAMPLE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
E.goto(page, "Recommendations", "WebFormRecommendations.aspx")
|
||||
page.wait_for_timeout(1500)
|
||||
with page.expect_navigation(wait_until="networkidle", timeout=40000):
|
||||
page.click("#ContentBody_buttonActionSummary_Link", timeout=15000)
|
||||
page.wait_for_timeout(2000)
|
||||
for btn, fname in (("ContentBody_ContentPlaceHolder1_LinkButtonSummary", "elmhurst_summary.pdf"),
|
||||
("ContentBody_ContentPlaceHolder1_ButtonDebugInforPdf", "elmhurst_worksheet.pdf")):
|
||||
try:
|
||||
with page.expect_download(timeout=60000) as dl:
|
||||
page.click(f"#{btn}", timeout=15000)
|
||||
dl.value.save_as(str(_SAMPLE_DIR / fname))
|
||||
print("saved", fname)
|
||||
except Exception as e:
|
||||
print("!! download failed", fname, type(e).__name__, str(e)[:120])
|
||||
|
||||
|
||||
_ORDER = ["property_description", "flats", "dimensions", "walls", "roofs",
|
||||
"floors", "openings", "ventilation", "space_heating", "water_heating",
|
||||
"download"]
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in _ORDER:
|
||||
print("usage: build_47084930.py <" + "|".join(_ORDER) + ">")
|
||||
return 2
|
||||
with E.session() as (ctx, page):
|
||||
globals()[sys.argv[1]](page)
|
||||
print("done:", sys.argv[1], "->", page.url)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -235,24 +235,129 @@ def add_combined_window(
|
|||
) -> None:
|
||||
"""Add ONE window row of `total_area_m2` (width=area, height=1.0). Sets the
|
||||
required per-row fields (orientation/location/building-part) so the
|
||||
Recommendations validation passes. Caller then deletes the prior rows."""
|
||||
Recommendations validation passes. Caller then deletes the prior rows.
|
||||
|
||||
Glazing MUST be selected first (it AutoPostBacks and re-renders the form);
|
||||
width/height are then set via a Playwright `.fill()` + Tab so the ASP.NET
|
||||
AutoPostBack fires and the server-side Area (w×h) commits — a JS-set of the
|
||||
value bypasses the postback and Elmhurst persists a 0.00-area row.
|
||||
Frame Type + Glazing Gap are REQUIRED by the Recommendations validation
|
||||
gate (else "Frame Type/Glazing Gap must be entered" blocks the worksheet)."""
|
||||
set_select(page, f"{_WP}DropDownListExtGlazing", glazing)
|
||||
page.locator(f"#{FP}{_WP}TextBoxExtWidth").fill(str(total_area_m2))
|
||||
h = page.locator(f"#{FP}{_WP}TextBoxExtHeight")
|
||||
h.fill("1.00")
|
||||
commit(page, lambda: h.press("Tab"))
|
||||
page.locator(f"#{FP}{_WP}DropDownListExtOrientation").select_option(orientation)
|
||||
page.locator(f"#{FP}{_WP}DropDownListExtBuildingPartId").select_option("Main")
|
||||
page.locator(f"#{FP}{_WP}DropDownListExtLocation").select_option("External wall")
|
||||
page.wait_for_timeout(200)
|
||||
page.wait_for_timeout(SETTLE_MS)
|
||||
|
||||
def _fill_tab(suffix: str, value: str) -> None:
|
||||
loc = page.locator(f"#{FP}{suffix}")
|
||||
loc.fill(value)
|
||||
loc.press("Tab")
|
||||
page.wait_for_timeout(1200) # AutoPostBack settle
|
||||
|
||||
_fill_tab(f"{_WP}TextBoxExtWidth", str(total_area_m2))
|
||||
_fill_tab(f"{_WP}TextBoxExtHeight", "1.00")
|
||||
# Frame Type (PVC) + Glazing Gap (12 mm) are validation-required per row.
|
||||
for suffix, opt in (
|
||||
(f"{_WP}DropDownListExtFrameType", "PVC"),
|
||||
(f"{_WP}DropDownListExtGlazingGap", "12 mm"),
|
||||
(f"{_WP}DropDownListExtOrientation", orientation),
|
||||
(f"{_WP}DropDownListExtBuildingPartId", "Main"),
|
||||
(f"{_WP}DropDownListExtLocation", "External wall"),
|
||||
):
|
||||
loc = page.locator(f"#{FP}{suffix}")
|
||||
if loc.count():
|
||||
try:
|
||||
loc.select_option(opt)
|
||||
except Exception:
|
||||
pass
|
||||
page.wait_for_timeout(250)
|
||||
page.wait_for_timeout(300)
|
||||
before = window_row_count(page)
|
||||
_jsclick(page, f"{FP}{_WP}ButtonAddWindow")
|
||||
for _ in range(20):
|
||||
page.wait_for_timeout(200)
|
||||
page.wait_for_timeout(250)
|
||||
if window_row_count(page) > before:
|
||||
break
|
||||
|
||||
|
||||
def _delete_window_at_index(page: Page, i: int) -> bool:
|
||||
"""Delete the window grid row at DOM index `i` via its per-row Delete
|
||||
image button + the Yes modal (JS-clicked to bypass the modal background
|
||||
that intercepts real clicks). Returns True if a delete fired."""
|
||||
bid = (
|
||||
"ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_"
|
||||
f"GridViewExtendedWidows_DeleteButton_{i}"
|
||||
)
|
||||
if not page.evaluate("(id)=>!!document.getElementById(id)", bid):
|
||||
return False
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id);if(e)e.click();}", bid)
|
||||
page.wait_for_timeout(2000)
|
||||
yid = "ContentBody_ContentPlaceHolder1_DeleteWindowDialog_LinkButtonYes"
|
||||
if not page.evaluate(
|
||||
"(id)=>{const e=document.getElementById(id);return e?e.offsetParent!==null:false;}",
|
||||
yid,
|
||||
):
|
||||
return False
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id);if(e)e.click();}", yid)
|
||||
page.wait_for_timeout(2500)
|
||||
return True
|
||||
|
||||
|
||||
def set_single_window(
|
||||
page: Page, total_area_m2: float, orientation: str = "North",
|
||||
glazing: str = "Double post or during 2022",
|
||||
) -> None:
|
||||
"""Leave the window grid holding EXACTLY one correct row of
|
||||
`total_area_m2`, cleaning any rows carried over from a prior build of the
|
||||
SHARED assessment.
|
||||
|
||||
The grid can't be emptied (Elmhurst refuses to delete the last row) and it
|
||||
has no inline edit, so per [[window-grid]] the reliable pattern is: ADD the
|
||||
fresh window (it inserts at DOM index 0), then delete the STALE rows BELOW
|
||||
it (indices high->low, never index 0) so the freshly-added row survives as
|
||||
the single remaining window. Deleting index 0 (delete_first_window) would
|
||||
drop the good row and strand a 0.00-area junk row that then blocks the
|
||||
Recommendations gate."""
|
||||
add_combined_window(page, total_area_m2, orientation, glazing)
|
||||
page.wait_for_timeout(500)
|
||||
guard = 0
|
||||
while window_row_count(page) > 1 and guard < 25:
|
||||
guard += 1
|
||||
i = window_row_count(page) - 1 # bottom-most (oldest) stale row
|
||||
if not _delete_window_at_index(page, i):
|
||||
break
|
||||
|
||||
|
||||
def reset_transient_state(page: Page) -> None:
|
||||
"""Return the shared assessment to a NEUTRAL state after a run so the next
|
||||
build (or Khalim's) is not polluted by this cert's carried-over settings.
|
||||
Clears the room-in-roof age band, forces secondary-heating present = No,
|
||||
and the electricity meter back to Single. The window grid is left to the
|
||||
next build's `set_single_window` (Elmhurst can't empty it to zero rows).
|
||||
Call once at the end of a build session."""
|
||||
goto(page, "PropertyDescription", "WebFormPropertyDescription.aspx")
|
||||
rr = page.locator("#ContentBody_ContentPlaceHolder1_DropDownListRoomInRoofMain")
|
||||
if rr.count() and rr.input_value():
|
||||
rr.select_option("")
|
||||
page.wait_for_timeout(SETTLE_MS)
|
||||
save_close(page)
|
||||
goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(SETTLE_MS)
|
||||
sec = page.locator(f"#{FP}DropDownListSecondaryHeatingPresent")
|
||||
if sec.count():
|
||||
try:
|
||||
set_select(page, "DropDownListSecondaryHeatingPresent", "No")
|
||||
except Exception:
|
||||
pass
|
||||
meters = "TabContainer_TabPanelMeters"
|
||||
click_tab(page, meters)
|
||||
et = page.locator(f"#{FP}{meters}_RadioButtonListElectricityType")
|
||||
if et.count():
|
||||
try:
|
||||
set_select(page, f"{meters}_RadioButtonListElectricityType", "Single")
|
||||
except Exception:
|
||||
pass
|
||||
save_close(page)
|
||||
|
||||
|
||||
def delete_first_window(page: Page) -> None:
|
||||
"""Delete the first window row via the Yes/No modal. Will refuse to drop the
|
||||
LAST row (Elmhurst blocks it) — so add the replacement BEFORE deleting."""
|
||||
|
|
@ -362,6 +467,63 @@ def select_boiler(page: Page, query: str, ref: str) -> str:
|
|||
return "ok"
|
||||
|
||||
|
||||
def clear_main_heating_code(page: Page) -> None:
|
||||
"""Empty Main Heating 1's SAP code via a real server postback (set the
|
||||
cascade's top-level dropdown to '' + OK). REQUIRED before `set_pcdb_boiler`:
|
||||
while any code is set, the PCDF reference textbox and the boiler-search
|
||||
magnifying glass both render `aspNetDisabled`. They only enable when the
|
||||
code is empty server-side (a JS-clear alone flips the class cosmetically but
|
||||
leaves the postback unwired)."""
|
||||
page.evaluate(
|
||||
"(id)=>{const e=document.getElementById(id); if(e)e.click();}",
|
||||
f"{MH1}ButtonMainHeatingCode",
|
||||
)
|
||||
page.wait_for_timeout(SETTLE_MS * 3)
|
||||
page.evaluate(
|
||||
"""(id)=>{const s=document.getElementById(id);if(s){s.value='';
|
||||
s.dispatchEvent(new Event('change',{bubbles:true}));}}""",
|
||||
f"{HEATING_DIALOG}DropDownList1",
|
||||
)
|
||||
page.wait_for_timeout(SETTLE_MS * 3)
|
||||
dialog_commit(page, "OK")
|
||||
page.wait_for_timeout(SETTLE_MS * 2)
|
||||
|
||||
|
||||
def set_pcdb_boiler(page: Page, pcdb_index: "int | str") -> str:
|
||||
"""Set Main Heating 1 to the EXACT PCDB boiler by its EPC
|
||||
`main_heating_index_number` (e.g. 18908 = Worcester Greenstar 4000,
|
||||
GR4700iW 25 C NG, 88.70%). Elmhurst resolves it to the product's PCDB
|
||||
winter efficiency — the same value the SAP engine uses — so the worksheet
|
||||
and our calculator compare like-for-like (not against the generic BGW
|
||||
SAP-Table 84%). Returns the resolved boiler description.
|
||||
|
||||
Hard-won mechanism (supersedes the old "PCDB disabled" note):
|
||||
1. The PCDF reference textbox is only writable while the main heating CODE
|
||||
is empty — call `clear_main_heating_code(page)` first.
|
||||
2. The index must be entered via REAL keyboard + Tab. A JS-set `.value`
|
||||
does NOT survive the field's AutoPostBack, so the boiler never
|
||||
resolves (the original failure mode). Ctrl-A + type + Tab works.
|
||||
3. The `TextBoxPCDFBoilerReference` direct-entry path is far simpler than
|
||||
the magnifying-glass grid (whose modal has no layout box, so
|
||||
mouse/Playwright clicks can't target rows). The grid search DOES work
|
||||
(JS-focus the FullSearch box + `keyboard.type` + fire
|
||||
HyperLinkActionBoilerSearch → "Ref No" column == the PCDB index) if a
|
||||
name lookup is ever needed, but direct entry is preferred."""
|
||||
fld = f"{MH1}TextBoxPCDFBoilerReference"
|
||||
page.evaluate(
|
||||
"(id)=>{const e=document.getElementById(id);if(e){e.focus();e.select();}}", fld
|
||||
)
|
||||
page.wait_for_timeout(SETTLE_MS)
|
||||
page.keyboard.press("Control+a")
|
||||
page.keyboard.type(str(pcdb_index), delay=40)
|
||||
page.keyboard.press("Tab")
|
||||
page.wait_for_timeout(SETTLE_MS * 5) # AutoPostBack resolves the PCDB record
|
||||
return page.evaluate(
|
||||
"""()=>{const e=document.querySelector('[id*=LabelMHSSapCodeDescription]');
|
||||
return e?e.innerText.trim():'';}"""
|
||||
)
|
||||
|
||||
|
||||
def read_default_uvalue(
|
||||
page: Page, suffix: Optional[str] = None, label_regex: str = r"Default U-value"
|
||||
) -> Optional[float]:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from datatypes.epc.surveys.elmhurst_site_notes import (
|
|||
)
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import (
|
||||
EnergyElement,
|
||||
EpcPropertyData,
|
||||
MainHeatingDetail,
|
||||
PhotovoltaicArray,
|
||||
|
|
@ -738,6 +739,30 @@ def test_cylinder_present_but_size_not_determined_defaults_to_normal_110l() -> N
|
|||
assert volume_l is not None and abs(volume_l - 110.0) <= 1e-9
|
||||
|
||||
|
||||
def test_cylinder_size_not_determined_still_incurs_storage_loss() -> None:
|
||||
# Arrange — the size-0 (not determined) cylinder's Table 28 default of
|
||||
# 110 L must flow into the STORAGE-LOSS override too, not just the Table
|
||||
# 13 high-rate fraction. `_cylinder_storage_loss_override` read the
|
||||
# volume via `_cylinder_volume_l_from_code` (None for size 0) and bailed
|
||||
# `if volume_l is None: return None`, dropping the loss entirely — while
|
||||
# the rest of the DHW cascade used `_hot_water_cylinder_volume_l`
|
||||
# (110 L). A present cylinder with no storage loss under-costs the DHW
|
||||
# and over-rates the dwelling (7 corpus certs lodge cylinder_size=0 +
|
||||
# has_cylinder=true). RdSAP 10 §10.5 Table 28: a present-but-unsized
|
||||
# cylinder is a 110 L cylinder and incurs the Table 2 storage loss.
|
||||
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||
_cylinder_storage_loss_override, # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
epc = _cylinder_epc(cylinder_size=0)
|
||||
|
||||
# Act
|
||||
storage_56m = _cylinder_storage_loss_override(epc, None)
|
||||
|
||||
# Assert — a real 110 L cylinder loss series, not the dropped None.
|
||||
assert storage_56m is not None
|
||||
assert sum(storage_56m) > 0.0
|
||||
|
||||
|
||||
def test_cylinder_size_inaccessible_code_5_solid_fuel_boiler_uses_160l() -> None:
|
||||
# Arrange — RdSAP 10 §10.5 Table 28: an "Inaccessible" cylinder (code 5)
|
||||
# heated "from a solid fuel boiler" uses 160 litres.
|
||||
|
|
@ -1867,6 +1892,79 @@ def test_main_floor_u_value_routes_suspended_timber_via_floor_construction_type(
|
|||
assert sealed is False
|
||||
|
||||
|
||||
def test_has_suspended_timber_floor_per_spec_falls_back_to_floors_description() -> None:
|
||||
"""RdSAP 10 §5 (12) reads the Main bp's `floor_construction_type`
|
||||
lodgement, but the gov-API mapper often leaves that field `None`
|
||||
while the global `epc.floors[].description` still carries the
|
||||
assessor's "Suspended, ..." observation (cert 100061275133: Main bp
|
||||
`floor_construction_type=None`, `epc.floors[0].description=
|
||||
"Suspended, no insulation (assumed)"`). `_main_floor_u_value` already
|
||||
falls back to this joined description (`effective_floor_description`);
|
||||
the (12) infiltration rule must apply the same fallback so a
|
||||
suspended-timber ground floor doesn't silently enter (12)=0."""
|
||||
from dataclasses import replace
|
||||
# Arrange
|
||||
main = replace(
|
||||
make_building_part(
|
||||
construction_age_band="B",
|
||||
floor_dimensions=[make_floor_dimension(floor=0)],
|
||||
),
|
||||
floor_type="Ground floor",
|
||||
floor_construction_type=None,
|
||||
)
|
||||
epc = make_minimal_sap10_epc(sap_building_parts=[main])
|
||||
epc.floors = [
|
||||
EnergyElement(
|
||||
description="Suspended, no insulation (assumed)",
|
||||
energy_efficiency_rating=0,
|
||||
environmental_efficiency_rating=0,
|
||||
),
|
||||
]
|
||||
|
||||
# Act
|
||||
has_susp, sealed = _has_suspended_timber_floor_per_spec(epc)
|
||||
|
||||
# Assert — description signal routes to the suspended-timber branch;
|
||||
# band B is A-E with no supplied U-value and no retrofit insulation
|
||||
# signal, so rule (b) applies "unsealed" → (12) = 0.2.
|
||||
assert has_susp is True
|
||||
assert sealed is False
|
||||
|
||||
|
||||
def test_has_suspended_timber_floor_per_spec_excludes_suspended_not_timber() -> None:
|
||||
""""Suspended, not timber" (e.g. suspended concrete) starts with the
|
||||
same "Suspended" prefix as timber floors but must NOT trigger the
|
||||
§5 (12) rule — the rule is specifically about air infiltration
|
||||
through timber floorboard gaps (see the API mapper's
|
||||
`_API_FLOOR_CONSTRUCTION_TO_STR` comment: "only 'Suspended timber'
|
||||
triggers the §5 (12)... adjustment")."""
|
||||
from dataclasses import replace
|
||||
# Arrange
|
||||
main = replace(
|
||||
make_building_part(
|
||||
construction_age_band="B",
|
||||
floor_dimensions=[make_floor_dimension(floor=0)],
|
||||
),
|
||||
floor_type="Ground floor",
|
||||
floor_construction_type=None,
|
||||
)
|
||||
epc = make_minimal_sap10_epc(sap_building_parts=[main])
|
||||
epc.floors = [
|
||||
EnergyElement(
|
||||
description="Suspended, not timber",
|
||||
energy_efficiency_rating=0,
|
||||
environmental_efficiency_rating=0,
|
||||
),
|
||||
]
|
||||
|
||||
# Act
|
||||
has_susp, sealed = _has_suspended_timber_floor_per_spec(epc)
|
||||
|
||||
# Assert
|
||||
assert has_susp is False
|
||||
assert sealed is False
|
||||
|
||||
|
||||
def test_rdsap_extract_fans_default_per_table_5() -> None:
|
||||
# Arrange — RdSAP 10 §4.1 Table 5 (PDF p.28) "Extract fans" default
|
||||
# when the lodged number is unknown. The Summary §12.0 "No. of
|
||||
|
|
@ -2686,6 +2784,46 @@ def test_dual_fuel_secondary_api_enum_9_prices_as_dual_fuel_not_lpg() -> None:
|
|||
assert secondary_factor_code == 10
|
||||
|
||||
|
||||
def test_fuel_cost_secondary_colliding_fuel_priced_at_canonical_rate() -> None:
|
||||
# Arrange — the SAP-DRIVING `_fuel_cost` cascade prices the secondary
|
||||
# via its own inline `secondary_high_rate_gbp_per_kwh`, distinct from
|
||||
# the CO2/display `_secondary_fuel_cost_gbp_per_kwh`. It must apply the
|
||||
# SAME `_GOV_API_COLLISION_FUELS` canonicalisation, else an ANTHRACITE
|
||||
# secondary (gov-API `secondary_fuel_type=5`) resolves to the same-value
|
||||
# Table-32 code 5 = bulk LPG (12.19 p/kWh) instead of anthracite
|
||||
# (3.64 p/kWh) — a 3× over-cost that sank corpus cert 100050355518
|
||||
# (lodged 36, engine 20.8). Anthracite room-heater secondary on a
|
||||
# mains-gas boiler main.
|
||||
gas_boiler_main = MainHeatingDetail(
|
||||
has_fghrs=False, main_fuel_type=26, heat_emitter_type=1,
|
||||
emitter_temperature=1, main_heating_control=2106,
|
||||
main_heating_category=2, sap_main_heating_code=102,
|
||||
)
|
||||
anthracite_secondary_epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=_TYPICAL_TFA_M2,
|
||||
habitable_rooms_count=4,
|
||||
country_code="ENG",
|
||||
sap_building_parts=[make_building_part(construction_age_band="D")],
|
||||
sap_heating=make_sap_heating(
|
||||
main_heating_details=[gas_boiler_main],
|
||||
secondary_fuel_type=5, # gov-API enum: anthracite
|
||||
secondary_heating_type=631, # room heater
|
||||
),
|
||||
)
|
||||
|
||||
# Act
|
||||
inputs = cert_to_inputs(anthracite_secondary_epc)
|
||||
result = Sap10Calculator().calculate(anthracite_secondary_epc)
|
||||
fc = inputs.fuel_cost
|
||||
secondary_kwh = result.secondary_heating_fuel_kwh_per_yr
|
||||
|
||||
# Assert — anthracite 3.64 p/kWh (0.0364), NOT the collision LPG
|
||||
# 12.19 p/kWh the raw same-value Table-32 lookup returns.
|
||||
assert secondary_kwh > 0.0
|
||||
assert abs(fc.secondary_total_cost_gbp - secondary_kwh * 0.0364) <= 1e-6
|
||||
assert abs(fc.secondary_total_cost_gbp - secondary_kwh * 0.1219) > 1.0
|
||||
|
||||
|
||||
def test_is_electric_water_dual_fuel_table_32_code_10_is_not_electric() -> None:
|
||||
# Arrange — same API/Table 32 collision as `_is_electric_main` per
|
||||
# S0380.136 docstring.
|
||||
|
|
@ -4891,6 +5029,42 @@ def test_hot_water_from_pcdb_heat_pump_bills_at_app_n_wh_high_rate() -> None:
|
|||
assert abs(rate_immersion - 0.0750) <= 1e-6
|
||||
|
||||
|
||||
def test_instantaneous_electric_dhw_off_peak_bills_at_high_rate() -> None:
|
||||
# Arrange — SAP 10.2 §12 (PDF line 2680): the off-peak on-peak HW
|
||||
# proportion (Table 13) is "dependent on the total floor area and the
|
||||
# CYLINDER SIZE" — it presumes a stored-water cylinder that can be
|
||||
# charged overnight. An INSTANTANEOUS point-of-use electric heater
|
||||
# (WHC 907/909, no cylinder) heats water on demand and CANNOT shift to
|
||||
# the off-peak window, so 100% of its consumption is at the high rate
|
||||
# (Table 12a high-rate fraction 1.0). The generic off-peak else-branch
|
||||
# billed it at 100% off-peak LOW rate (5.50 p/kWh at 7-hour), 3× under-
|
||||
# costing the DHW and over-rating the dwelling — corpus cert 74061136
|
||||
# (HHR-storage flat, WHC 909) lodged 73, engine 80.7.
|
||||
from domain.sap10_calculator.tables.table_12a import Tariff
|
||||
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||
_hot_water_fuel_cost_gbp_per_kwh, # pyright: ignore[reportPrivateUsage]
|
||||
_hot_water_high_rate_fraction, # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
storage_main = MainHeatingDetail(
|
||||
has_fghrs=False, main_fuel_type=29, heat_emitter_type=0,
|
||||
emitter_temperature="NA", main_heating_control=2404,
|
||||
main_heating_category=7, sap_main_heating_code=409,
|
||||
)
|
||||
|
||||
# Act — instantaneous electric DHW (WHC 909) on a 7-hour off-peak tariff.
|
||||
rate = _hot_water_fuel_cost_gbp_per_kwh(
|
||||
29, storage_main, Tariff.SEVEN_HOUR, SAP_10_2_SPEC_PRICES,
|
||||
water_heating_code=909,
|
||||
)
|
||||
fraction = _hot_water_high_rate_fraction(
|
||||
29, storage_main, Tariff.SEVEN_HOUR, water_heating_code=909,
|
||||
)
|
||||
|
||||
# Assert — 100% at the 7-hour HIGH rate (15.29 p/kWh), NOT the low 5.50.
|
||||
assert abs(rate - 0.1529) <= 1e-6
|
||||
assert fraction == 1.0
|
||||
|
||||
|
||||
def test_heat_pump_water_efficiency_is_floored_at_100pct_per_app_n3_7() -> None:
|
||||
# Arrange — SAP 10.2 Appendix N3.7 ("Thermal efficiency for water
|
||||
# heating – heat pumps", PDF p.109): "multiply the thermal efficiency
|
||||
|
|
|
|||
|
|
@ -727,6 +727,313 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = (
|
|||
cert_num="0018-2049-6232-4431-6934",
|
||||
sap_score=54,
|
||||
),
|
||||
# UPRN 4510053280 (corpus-1000 C001; the corpus doc lodges no cert number).
|
||||
# RdSAP-Schema-21.0.1 native — GROUND-FLOOR FLAT 47 m², band B, solid brick
|
||||
# 520 mm uninsulated (+ 8.12 m² sheltered alternative wall), roof "(another
|
||||
# dwelling above)", ASHP radiators PCDB 100053 (Mitsubishi Ecodan 5 kW,
|
||||
# 2010 record: PSR table 0.2–2.0, η_space 332.8% @ 2.0, η_water 187.5%,
|
||||
# duration "24"), no cylinder, electric room-heater secondary, no PV.
|
||||
# Lodged 51 / engine 74.94 (Δ +23.9, the corpus' worst cert). Dwelling PSR
|
||||
# = 4.62 kW / (94.0 W/K × 24.2 K) = 2.031, just past the record's largest
|
||||
# 2.0 → SAP 10.2 N2 (PDF p.101 footnotes 44/45) heat-pump extension:
|
||||
# reciprocal-interpolate η_space toward 100% at PSR 4.0 → 321% × 0.95 =
|
||||
# 305%. That is what the engine does, and what accredited Elmhurst did on
|
||||
# the SAME record family / same PSR shape (golden case 56, record 100061,
|
||||
# PSR 3.107 → 139.66%). The LODGED software instead treated the
|
||||
# out-of-range record as invalid and billed direct electric: forcing
|
||||
# η_space = η_water = 100% + the standard 9-h schedule reproduces the
|
||||
# lodged CO2 to 1% (0.808 vs 0.800 t) and PE within 6% (178 vs 190),
|
||||
# whereas the engine's spec path gives 0.444 t / 98.8. A lodged-software
|
||||
# methodology gap (worklist ⚠), NOT an engine bug — do not tune. PINNED to
|
||||
# the observed engine 75.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_4510053280",
|
||||
cert_num="uprn-4510053280",
|
||||
sap_score=75,
|
||||
),
|
||||
# UPRN 10094975827 (corpus; no cert number lodged). RdSAP-Schema-21.0.1
|
||||
# native — TOP-FLOOR FLAT, band B, solid brick, pitched roof lodged
|
||||
# thickness "NI" + description "Pitched, insulated (assumed)", electric
|
||||
# room heaters. The "insulated (assumed)" Table-18 cert: presence-assumed
|
||||
# insulation is NOT the §5.11.4 observed-retrofit 50 mm row (0.68) — it
|
||||
# takes the Table 18 age-band default (band B pitched → 0.40), the same
|
||||
# value the "ND" lodgement of this description reaches. Was engine 64.13
|
||||
# (Δ −1.87 vs lodged 66); the fix lands 65.83 → rounds to 66 = lodged.
|
||||
# PINNED to the observed engine 66 = lodged.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_10094975827",
|
||||
cert_num="uprn-10094975827",
|
||||
sap_score=66,
|
||||
),
|
||||
# UPRN 100050355518 (corpus C003; no cert number lodged). RdSAP-21.0.1
|
||||
# native — SEMI-DETACHED HOUSE, 122 m², 2 extensions (3 building parts),
|
||||
# mains-gas boiler main (SAP 117) + ANTHRACITE room-heater secondary
|
||||
# (gov-API secondary_fuel_type=5, SAP 631). The secondary-fuel collision
|
||||
# cert: enum 5 collides with Table-32 code 5 (bulk LPG 12.19 p/kWh); the
|
||||
# SAP-driving _fuel_cost path priced the anthracite secondary at the LPG
|
||||
# rate instead of anthracite 3.64 p/kWh — a 3× over-cost on ~1431 kWh
|
||||
# (+£100/yr → ECF 6.24 vs 4.67). Engine was 20.8 (Δ −15.2 vs lodged 36);
|
||||
# canonicalising the secondary fuel lands 35.5 = lodged 36. CO2/PE
|
||||
# already matched lodged (0.055 t / 1.7 kWh) — a pure cost-side gap, the
|
||||
# signature that localised it. PINNED to the observed engine 36 = lodged.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_100050355518",
|
||||
cert_num="uprn-100050355518",
|
||||
sap_score=36,
|
||||
),
|
||||
# UPRN 74061136 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# MID-FLOOR FLAT 24 m², band B, HHR electric storage heaters (SAP 409) on
|
||||
# an Unknown meter (type 3 → off-peak 7-hour per RdSAP 10 §12 Rule 2),
|
||||
# INSTANTANEOUS electric point-of-use DHW (WHC 909, no cylinder). The
|
||||
# instantaneous-DHW off-peak cert: its DHW was billed 100% at the 7-hour
|
||||
# LOW rate (5.50 p) but SAP 10.2 §12 (Table-13 on-peak split is keyed on
|
||||
# CYLINDER size) presumes stored water — an instantaneous heater has no
|
||||
# cylinder, heats on demand, so 100% bills at the HIGH rate (15.29 p).
|
||||
# Mid-floor flat → roof/floor both zero-loss, so PE matched lodged (+1.6)
|
||||
# and the +7.72 over-rate isolated cleanly to the DHW tariff. Fix lands
|
||||
# 71.75 (Δ −1.25 vs lodged 73); the tariff error is gone, the residual is
|
||||
# a separate small fabric effect. PINNED to the observed engine 72.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_74061136",
|
||||
cert_num="uprn-74061136",
|
||||
sap_score=72,
|
||||
),
|
||||
# UPRN 200004296092 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# END-TERRACE HOUSE, TFA 62, age B, mains-gas COMBI (PCDB 16137), SOLID
|
||||
# BRICK 360 mm "with external insulation" (wall_insulation_type 1) but
|
||||
# thickness lodged "NI" (unknown). The wall added-insulation unknown-
|
||||
# thickness cert: the §5.8 R-value gate needs thickness > 0, so the
|
||||
# externally-insulated wall fell through to its UNINSULATED §5.6 U (1.40,
|
||||
# walls 101.7 W/K, HLP 3.46) → PE +59.6, engine 64.35 (Δ −6.65 vs lodged
|
||||
# 71). RdSAP 10 §5.4 (PDF p.32) "assume 100 mm if thickness is unknown"
|
||||
# applies the 100 mm §5.8 default → U ~0.29, engine 71.74 = lodged. PINNED
|
||||
# to the observed engine 72.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_200004296092",
|
||||
cert_num="uprn-200004296092",
|
||||
sap_score=72,
|
||||
),
|
||||
# UPRN 200004017091 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# END-TERRACE HOUSE, mains-gas boiler, hot-water cylinder lodged with
|
||||
# `cylinder_size=0` (present but "size not determined") + has_cylinder=true.
|
||||
# The unsized-cylinder storage-loss cert: `_cylinder_storage_loss_override`
|
||||
# read the volume via `_cylinder_volume_l_from_code` → None for size 0 →
|
||||
# bailed and DROPPED the cylinder's Table-2 storage loss, under-costing the
|
||||
# DHW → engine 72.75 (Δ +1.75 vs lodged 71). RdSAP 10 §10.5 Table 28: an
|
||||
# unsized present cylinder is the 110 L "Normal" default and still incurs
|
||||
# the loss → engine 71.39 = lodged 71. PINNED to the observed engine 71.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_200004017091",
|
||||
cert_num="uprn-200004017091",
|
||||
sap_score=71,
|
||||
),
|
||||
# UPRN 47084930 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# TOP-FLOOR FLAT, band E, cavity wall + 100 mm internal insulation, FLAT
|
||||
# ROOF insulated 75 mm, floor over other premises, mains-gas combi. The
|
||||
# flat-roof insulation-thickness cert: the 75 mm is lodged in
|
||||
# `flat_roof_insulation_thickness`, but heat_transmission read
|
||||
# `roof_insulation_thickness` (None) → billed the flat roof at the
|
||||
# uninsulated age-E default 1.5 (roof 96.4 W/K) → PE +47, engine 69.51
|
||||
# (Δ −4.49 vs lodged 74). Preferring the flat field → U 0.5 (roof 32.1
|
||||
# W/K), engine 74.34 = lodged 74. VALIDATED against the accredited
|
||||
# Elmhurst worksheet (built on the lodged inputs, evidence saved:
|
||||
# elmhurst_summary.pdf / elmhurst_worksheet.pdf): "insulated flat roof"
|
||||
# U 0.5, floor 0.70 and wall 0.25 both matching the engine. PINNED 74.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_47084930",
|
||||
cert_num="uprn-47084930",
|
||||
sap_score=74,
|
||||
),
|
||||
# UPRN 100050881708 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# SEMI-DETACHED HOUSE, band B, cavity FILLED wall, pitched 225 mm loft,
|
||||
# SUSPENDED ground floor "to unheated space", mains-gas combi, no cylinder.
|
||||
# Lodged 70 / engine 72 (71.55). Built in accredited Elmhurst RdSAP10
|
||||
# (evidence saved: elmhurst_summary.pdf / elmhurst_worksheet.pdf): the WALL
|
||||
# (cavity, U 0.70, 69.56 m²) and ROOF (insulated slope 225 mm, U 0.19)
|
||||
# match the engine EXACTLY (walls 48.8 W/K, roof 6.0 W/K). The engine is
|
||||
# faithful on the checkable fabric; the +1.55 vs lodged is a small
|
||||
# methodology gap. (Elmhurst''s worksheet SAP 61 is NOT comparable — the
|
||||
# build entered the suspended ground floor as an upper "above unheated
|
||||
# space" floor, which Elmhurst bills via Table-20 exposed 1.20; the cert''s
|
||||
# §3.12 suspended-ground-floor U is 0.74, which the engine uses.) PINNED to
|
||||
# the observed engine 72.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_100050881708",
|
||||
cert_num="uprn-100050881708",
|
||||
sap_score=72,
|
||||
),
|
||||
# UPRN 217091901 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# TOP-FLOOR FLAT, band A (before 1900), SOLID BRICK 280 mm uninsulated,
|
||||
# FLAT ROOF no insulation (As Built), floor over ANOTHER DWELLING below
|
||||
# (zero loss), mains-gas COMBI, SINGLE glazing (glazed_type 5), TFA 63.
|
||||
# Lodged 62 / engine 61 (60.82). Built in accredited Elmhurst RdSAP10
|
||||
# (evidence saved: elmhurst_summary.pdf / elmhurst_worksheet.pdf): EVERY
|
||||
# heat-loss element matches the engine EXACTLY — walls (solid U 1.70,
|
||||
# 53.18 W/K), flat roof (uninsulated U 2.30, 145.57 W/K), floor (0),
|
||||
# party (0), and — with the faithful SINGLE glazing entered — WINDOWS
|
||||
# (8.62 m² × effective U 4.0268 = 34.7114 W/K, identical to the engine).
|
||||
# This confirms the mapper''s glazed_type 5 → single (raw U 4.8) is
|
||||
# spec-correct (epc_codes.csv: glazed_type 5 = single glazing), NOT the
|
||||
# double it superficially resembles. The −1.18 vs lodged is a cost-side
|
||||
# methodology residual (PE +8.7 kWh/m², CO2/PE-driven), not a fabric bug.
|
||||
# PINNED to the observed engine 61.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_217091901",
|
||||
cert_num="uprn-217091901",
|
||||
sap_score=61,
|
||||
),
|
||||
# UPRN 100061275133 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# 2-storey SEMI-DETACHED HOUSE, band B, SOLID BRICK 400 mm with EXTERNAL
|
||||
# insulation, pitched 300 mm loft, SUSPENDED uninsulated ground floor,
|
||||
# mains-gas COMBI, double glazing (glazed_type 13, 2022+), TFA 78.
|
||||
# Lodged 76 / engine 76 (76.33, was 77.24 before the §5 (12) fix below).
|
||||
# Built in accredited Elmhurst RdSAP10 (evidence saved: elmhurst_summary.pdf
|
||||
# / elmhurst_worksheet.pdf): the checkable FABRIC matches the engine
|
||||
# closely — ROOF (300 mm loft, U 0.14) and WINDOWS (15.32 m² × eff U
|
||||
# 1.3258 = 20.3106 W/K) match EXACTLY; the externally-insulated WALL is
|
||||
# U 0.29 (engine) vs 0.30 (Elmhurst) — which VALIDATES the slice-5
|
||||
# external-insulation default (100 mm added over solid brick,
|
||||
# `_WALL_ADDED_INSULATION_UNKNOWN_THICKNESS_MM`); the suspended ground
|
||||
# floor is U 0.68 (engine) vs 0.68 (Elmhurst) — EXACT after rebuilding
|
||||
# with 0 extract fans + PCDB 18908 (was 0.70 pre-rebuild). Ventilation
|
||||
# (21) shelter-adjusted ACH is EXACT too (0.6938 = 0.6938), and dwelling
|
||||
# volume (5) 196.8995 m³ matches (confirming the mapper's +0.25 m
|
||||
# upper-floor height convention mirrors Elmhurst's own internal one).
|
||||
# §5 (12) fix: the Main bp lodges no `floor_construction_type` (gov-API
|
||||
# leaves it None), but `epc.floors[0].description` = "Suspended, no
|
||||
# insulation (assumed)" — `_has_suspended_timber_floor_per_spec` was
|
||||
# only reading the per-bp field and silently entered (12)=0 for a
|
||||
# genuinely suspended-timber floor. Now falls back to the joined
|
||||
# description (mirroring `_main_floor_u_value`'s existing fallback),
|
||||
# applying (12)=0.2 unsealed; engine converges EXACTLY on lodged (76).
|
||||
# Elmhurst's OWN overall worksheet SAP (72) still differs from ours
|
||||
# (76.33) even on these now-identical inputs — traced to SAP 10.2
|
||||
# Table 11: Elmhurst's (201)/(202)/(215) show it assumes 10% of space
|
||||
# heat comes from a portable-electric secondary (658 kWh/yr @ the
|
||||
# electric rate) purely from the main system's category (gas boiler),
|
||||
# with NO secondary lodged (`secondary_heating.description == "None"`).
|
||||
# `_secondary_fraction` only applies Table 11 when a secondary is
|
||||
# actually lodged or forced by §A.2.2 — deliberately, per the S-B30
|
||||
# note a few lines below: applying Table 11 unconditionally regressed
|
||||
# the full 1000-cert corpus MAE by +0.16 (lodged software doesn't
|
||||
# apply Elmhurst's blanket convention). Verified: forcing Table 11 on
|
||||
# for just this cert moves 76.33 -> 73.62, ~62% of the 76.33-vs-72 gap.
|
||||
# The remaining ~1.6 pts is a second, separate cause: (6) Solar gains —
|
||||
# the BUILD entered all 12 windows as one 15.32 m2 "North" group
|
||||
# (E.set_single_window), but the cert's real sap_windows lodge 3
|
||||
# orientations (NW 7.05 m2, NE 1.21 m2, SE 7.06 m2 incl. the largest
|
||||
# single window) — collapsing to due-north understates solar gain
|
||||
# (Elmhurst Jan (83)=56.9 W vs our real-orientation 123.4 W). Verified:
|
||||
# forcing our engine to ALSO use a single synthetic North window (i.e.
|
||||
# replicating the build's simplification, not a cert or engine fact)
|
||||
# plus the forced Table 11 above reproduces 72.62 (precise Elmhurst
|
||||
# value is 72.0681, not the rounded "72" -- true residual after Table
|
||||
# 11 + windows was ~0.55, not ~1.6). Third cause found by walking the
|
||||
# rest of the worksheet monthly: (43) Average daily hot water use is
|
||||
# 117.4056 L/day (Elmhurst) vs our 110.7281 L/day -- SAME occupancy
|
||||
# (42)=2.4234 both sides, so the gap is in shower/bath demand, not
|
||||
# occupancy. Root cause: cert's `mixer_shower_count` is unlodged (raw
|
||||
# `shower_outlets=None`), which `_mixer_shower_flow_rates_from_cert`
|
||||
# maps to 0 showers (see its docstring: gov-API "no shower_outlets
|
||||
# entry" convention, validated on cert 2225) -- but Elmhurst's build
|
||||
# shows a "Non-electric shower" fixture (1 vented mixer @ Table J4),
|
||||
# since its UI silently defaults one when the build script never
|
||||
# explicitly configured "0 showers" to match the cert. Verified: a
|
||||
# single 7 L/min vented mixer reproduces Elmhurst's (42a) monthly
|
||||
# series EXACTLY (4 d.p., all 12 months) and closes hot water to
|
||||
# 2616.04 vs Elmhurst's 2616.03 (Δ0.005). Corpus-wide check (170
|
||||
# certs sharing "bath present, no shower data lodged") shows NO
|
||||
# systematic bias (mean dSAP +0.12, median -0.04, 46.5% over-rated) --
|
||||
# confirms the mapper's 0-showers convention is correct on average;
|
||||
# this is a THIRD build-entry artifact, not a mapper bug. Combining
|
||||
# all three (windows + Table 11 + 1 shower): SAP 72.4567 vs Elmhurst's
|
||||
# precise 72.0681 -- residual 0.39, ~91% of the original 4.26-point
|
||||
# gap (76.33 vs 72.07) now precisely explained. Conclusion: the
|
||||
# calculator is faithful (matches lodged 76 exactly using the cert's
|
||||
# REAL windows and REAL shower data); all three residuals are Elmhurst
|
||||
# -reconciliation-build artifacts or deliberate spec choices, not
|
||||
# mapping or calculator bugs. PINNED to the observed exact match vs
|
||||
# lodged.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_100061275133",
|
||||
cert_num="uprn-100061275133",
|
||||
sap_score=76,
|
||||
),
|
||||
# UPRN 100051051866 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# DETACHED BUNGALOW, band I, CAVITY wall as-built insulated, pitched
|
||||
# insulated loft, SUSPENDED limited-insulation floor, mains-gas regular
|
||||
# boiler + cylinder (size 2, insulation type 1, 50 mm — a properly
|
||||
# insulated cylinder, so NOT the type-0/NULL zero-storage-loss bug), TFA 60.
|
||||
# Lodged 70 / engine 71 (71.04). Reduced-field cavity fabric already
|
||||
# Elmhurst-validated on sibling certs; the +1.04 is a cost-side residual
|
||||
# (PE −13.6 kWh/m² under-count) with no fabric anomaly — walls 29.9, roof
|
||||
# 15.6, floor 29.4, windows 15.6 W/K all register-explained. PINNED to the
|
||||
# observed engine 71; the register data fully accounts for the engine output.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_100051051866",
|
||||
cert_num="uprn-100051051866",
|
||||
sap_score=71,
|
||||
),
|
||||
# UPRN 10070056075 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# DETACHED HOUSE, band K, CAVITY wall as-built insulated, pitched insulated
|
||||
# loft, SUSPENDED insulated floor, mains-gas regular boiler + cylinder
|
||||
# (size 4, insulation type 1, 80 mm — properly insulated), dual meter,
|
||||
# TFA 139. Lodged 80 / engine 79 (78.99). Cavity fabric already
|
||||
# Elmhurst-validated on sibling certs; the −1.01 is a cost-side residual
|
||||
# (PE +5.4 kWh/m² over-count) with no fabric anomaly — walls 50.0, roof
|
||||
# 20.1, floor 16.7, windows 26.7 W/K all register-explained. PINNED to the
|
||||
# observed engine 79; the register data fully accounts for the engine output.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_10070056075",
|
||||
cert_num="uprn-10070056075",
|
||||
sap_score=79,
|
||||
),
|
||||
# UPRN 10012334488 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# mid-floor FLAT, mains-electric with SAP heating code 195 ("Electric water
|
||||
# storage boiler", Table 4a PDF p.170) serving underfloor heating, on an
|
||||
# off-peak (7-hour) meter, TFA 62. Lodged 72 / engine 85 (85.20). This is a
|
||||
# PURE COST gap, NOT a fabric bug: PE (−1.4 kWh/m²) and CO2 (+0.01 t) both
|
||||
# match lodged, so demand is right. Code 195 is a STORAGE boiler → SAP 10.2
|
||||
# §12 Rule 2 routes it to the 7-hour tariff and bills space+water heat mostly
|
||||
# at the off-peak low rate (5.5 p/kWh) — the spec-faithful treatment. The
|
||||
# lodged software billed it far higher (implying peak-rate billing of a
|
||||
# storage system), the same lodged-outlier pattern as the §12/PSR xfail
|
||||
# cohort. PINNED to the spec-faithful engine 85 — the engine follows SAP
|
||||
# §12/Table-4a; matching lodged 72 would require billing a storage boiler at
|
||||
# peak rate, against spec. (Not Elmhurst-verified: my RdSAP10 builds carry a
|
||||
# combi/heating artifact that makes them unreliable on the E7 storage side.)
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_10012334488",
|
||||
cert_num="uprn-10012334488",
|
||||
sap_score=85,
|
||||
),
|
||||
# UPRN 10091578598 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# FLAT, mains-electric SAP code 195 ("Electric water storage boiler"),
|
||||
# off-peak meter, TFA 60. Lodged 71 / engine 79 (78.81). Same signature as
|
||||
# 10012334488: PURE COST gap (PE +1.6, CO2 −0.04 both match lodged → demand
|
||||
# right); code 195 is a §12-Rule-2 storage system billed at the 7-hour
|
||||
# off-peak rate per spec. Lodged over-billed the storage boiler at a higher
|
||||
# rate. PINNED to the spec-faithful engine 79 — matching lodged would tune
|
||||
# against SAP §12/Table-4a.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_10091578598",
|
||||
cert_num="uprn-10091578598",
|
||||
sap_score=79,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -245,15 +245,18 @@ def test_part_geometry_floorless_part_honours_full_key_contract() -> None:
|
|||
assert early["cantilever_floor_area_m2"] == 0.0
|
||||
|
||||
|
||||
def test_roof_insulated_assumed_with_ni_thickness_uses_50mm_per_section_5_11_4() -> None:
|
||||
# Arrange — 346 corpus certs lodge roof_insulation_thickness="NI"
|
||||
# with descriptions like "Pitched, insulated (assumed)". The
|
||||
# retrofit-insulation signal in the description must flow from
|
||||
# epc.roofs[0].description through heat_transmission_from_cert
|
||||
# into u_roof so the 50 mm assumption fires per RdSAP 10 §5.11.4.
|
||||
# Geometry: 100 m² ground floor → top floor area also 100 m² →
|
||||
# roof_area = 100 m². roof_w_per_k expected = 0.68 × 100 = 68
|
||||
# W/K (vs uninsulated 2.30 × 100 = 230 W/K).
|
||||
def test_roof_insulated_assumed_with_ni_thickness_uses_table18_default() -> None:
|
||||
# Arrange — corpus certs lodge roof_insulation_thickness="NI" with
|
||||
# descriptions like "Pitched, insulated (assumed)". The description
|
||||
# must flow from epc.roofs[0].description through
|
||||
# heat_transmission_from_cert into u_roof, where "(assumed)"
|
||||
# (presence assumed, not observed retrofit) routes to the Table 18
|
||||
# age-band default per RdSAP 10 §5.11.4 — band C pitched → 0.40 —
|
||||
# matching the "ND" lodgement of the same description. (The bare
|
||||
# "insulated" form keeps the §5.11.4 50 mm row; unit-pinned in
|
||||
# test_rdsap_uvalues.) Geometry: 100 m² ground floor → top floor
|
||||
# area also 100 m² → roof_area = 100 m². roof_w_per_k expected =
|
||||
# 0.40 × 100 = 40 W/K (vs uninsulated 2.30 × 100 = 230 W/K).
|
||||
main = make_building_part(
|
||||
identifier="Main Dwelling",
|
||||
construction_age_band="C",
|
||||
|
|
@ -287,7 +290,7 @@ def test_roof_insulated_assumed_with_ni_thickness_uses_50mm_per_section_5_11_4()
|
|||
result = heat_transmission_from_cert(epc)
|
||||
|
||||
# Assert
|
||||
assert result.roof_w_per_k == pytest.approx(68.0, abs=2.0)
|
||||
assert result.roof_w_per_k == pytest.approx(40.0, abs=2.0)
|
||||
|
||||
|
||||
def test_roof_insulation_location_rafters_drives_table16_column_2_api_int_path() -> None:
|
||||
|
|
@ -367,6 +370,46 @@ def test_rafter_insulation_thickness_field_drives_table16_column_2() -> None:
|
|||
assert abs(result.roof_w_per_k - 25.0) <= 1e-4
|
||||
|
||||
|
||||
def test_flat_roof_insulation_thickness_field_drives_insulated_u() -> None:
|
||||
# Arrange — a FLAT roof lodges its insulation thickness in the DEDICATED
|
||||
# gov-EPC API `flat_roof_insulation_thickness` field (e.g. "75mm"),
|
||||
# leaving `roof_insulation_thickness` None (that field is for pitched-loft
|
||||
# joists). heat_transmission must prefer the flat field when the part is a
|
||||
# flat roof so the measured thickness bills at its RdSAP 10 §5.11 Table-16
|
||||
# U (75 mm → 0.5) instead of the uninsulated age-band flat default (age E
|
||||
# → 1.5). Cert 47084930 (top-floor flat, flat roof "75mm"): roof 96.4 ->
|
||||
# 32.1 W/K, engine 69.51 -> 74.34 = lodged 74, matching the accredited
|
||||
# Elmhurst worksheet ("insulated flat roof", U 0.5). Mirrors the rafter
|
||||
# field. Geometry: 100 m² plan → roof 100 m². 0.5 × 100 = 50 W/K.
|
||||
main = make_building_part(
|
||||
construction_age_band="E",
|
||||
wall_construction=4,
|
||||
wall_insulation_type=4,
|
||||
party_wall_construction=1,
|
||||
roof_construction=1, # flat
|
||||
floor_dimensions=[
|
||||
make_floor_dimension(
|
||||
total_floor_area_m2=100.0, room_height_m=2.5,
|
||||
party_wall_length_m=0.0, heat_loss_perimeter_m=40.0, floor=0,
|
||||
),
|
||||
],
|
||||
)
|
||||
main.roof_construction_type = "Flat"
|
||||
main.roof_insulation_thickness = None # gov leaves this None for flat roofs
|
||||
main.flat_roof_insulation_thickness = "75mm" # the thickness lives here
|
||||
epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=100.0,
|
||||
country_code="ENG",
|
||||
sap_building_parts=[main],
|
||||
)
|
||||
|
||||
# Act
|
||||
result = heat_transmission_from_cert(epc)
|
||||
|
||||
# Assert — 75 mm insulated flat roof at U 0.5, not the age-E default 1.5.
|
||||
assert abs(result.roof_w_per_k - 50.0) <= 1e-4
|
||||
|
||||
|
||||
def test_lodged_roof_u_value_overrides_construction_default() -> None:
|
||||
# Arrange — RdSAP 10 §5.1: where an element's U-value is known from the
|
||||
# assessment (documentary evidence / the lodged RdSAP output) it is used
|
||||
|
|
|
|||
|
|
@ -196,7 +196,50 @@ _CORPUS = Path(
|
|||
# 0.74 -> 0.742 via the heat-pump water-heating 100% floor (App N3.7): cert
|
||||
# 100110101713 moves inside +-0.5 (|err| 4.97 -> 0.49). See the _MAX_SAP_MAE
|
||||
# log below for the paired space-heating PSR-extension + water-floor slices.
|
||||
_MIN_WITHIN_HALF_SAP = 0.742
|
||||
# 0.742 -> 0.755 (MAE 0.722 -> 0.708, CO2 0.09 -> 0.075, PE 3.5 -> 3.2) via
|
||||
# the "insulated (assumed)" roof Table-18 fix (RdSAP 10 §5.11.4): a pitched
|
||||
# roof lodging thickness "NI" + description "insulated (assumed)" was hitting
|
||||
# the §5.11.4 retrofit-50 mm row (0.68) while the SAME description lodged
|
||||
# with "ND" fell through to the Table 18 age-band default (e.g. 0.40 A-G) —
|
||||
# the sentinel choice is lodging-software noise, and "(assumed)" means the
|
||||
# insulation PRESENCE is an age-band assumption, not an observed retrofit,
|
||||
# so Table 18's "thickness cannot be determined" clause governs both forms.
|
||||
# The bare "insulated" description (observed retrofit) keeps the 50 mm row.
|
||||
# The 27-cert loc4+NI cohort was systematically under-rated ~1 SAP (e.g.
|
||||
# 10094975827 band B -1.87 -> inside). Unit-pinned in test_rdsap_uvalues.
|
||||
# 0.755 -> 0.770 (MAE 0.708 -> 0.668) via the secondary-fuel collision fix
|
||||
# on the SAP-DRIVING cost path (`_fuel_cost`): the gov-API secondary fuel
|
||||
# enums 5 (anthracite) / 9 (dual fuel) / 33 (coal) collide with same-valued
|
||||
# Table-32 codes for OTHER fuels, and `_fuel_cost`'s inline secondary rate
|
||||
# passed the raw enum to the price lookup (unlike the already-canonicalised
|
||||
# main-fuel boundary + CO2/display helper). Anthracite priced at the bulk-LPG
|
||||
# code-5 rate (12.19 vs 3.64 p/kWh) 3x over-cost the secondary -> under-rate.
|
||||
# Worst: cert 100050355518 (2 extensions, anthracite room-heater 2ndary)
|
||||
# lodged 36 / engine 20.8 -> 35.9. 55 corpus certs carry a colliding
|
||||
# secondary. Unit-pinned in test_cert_to_inputs (fuel_cost cost path, not
|
||||
# just the display helper the prior enum-9 test guarded).
|
||||
# 0.770 -> 0.773 via the unsized-cylinder storage-loss fix: a present
|
||||
# cylinder lodging gov-API cylinder_size=0 ("size not determined") resolved
|
||||
# to None volume in `_cylinder_storage_loss_override`, so `if volume_l is
|
||||
# None: return None` DROPPED its storage loss (under-costing the DHW ->
|
||||
# over-rate). RdSAP 10 §10.5 Table 28: an unsized present cylinder is the
|
||||
# 110 L "Normal" default and still incurs the Table-2 loss (the value the
|
||||
# Table-13 fraction path already used). Gated on the explicit 0 so a
|
||||
# full-SAP unlodged size (None) keeps its own handling. 7 corpus certs;
|
||||
# within 77.0% -> 77.3%, PE 3.2 -> 3.05, CO2 0.074 -> 0.073.
|
||||
# 0.773 -> 0.777 via the flat-roof insulation-thickness fix: a FLAT roof
|
||||
# lodges its thickness in the dedicated `flat_roof_insulation_thickness`
|
||||
# field (e.g. "75mm"), but heat_transmission read `roof_insulation_thickness`
|
||||
# (None for flat roofs) -> billed the roof at the uninsulated age-band flat
|
||||
# default (age E 1.5) instead of the insulated Table-16 U. Prefer the flat
|
||||
# field when the part is flat (mirrors the rafter-thickness branch). Cert
|
||||
# 47084930 (top-floor flat, flat roof 75mm): roof 96.4 -> 32.1 W/K, engine
|
||||
# 69.51 -> 74.34 = lodged 74, VALIDATED against the accredited Elmhurst
|
||||
# worksheet ("insulated flat roof" U 0.5). within 77.3% -> 77.7%, PE 3.1 ->
|
||||
# 2.97, CO2 0.074 -> 0.072.
|
||||
# 77.7% -> 77.8% via the §5 (12) suspended-timber floor-infiltration
|
||||
# fallback (see the `_MAX_SAP_MAE` note below for the mechanism).
|
||||
_MIN_WITHIN_HALF_SAP = 0.778
|
||||
# 0.793 -> 0.789 via the §12 Unknown-meter + dual-electric-immersion off-peak
|
||||
# trigger (RdSAP 10 PDF p.62): Apartment 241 (main 691 + 903 dual immersion)
|
||||
# -5.38 -> -1.05. Worksheet-validated on "simulated case 48" (Elmhurst SAP 57,
|
||||
|
|
@ -266,9 +309,34 @@ _MIN_WITHIN_HALF_SAP = 0.742
|
|||
# oversized-PSR water eff 0.60 x 128.55% = 77.13% is floored to 100% (=
|
||||
# accredited Elmhurst (216)), taking the cert 68.03 -> 72.51 (|err| 4.97 ->
|
||||
# 0.49, now inside +-0.5). In-range heat pumps keep their > 100% water COP.
|
||||
_MAX_SAP_MAE = 0.722
|
||||
_MAX_CO2_MAE_TONNES = 0.09 # t CO2 / yr vs co2_emissions_current
|
||||
_MAX_PE_PER_M2_MAE = 3.5 # kWh / m2 / yr vs energy_consumption_current
|
||||
# 0.669 -> 0.659 via the instantaneous-electric-DHW off-peak fix: WHC 907/909
|
||||
# (point-of-use, no cylinder) on an off-peak tariff was billed 100% at the
|
||||
# off-peak LOW rate, but SAP 10.2 §12 (Table 13 on-peak proportion is keyed on
|
||||
# CYLINDER size) presumes stored water; an instantaneous heater has no cylinder
|
||||
# and heats on demand -> 100% high rate. Cert 74061136 (HHR-storage flat,
|
||||
# WHC 909) +7.72 -> -1.25. Both the cost rate and the ADR-0014/CO2-PE fraction
|
||||
# fixed (fraction 0 -> 1.0). Unit-pinned in test_cert_to_inputs.
|
||||
# 0.659 -> 0.648 via the wall added-insulation unknown-thickness fix: a
|
||||
# SOLID-BRICK/STONE wall lodging External/Internal insulation (type 1/3) with
|
||||
# thickness "NI" (unknown) was billed UNINSULATED (§5.8 R-value gate needs a
|
||||
# thickness > 0), over-counting wall heat loss. RdSAP 10 §5.4 (PDF p.32):
|
||||
# "assume 100 mm if thickness is unknown" -> apply the 100 mm §5.8 default.
|
||||
# Cert 200004296092 (age-B solid brick 360 mm + external, NI): walls 101.7 W/K
|
||||
# (U~1.4) -> ~0.29, PE +59.6 -> ~0, SAP 64.35 -> 71.74 (lodged 71). Scoped to
|
||||
# brick/stone (the only §5.8 R-value path); within-0.5 held at 77.0%, MAE
|
||||
# 0.658 -> 0.647, PE 3.2 -> 3.1. Unit-pinned in test_rdsap_uvalues.
|
||||
# 77.7% -> 77.8% (MAE 0.637 -> 0.636) via the §5 (12) suspended-timber
|
||||
# floor-infiltration fallback: `_has_suspended_timber_floor_per_spec`
|
||||
# only read the Main bp's per-part `floor_construction_type` lodgement,
|
||||
# which the gov-API mapper frequently leaves `None` even when the global
|
||||
# `epc.floors[].description` carries an explicit "Suspended, ..."
|
||||
# observation (`_main_floor_u_value` already fell back to this joined
|
||||
# description for the U-value calc — the infiltration rule did not).
|
||||
# Cert 100061275133 (Elmhurst-validated build): SAP 77.24 -> 76.33, now
|
||||
# an EXACT match to lodged (76). Unit-pinned in test_cert_to_inputs.
|
||||
_MAX_SAP_MAE = 0.637
|
||||
_MAX_CO2_MAE_TONNES = 0.072 # t CO2 / yr vs co2_emissions_current
|
||||
_MAX_PE_PER_M2_MAE = 3.0 # kWh / m2 / yr vs energy_consumption_current
|
||||
|
||||
|
||||
def _load_corpus() -> list[dict[str, Any]]:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue