mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
docs(elmhurst): validate slice-5 wall fix against accredited Elmhurst worksheet (uprn 200004296092)
Built the cert in accredited Elmhurst RdSAP10-Online on the lodged register inputs; downloaded Input Summary + SAP Worksheets (committed alongside). Confirmed: - Slice 5 DIRECTION validated: the externally-insulated solid-brick wall is billed insulated by both tools (Elmhurst U 0.48, engine U 0.29), far below the uninsulated 1.40 the pre-fix §5.8 gate produced. - ROOF matches EXACTLY (U 0.30, 9.35 W/K). - Engine 71.74 = lodged 71; Elmhurst current SAP 66 (its "unknown external insulation" default is more conservative + this build used a generic combi and no keyed wall thickness, so 66 is not a clean engine-comparison). Follow-up lead (documented in ELMHURST_FINDINGS.md): the engine feeds the UNMEASURED lodged wall_thickness (360 mm, measured=N) into the §5.6 formula, whereas RdSAP §5.3 uses measured thickness / Table-3 default otherwise. The party-wall "difference" is NOT a bug (engine 0.25 is spec-correct, Table 15 row 4). Also adds the build script scripts/hyde/build_200004296092.py. No engine change in this commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0e784cbb1c
commit
8e487a9bae
6 changed files with 1932 additions and 0 deletions
|
|
@ -0,0 +1,70 @@
|
|||
# Elmhurst validation — UPRN 200004296092 (corpus, slice-5 wall fix)
|
||||
|
||||
Built in accredited Elmhurst RdSAP10-Online on the lodged register inputs
|
||||
(end-terrace house, solid brick + **external insulation, thickness "NI"**,
|
||||
pitched 150 mm loft, solid ground floor, mains-gas combi, TFA 62).
|
||||
Artefacts: `elmhurst_summary.pdf` (Input Summary), `elmhurst_worksheet.pdf`
|
||||
(SAP Worksheets).
|
||||
|
||||
## Headline
|
||||
- **Lodged SAP 71**; **engine 71.74** (post slice-5 §5.4 100 mm fix, was 64.35);
|
||||
**accredited Elmhurst current SAP 66** (band D), potential 75.
|
||||
- Slice 5 is **directionally validated**: the externally-insulated wall must be
|
||||
billed insulated, not at the uninsulated 1.40 the §5.8 gate produced. Both
|
||||
Elmhurst (0.48) and the engine (0.29) are far below 1.40.
|
||||
|
||||
## Fabric line-by-line (engine dive vs Elmhurst worksheet)
|
||||
| Element | Engine | Elmhurst | Note |
|
||||
|---|---|---|---|
|
||||
| External wall | 21.1 W/K (U 0.29) | 30.7 W/K (U **0.48**, "SolidWallDensePlaster**200**") | see WALL below |
|
||||
| Roof | 9.35 W/K (U 0.30) | 9.35 W/K (U 0.30) | **exact match** |
|
||||
| Ground floor | 21.2 W/K | 22.7 W/K (U 0.73) | close |
|
||||
| Party wall | **8.57 W/K** | **0.00 W/K** | see PARTY below |
|
||||
| Windows | 7.96 W/K | ~same area (4.31 + 4×2.89 split) | ~ |
|
||||
|
||||
The wall (engine −9.6) and party wall (engine +8.6) differences nearly cancel,
|
||||
so the two HTCs are close; the residual SAP gap (71.7 vs 66) is dominated by the
|
||||
heating system — this build used a GENERIC Post-98 condensing combi, whereas the
|
||||
engine uses the lodged PCDB 16137 (Ideal Logic combi 30). Not a clean 1e-4
|
||||
comparison for that reason.
|
||||
|
||||
## Leads (both partly confounded by an imperfect build — see caveats)
|
||||
1. **WALL — base thickness on an unmeasured wall.** The cert lodges
|
||||
`wall_thickness 360` but `wall_thickness_measured = N`. Elmhurst modelled a
|
||||
**~200 mm** base wall ("SolidWallDensePlaster200") — but note my build did
|
||||
NOT enter a wall thickness, so Elmhurst used its own default; had I keyed
|
||||
360 mm it might match the engine. The open question is genuine though:
|
||||
RdSAP §5.3 uses the documentary/measured thickness, and when NOT measured
|
||||
the engine perhaps should fall to the Table-3 default rather than trust the
|
||||
unmeasured lodged 360 mm. WORTH A DEDICATED CHECK (build with the thickness
|
||||
keyed both ways).
|
||||
2. **PARTY WALL — NOT an engine gap.** Elmhurst rendered the party wall as
|
||||
"Solid, U 0.00", but the engine's **0.25** is SPEC-CORRECT: RdSAP §5.10
|
||||
Table 15 row 4 ("unable to determine, house") = 0.25. The cert lodges
|
||||
`party_wall_construction = 0` (unable to determine), so 0.25 is right; the
|
||||
Elmhurst 0.00 reflects how my build's party-wall dropdown resolved (a
|
||||
"Solid" masonry party wall, Table 15 row 1 = 0), not the cert's "unknown".
|
||||
Build artifact, not a bug.
|
||||
|
||||
## Build caveats (why the 71.7-vs-66 residual isn't cleanly attributable)
|
||||
- Heating: built a GENERIC Post-98 condensing combi; the engine uses the lodged
|
||||
PCDB 16137 (Ideal Logic combi 30) — different efficiency.
|
||||
- Wall thickness: not keyed into Elmhurst (defaulted 200 mm) vs engine's 360 mm.
|
||||
- Party wall: entered as a resolved type (0.00) vs the cert's "unable to
|
||||
determine" (engine 0.25).
|
||||
So this build's clean, confirmed results are: (a) the ROOF matches exactly
|
||||
(U 0.30, 9.35 W/K), (b) slice 5's DIRECTION is validated (insulated wall, not
|
||||
the uninsulated 1.40). The wall-thickness question (lead 1) is the one genuine
|
||||
follow-up.
|
||||
|
||||
## Slice-5 calibration note (banked)
|
||||
The accredited "External insulation, **Unknown** thickness" selection in Elmhurst
|
||||
produced U 0.48 (≈ a 50 mm equivalent on its 200 mm base), NOT the 0.29 the
|
||||
§5.4-literal "assume 100 mm" gives on the engine's 360 mm base. The 100 mm fix
|
||||
nevertheless matches the LODGED corpus better (MAE 0.658 → 0.647, within held) —
|
||||
lodged assessors typically knew the real (~100 mm) thickness the register then
|
||||
flattens to "NI". Per the campaign rule (engine = lodged is the goal; Elmhurst
|
||||
arbitrates only when lodged can't be explained from register data), the 100 mm
|
||||
fix STANDS; the lodged-vs-Elmhurst gap here is explained by the register's "NI"
|
||||
discarding the assessor's thickness knowledge. But the base-thickness and
|
||||
party-wall leads above are genuine and independent of the insulation default.
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,254 @@
|
|||
Summary Information
|
||||
Surveyor: P960-0001
|
||||
Name: Richard Matthew Ratcliff Title: Mr. Tel Number: 07760 443 469
|
||||
Survey Reference: 001431 My Reference: Khalim-test
|
||||
Current SAP rating: D 66 Potential SAP rating: C 75 Emissions (t/year): 1.927 tonnes
|
||||
Current EI rating: C 72 Potential EI rating: C 76 Fuel Bill: £1026
|
||||
Property Details:
|
||||
RdSAP version: RdSAP10
|
||||
Reference Number: P960-0001-001431
|
||||
My Reference: Khalim-test
|
||||
Lodgement Required: No
|
||||
Regs Region: England
|
||||
EPC Language: English
|
||||
UPRN:
|
||||
Postcode: W6 9BF
|
||||
Region: Thames Valley
|
||||
House Name: 28 Distillery Wharf
|
||||
House No: 28
|
||||
Street: Distillery Wharf
|
||||
Locality:
|
||||
Town: London
|
||||
County:
|
||||
Property Tenure: Owner-occupied
|
||||
Transaction Type: Marketed sale
|
||||
Inspection Date: 25/06/2025
|
||||
Process date: 02/07/2026
|
||||
Check for the existence of No
|
||||
an EPC:
|
||||
Does an EPC exist at the No
|
||||
point of carrying out this
|
||||
energy assessment:
|
||||
Reason why another energy
|
||||
assessment needs to be
|
||||
undertaken:
|
||||
|
||||
RdSAP Inputs
|
||||
|
||||
Property Description:
|
||||
1.0 Property type: H House
|
||||
E End-Terrace
|
||||
2.0 Number of Storeys: 2
|
||||
Habitable Rooms: 3
|
||||
Heated Habitable Rooms: 3
|
||||
3.0 Date Built: Main Property B 1900-1929
|
||||
|
||||
|
||||
4.0 Dimensions:
|
||||
Dimension type: Internal
|
||||
Main Property
|
||||
Floor Room Heat Loss Party Wall
|
||||
Area Height Wall Perimeter Length
|
||||
[m2] [m] [m] [m]
|
||||
|
||||
1st Floor: 31.16 2.40 15.97 6.79
|
||||
Lowest Floor: 31.16 2.40 15.97 6.79 No
|
||||
|
||||
5.0 Conservatory:
|
||||
Is there a conservatory? No
|
||||
7.0 Walls:
|
||||
|
||||
|
||||
|
||||
|
||||
© Elmhurst Energy Systems Limited Registered Office Unit 16, St Johns Business Park, Lutterworth, Leicestershire LE17 4HB
|
||||
Summary Information
|
||||
Main Property
|
||||
|
||||
Type SO Solid Brick
|
||||
Insulation E External
|
||||
Insulation Thickness Unknown
|
||||
Conductivity Known No
|
||||
Thermal Conductivity Lambda = 0.04 W/m*K
|
||||
Wall Thickness Unknown No
|
||||
Wall Thickness 260 mm
|
||||
U-value Known No
|
||||
Party Wall Type S Solid masonry / timber / system build
|
||||
8.0 Roofs:
|
||||
Main Property
|
||||
|
||||
Type PA Pitched (slates/tiles), access to loft
|
||||
Insulation J Joists
|
||||
Insulation Thickness 150 mm
|
||||
U-value Known No
|
||||
8.1 Rooms in Roof:
|
||||
9.0 Floors:
|
||||
Main Property
|
||||
|
||||
Location G Ground floor
|
||||
Type S Solid
|
||||
Insulation A As built
|
||||
Default U-value 0.73
|
||||
U-value Known No
|
||||
10.0 Doors:
|
||||
Total Number of Doors 2
|
||||
Number of Insulated Doors 0
|
||||
11.0 Windows:
|
||||
Frame Frame Glazing Building U g Draught Permanent
|
||||
W H Area Glazing Type Location Orient. Data-Source
|
||||
Type Factor Gap Part value value Proofed Shutters
|
||||
Double with unknown
|
||||
4.31 1.00 4.31 PVC 0.70 12 mm Main External wall North Manufacturer 2.80 0.76 Yes None
|
||||
install date
|
||||
Double with unknown
|
||||
2.89 1.00 2.89 PVC 0.70 12 mm Main External wall West Manufacturer 2.80 0.76 Yes None
|
||||
install date
|
||||
Double with unknown
|
||||
2.89 1.00 2.89 PVC 0.70 12 mm Main External wall North Manufacturer 2.80 0.76 Yes None
|
||||
install date
|
||||
Double with unknown
|
||||
2.89 1.00 2.89 PVC 0.70 12 mm Main External wall East Manufacturer 2.80 0.76 Yes None
|
||||
install date
|
||||
|
||||
Draught Proofing 67 %
|
||||
12.0 Ventilation & Cooling
|
||||
No. of open chimneys 0
|
||||
No. of open flues 0
|
||||
No. of open chimneys/open flues attached to closed fire 0
|
||||
No. of flues attached to solid fuel boiler 0
|
||||
No. of open flues attached to other heater 0
|
||||
No. of blocked chimneys 0
|
||||
No. of intermittent extract fans 0
|
||||
No. of passive vents 0
|
||||
No. of flueless gas fires 0
|
||||
Fixed Space Cooling No
|
||||
Draught Lobby Unable to determine
|
||||
|
||||
|
||||
|
||||
|
||||
© Elmhurst Energy Systems Limited Registered Office Unit 16, St Johns Business Park, Lutterworth, Leicestershire LE17 4HB
|
||||
Summary Information
|
||||
12.1 Mechanical Ventilation
|
||||
Mechanical Ventilation No
|
||||
12.2 Air Pressure Test
|
||||
Test Method Not available
|
||||
13.0 Lighting
|
||||
Total number of bulbs 6
|
||||
Number of LED and CFL Known Yes
|
||||
Number of LED lights 0
|
||||
Number of CFL lights 0
|
||||
Total number of Low Energy 0
|
||||
Total number of incandescents 6
|
||||
14.0 Main Heating1
|
||||
PCDF boiler Reference 0
|
||||
Main Heating EES Code BGW
|
||||
Main Heating SAP Code 104
|
||||
Heat Emitter Radiators
|
||||
Heat pump age 2013 or later
|
||||
Flue Type Balanced
|
||||
Fan Assisted Flue Yes
|
||||
Design flow temperature <= 35°C
|
||||
PCDF Heating Controls 0
|
||||
Main Heating Controls EES CBE
|
||||
Main Heating Controls Sap SAP code 2106, Programmer, room thermostat and TRVs
|
||||
PCDF Compensator 0
|
||||
Percentage of Heat 100 %
|
||||
14.1 Main Heating2
|
||||
PCDF boiler Reference 0
|
||||
Main Heating EES Code
|
||||
Main Heating SAP Code 0
|
||||
Percentage of Heat 0%
|
||||
14.1 Community Heating/Heat Network
|
||||
Heating Type None
|
||||
14.2 Meters
|
||||
Electricity meter type Single
|
||||
Main gas Yes
|
||||
Electricity Smart Meter Present No
|
||||
Gas Smart Meter Present No
|
||||
15.0 Water Heating
|
||||
Water Heating Code HWP
|
||||
Water Heating SapCode 901
|
||||
Water Heating Fuel Type Mains gas
|
||||
15.1 Hot Water Cylinder
|
||||
Hot Water Cylinder Present No
|
||||
15.2 Community Hot Water
|
||||
PCDF boiler Reference 0
|
||||
16.0 Solar water heating
|
||||
Solar Water Heating No
|
||||
17.0 Waste Water Heat Recovery System
|
||||
Is WWHRS present in the property? No / Unknown
|
||||
1x.0 Baths and Showers
|
||||
Total Number of Baths 2
|
||||
Number of Baths Connected 0
|
||||
|
||||
Description Type Connected
|
||||
|
||||
1 Non-electric shower None
|
||||
|
||||
|
||||
|
||||
|
||||
© Elmhurst Energy Systems Limited Registered Office Unit 16, St Johns Business Park, Lutterworth, Leicestershire LE17 4HB
|
||||
Summary Information
|
||||
18.0 Flue Gas Heat Recovery System
|
||||
Present No
|
||||
19.0 Photovoltaic Panel
|
||||
Photovoltaic Panel None
|
||||
Export capable meter No
|
||||
20.0 Wind Turbine
|
||||
Terrain Type Urban
|
||||
Wind turbine present? No
|
||||
22.0 Special Features
|
||||
21.0 Small-Scale Hydro
|
||||
Electricity generated [kWh/year] 0.00
|
||||
|
||||
|
||||
|
||||
|
||||
© Elmhurst Energy Systems Limited Registered Office Unit 16, St Johns Business Park, Lutterworth, Leicestershire LE17 4HB
|
||||
Summary Information
|
||||
|
||||
Recommendations
|
||||
|
||||
Loft insulation (SAP increase too small)
|
||||
Flat roof insulation (Not applicable)
|
||||
Room-in-roof insulation (Not applicable)
|
||||
Cavity wall insulation (Not applicable)
|
||||
Solid wall insulation (SAP increase too small)
|
||||
Floor insulation (solid floor) (Recommended)
|
||||
Hot water cylinder insulation (Not applicable)
|
||||
Draught proofing (SAP increase too small)
|
||||
Low energy lighting (Recommended)
|
||||
Cylinder thermostat (Not applicable)
|
||||
Heating controls for wet central heating system (Already installed)
|
||||
Upgrade boiler, same fuel (Already installed)
|
||||
Change heating to condensing gas condensing boiler (fuel switch) (Not applicable)
|
||||
Flue gas heat recovery in conjunction with new boiler (Not applicable)
|
||||
Solar water heating (SAP increase too small)
|
||||
Heat recovery system for mixer showers (SAP increase too small)
|
||||
Double glazed windows (Already installed)
|
||||
Insulated doors (SAP increase too small)
|
||||
Solar photovoltaic panels (Recommended)
|
||||
Wind turbine (Not applicable)
|
||||
PV diverter (Not applicable)
|
||||
PV battery (Not applicable)
|
||||
Water heating controls (Not applicable)
|
||||
|
||||
Alternative Recommendations
|
||||
External wall insulation with cavity insulation (Not applicable)
|
||||
Biomass boiler (alternative) (Not applicable)
|
||||
Micro CHP (alternative) (Not applicable)
|
||||
|
||||
Related Party Disclosure
|
||||
|
||||
No related party
|
||||
|
||||
Addenda
|
||||
When considering the PV installation consider installing PV battery and a PV diverter for water heating
|
||||
|
||||
|
||||
|
||||
|
||||
© Elmhurst Energy Systems Limited Registered Office Unit 16, St Johns Business Park, Lutterworth, Leicestershire LE17 4HB
|
||||
|
||||
File diff suppressed because it is too large
Load diff
297
scripts/hyde/build_200004296092.py
Normal file
297
scripts/hyde/build_200004296092.py
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
"""Elmhurst build for UPRN 200004296092 (RdSAP-Schema-21.0.1, END-TERRACE HOUSE,
|
||||
2-storey, band B, SOLID BRICK 360 mm WITH EXTERNAL INSULATION (thickness lodged
|
||||
"NI" = unknown), pitched 150 mm loft, solid uninsulated ground floor, mains-gas
|
||||
COMBI (PCDB 16137 Ideal Logic combi 30 ~ generic Post-98 condensing combi),
|
||||
control 2106 (CBE), double glazed ~4.31 m², 2 doors, TFA ~62, party wall 6.79 m.
|
||||
Engine 71.74 = lodged 71 AFTER the §5.4 "assume 100 mm if unknown" wall fix
|
||||
(slice 5); this build validates that against the accredited Elmhurst worksheet.
|
||||
|
||||
The KEY page is `walls`: it prints Elmhurst's insulation dropdown so we can see
|
||||
how the accredited tool handles External insulation of unknown thickness. Run:
|
||||
DISPLAY=:99 python scripts/hyde/build_200004296092.py <page>
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import elmhurst_lib as E
|
||||
|
||||
DIM = "TabContainer_TabPanelMain_WebUserControlDimensionsMain_"
|
||||
WALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelExternalWallMain_WebUserControlWallMain_")
|
||||
PWALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
|
||||
"TabPanelPartyWallMain_WebUserControlPartyWallMain_")
|
||||
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_"
|
||||
MH1B = "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_"
|
||||
|
||||
_SAMPLE_DIR = (Path(__file__).parent.parent.parent
|
||||
/ "backend/epc_api/json_samples/real_life_examples"
|
||||
/ "RdSAP-Schema-21.0.1/uprn_200004296092")
|
||||
|
||||
|
||||
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", "H House")
|
||||
print("built_form ->", _pick(page, "DropDownListPropertyType2", "end-terrace")
|
||||
or _pick(page, "DropDownListPropertyType2", "end terrace"))
|
||||
E.set_text(page, "TextBoxStoreys", "2")
|
||||
E.set_text(page, "TextBoxHabitableRooms", "3")
|
||||
E.set_text(page, "TextBoxHeatedHabitableRooms", "3")
|
||||
print("date ->", _pick(page, "DropDownListDateBuiltMain", "1900-1929")
|
||||
or _pick(page, "DropDownListDateBuiltMain", "1900"))
|
||||
E.set_select(page, "DropDownListDateBuiltFirst", "")
|
||||
E.set_select(page, "DropDownListRoomInRoofMain", "")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def dimensions(page):
|
||||
E.goto(page, "Dimensions", "WebFormDimensions.aspx")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorAreaLowestFloor", "31.16")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.40")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "15.97")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "6.79")
|
||||
E.set_text(page, f"{DIM}TextBoxFloorArea1stFloor", "31.16")
|
||||
E.set_text(page, f"{DIM}TextBoxRoomHeight1stFloor", "2.40")
|
||||
E.set_text(page, f"{DIM}TextBoxWallPerimeter1stFloor", "15.97")
|
||||
E.set_text(page, f"{DIM}TextBoxPartyWallLength1stFloor", "6.79")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def walls(page):
|
||||
E.goto(page, "Walls", "WebFormWalls.aspx")
|
||||
print("WALL TYPE options:", _options(page, f"{WALL}DropDownListType"))
|
||||
_pick(page, f"{WALL}DropDownListType", "solid brick") \
|
||||
or _pick(page, f"{WALL}DropDownListType", "brick")
|
||||
page.wait_for_timeout(600)
|
||||
print("INSULATION options:", _options(page, f"{WALL}DropDownListInsulation"))
|
||||
ins = (_pick(page, f"{WALL}DropDownListInsulation", "external")
|
||||
or _pick(page, f"{WALL}DropDownListInsulation", "ext"))
|
||||
print("insulation ->", ins)
|
||||
page.wait_for_timeout(600)
|
||||
# If a thickness dropdown appears for external insulation, show it (this is
|
||||
# the slice-5 question: what does Elmhurst do with UNKNOWN thickness?).
|
||||
for suf in (f"{WALL}DropDownListInsulationThickness",
|
||||
f"{WALL}DropDownListExternalInsulationThickness"):
|
||||
if page.locator(f"#{E.FP}{suf}").count():
|
||||
print("INSULATION THICKNESS options:", _options(page, suf),
|
||||
"current:", page.locator(f"#{E.FP}{suf}").input_value())
|
||||
print("party wall ->",
|
||||
_pick(page, f"{PWALL}DropDownListPartyWallType", "determine")
|
||||
or _pick(page, f"{PWALL}DropDownListPartyWallType", "masonry"))
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def roofs(page):
|
||||
E.goto(page, "Roofs", "WebFormRoofs.aspx")
|
||||
_pick(page, f"{ROOF}DropDownListType", "access to loft") \
|
||||
or _pick(page, f"{ROOF}DropDownListType", "loft")
|
||||
_pick(page, f"{ROOF}DropDownListInsulationAt", "joists")
|
||||
E.set_select(page, f"{ROOF}DropDownListThickness", "150 mm")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def floors(page):
|
||||
E.goto(page, "Floors", "WebFormFloors.aspx")
|
||||
E.set_select(page, f"{FLOOR}DropDownListLocation", "G Ground floor")
|
||||
_pick(page, f"{FLOOR}DropDownListType", "solid")
|
||||
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.31, "North", _glazing(page))
|
||||
_delete_zero_rows(page)
|
||||
E.click_tab(page, "TabContainer_TabPanelDoorsPanel")
|
||||
E.set_text(page, f"{DP}TextBoxDoors", "2")
|
||||
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", "6")
|
||||
E.set_text(page, f"{LP}TextBoxLedLightsTotal", "0")
|
||||
E.set_text(page, f"{LP}TextBoxCflLightsTotal", "0")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def space_heating(page):
|
||||
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
|
||||
page.wait_for_timeout(1000)
|
||||
mhc = page.locator(f"#{E.MH1}TextBoxMainHeatingCode")
|
||||
code = mhc.input_value() if mhc.count() else ""
|
||||
if code and code not in ("0",):
|
||||
print(f"clearing leftover MainHeatingCode {code}")
|
||||
page.evaluate("""(id)=>{const e=document.getElementById(id);if(e){e.value='';
|
||||
e.dispatchEvent(new Event('change',{bubbles:true}));}}""", f"{E.MH1}TextBoxMainHeatingCode")
|
||||
page.wait_for_timeout(400)
|
||||
E.save_close(page)
|
||||
return
|
||||
# Generic Post-1998 condensing combi (~89% SAP 4b, ≈ PCDB 16137 Ideal Logic
|
||||
# combi 30). combi vs regular is set on the water page (from primary + no
|
||||
# cylinder). Control 2106 = CBE (programmer, room thermostat and TRVs).
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingCode",
|
||||
"^Gas", "Mains gas", "Boilers", "Post 1998", "Condensing",
|
||||
"Combi condens")
|
||||
print("code:", page.locator(f"#{E.MH1}TextBoxMainHeatingCode").input_value())
|
||||
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingControls",
|
||||
"^Boilers", "^Standard", "CBE Programmer, room thermostat and TRVs")
|
||||
print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value())
|
||||
E.set_select(page, "DropDownListSecondaryHeatingPresent", "No")
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def water_heating(page):
|
||||
E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx")
|
||||
E.click_tab(page, "TabContainer_TabPanelWaterHeating")
|
||||
page.wait_for_timeout(400)
|
||||
E.clear_hot_water_cylinder(page)
|
||||
E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode",
|
||||
"From Space Heating", "From the primary heating system")
|
||||
print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value())
|
||||
E.save_close(page)
|
||||
|
||||
|
||||
def download(page):
|
||||
_SAMPLE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
print("navigating via Recommendations ...")
|
||||
with page.expect_navigation(wait_until="networkidle", timeout=60_000):
|
||||
page.click("#ContentBody_buttonActionRecommendations_Link", timeout=15_000)
|
||||
page.wait_for_timeout(1500)
|
||||
for btn, fname in (("ContentBody_ContentPlaceHolder1_LinkButtonSummary", "elmhurst_summary.pdf"),
|
||||
("ContentBody_ContentPlaceHolder1_ButtonDebugInforPdf", "elmhurst_worksheet.pdf")):
|
||||
try:
|
||||
with page.expect_download(timeout=60_000) as dl:
|
||||
page.click(f"#{btn}", timeout=15_000)
|
||||
dl.value.save_as(str(_SAMPLE_DIR / fname))
|
||||
print("saved", fname)
|
||||
except Exception as e:
|
||||
print("!! download failed", fname, type(e).__name__, str(e)[:120])
|
||||
|
||||
|
||||
_ORDER = ["property_description", "dimensions", "walls", "roofs", "floors",
|
||||
"openings", "ventilation", "space_heating", "water_heating", "download"]
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in _ORDER:
|
||||
print("usage: build_200004296092.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())
|
||||
Loading…
Add table
Reference in a new issue