test(accuracy): pin SAP-16.0 storage-flat uprn_10070004512 (built_form fix corpus)

Corpus validation of the modelling_e2e built_form fix. Cert 8742-6624-9300-2780-4926
(SAP-Schema-16.0, ground-floor electric-storage-heater flat) omits built_form; the
mapper now derives it from dwelling_type. built_form is ML-only so the fix is
SAP-neutral: engine 66 = lodged 66 exactly. Built in Elmhurst (evidence: epc.json +
summary + worksheet): worksheet 54, engine-on-Elmhurst-inputs 53 ≈ 54 → calculator
faithful. The +12 engine-vs-Elmhurst is a build/input gap (cert size-1 small cylinder
unrepresentable in Elmhurst's Normal/110L-minimum entry → higher HW + reduced-field
16.0 defaults). Pinned engine 66.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-06-23 15:35:06 +00:00
parent 5737923622
commit 17b1d63f0e
5 changed files with 591 additions and 0 deletions

View file

@ -0,0 +1,281 @@
{
"uprn": 10070004512,
"roofs": [
{
"description": "(another dwelling above)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"walls": [
{
"description": "Cavity wall, filled cavity",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"floors": [
{
"description": "To external air, no insulation (assumed)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"status": "entered",
"windows": [
{
"description": "Fully double glazed",
"energy_efficiency_rating": 3,
"environmental_efficiency_rating": 3
}
],
"lighting": {
"description": "Low energy lighting in all fixed outlets",
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
},
"postcode": "BR1 4QF",
"hot_water": {
"description": "Electric immersion, off-peak",
"energy_efficiency_rating": 3,
"environmental_efficiency_rating": 1
},
"post_town": "BROMLEY",
"created_at": "2012-04-20 10:59:22.000000",
"door_count": 1,
"glazed_area": 1,
"region_code": 14,
"report_type": 2,
"sap_heating": {
"wwhrs": {
"rooms_with_bath_and_or_shower": 1,
"rooms_with_mixer_shower_no_bath": 0,
"rooms_with_bath_and_mixer_shower": 0
},
"cylinder_size": 1,
"water_heating_code": 903,
"water_heating_fuel": 29,
"main_heating_details": [
{
"has_fghrs": "N",
"main_fuel_type": 29,
"heat_emitter_type": 0,
"main_heating_number": 1,
"main_heating_control": 2401,
"main_heating_category": 7,
"main_heating_fraction": 1,
"sap_main_heating_code": 402,
"main_heating_data_source": 2
}
],
"immersion_heating_type": 1,
"has_fixed_air_conditioning": "false"
},
"sap_version": 9.91,
"schema_type": "SAP-Schema-16.0",
"uprn_source": "Energy Assessor",
"country_code": "EAW",
"main_heating": [
{
"description": "Electric storage heaters",
"energy_efficiency_rating": 3,
"environmental_efficiency_rating": 1
}
],
"dwelling_type": "Ground-floor flat",
"language_code": 1,
"property_type": 2,
"address_line_1": "54a, Boyland Road",
"schema_version": "LIG-16.0",
"assessment_type": "RdSAP",
"completion_date": "2012-04-20",
"inspection_date": "2012-04-20",
"extensions_count": 0,
"measurement_type": 1,
"sap_flat_details": {
"level": 1,
"top_storey": "N",
"flat_location": 0,
"heat_loss_corridor": 0
},
"total_floor_area": 33,
"transaction_type": 3,
"conservatory_type": 1,
"heated_room_count": 2,
"registration_date": "2012-04-20",
"restricted_access": 1,
"sap_energy_source": {
"main_gas": "N",
"meter_type": 1,
"photovoltaic_supply": {
"percent_roof_area": 0
},
"wind_turbines_count": 0,
"wind_turbines_terrain_type": 1
},
"secondary_heating": {
"description": "Portable electric heaters (assumed)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
},
"sap_building_parts": [
{
"identifier": "Main Dwelling",
"wall_dry_lined": "N",
"floor_heat_loss": 1,
"roof_construction": 3,
"wall_construction": 4,
"building_part_number": 1,
"sap_floor_dimensions": [
{
"floor": 0,
"room_height": 2.42,
"floor_insulation": 1,
"total_floor_area": 33.24,
"floor_construction": 0,
"heat_loss_perimeter": 10.39
}
],
"wall_insulation_type": 2,
"construction_age_band": "B",
"wall_thickness_measured": "N",
"roof_insulation_location": "ND",
"roof_insulation_thickness": "ND"
}
],
"low_energy_lighting": 100,
"solar_water_heating": "N",
"bedf_revision_number": 321,
"habitable_room_count": 2,
"heating_cost_current": {
"value": 303,
"currency": "GBP"
},
"insulated_door_count": 0,
"co2_emissions_current": {
"value": 2.9,
"quantity": "tonnes per year"
},
"energy_rating_average": 60,
"energy_rating_current": 66,
"lighting_cost_current": {
"value": 23,
"currency": "GBP"
},
"main_heating_controls": [
{
"description": "Manual charge control",
"energy_efficiency_rating": 2,
"environmental_efficiency_rating": 2
}
],
"multiple_glazing_type": 3,
"open_fireplaces_count": 0,
"has_hot_water_cylinder": "false",
"heating_cost_potential": {
"value": 190,
"currency": "GBP"
},
"hot_water_cost_current": {
"value": 106,
"currency": "GBP"
},
"mechanical_ventilation": 0,
"percent_draughtproofed": 100,
"suggested_improvements": [
{
"sequence": 1,
"typical_saving": {
"value": 86,
"currency": "GBP"
},
"indicative_cost": "\u00a3800 - \u00a31,200",
"improvement_type": "W",
"improvement_details": {
"improvement_number": 47
},
"improvement_category": 5,
"energy_performance_rating": 73,
"environmental_impact_rating": 58
},
{
"sequence": 2,
"typical_saving": {
"value": 27,
"currency": "GBP"
},
"indicative_cost": "\u00a3600 - \u00a3800",
"improvement_type": "L",
"improvement_details": {
"improvement_number": 25
},
"improvement_category": 5,
"energy_performance_rating": 75,
"environmental_impact_rating": 60
}
],
"co2_emissions_potential": {
"value": 2.1,
"quantity": "tonnes per year"
},
"energy_rating_potential": 75,
"lighting_cost_potential": {
"value": 23,
"currency": "GBP"
},
"alternative_improvements": [
{
"sequence": 1,
"typical_saving": {
"value": 24,
"currency": "GBP"
},
"improvement_type": "J2",
"improvement_details": {
"improvement_number": 54
},
"improvement_category": 6,
"energy_performance_rating": 76,
"environmental_impact_rating": 93
},
{
"sequence": 2,
"typical_saving": {
"value": 84,
"currency": "GBP"
},
"improvement_type": "Z1",
"improvement_details": {
"improvement_number": 51
},
"improvement_category": 6,
"energy_performance_rating": 80,
"environmental_impact_rating": 81
}
],
"hot_water_cost_potential": {
"value": 106,
"currency": "GBP"
},
"renewable_heat_incentive": {
"water_heating": 1434,
"space_heating_existing_dwelling": 4064
},
"seller_commission_report": "Y",
"energy_consumption_current": 497,
"has_fixed_air_conditioning": "false",
"multiple_glazed_proportion": 100,
"calculation_software_version": 4.1,
"energy_consumption_potential": 365,
"environmental_impact_current": 47,
"fixed_lighting_outlets_count": 5,
"current_energy_efficiency_band": "D",
"environmental_impact_potential": 60,
"has_heated_separate_conservatory": "false",
"potential_energy_efficiency_band": "C",
"co2_emissions_current_per_floor_area": {
"value": 88,
"quantity": "kg/m2 per year"
},
"low_energy_fixed_lighting_outlets_count": 5
}

View file

@ -0,0 +1,288 @@
"""Elmhurst build for UPRN 10070004512 (SAP-Schema-16.0, GROUND-FLOOR FLAT,
band B, cavity FILLED, ELECTRIC STORAGE HEATERS (SAP 402 SEB, manual charge
control CSA/2401) + electric immersion off-peak (Economy-7 Dual meter) with a
cylinder (size 1), roof = another dwelling above, floor to EXTERNAL AIR, double
glazed, TFA 33.24, window 4.88 . Engine 66 = lodged 66.
P1 of the modelling_e2e corpus validation the built_form fix cert (16.0 omitted
built_form; mapper derives it from dwelling_type flatmodal 4). built_form is
ML-only so SAP-neutral; engine reproduces lodged exactly. Storage-heater build
(see build_10022893721.py). Engine models NO secondary (sap_heating.
secondary_heating_type is None) secondary present=No to match. Run:
DISPLAY=:99 python scripts/hyde/build_10070004512.py <page>
"""
from __future__ import annotations
import sys
import elmhurst_lib as E
DIM = "TabContainer_TabPanelMain_WebUserControlDimensionsMain_"
WALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_"
"TabPanelExternalWallMain_WebUserControlWallMain_")
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_"
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", "F Flat")
_pick(page, "DropDownListPropertyType2", "mid-terrace") # built_form 4
E.set_text(page, "TextBoxStoreys", "1")
E.set_text(page, "TextBoxHabitableRooms", "2")
E.set_text(page, "TextBoxHeatedHabitableRooms", "2")
print("date ->", _pick(page, "DropDownListDateBuiltMain", "1900-1929")) # band B
E.set_select(page, "DropDownListDateBuiltFirst", "")
E.set_select(page, "DropDownListRoomInRoofMain", "")
E.save_close(page)
def flats(page):
E.goto(page, "Flats", "WebFormFlats.aspx")
E.set_select(page, "DropDownListPositionOfFlat", "Ground Floor")
E.set_text(page, "TextBoxFloor", "0")
E.set_select(page, "RadioButtonListFlatCoridor", "None")
E.save_close(page)
def dimensions(page):
E.goto(page, "Dimensions", "WebFormDimensions.aspx")
E.set_text(page, f"{DIM}TextBoxFloorAreaLowestFloor", "33.24")
E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.42")
E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "10.39")
# 16.0 lodges no party_wall_length; a flat's side party walls are unmodelled by
# the engine. Try 0 (Elmhurst may require non-zero — adjust if Recommendations
# complains).
E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "0")
E.save_close(page)
def walls(page):
E.goto(page, "Walls", "WebFormWalls.aspx")
E.set_select(page, f"{WALL}DropDownListType", "CA Cavity")
page.wait_for_timeout(400)
print("insulation ->", _pick(page, f"{WALL}DropDownListInsulation", "filled"))
E.save_close(page)
def roofs(page):
E.goto(page, "Roofs", "WebFormRoofs.aspx")
_pick(page, f"{ROOF}DropDownListType", "another dwelling above")
E.save_close(page)
def floors(page):
E.goto(page, "Floors", "WebFormFloors.aspx")
# Floor is "to external air" — that is the LOCATION/exposure, not a TYPE.
_pick(page, f"{FLOOR}DropDownListLocation", "external air") # E To external air
page.wait_for_timeout(400)
_pick(page, f"{FLOOR}DropDownListType", "solid") # construction; U from exposure
ins = page.locator(f"#{E.FP}{FLOOR}DropDownListInsulation")
if ins.count():
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.88, "North", _glazing(page))
_delete_zero_rows(page)
E.click_tab(page, "TabContainer_TabPanelDoorsPanel")
E.set_text(page, f"{DP}TextBoxDoors", "1")
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", "5")
E.set_text(page, f"{LP}TextBoxLedLightsTotal", "5") # 100% low energy
E.set_text(page, f"{LP}TextBoxCflLightsTotal", "0")
E.save_close(page)
def space_heating(page):
# Electric storage heaters (SAP 402 = SEB), manual charge control (SAP 2401 =
# CSA). Two passes: clear bound PCDB boiler first, then set the SAP-table code.
E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx")
page.wait_for_timeout(1000)
rid = f"{E.MH1}TextBoxPCDFBoilerReference"
ref = page.locator(f"#{rid}").input_value()
if ref not in ("0", ""):
print(f"clearing bound PCDB boiler {ref} -> 0 (rerun space_heating)")
page.evaluate("""(rid)=>{const r=document.getElementById(rid);r.value='0';
r.dispatchEvent(new Event('change',{bubbles:true}));}""", rid)
page.wait_for_timeout(500)
E.save_close(page)
return
E.set_heating_dialog(page, "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_ButtonMainHeatingCode",
"^Electric", "^Electric", "Storage", "SEB Modern slimline")
print("code:", page.locator(f"#{E.MH1}TextBoxMainHeatingCode").input_value())
E.set_heating_dialog(page, "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_ButtonMainHeatingControls",
"Storage Radiator", "CSA Manual charge control")
print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value())
E.set_select(page, "DropDownListSecondaryHeatingPresent", "No")
# Economy-7 Dual meter (cert meter_type 1) — hidden Meters sub-tab.
E.click_tab(page, "TabContainer_TabPanelMeters")
E.set_select(page, "TabContainer_TabPanelMeters_RadioButtonListElectricityType", "Dual")
print("meter:", page.locator("#ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMeters_RadioButtonListElectricityType").input_value())
E.save_close(page)
def water_heating(page):
# Electric immersion off-peak (Dual) WITH cylinder (size 1 = small). The
# immersion code REQUIRES a cylinder.
E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx")
E.click_tab(page, "TabContainer_TabPanelWaterHeating")
page.wait_for_timeout(600)
E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode",
"Water Heater", "^Electric", "Immersion")
print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value())
cid = f"{E.FP}{WH}CheckBoxHotWaterCylinder"
cyl = page.locator(f"#{cid}")
if cyl.count() and not cyl.is_checked():
try:
with page.expect_navigation(wait_until="load", timeout=8000):
page.evaluate(
"""(id)=>{const c=document.getElementById(id);if(c){c.checked=true;
c.dispatchEvent(new Event('click',{bubbles:true}));
c.dispatchEvent(new Event('change',{bubbles:true}));}}""", cid)
except Exception:
page.wait_for_timeout(2000)
print("cylinder present:", cyl.is_checked() if cyl.count() else "n/a")
print("cyl sizes:", _options(page, f"{WH}DropDownListCylinderSize"))
_pick(page, f"{WH}DropDownListCylinderSize", "small") or \
E.set_select(page, f"{WH}DropDownListCylinderSize", "Normal")
E.set_select(page, f"{WH}DropDownListInsulated", "Foam")
isuf = f"{WH}DropDownListInsulationThickness"
if page.locator(f"#{E.FP}{isuf}").count():
_pick(page, isuf, "Unknown") or E.set_select(page, isuf, "25 mm")
imm = page.locator(f"#{E.FP}{WH}RadioButtonListImmersionHeater")
if imm.count():
E.set_select(page, f"{WH}RadioButtonListImmersionHeater", "Dual")
E.save_close(page)
_ORDER = ["property_description", "flats", "dimensions", "walls", "roofs",
"floors", "openings", "ventilation", "space_heating", "water_heating"]
def main():
if len(sys.argv) < 2 or sys.argv[1] not in _ORDER:
print("usage: build_10070004512.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())

View file

@ -603,6 +603,28 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = (
"engine prices 100% at the low rate; see elmhurst_worksheet.pdf (243-246)"
),
),
# UPRN 10070004512 → cert 8742-6624-9300-2780-4926. SAP-Schema-16.0, GROUND-
# FLOOR FLAT, band B, cavity FILLED, ELECTRIC STORAGE HEATERS (SAP 402 SEB,
# manual charge CSA/2401) + electric immersion off-peak (Economy-7 Dual meter)
# with a small cylinder (size 1), roof = another dwelling above, floor to
# EXTERNAL AIR, double glazed, TFA 33.24. Engine 66 = lodged 66 EXACTLY.
# This is the modelling_e2e built_form fix cert: 16.0 omits `built_form`, which
# RdSapSchema17_1 requires; the mapper derives it from dwelling_type (flat →
# modal 4). built_form is ML-only (the SAP calculator never reads it) so the fix
# is SAP-NEUTRAL — the engine reproduces the lodged score regardless. Built in
# Elmhurst RdSAP10 (evidence saved: elmhurst_summary.pdf / elmhurst_worksheet.pdf):
# worksheet SAP 54, engine on Elmhurst's own parsed inputs 53 ≈ 54 → calculator
# faithful. The engine 66 vs Elmhurst 54 (+12) is an input/build gap dominated by
# HOT WATER (engine 1272 vs Elmhurst 1948 kWh): the cert lodges a size-1 (small)
# cylinder but Elmhurst's RdSAP entry has no "Small" option (Normal/110 L is the
# smallest), forcing a larger cylinder + more storage loss; plus the reduced-field
# 16.0 floor/party-wall defaults. PINNED to the observed engine 66 (= lodged 66).
RealCertExpectation(
schema="SAP-Schema-16.0",
sample="uprn_10070004512",
cert_num="8742-6624-9300-2780-4926",
sap_score=66,
),
)