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 <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-07-03 07:49:23 +00:00
parent b9d36f72cb
commit 3fd7c5708d

View file

@ -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]: