From 3fd7c5708daecbe4c05a2da4c1ed72aec3b87cc5 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 3 Jul 2026 07:49:23 +0000 Subject: [PATCH] feat(hyde): PCDB exact-boiler entry (set_pcdb_boiler + clear_main_heating_code) Cracks the 'PCDB disabled' blocker. The PCDF boiler reference resolves the exact lodged boiler (e.g. 18908 -> Worcester Greenstar 4000, 88.70%) when: (1) the main heating CODE is empty server-side (else the field + magnifying glass are aspNetDisabled), and (2) the index is entered via REAL keyboard + Tab (a JS-set .value doesn't survive the AutoPostBack). Now Elmhurst uses the same boiler efficiency as the SAP engine, so worksheet vs calculator compare like-for-like. Verified on 100061275133: Elmhurst now shows 'Efficiency of main space heating system 1 = 88.70%' (was generic 84%). Includes updated worksheet evidence. Co-Authored-By: Claude Opus 4.8 --- scripts/hyde/elmhurst_lib.py | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/scripts/hyde/elmhurst_lib.py b/scripts/hyde/elmhurst_lib.py index fb99966fb..72f2fb9bc 100644 --- a/scripts/hyde/elmhurst_lib.py +++ b/scripts/hyde/elmhurst_lib.py @@ -467,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]: