From e6a829aaeafbef9ce501ef63caac08cb5f03e885 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 09:51:49 +0000 Subject: [PATCH] more examples --- .../expand-sap-accuracy-corpus/SKILL.md | 53 +- .../expand-sap-accuracy-corpus/worklist.md | 19 +- .../uprn_100021943298/elmhurst_inputs.md | 98 ++++ .../uprn_100021943298/elmhurst_summary.pdf | Bin 0 -> 63418 bytes .../uprn_100021943298/elmhurst_worksheet.pdf | Bin 0 -> 43041 bytes .../uprn_100021943298/epc.json | 232 +++++++++ .../uprn_100020933699/elmhurst_summary.pdf | Bin 0 -> 80690 bytes .../uprn_100020933699/elmhurst_worksheet.pdf | Bin 0 -> 43088 bytes .../uprn_44012843/elmhurst_summary.pdf | Bin 0 -> 62686 bytes .../uprn_44012843/elmhurst_worksheet.pdf | Bin 0 -> 43994 bytes .../uprn_10023444320/elmhurst_summary.pdf | Bin 0 -> 62601 bytes .../uprn_10023444320/elmhurst_worksheet.pdf | Bin 0 -> 23393 bytes .../uprn_10023444324/elmhurst_summary.pdf | Bin 0 -> 62607 bytes .../uprn_10023444324/elmhurst_worksheet.pdf | Bin 0 -> 23365 bytes .../uprn_10096028301/elmhurst_inputs.md | 96 ++++ .../uprn_10096028301/elmhurst_summary.pdf | Bin 0 -> 62575 bytes .../uprn_10096028301/elmhurst_worksheet.pdf | Bin 0 -> 23200 bytes .../uprn_10096028301/epc.json | 456 ++++++++++++++++++ datatypes/epc/domain/mapper.py | 111 ++++- domain/elmhurst/README.md | 28 ++ scripts/hyde/elmhurst_dom/conservatories.json | 6 - scripts/hyde/elmhurst_dom/dimensions.json | 54 ++- scripts/hyde/elmhurst_dom/floors.json | 6 - .../hyde/elmhurst_dom/newtechnologies.json | 6 - scripts/hyde/elmhurst_dom/openings.json | 172 +------ .../elmhurst_dom/propertydescription.json | 71 ++- .../hyde/elmhurst_dom/propertydetails.json | 6 - scripts/hyde/elmhurst_dom/roofs.json | 12 +- scripts/hyde/elmhurst_dom/spaceheating.json | 36 +- .../elmhurst_dom/ventilationandcooling.json | 42 +- scripts/hyde/elmhurst_dom/walls.json | 48 +- scripts/hyde/elmhurst_dom/waterheating.json | 155 +----- scripts/hyde/elmhurst_download.py | 8 +- scripts/hyde/elmhurst_lib.py | 110 +++++ .../test_real_cert_sap_accuracy.py | 93 ++++ 35 files changed, 1467 insertions(+), 451 deletions(-) create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.1/uprn_100021943298/elmhurst_inputs.md create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.1/uprn_100021943298/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.1/uprn_100021943298/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.1/uprn_100021943298/epc.json create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100020933699/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100020933699/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.3/uprn_44012843/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.3/uprn_44012843/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.0/uprn_10023444320/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.0/uprn_10023444320/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.0/uprn_10023444324/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.0/uprn_10023444324/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-19.1.0/uprn_10096028301/elmhurst_inputs.md create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-19.1.0/uprn_10096028301/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-19.1.0/uprn_10096028301/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-19.1.0/uprn_10096028301/epc.json create mode 100644 domain/elmhurst/README.md diff --git a/.claude/skills/expand-sap-accuracy-corpus/SKILL.md b/.claude/skills/expand-sap-accuracy-corpus/SKILL.md index 9c2d4062..437c305e 100644 --- a/.claude/skills/expand-sap-accuracy-corpus/SKILL.md +++ b/.claude/skills/expand-sap-accuracy-corpus/SKILL.md @@ -105,13 +105,52 @@ Pattern: `with E.session() as (ctx,page): E.goto(...); E.set_text/set_select(... default. Pin the engine's observed value; document the Elmhurst delta (don't tune). - **FGHRS** (full-SAP `has_fghrs`/`fghrs_index_number`) is dropped by the mapper and not yet modelled — omit it on BOTH sides so the comparison stays clean. +- **Validation errors live on the Recommendations page as LINKS** (red ✗ anchors), + not coloured spans — parse `[id*=ContentPlaceHolder1] a` text, not CSS colour. + Until Recommendations shows ZERO errors the **Energy Report Summary nav silently + redirects to the Address page** and the worksheet/Results PDFs won't generate + (Results renders empty). Two errors that bit the 16.1/19.1.0 builds: *"Walls + (Main): Insulation Thickness must be entered"* (set the insulation-thickness + dropdown — "Unknown" is fine for a reduced cert) and *"Incorrect Controls (NNNN + ctrl-group-X) for Heating System (idx ctrl-group-Y)"* (the heating control's + system-type must match the boiler's — see the controls dialog below). +- **All Elmhurst modal dialogs sit above a `modalBackground` that defeats element + clicks** (Playwright sees it "intercepting pointer events", even with force). + The cracked pattern is now in `elmhurst_lib.py`: open via JS click, set cascade + ` values by JS (set .value + dispatch 'change') — this drives +# the AutoPostBack cascade without a real click, +# * commit by a raw `page.mouse.click(x, y)` at the OK/Select control's centre +# (coordinate clicks land on the topmost element = the dialog, not the +# background). +# Typing into the boiler search box needs force-click-then-keyboard (the box is +# overlapped); result cells are clicked by coordinate (the model column is +# display:none at >=1024px, so match the row by its Ref-No cell and click the +# first VISIBLE cell). Direct-writing the PCDF reference number is COSMETIC — it +# does NOT re-resolve the boiler type/efficiency; only the search dialog does. +MH1 = ( + "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMainHeating1_" + "WebUserControlMainHeating1_" +) +HEATING_DIALOG = "ContentBody_OutsideUpdatePanel_SelectHeatingDialog_" + + +def dialog_commit(page: Page, label: str = "OK") -> bool: + """Coordinate-click a dialog's commit control (a styled , e.g. 'OK' or + 'Select') — bypasses the modalBackground that defeats element clicks.""" + pt = page.evaluate( + """(lbl)=>{const e=[...document.querySelectorAll('[id*=SelectHeatingDialog] span,[id*=SelectHeatingDialog] a,[id*=SelectHeatingDialog] input,[id*=SelectBoilerDialog] span')] + .find(x=>new RegExp('^'+lbl+'$','i').test((x.value||x.innerText||'').trim()) && x.offsetParent); + if(!e)return null;const c=e.getBoundingClientRect();return {x:c.x+c.width/2,y:c.y+c.height/2};}""", + label, + ) + if not pt: + return False + page.mouse.click(pt["x"], pt["y"]) + page.wait_for_timeout(SETTLE_MS * 3) + return True + + +def set_heating_dialog(page: Page, open_button_suffix: str, *level_regexes: str) -> None: + """Open a SelectHeatingDialog cascade (water-heating method or main-heating + controls) via the button at FP+`open_button_suffix`, set DropDownList1..N to + the options matching each regex (JS, fires the cascade), then coordinate-OK. + e.g. water (combi): set_heating_dialog(page, MH..ButtonWaterHeatingCode, + 'From Space Heating', 'From the primary heating system'); + control 2106: set_heating_dialog(page, ..ButtonMainHeatingControls, + '^Boilers', '^Standard', 'CBE Programmer, room thermostat and TRVs').""" + page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", f"{FP}{open_button_suffix}") + page.wait_for_timeout(SETTLE_MS * 3) + for i, rx in enumerate(level_regexes, start=1): + page.evaluate( + """(a)=>{const s=document.getElementById(a[0]);if(!s)return; + const o=[...s.options].find(o=>new RegExp(a[1],'i').test(o.text)); + if(o){s.value=o.value;s.dispatchEvent(new Event('change',{bubbles:true}));}}""", + [f"{HEATING_DIALOG}DropDownList{i}", rx], + ) + page.wait_for_timeout(SETTLE_MS * 4) # AutoPostBack re-renders the next level + dialog_commit(page, "OK") + + +def select_boiler(page: Page, query: str, ref: str) -> str: + """Set the main-heating boiler to PCDB `ref` via the search dialog. `query` + is a full-text search (brand/model) that surfaces `ref` on page 1. TWO-STEP + commit: click the result ROW (highlights it) then the top 'Select' span.""" + page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", f"{MH1}ButtonBoilerReference") + page.wait_for_selector("[id*='SelectBoilerDialog_TextBoxFullSearch']", state="visible", timeout=10_000) + page.locator("[id*='SelectBoilerDialog_TextBoxFullSearch']").click(force=True) + page.keyboard.type(query) + page.locator("[id*='SelectBoilerDialog_HyperLinkActionBoilerSearch']").click(force=True) + page.wait_for_timeout(SETTLE_MS * 4) + pt = page.evaluate( + """(ref)=>{const t=document.querySelector("[id*=SelectBoilerDialog] [id*=GridView]");if(!t)return null; + for(const r of t.querySelectorAll('tr')){const tds=[...r.querySelectorAll('td')]; + if(tds.length>2 && tds[2].innerText.replace(/\\s+/g,' ').trim()===ref){ + const v=tds.find(td=>td.offsetParent&&td.getClientRects().length); + if(v){const c=v.getBoundingClientRect();return {x:c.x+c.width/2,y:c.y+c.height/2};}}}return null;}""", + ref, + ) + if not pt: + return "row-not-found" + page.mouse.click(pt["x"], pt["y"]) # highlight the row + page.wait_for_timeout(SETTLE_MS) + page.locator("[id*=SelectBoilerDialog] span", has_text="Select").filter( + visible=True + ).first.click(force=True, timeout=8_000) + page.wait_for_timeout(SETTLE_MS * 3) + return "ok" + + +def clear_hot_water_cylinder(page: Page, panel: str = "TabContainer_TabPanelWaterHeating_") -> None: + """Uncheck the HW cylinder (combi). The checkbox won't uncheck via + .uncheck()/commit (a regular-boiler default re-asserts it); JS-set + checked=false + fire click + change drives the AutoPostBack.""" + cid = f"{FP}{panel}CheckBoxHotWaterCylinder" + if not page.locator(f"#{cid}").is_checked(): + return + try: + with page.expect_navigation(wait_until="load", timeout=8_000): + page.evaluate( + """(id)=>{const c=document.getElementById(id);if(c){c.checked=false; + c.dispatchEvent(new Event('click',{bubbles:true})); + c.dispatchEvent(new Event('change',{bubbles:true}));}}""", + cid, + ) + except PlaywrightTimeoutError: + page.wait_for_timeout(2_000) diff --git a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py index 715fb053..aa657543 100644 --- a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py +++ b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py @@ -256,6 +256,99 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( cert_num="0215-2818-7357-9703-2145", sap_score=61, ), + # UPRN 100021943298 → cert 0498-6963-7270-0822-1980. SAP-Schema-16.1 — a + # reduced-field (RdSAP-shaped) ground-floor FLAT, band B (1900-1929), solid + # brick with internal insulation, mains-gas COMBI (PCDB 10328 Vaillant Ecotec + # Pro 28), double glazed, TFA 78 m². New-schema coverage: mapped via the + # dedicated `from_sap_schema_16_1` → `_normalize_sap_schema_16_x` → + # `from_rdsap_schema_17_1`. Lodged 72 (old 2012 assessment); engine 76. + # Built in Elmhurst RdSAP10 on the mapped inputs (evidence saved: + # elmhurst_summary.pdf / elmhurst_worksheet.pdf): Elmhurst worksheet SAP 75 — + # engine within ~1 (continuous 75.70 vs 75), the tightest agreement in the + # cohort because this is reduced-field (both sides use RdSAP age-band/ + # description U-values, not measured). Boiler set to the cert's exact PCDB + # 10328 via the search dialog; control 2106 (CBE programmer + room stat + + # TRVs); water from primary (combi, no cylinder). The ~0.7 residual is minor + # RdSAP defaults (wall thickness 280 vs 300 mm, internal-insulation thickness + # Unknown vs lodged 50 mm). PINNED to the observed 76 — mapping untuned. + RealCertExpectation( + schema="SAP-Schema-16.1", + sample="uprn_100021943298", + cert_num="0498-6963-7270-0822-1980", + sap_score=76, + ), + # UPRN 10096028301 → cert 0390-3321-6060-2405-7985. SAP-Schema-19.1.0 — a + # FULL-SAP ground-floor FLAT (band M 2023+, mains-gas COMBI Ideal Logic Combi + # PCDB 17929, decentralised MEV, measured AP50 3.5, TFA 73 m²). New-schema + # coverage: mapped via the dedicated `from_sap_schema_19_1_0` (parses with the + # 17.1 dataclass, delegates to from_sap_schema_17_1). Lodged 85; engine 82. + # Built in Elmhurst RdSAP10 on the mapped inputs (evidence saved: + # elmhurst_summary.pdf / elmhurst_worksheet.pdf): Elmhurst worksheet SAP 82 — + # engine EXACTLY matches (82.11 vs 82), and engine-on-Elmhurst's-own-parsed- + # inputs is 82.16 ≈ 82, so the calculator is confirmed faithful on identical + # inputs. Boiler set to the cert's exact PCDB 17929 via the search dialog; + # control 2106 (CBE); water from primary (combi); MEV on; AP50 Blower Door 3.5. + # The −3 vs lodged 85 is the documented full-SAP→RdSAP gap: the engine uses the + # cert's MEASURED U (wall 0.24 / floor 0.13, WORSE than RdSAP band-M defaults) + # + MEV priced as extract loss not heat recovery. PINNED to the observed 82 — + # mapping untuned; engine == Elmhurst. + RealCertExpectation( + schema="SAP-Schema-19.1.0", + sample="uprn_10096028301", + cert_num="0390-3321-6060-2405-7985", + sap_score=82, + ), + # UPRN 44012843 → cert 0775-2898-6628-9594-8005. SAP-Schema-16.3 — a + # reduced-field (RdSAP-shaped) ground-floor FLAT, band K (2007-2011), cavity + # insulated, mains-gas REGULAR boiler (PCDB 9895 Ideal icos HE15) + cylinder, + # double glazed, TFA 55 m². New-schema coverage via the dedicated + # `from_sap_schema_16_3` → `_normalize_sap_schema_16_x` → from_rdsap_schema_17_1. + # Lodged 81; engine 79. Built in Elmhurst RdSAP10 on the mapped inputs + # (evidence saved: elmhurst_summary.pdf / elmhurst_worksheet.pdf): Elmhurst + # worksheet SAP 78 — engine within ~1 (continuous 78.82 vs 78), and engine-on- + # Elmhurst's-own-parsed-inputs is 78.48 ≈ 78 → calculator confirmed faithful. + # Boiler set to the cert's exact PCDB 9895 via the search dialog; control 2106 + # (CBE); regular boiler + cylinder (Large, foam, 50 mm), water from primary. + # The −2 vs lodged 81 is reduced-field RdSAP defaults. PINNED to the observed + # 79 — mapping untuned. + RealCertExpectation( + schema="SAP-Schema-16.3", + sample="uprn_44012843", + cert_num="0775-2898-6628-9594-8005", + sap_score=79, + ), + # UPRN 10023444324 → cert 8501-5064-6739-1407-0163. SAP-Schema-17.0 — a + # FULL-SAP ground-floor FLAT (band M, mains-gas COMBI Ideal Logic+ combi 35 + # PCDB 16211, decentralised MEV, measured AP50 3.2, lodged party wall 6.43 m, + # TFA 50 m²). Full-SAP shape ≡ 17.1, dispatched via from_sap_schema_17_1. + # Lodged 82; engine 80. Built in Elmhurst RdSAP10 on the mapped inputs + # (evidence saved: elmhurst_summary.pdf / elmhurst_worksheet.pdf): Elmhurst + # worksheet SAP 80 — engine EXACTLY matches (80.13 vs 80); engine-on-Elmhurst's- + # own-parsed-inputs 81.03 ≈ 80 → calculator faithful. Boiler set to the cert's + # exact PCDB 16211 via the search dialog; control 2106 (CBE); water from primary + # (combi); MEV on; AP50 Blower Door 3.2; party wall 6.43 m entered. The −2 vs + # lodged 82 is the documented full-SAP→RdSAP gap (measured U 0.2/0.1 + MEV + # extract loss). PINNED to the observed 80 — mapping untuned; engine == Elmhurst. + RealCertExpectation( + schema="SAP-Schema-17.0", + sample="uprn_10023444324", + cert_num="8501-5064-6739-1407-0163", + sap_score=80, + ), + # UPRN 10023444320 → cert 0868-6045-7331-4376-0914. SAP-Schema-17.0 — FULL-SAP + # MID-FLOOR FLAT (sibling of 10023444324, same block / combi PCDB 16211 / MEV), + # band M, no party wall, floor + roof both to other dwellings, TFA 50.68 m². + # Lodged 81; engine 81. Built in Elmhurst RdSAP10 (evidence saved): Elmhurst + # worksheet 82 — engine within ~1 (81.38 vs 82); engine-on-Elmhurst-inputs 82.46 + # ≈ 82 → calculator faithful. Boiler PCDB 16211 via search; control 2106 (CBE); + # water from primary (combi); MEV on; AP50 Blower Door 3.09; mid-floor (floor = + # another dwelling below). PINNED to the observed 81 — mapping untuned. + RealCertExpectation( + schema="SAP-Schema-17.0", + sample="uprn_10023444320", + cert_num="0868-6045-7331-4376-0914", + sap_score=81, + ), )