"""Elmhurst build for UPRN 10070004512 (SAP-Schema-16.0, GROUND-FLOOR FLAT, band B, cavity FILLED, ELECTRIC STORAGE HEATERS (SAP 402 SEB, manual charge control CSA/2401) + electric immersion off-peak (Economy-7 Dual meter) with a cylinder (size 1), roof = another dwelling above, floor to EXTERNAL AIR, double glazed, TFA 33.24, window 4.88 m². Engine 66 = lodged 66. P1 of the modelling_e2e corpus validation — the built_form fix cert (16.0 omitted built_form; mapper derives it from dwelling_type → flat→modal 4). built_form is ML-only so SAP-neutral; engine reproduces lodged exactly. Storage-heater build (see build_10022893721.py). Engine models NO secondary (sap_heating. secondary_heating_type is None) → secondary present=No to match. Run: DISPLAY=:99 python scripts/hyde/build_10070004512.py """ from __future__ import annotations import sys 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_" 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", "F Flat") _pick(page, "DropDownListPropertyType2", "mid-terrace") # built_form 4 E.set_text(page, "TextBoxStoreys", "1") E.set_text(page, "TextBoxHabitableRooms", "2") E.set_text(page, "TextBoxHeatedHabitableRooms", "2") print("date ->", _pick(page, "DropDownListDateBuiltMain", "1900-1929")) # band B E.set_select(page, "DropDownListDateBuiltFirst", "") E.set_select(page, "DropDownListRoomInRoofMain", "") E.save_close(page) def flats(page): E.goto(page, "Flats", "WebFormFlats.aspx") E.set_select(page, "DropDownListPositionOfFlat", "Ground Floor") E.set_text(page, "TextBoxFloor", "0") 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", "33.24") E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.42") E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "10.39") # 16.0 lodges no party_wall_length; a flat's side party walls are unmodelled by # the engine. Try 0 (Elmhurst may require non-zero — adjust if Recommendations # complains). E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "0") 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(400) print("insulation ->", _pick(page, f"{WALL}DropDownListInsulation", "filled")) E.save_close(page) def roofs(page): E.goto(page, "Roofs", "WebFormRoofs.aspx") _pick(page, f"{ROOF}DropDownListType", "another dwelling above") E.save_close(page) def floors(page): E.goto(page, "Floors", "WebFormFloors.aspx") # Floor is "to external air" — that is the LOCATION/exposure, not a TYPE. _pick(page, f"{FLOOR}DropDownListLocation", "external air") # E To external air page.wait_for_timeout(400) _pick(page, f"{FLOOR}DropDownListType", "solid") # construction; U from exposure 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") _add_window(page, 4.88, "North", _glazing(page)) _delete_zero_rows(page) 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): 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", "5") E.set_text(page, f"{LP}TextBoxLedLightsTotal", "5") # 100% low energy E.set_text(page, f"{LP}TextBoxCflLightsTotal", "0") E.save_close(page) def space_heating(page): # Electric storage heaters (SAP 402 = SEB), manual charge control (SAP 2401 = # CSA). Two passes: clear bound PCDB boiler first, then set the SAP-table code. E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx") page.wait_for_timeout(1000) rid = f"{E.MH1}TextBoxPCDFBoilerReference" ref = page.locator(f"#{rid}").input_value() if ref not in ("0", ""): print(f"clearing bound PCDB boiler {ref} -> 0 (rerun space_heating)") page.evaluate("""(rid)=>{const r=document.getElementById(rid);r.value='0'; r.dispatchEvent(new Event('change',{bubbles:true}));}""", rid) page.wait_for_timeout(500) E.save_close(page) return E.set_heating_dialog(page, "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_ButtonMainHeatingCode", "^Electric", "^Electric", "Storage", "SEB Modern slimline") print("code:", page.locator(f"#{E.MH1}TextBoxMainHeatingCode").input_value()) E.set_heating_dialog(page, "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_ButtonMainHeatingControls", "Storage Radiator", "CSA Manual charge control") print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value()) E.set_select(page, "DropDownListSecondaryHeatingPresent", "No") # Economy-7 Dual meter (cert meter_type 1) — hidden Meters sub-tab. E.click_tab(page, "TabContainer_TabPanelMeters") E.set_select(page, "TabContainer_TabPanelMeters_RadioButtonListElectricityType", "Dual") print("meter:", page.locator("#ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMeters_RadioButtonListElectricityType").input_value()) E.save_close(page) def water_heating(page): # Electric immersion off-peak (Dual) WITH cylinder (size 1 = small). The # immersion code REQUIRES a cylinder. E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx") E.click_tab(page, "TabContainer_TabPanelWaterHeating") page.wait_for_timeout(600) E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode", "Water Heater", "^Electric", "Immersion") print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value()) cid = f"{E.FP}{WH}CheckBoxHotWaterCylinder" cyl = page.locator(f"#{cid}") if cyl.count() and not cyl.is_checked(): try: with page.expect_navigation(wait_until="load", timeout=8000): page.evaluate( """(id)=>{const c=document.getElementById(id);if(c){c.checked=true; c.dispatchEvent(new Event('click',{bubbles:true})); c.dispatchEvent(new Event('change',{bubbles:true}));}}""", cid) except Exception: page.wait_for_timeout(2000) print("cylinder present:", cyl.is_checked() if cyl.count() else "n/a") print("cyl sizes:", _options(page, f"{WH}DropDownListCylinderSize")) _pick(page, f"{WH}DropDownListCylinderSize", "small") or \ E.set_select(page, f"{WH}DropDownListCylinderSize", "Normal") E.set_select(page, f"{WH}DropDownListInsulated", "Foam") isuf = f"{WH}DropDownListInsulationThickness" if page.locator(f"#{E.FP}{isuf}").count(): _pick(page, isuf, "Unknown") or E.set_select(page, isuf, "25 mm") imm = page.locator(f"#{E.FP}{WH}RadioButtonListImmersionHeater") if imm.count(): E.set_select(page, f"{WH}RadioButtonListImmersionHeater", "Dual") E.save_close(page) _ORDER = ["property_description", "flats", "dimensions", "walls", "roofs", "floors", "openings", "ventilation", "space_heating", "water_heating"] def main(): if len(sys.argv) < 2 or sys.argv[1] not in _ORDER: print("usage: build_10070004512.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())