mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
test(accuracy): ratchet 75.5% -> 77.0% via secondary-fuel collision fix on cost path
The gov-API `secondary_fuel_type` enums 5 (anthracite) / 9 (dual fuel) / 33 (coal) collide in VALUE with same-valued RdSAP-10 Table-32 codes for OTHER fuels (5=bulk LPG, 9=LPG SC11F, 33=elec 10h-low). The main-fuel boundary and the CO2/display `_secondary_fuel_cost_gbp_per_kwh` already canonicalise these via `canonical_fuel_code`, but the SAP-DRIVING `_fuel_cost` cascade had a SEPARATE inline secondary rate that passed the raw enum straight to the price lookup — so the two paths diverged and the cost path silently mis-priced. Anthracite (enum 5) was billed at the bulk-LPG code-5 rate 12.19 p/kWh instead of 3.64 — a 3x over-cost on the secondary that under-rated every solid-fuel-secondary dwelling. Worst: corpus cert 100050355518 (semi, 2 extensions, anthracite room-heater secondary) lodged 36 / engine 20.8 -> 35.5. Its CO2/PE already matched lodged (the demand cascade was right) — a pure cost-side gap, which is what localised it. Fix: canonicalise `secondary_fuel` before the Table-32 lookup in `_fuel_cost`, mirroring the main-fuel boundary. 55 corpus certs carry a colliding secondary. Gauge: within-0.5 75.5% -> 77.0%, SAP MAE 0.708 -> 0.668 (floors ratcheted). The prior enum-9 test only guarded the display helper; new test_fuel_cost_secondary_colliding_fuel_priced_at_canonical_rate pins the _fuel_cost cost path directly. RealCertExpectation pinned for 100050355518 (36 = lodged). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a11b10f427
commit
06a2c413f5
6 changed files with 1051 additions and 304 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
@ -7517,8 +7517,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
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2686,6 +2686,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.
|
||||
|
|
|
|||
|
|
@ -739,6 +739,23 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = (
|
|||
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,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -207,7 +207,18 @@ _CORPUS = Path(
|
|||
# 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.
|
||||
_MIN_WITHIN_HALF_SAP = 0.755
|
||||
# 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).
|
||||
_MIN_WITHIN_HALF_SAP = 0.770
|
||||
# 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,
|
||||
|
|
@ -277,7 +288,7 @@ _MIN_WITHIN_HALF_SAP = 0.755
|
|||
# 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.708
|
||||
_MAX_SAP_MAE = 0.669
|
||||
_MAX_CO2_MAE_TONNES = 0.075 # t CO2 / yr vs co2_emissions_current
|
||||
_MAX_PE_PER_M2_MAE = 3.2 # kWh / m2 / yr vs energy_consumption_current
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue