"""Elmhurst build for UPRN 100021969385 (SAP-Schema-16.2, SEMI-DETACHED HOUSE, 2-storey, band C 1930-1949, solid brick uninsulated, mains-gas boiler + radiators (PCDB index 8108, control 2102), 2 roof elements (pitched no insulation + insulated roof room), suspended uninsulated ground floor, no party wall, 2 real lodged windows (18.66 + 1.77 m^2, "Fully double glazed"), secondary solid-smokeless-fuel room heater, no hot water cylinder (from main system), TFA 128, 2 doors uninsulated. Engine 42 / lodged 45. Validates the multiple_glazed_proportion derivation fix (PR #1503, property 753950 / worklist P4): this cert's `multiple_glazing_type` is "ND" and `multiple_glazed_proportion` is entirely absent from the gov-API payload — previously a hard mapper failure. Run: DISPLAY=:99 python scripts/hyde/build_100021969385.py """ from __future__ import annotations import sys 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_" 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") _pick(page, "DropDownListPropertyType2", "semi") # built_form 2 E.set_text(page, "TextBoxStoreys", "2") E.set_text(page, "TextBoxHabitableRooms", "6") E.set_text(page, "TextBoxHeatedHabitableRooms", "6") print("date ->", _pick(page, "DropDownListDateBuiltMain", "1930-1949")) # band C E.set_select(page, "DropDownListDateBuiltFirst", "") print("room-in-roof ->", _pick(page, "DropDownListRoomInRoofMain", "1930-1949")) E.save_close(page) def dimensions(page): E.goto(page, "Dimensions", "WebFormDimensions.aspx") E.set_text(page, f"{DIM}TextBoxFloorAreaLowestFloor", "48.31") E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.67") E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "21.24") E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "0") E.set_text(page, f"{DIM}TextBoxFloorArea1stFloor", "48.31") E.set_text(page, f"{DIM}TextBoxRoomHeight1stFloor", "2.81") E.set_text(page, f"{DIM}TextBoxWallPerimeter1stFloor", "21.24") E.set_text(page, f"{DIM}TextBoxPartyWallLength1stFloor", "0") E.save_close(page) def walls(page): E.goto(page, "Walls", "WebFormWalls.aspx") print("wall type ->", _pick(page, f"{WALL}DropDownListType", "solid brick")) page.wait_for_timeout(400) print("insulation ->", _pick(page, f"{WALL}DropDownListInsulation", "as built")) # No party wall lodged (party_wall_length_m = 0 on both floors). pw = _pick(page, f"{PWALL}DropDownListPartyWallType", "determine") or \ _pick(page, f"{PWALL}DropDownListPartyWallType", "none") print("party wall ->", pw) E.save_close(page) def roofs(page): # Two lodged roof elements: "Pitched, no insulation (assumed)" (main) and # "Roof room(s), insulated". Enter the main roof as no-insulation pitched; # the insulated roof-room element is flagged via Property Description's # Room-in-Roof age band (set above) rather than a 2nd roof row (Elmhurst's # main-roof tab is single-element). E.goto(page, "Roofs", "WebFormRoofs.aspx") print("roof type ->", _pick(page, f"{ROOF}DropDownListType", "access to loft")) print("insulation at ->", _pick(page, f"{ROOF}DropDownListInsulationAt", "joists")) print("thickness ->", _pick(page, f"{ROOF}DropDownListThickness", "no insulation") or _pick(page, f"{ROOF}DropDownListThickness", "0 mm")) E.save_close(page) def floors(page): E.goto(page, "Floors", "WebFormFloors.aspx") print("location ->", _pick(page, f"{FLOOR}DropDownListLocation", "ground")) print("floor type ->", _pick(page, f"{FLOOR}DropDownListType", "suspended")) print("insulation ->", _pick(page, f"{FLOOR}DropDownListInsulation", "as built")) E.save_close(page) def openings(page): E.goto(page, "Openings", "WebFormOpenings.aspx") E.click_tab(page, "TabContainer_TabPanelWindowsPanel") # windows[].description = "Fully double glazed", no install-date band lodged. glazing = "Double post or during 2022" for opt in _options(page, "TabContainer_TabPanelWindowsPanel_DropDownListExtGlazing"): low = opt.lower() if "unknown install date" in low and "single" not in low and "triple" not in low: glazing = opt break print("glazing ->", glazing) E.set_single_window(page, 18.66 + 1.77, orientation="South", glazing=glazing) 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") E.save_close(page) def ventilation(page): E.goto(page, "VentilationAndCooling", "WebFormVentilationAndCooling.aspx") E.click_tab(page, "TabContainer_TabPanelVentilationPanel") E.set_text(page, f"{VP}TextBoxIntermittentFans", "0") E.set_text(page, f"{VP}TextBoxOpenChimneys", "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", "14") E.set_text(page, f"{LP}TextBoxLedLightsTotal", "5") E.set_text(page, f"{LP}TextBoxCflLightsTotal", "0") E.save_close(page) def space_heating(page): # Mains gas boiler + radiators, exact PCDB index 8108, controls SAP 2102. E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx") page.wait_for_timeout(1000) E.clear_main_heating_code(page) desc = E.set_pcdb_boiler(page, 8108) print("boiler resolved ->", desc) E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingControls", "^Boilers", "^Standard", "programmer, room thermostat") print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value()) E.save_close(page) def secondary(page): # Lodged secondary: "Room heaters, smokeless fuel". 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", "Solid", "Room Heater", "smokeless") 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): # has_hot_water_cylinder = false -> from the main heating system, no cylinder. 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) _ORDER = ["property_description", "dimensions", "walls", "roofs", "floors", "openings", "ventilation", "space_heating", "secondary", "water_heating"] def main(): if len(sys.argv) < 2 or sys.argv[1] not in _ORDER + ["all"]: print("usage: build_100021969385.py <" + "|".join(_ORDER + ["all"]) + ">") return 2 with E.session() as (ctx, page): if sys.argv[1] == "all": for step in _ORDER: print(f"=== {step} ===") globals()[step](page) else: globals()[sys.argv[1]](page) print("done:", sys.argv[1], "->", page.url) return 0 if __name__ == "__main__": raise SystemExit(main())