mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
chore(hyde): fix window-grid automation (keep good row, delete stale below) + pin 100050881708
SOLVED the window-grid blocker (per Junte's "keep one and edit, don't delete all"): set_single_window now ADDS the fresh window (it inserts at DOM index 0), then deletes the STALE rows BELOW it by index (high->low, never index 0) via the per-row Delete image button + JS-clicked Yes modal. The freshly-added row survives as the single window; no 0.00 junk strands to block the Recommendations gate. Verified end-to-end: cert 100050881708 built, worksheet downloaded, walls (0.70) + roof (0.19) match the engine EXACTLY. Combined with the earlier fill+Tab (area postback) + frame/gap (validation) fixes, the per-cert loop now runs autonomously on clean single-main certs. Pin: 100050881708 (semi, cavity filled, pitched 225mm, suspended ground floor, combi) engine 72 = lodged 70 +2; walls/roof accredited-Elmhurst- matched. NB the build entered the suspended ground floor as an upper "above unheated space" floor, so Elmhurst's worksheet SAP (61) is not a clean comparison — the engine uses the correct §3.12 suspended-ground U (0.74); the pin rests on the walls+roof match + lodged. No engine change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b4dbaef7f2
commit
6f2b73e613
5 changed files with 483 additions and 10 deletions
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,426 @@
|
|||
{
|
||||
"uprn": 100050881708,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 225 mm loft insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, filled cavity",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "To unheated space, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 3,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Below average lighting efficiency",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"postcode": "S65 3RW",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "ROTHERHAM",
|
||||
"built_form": 2,
|
||||
"created_at": "2026-05-27 15:09:39",
|
||||
"door_count": 2,
|
||||
"region_code": 3,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"number_baths": 1,
|
||||
"cylinder_size": 1,
|
||||
"number_baths_wwhrs": 0,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 0,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 17741
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 10.2,
|
||||
"sap_windows": [
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.7,
|
||||
"window_height": 1.4,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.2,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 7,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.6,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.6,
|
||||
"window_height": 0.7,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.6,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.2,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.2,
|
||||
"window_height": 1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 3,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 1.1,
|
||||
"window_height": 0.8,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.6,
|
||||
"window_height": 0.7,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
},
|
||||
{
|
||||
"pvc_frame": "true",
|
||||
"orientation": 1,
|
||||
"window_type": 1,
|
||||
"glazing_type": 2,
|
||||
"window_width": 0.6,
|
||||
"window_height": 1.1,
|
||||
"draught_proofed": "true",
|
||||
"window_location": 0,
|
||||
"window_wall_type": 1,
|
||||
"permanent_shutters_present": "N",
|
||||
"permanent_shutters_insulated": "N"
|
||||
}
|
||||
],
|
||||
"schema_type": "RdSAP-Schema-21.0.1",
|
||||
"uprn_source": "Energy Assessor",
|
||||
"country_code": "ENG",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Boiler and radiators, mains gas",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"air_tightness": {
|
||||
"description": "(not tested)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"dwelling_type": "Semi-detached house",
|
||||
"language_code": 1,
|
||||
"pressure_test": 4,
|
||||
"property_type": 0,
|
||||
"address_line_1": "9 Woodgrove Road",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2026-05-27",
|
||||
"inspection_date": "2026-05-27",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"total_floor_area": 63,
|
||||
"transaction_type": 14,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 3,
|
||||
"registration_date": "2026-05-27",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"pv_connection": 0,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"gas_smart_meter_present": "true",
|
||||
"is_dwelling_export_capable": "false",
|
||||
"wind_turbines_terrain_type": 2,
|
||||
"electricity_smart_meter_present": "true"
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"extract_fans_count": 2,
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"floor_heat_loss": 2,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.4,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 31.62,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 6.2,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 2,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 16.4,
|
||||
"quantity": "metres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": {
|
||||
"value": 2.41,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 31.62,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 6.2,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 16.4,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 2,
|
||||
"construction_age_band": "B",
|
||||
"party_wall_construction": 1,
|
||||
"wall_thickness_measured": "N",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "225mm",
|
||||
"wall_insulation_thickness": "NI",
|
||||
"floor_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 3,
|
||||
"heating_cost_current": {
|
||||
"value": 777,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 2.3,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 70,
|
||||
"lighting_cost_current": {
|
||||
"value": 79,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, room thermostat and TRVs",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"has_hot_water_cylinder": "false",
|
||||
"heating_cost_potential": {
|
||||
"value": 671,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 172,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 110,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a35,000 - \u00a310,000",
|
||||
"improvement_type": "W1",
|
||||
"improvement_details": {
|
||||
"improvement_number": 57
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 73,
|
||||
"environmental_impact_rating": 75
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 30,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3120 - \u00a3140",
|
||||
"improvement_type": "E",
|
||||
"improvement_details": {
|
||||
"improvement_number": 35
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 74,
|
||||
"environmental_impact_rating": 75
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": {
|
||||
"value": 184,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a38,000 - \u00a310,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 79,
|
||||
"environmental_impact_rating": 77
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.9,
|
||||
"energy_rating_potential": 79,
|
||||
"lighting_cost_potential": {
|
||||
"value": 45,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "21.0.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 172,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2070.91,
|
||||
"space_heating_existing_dwelling": 7543.77
|
||||
},
|
||||
"draughtproofed_door_count": 2,
|
||||
"energy_consumption_current": 205,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "5.02r0344",
|
||||
"energy_consumption_potential": 161,
|
||||
"environmental_impact_current": 71,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 77,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "C",
|
||||
"co2_emissions_current_per_floor_area": 37,
|
||||
"low_energy_fixed_lighting_bulbs_count": 4,
|
||||
"incandescent_fixed_lighting_bulbs_count": 4
|
||||
}
|
||||
|
|
@ -278,24 +278,52 @@ def add_combined_window(
|
|||
break
|
||||
|
||||
|
||||
def _delete_window_at_index(page: Page, i: int) -> bool:
|
||||
"""Delete the window grid row at DOM index `i` via its per-row Delete
|
||||
image button + the Yes modal (JS-clicked to bypass the modal background
|
||||
that intercepts real clicks). Returns True if a delete fired."""
|
||||
bid = (
|
||||
"ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_"
|
||||
f"GridViewExtendedWidows_DeleteButton_{i}"
|
||||
)
|
||||
if not page.evaluate("(id)=>!!document.getElementById(id)", bid):
|
||||
return False
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id);if(e)e.click();}", bid)
|
||||
page.wait_for_timeout(2000)
|
||||
yid = "ContentBody_ContentPlaceHolder1_DeleteWindowDialog_LinkButtonYes"
|
||||
if not page.evaluate(
|
||||
"(id)=>{const e=document.getElementById(id);return e?e.offsetParent!==null:false;}",
|
||||
yid,
|
||||
):
|
||||
return False
|
||||
page.evaluate("(id)=>{const e=document.getElementById(id);if(e)e.click();}", yid)
|
||||
page.wait_for_timeout(2500)
|
||||
return True
|
||||
|
||||
|
||||
def set_single_window(
|
||||
page: Page, total_area_m2: float, orientation: str = "North",
|
||||
glazing: str = "Double post or during 2022",
|
||||
) -> None:
|
||||
"""Leave the window grid holding EXACTLY one row of `total_area_m2`,
|
||||
deleting any rows carried over from a prior build of the shared
|
||||
assessment. Elmhurst refuses to delete the last row, so we add the fresh
|
||||
row FIRST, then delete every other row (they sort ahead of the new one).
|
||||
This is the accurate-per-run cleanup: the window grid is the only page
|
||||
that ACCUMULATES rather than overwrites across builds."""
|
||||
"""Leave the window grid holding EXACTLY one correct row of
|
||||
`total_area_m2`, cleaning any rows carried over from a prior build of the
|
||||
SHARED assessment.
|
||||
|
||||
The grid can't be emptied (Elmhurst refuses to delete the last row) and it
|
||||
has no inline edit, so per [[window-grid]] the reliable pattern is: ADD the
|
||||
fresh window (it inserts at DOM index 0), then delete the STALE rows BELOW
|
||||
it (indices high->low, never index 0) so the freshly-added row survives as
|
||||
the single remaining window. Deleting index 0 (delete_first_window) would
|
||||
drop the good row and strand a 0.00-area junk row that then blocks the
|
||||
Recommendations gate."""
|
||||
add_combined_window(page, total_area_m2, orientation, glazing)
|
||||
page.wait_for_timeout(500)
|
||||
guard = 0
|
||||
while window_row_count(page) > 1 and guard < 25:
|
||||
guard += 1
|
||||
before = window_row_count(page)
|
||||
delete_first_window(page)
|
||||
if window_row_count(page) >= before:
|
||||
break # couldn't delete (last-row guard / stuck) — stop
|
||||
i = window_row_count(page) - 1 # bottom-most (oldest) stale row
|
||||
if not _delete_window_at_index(page, i):
|
||||
break
|
||||
|
||||
|
||||
def reset_transient_state(page: Page) -> None:
|
||||
|
|
|
|||
|
|
@ -823,6 +823,25 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = (
|
|||
cert_num="uprn-47084930",
|
||||
sap_score=74,
|
||||
),
|
||||
# UPRN 100050881708 (corpus; no cert number lodged). RdSAP-21.0.1 native —
|
||||
# SEMI-DETACHED HOUSE, band B, cavity FILLED wall, pitched 225 mm loft,
|
||||
# SUSPENDED ground floor "to unheated space", mains-gas combi, no cylinder.
|
||||
# Lodged 70 / engine 72 (71.55). Built in accredited Elmhurst RdSAP10
|
||||
# (evidence saved: elmhurst_summary.pdf / elmhurst_worksheet.pdf): the WALL
|
||||
# (cavity, U 0.70, 69.56 m²) and ROOF (insulated slope 225 mm, U 0.19)
|
||||
# match the engine EXACTLY (walls 48.8 W/K, roof 6.0 W/K). The engine is
|
||||
# faithful on the checkable fabric; the +1.55 vs lodged is a small
|
||||
# methodology gap. (Elmhurst''s worksheet SAP 61 is NOT comparable — the
|
||||
# build entered the suspended ground floor as an upper "above unheated
|
||||
# space" floor, which Elmhurst bills via Table-20 exposed 1.20; the cert''s
|
||||
# §3.12 suspended-ground-floor U is 0.74, which the engine uses.) PINNED to
|
||||
# the observed engine 72.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_100050881708",
|
||||
cert_num="uprn-100050881708",
|
||||
sap_score=72,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue