mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
test(accuracy): validate + pin 217091901 (single-glazing confirmed vs Elmhurst)
Top-floor flat, solid brick uninsulated, uninsulated flat roof, gas combi, SINGLE glazing (glazed_type 5). Built in accredited Elmhurst RdSAP10 and every heat-loss element matches the engine EXACTLY: walls 53.18, flat roof 145.57, floor 0, party 0, and — with the faithful single glazing — windows 34.7114 W/K (8.62 m2 x eff U 4.0268). Confirms the mapper's glazed_type 5 -> single (raw U 4.8) is spec-correct per epc_codes.csv, not the double it superficially resembles. Engine 61 vs lodged 62; the -1.18 is a cost-side residual, not a fabric bug. Pinned to the observed engine 61. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6f2b73e613
commit
17ced10e94
5 changed files with 692 additions and 0 deletions
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
|
||||
}
|
||||
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())
|
||||
|
|
@ -842,6 +842,27 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = (
|
|||
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,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue