Model/scripts/hyde/elmhurst_download.py
Jun-te Kim 2f0eb49eee Checkpoint: UPRN 10093116543 Elmhurst build + devcontainer VNC/Playwright + perms
- Add SAP-accuracy sample for uprn_10093116543 (epc.json, elmhurst_inputs.md,
  summary/worksheet PDFs)
- Persist hyde viewer stack (xvfb/fluxbox/x11vnc/novnc/websockify) and Playwright
  chromium in the backend devcontainer; forward noVNC 6080
- Broaden .claude/settings.local.json allowlist (display/python/grep/tail)
- In-progress campaign mapper/cert_to_inputs work carried from prior cert

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:21:56 +00:00

103 lines
3.8 KiB
Python

"""Download the Input Summary + SAP Worksheets PDFs from Elmhurst (POC).
Closes the validate-cert loop's only manual export: pulls the two PDFs off the
Energy Report Summary page and saves them where `compare_epc_paths.py` reads:
* Input Summary -> elmhurst_summary.pdf (inputs; parse_site_notes_pdf -> EpcPropertyData)
* SAP Worksheets -> elmhurst_worksheet.pdf (the SAP calculation)
DISPLAY=:99 python scripts/hyde/elmhurst_download.py
SAFETY: this script clicks ONLY the two report buttons below. It NEVER clicks
Submit (`LinkButtonSubmitEPR`) — submitting lodges the assessment. The allow-list
is enforced in code.
"""
from __future__ import annotations
import sys
from pathlib import Path
from playwright.sync_api import (
Page,
TimeoutError as PlaywrightTimeoutError,
sync_playwright,
)
HERE = Path(__file__).parent
SESSION_DIR = HERE / ".elmhurst-session"
SAMPLE_DIR = (
HERE.parent.parent
/ "backend/epc_api/json_samples/real_life_examples"
/ "SAP-Schema-17.1/uprn_10092973954"
)
ASSESSMENT_GUID = "B44A0DB4-4C08-4241-B818-86F060172105"
ENTRY_URL = (
"https://rdsap10online.elmhurstenergy.co.uk/Processing/WebFormAddress.aspx"
f"?Guid={ASSESSMENT_GUID}"
)
# (button id, output filename). The ONLY buttons this script may click.
REPORTS: list[tuple[str, str]] = [
("ContentBody_ContentPlaceHolder1_LinkButtonSummary", "elmhurst_summary.pdf"),
("ContentBody_ContentPlaceHolder1_ButtonDebugInforPdf", "elmhurst_worksheet.pdf"),
]
# Buttons this script must NEVER touch (belt-and-braces).
_FORBIDDEN = {"ContentBody_ContentPlaceHolder1_LinkButtonSubmitEPR"}
def _download_one(page: Page, button_id: str, out: Path) -> bool:
assert button_id not in _FORBIDDEN, f"refusing to click forbidden {button_id}"
print(f" clicking {button_id} -> {out.name} ...", flush=True)
try:
with page.expect_download(timeout=60_000) as dl:
page.click(f"#{button_id}", timeout=20_000)
dl.value.save_as(str(out))
except PlaywrightTimeoutError:
# Some report buttons open the PDF in a popup rather than a download.
try:
with page.expect_popup(timeout=10_000) as pop:
page.click(f"#{button_id}", timeout=20_000)
popup = pop.value
with popup.expect_download(timeout=30_000) as dl2:
pass
dl2.value.save_as(str(out))
except PlaywrightTimeoutError:
print(f" !! no download/popup for {button_id}", flush=True)
return False
size = out.stat().st_size if out.exists() else 0
print(f" saved {out} ({size:,} bytes)", flush=True)
return out.exists() and size > 0
def download_reports() -> int:
SAMPLE_DIR.mkdir(parents=True, exist_ok=True)
ok = True
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir=str(SESSION_DIR),
headless=False,
accept_downloads=True,
viewport={"width": 1400, "height": 1000},
)
page = context.pages[0] if context.pages else context.new_page()
page.on("dialog", lambda d: d.accept())
print(f"entering assessment {ASSESSMENT_GUID} ...", flush=True)
page.goto(ENTRY_URL, wait_until="networkidle", timeout=60_000)
print("navigating to Energy Report Summary ...", flush=True)
with page.expect_navigation(wait_until="networkidle", timeout=60_000):
page.click("#ContentBody_buttonActionSummary_Link", timeout=15_000)
page.wait_for_timeout(600)
for button_id, fname in REPORTS:
ok = _download_one(page, button_id, SAMPLE_DIR / fname) and ok
page.wait_for_timeout(600)
context.close()
print("DONE." if ok else "DONE (with errors — see above).", flush=True)
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(download_reports())