more examples

This commit is contained in:
Jun-te Kim 2026-06-19 09:51:49 +00:00
parent 269a7fdaa7
commit e6a829aaea
35 changed files with 1467 additions and 451 deletions

View file

@ -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
`<select>`s by JS (`.value` + dispatch `change` → drives the AutoPostBack), and
COMMIT by `page.mouse.click(x,y)` at the OK/Select control's centre (coordinate
clicks hit the topmost element = the dialog). Helpers: `E.select_boiler(page,
query, ref)`, `E.set_heating_dialog(page, button_suffix, *level_regexes)` (water
method + main-heating controls), `E.dialog_commit(page, label)`,
`E.clear_hot_water_cylinder(page)`.
- **Boiler by exact PCDB code** (eliminates the boiler-efficiency divergence):
`E.select_boiler(page, "<brand/model search>", "<pcdb_id>")` drives the search
dialog two-step (click the result ROW to highlight → click the top **Select**
span). The lodged `main_heating_index_number` IS the `pcdb_id`. Look up the Ref/
type/efficiency in **`domain/elmhurst/pcdb_gas_oil_boiler_codes.csv`**. NB:
typing the number straight into the *PCDF boiler Reference* box is COSMETIC — it
doesn't re-resolve the boiler; only the search dialog sets type/efficiency. A
combi keeps the cylinder checked from a prior regular boiler → `clear_hot_water_
cylinder()`, then set water method "From Space Heating → primary" via
`set_heating_dialog(..ButtonWaterHeatingCode, 'From Space Heating', 'From the
primary heating system')`.
- **Main-heating control must match the boiler's system type.** Heat-pump leftover
control (e.g. CHJ/2210, ctrl-group-2) is invalid for a gas boiler (ctrl-group-1)
and blocks the report. For a boiler programmer+room-stat+TRVs (SAP 2106):
`set_heating_dialog(..ButtonMainHeatingControls, '^Boilers', '^Standard', 'CBE
Programmer, room thermostat and TRVs')`.
## Limitations / next improvements (make the campaign scale)
- **Per-assessment GUID**`elmhurst_lib.py` reuses one `ASSESSMENT_GUID`
(Khalim-test), overwritten per UPRN. For parallelism, create one assessment per
UPRN and parameterize the GUID.
- **Data-driven fill**`elmhurst_lib.py` gives the primitives; a per-cert driver
that reads field values straight from `elmhurst_inputs.md` (or a JSON sidecar)
would remove the remaining hand-keying. `elmhurst_fill.py` is the older template.
- **More dialogs** — boiler cascade is automatable; the control-code (2110) and
FGHRS/MV-unit search dialogs still need the open→search→select→OK pattern.
(Khalim-test), overwritten per UPRN. ⚠ This is a SHARED assessment: a concurrent
user (Khalim) overwrote a build mid-session once — coordinate before long runs,
or (better) create one assessment per UPRN and parameterize the GUID.
- **Data-driven fill**`build_<uprn>.py` per-cert scripts now drive every page
incl. all dialogs via the `elmhurst_lib` helpers (see build_100021943298.py /
build_10096028301.py as templates). A generic driver reading `elmhurst_inputs.md`
is the remaining step. `elmhurst_fill.py` is the older single-cert template.
- **Per-schema mappers** — every SAP/RdSAP schema gets its own dedicated
`from_*_schema_*` mapper in `datatypes/epc/domain/mapper.py` (the 16.x family
shares `_normalize_sap_schema_16_x`; full-SAP revisions delegate to
`from_sap_schema_17_1`). Add new schema coverage as a dedicated method + dispatch
branch, never by tuple-stuffing. Guard with the RdSAP-21.0.1 corpus gauge.

View file

@ -10,6 +10,19 @@ UPRN, tick it and annotate: `— <schema> · eng <X> / elm <Y> · <note>`.
2020 new-build flat) — full loop proven: eng 77 / elm 78, engine-on-Elmhurst-
inputs 79 (calculator faithful within ~1). Use it to sanity-check the pipeline.
## Do next — new schemas (need a mapper)
These two were flagged NOT MAPPABLE in the UI (red ✗). Mapper coverage now ADDED
(dedicated per-schema methods in `EpcPropertyDataMapper`; corpus gauge green, 0
new pyright errors). Elmhurst build + pin still pending — run the normal loop.
- [x] 🔧 10096028301 — SAP-Schema-19.1.0 (full-SAP g/f FLAT, band M, combi PCDB 17929, MEV, AP50 3.5) · eng 82 / elm 82 (lodged 85) · PINNED engine 82. 🔧 mapper added: `from_sap_schema_19_1_0`. Built in Elmhurst (boiler 17929 via search, control CBE/2106, water from primary, MEV on, AP50 Blower Door 3.5+cert). Engine EXACTLY matches Elmhurst worksheet (82.11 vs 82); engine-on-Elmhurst-inputs 82.16 ≈ 82 → calculator faithful. 3 vs lodged = measured-U-vs-RdSAP-default + MEV extract-not-recovery (documented). No mapper change beyond coverage.
- [x] 🔧 100021943298 — SAP-Schema-16.1 (g/f FLAT, band B, solid-brick internal, combi PCDB 10328) · eng 76 / elm 75 (lodged 72) · PINNED engine 76. 🔧 mapper added: `from_sap_schema_16_1`. Built in Elmhurst (boiler 10328 via search, control CBE/2106, water from primary, wall insulation thickness Unknown); worksheet 75 → engine within ~1 (tightest agreement, reduced-field). Boiler-select + water-heating + control dialogs all driven via automation (two-step row→Select / cascade + coordinate-OK). No mapper change beyond coverage.
(Refactor: split the full-SAP + 16.x dispatch tuples into dedicated per-schema
mappers — from_sap_schema_17_0/18_0_0/19_1_0 + from_sap_schema_16_0/1/2/3 — so
each schema has one mapper home for future divergence; all delegate to shared logic.)
## E2E testing set
UPRNs needed for end-to-end testing (also tracked in The 100 below).
@ -41,8 +54,8 @@ Skip the 🚩 MVHR / 🚩 heat-pump-fuel and ⛔ sparse certs.
- [x] 10093116543 — SAP-17.1 (2017 gas-combi semi) · eng 81 / elm 77 (lodged 82) · PINNED engine 81. +4 = documented full-SAP→RdSAP residual, NOT a mapper bug: ~1.5 floor-U (cert lodges measured 0.11 vs Elmhurst RdSAP solid default 0.23; U-known override disabled) + ~1 boiler-eff (cert PCDB 17644 88.5% vs Elmhurst generic BGW combi 84%; PCDB search disabled, 89% cascade option is a regular boiler needing a cylinder) + ~0.5 roof band-L/infil. Conservatory leftover from prior cert cleared (worksheet 73→77). No mapper change.
- [x] 🔧 10093116529 — SAP-17.1 (2017 gas-combi ground-floor FLAT, TFA 49) · eng 81 / elm 78 (lodged 81) · PINNED engine 81. 🔧 FIXED a real calc bug: full-SAP flats took the 0.25 house party-wall default instead of the RdSAP Table-15 flat 0.0 (flatness is in dwelling_type, not property_type) — heat_transmission._is_flat_or_maisonette_dwelling; +regression test. Cert lodges party u_value 0; Elmhurst worksheet 0.0; fix 80→81. Residual +3 vs Elmhurst = documented full-SAP→RdSAP gap (measured wall 0.184/floor 0.12 + PCDB 88.5% vs generic 84%). Calculator faithful: fed Elmhurst's Us, HTC 93.4 vs ~94. House→Flat Elmhurst switch (storeys→1, roof→another-dwelling-above). No mapper change.
- [ ] 🔧 100020933699 — SAP-16.2 SCHEMA COVERAGE ADDED (end-terrace house, band G). 16.2 is structurally RdSAP-17.1 (reduced fields, glazed_area band, construction-code building parts) under a different name; mapped via `_normalize_sap_schema_16_2` (renames windows→window, main_gas→mains_gas, boiler_index_number→main_heating_index_number, wwhrs→instantaneous_wwhrs + defaults) → reuses from_rdsap_schema_17_1. 🔧 Also fixed: "Single glazed" description honoured when multiple_glazing_type="ND" (was defaulting to double; RdSAP-21 code 5) → eng 72→71. +4 regression tests, sap_16_2.json fixture, 0 new pyright errors. eng 71 / lodged 70. ⚠ Known gap: 16.2 lodges no party_wall_length → end-terrace party wall unmodelled (likely the residual +1). ⏳ Elmhurst build (partial: PropDesc/Dims/Walls/Roofs done) + pin still pending.
- [ ] 🔧 44012843 — SAP-16.3 schema coverage (same _normalize_sap_schema_16_x reduced-field path as 16.2) · eng 79 / lodged 81 · g/f flat band K · Elmhurst pin pending
- [ ] 🔧 10023444324 — SAP-17.0 schema coverage (full-SAP shape ≡ 17.1; dispatched to from_sap_schema_17_1, no normalization) · eng 80 / lodged 82 · Elmhurst pin pending
- [x] 🔧 44012843 — SAP-16.3 (g/f FLAT, band K, cavity insulated, REGULAR boiler PCDB 9895 + cylinder, double glazed) · eng 79 / elm 78 (lodged 81) · PINNED engine 79. Built in Elmhurst (boiler 9895 via search, control CBE/2106, cylinder Large/foam/50mm, water from primary). Engine within ~1 (78.82 vs 78); engine-on-Elmhurst-inputs 78.48 ≈ 78 → calculator faithful. 2 vs lodged = reduced-field defaults. No mapper change beyond coverage.
- [x] 🔧 10023444324 — SAP-17.0 (full-SAP g/f FLAT, band M, combi PCDB 16211, MEV, AP50 3.2, party wall 6.43) · eng 80 / elm 80 (lodged 82) · PINNED engine 80. Built in Elmhurst (boiler 16211 via search, control CBE/2106, water from primary, MEV on, AP50 Blower Door 3.2, party wall 6.43). Engine EXACTLY matches Elmhurst worksheet (80.13 vs 80); engine-on-Elmhurst-inputs 81.03 ≈ 80 → calculator faithful. 2 vs lodged = full-SAP→RdSAP residual (measured U + MEV). No mapper change beyond coverage.
- [⚠] 10092970673 — SAP-17.0 · eng 77 / lodged 86 · 🚩 MVHR idx 500418 not credited (flagged)
- [⚠] 10094601287 — SAP-18.0.0 · eng 80 / lodged 84 · 🚩 MVHR idx 500230 not credited (flagged)
- [ ] 10090844932 — RdSAP-20.0.0 · eng 78 / lodged 78
@ -100,7 +113,7 @@ Skip the 🚩 MVHR / 🚩 heat-pump-fuel and ⛔ sparse certs.
- [⚠] 10093305101 — SAP-17.1 · eng 81 / lodged 85 · 🚩 MVHR idx 500140 not credited (flagged)
- [ ] 100020933894 — SAP-16.0 · eng 61 / lodged 56
- [ ] 100020937013 — RdSAP-20.0.0 · eng 70 / lodged 73
- [ ] 🔧 10023444320 — SAP-17.0 schema coverage (full-SAP ≡ 17.1) · eng 81 / lodged 81 · Elmhurst pin pending
- [x] 🔧 10023444320 — SAP-17.0 (full-SAP MID-FLOOR FLAT, sibling of 324, combi PCDB 16211, MEV, AP50 3.09, no party wall) · eng 81 / elm 82 (lodged 81) · PINNED engine 81. Built in Elmhurst (boiler 16211, control CBE/2106, water from primary, MEV, AP50 3.09, mid-floor=floor to dwelling below). Within ~1 (81.38 vs 82); engine-on-Elmhurst-inputs 82.46 ≈ 82 → faithful. No mapper change beyond coverage.
- [ ] 100062188801 — SAP-16.3 · eng 68 / lodged 70
- [ ] 10008048040 — SAP-16.2 · eng 77 / lodged 75
- [ ] 10093101966 — SAP-17.1 · eng 82 / lodged 84

View file

@ -0,0 +1,98 @@
# Elmhurst RdSAP inputs — UPRN 100021943298 (cert 0498-6963-7270-0822-1980, SAP-Schema-16.1)
**Lodged SAP:** 72 **Our engine:** 76 (continuous ~75.x) ← compare Elmhurst against the engine
**Property:** Ground-floor FLAT, age band B (19001929), mains-gas combi + radiators, TFA 78 m²
**Schema note:** SAP-Schema-16.1 is reduced-field (RdSAP-shaped) — mapped via the
new `from_sap_schema_16_1``_normalize_sap_schema_16_x``from_rdsap_schema_17_1`.
Descriptions + glazed-area band, no measured U-values, so engine and Elmhurst should
both use RdSAP age-band/description U-values — expect TIGHTER agreement (the +4 over
lodged is the reconciliation target).
**Known divergences / gaps to watch:**
- **Party wall:** 16.1 lodges no `party_wall_length`; normalizer defaults 0 → engine
models no party wall. For a ground-floor flat the side/party walls to other dwellings
are handled by Elmhurst's Flat position + corridor — keep party-wall length 0 and check
the worksheet party-wall line.
- Age band B → Elmhurst on-screen band **B 1900-1929**.
## Property Description
| Elmhurst field | Value | Notes |
|---|---|---|
| Property Type | **Flat** | property_type 2 |
| Built form | **End-Terrace** | built_form 3 |
| Age band | **B 1900-1929** | construction_age_band B |
| Storeys | 1 | ground-floor flat, single storey |
| Habitable rooms | 3 | habitable_room_count 3 |
## Flats
| Elmhurst field | Value | Notes |
|---|---|---|
| Position of flat | **Ground Floor** | dwelling_type "Ground-floor flat" |
| Floor | 0 | ground = storey 0 |
| Corridor | None | all-exposed (no unheated corridor lodged) |
## Dimensions
| Elmhurst field | Value | Notes |
|---|---|---|
| Floor area (lowest) | 77.69 m² | floor 0 |
| Room height | 2.65 m | room_height |
| Heat-loss perimeter | 28.9 m | heat_loss_perimeter |
| Party-wall length | **0** | 16.1 lodges none |
## Walls
| Elmhurst field | Value | Notes |
|---|---|---|
| Construction | **Solid Brick** | wall_construction 3 |
| Insulation | **Internal** | wall_insulation_type 3 = internal insulation ("Solid brick, with internal insulation") |
| Thickness | (as built) | wall_thickness_measured True |
## Roofs
| Elmhurst field | Value | Notes |
|---|---|---|
| Type | **Another dwelling above** | roof "(another dwelling above)" → no roof heat loss |
## Floors
| Elmhurst field | Value | Notes |
|---|---|---|
| Location / type | Ground floor / **Suspended** | "Suspended, no insulation (assumed)" |
| Insulation | **As built** (none) | no insulation |
## Openings
| Elmhurst field | Value | Notes |
|---|---|---|
| Windows | **Double glazed**, total 11.544 m² (4 × 2.886, N/E/S/W) | glazed_area band 1, multiple_glazing_type 2 (double). Enter ONE combined "Double …" row of 11.544 m². |
| Doors | **2, uninsulated** | door_count 2, insulated_door_count 0 |
## Ventilation & Lighting
| Elmhurst field | Value | Notes |
|---|---|---|
| Ventilation | Natural | no MV |
| Extract fans / flues | 0 / 0 | none lodged |
| Sheltered sides | 1 | sheltered_sides 1 |
| Air Pressure Test | **Not available** (no test lodged) | clear any Blower Door |
| Lighting | **100% low-energy** (6 of 6 outlets) | low_energy 6 / total 6 |
## Space Heating
| Elmhurst field | Value | Notes |
|---|---|---|
| Main heating | Mains gas **combi** + radiators | fuel 26, emitter 1, control 2106, fan flue, PCDB 10328, category 2, NO cylinder → BGW condensing combi cascade |
| Secondary | None | secondary "None" |
## Water Heating
| Elmhurst field | Value | Notes |
|---|---|---|
| Water heating | From main (901), gas | water_heating from combi |
| Hot water cylinder | **None** | has_hot_water_cylinder False (combi/instantaneous) |
| Solar / WWHRS / FGHRS | None | — |
## Fields to clear in Elmhurst (do NOT map)
| Elmhurst field | Set to | Why absent |
|---|---|---|
| Conservatory | none | not lodged |
| Air Pressure Test method | **Not available** | no test lodged (clear any prior Blower Door) |
| Mechanical ventilation | off | natural ventilation |
| Cylinder size/insulation | (gone — combi) | no cylinder |
| Main Heating 2 / Secondary | none | single combi system |
| PV / Wind / Hydro | none | none lodged |
| Room-in-roof / extensions | blank | single building part, no RiR |

View file

@ -0,0 +1,232 @@
{
"uprn": 100021943298,
"roofs": [
{
"description": "(another dwelling above)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"walls": [
{
"description": "Solid brick, with internal insulation",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"floors": [
{
"description": "Suspended, no insulation (assumed)",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"status": "entered",
"tenure": 2,
"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": "SE4 1YN",
"hot_water": {
"description": "From main system",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
},
"post_town": "LONDON",
"built_form": 3,
"created_at": "2012-10-02 23:16:59.000000",
"door_count": 2,
"glazed_area": 1,
"region_code": 17,
"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": 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,
"boiler_index_number": 10328,
"main_heating_number": 1,
"main_heating_control": 2106,
"main_heating_category": 2,
"main_heating_fraction": 1,
"main_heating_data_source": 1
}
],
"has_fixed_air_conditioning": "false"
},
"sap_version": 9.91,
"schema_type": "SAP-Schema-16.1",
"uprn_source": "Energy Assessor",
"country_code": "EAW",
"main_heating": [
{
"description": "Boiler and radiators, mains gas",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"dwelling_type": "Ground-floor flat",
"language_code": 1,
"property_type": 2,
"address_line_1": "11a, Chalsey Road",
"schema_version": "LIG-16.1",
"assessment_type": "RdSAP",
"completion_date": "2012-10-02",
"inspection_date": "2012-10-01",
"extensions_count": 0,
"measurement_type": 1,
"sap_flat_details": {
"level": 1,
"top_storey": "N",
"flat_location": 0,
"heat_loss_corridor": 0
},
"total_floor_area": 78,
"transaction_type": 8,
"conservatory_type": 1,
"heated_room_count": 3,
"registration_date": "2012-10-02",
"restricted_access": 0,
"sap_energy_source": {
"main_gas": "Y",
"meter_type": 2,
"photovoltaic_supply": {
"percent_roof_area": 0
},
"wind_turbines_count": 0,
"wind_turbines_terrain_type": 2
},
"secondary_heating": {
"description": "None",
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
},
"sap_building_parts": [
{
"identifier": "Main Dwelling",
"wall_dry_lined": "N",
"wall_thickness": 300,
"floor_heat_loss": 7,
"roof_construction": 3,
"wall_construction": 3,
"building_part_number": 1,
"sap_floor_dimensions": [
{
"floor": 0,
"room_height": 2.65,
"floor_insulation": 1,
"total_floor_area": 77.69,
"floor_construction": 2,
"heat_loss_perimeter": 28.9
}
],
"wall_insulation_type": 3,
"construction_age_band": "B",
"wall_thickness_measured": "Y",
"roof_insulation_location": "ND",
"roof_insulation_thickness": "ND",
"wall_insulation_thickness": "50mm"
}
],
"low_energy_lighting": 100,
"solar_water_heating": "N",
"bedf_revision_number": 329,
"habitable_room_count": 3,
"heating_cost_current": {
"value": 421,
"currency": "GBP"
},
"insulated_door_count": 0,
"co2_emissions_current": 2.4,
"energy_rating_average": 60,
"energy_rating_current": 72,
"lighting_cost_current": {
"value": 46,
"currency": "GBP"
},
"main_heating_controls": [
{
"description": "Programmer, room thermostat and TRVs",
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
}
],
"multiple_glazing_type": 1,
"open_fireplaces_count": 0,
"has_hot_water_cylinder": "false",
"heating_cost_potential": {
"value": 360,
"currency": "GBP"
},
"hot_water_cost_current": {
"value": 85,
"currency": "GBP"
},
"mechanical_ventilation": 0,
"percent_draughtproofed": 100,
"suggested_improvements": [
{
"sequence": 1,
"typical_saving": {
"value": 61,
"currency": "GBP"
},
"indicative_cost": "\u00a3800 - \u00a31,200",
"improvement_type": "W",
"improvement_details": {
"improvement_number": 47
},
"improvement_category": 5,
"energy_performance_rating": 75,
"environmental_impact_rating": 77
}
],
"co2_emissions_potential": 2.0,
"energy_rating_potential": 75,
"lighting_cost_potential": {
"value": 46,
"currency": "GBP"
},
"hot_water_cost_potential": {
"value": 85,
"currency": "GBP"
},
"renewable_heat_incentive": {
"water_heating": 2121,
"space_heating_existing_dwelling": 7355
},
"seller_commission_report": "Y",
"energy_consumption_current": 160,
"has_fixed_air_conditioning": "false",
"multiple_glazed_proportion": 100,
"calculation_software_version": 8.0,
"energy_consumption_potential": 137,
"environmental_impact_current": 72,
"fixed_lighting_outlets_count": 6,
"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": 31,
"low_energy_fixed_lighting_outlets_count": 6
}

View file

@ -0,0 +1,96 @@
# Elmhurst RdSAP inputs — UPRN 10096028301 (cert 0390-3321-6060-2405-7985, SAP-Schema-19.1.0)
**Lodged SAP:** 85 **Our engine:** 82 ← compare Elmhurst against the engine
**Property:** Ground-floor FLAT, age band M (2023 onwards), mains-gas combi + radiators, decentralised MEV, TFA 73 m²
**Schema note:** SAP-Schema-19.1.0 is FULL SAP (measured U-values, measured air
permeability) — mapped via the new `from_sap_schema_19_1_0` (parses with the 17.1
dataclass, delegates to `from_sap_schema_17_1`). Near-twin of the worked-ref flat
10092973954 (same PCDB combi 17929, MEV, AP50). Expect the documented full-SAP→RdSAP
residual: the engine uses the cert's MEASURED U (wall 0.24 / floor 0.13 — WORSE than
RdSAP band-M defaults), so here the engine *under*-rates vs RdSAP (eng 82 < lodged 85).
**Known divergences / gaps to watch:**
- **MEV (decentralised, EXTRACT_OR_PIV_OUTSIDE):** engine prices it as extract loss,
not heat recovery — the documented MVHR gap. Omit/parallel on both sides.
- **Measured vs RdSAP U:** Elmhurst can't set the U-value-known override → it uses band-M
age defaults (better than the cert's measured 0.24 wall). Note the divergence; don't tune.
- Age band M → Elmhurst on-screen band **M 2023 onwards**.
## Property Description
| Elmhurst field | Value | Notes |
|---|---|---|
| Property Type | **Flat** | ground-floor flat |
| Built form | **Detached** (block) | built_form not lodged (full SAP) — use Detached as for worked-ref flat |
| Age band | **M 2023 onwards** | construction_age_band M |
| Storeys | 1 | ground-floor flat |
| Habitable rooms | 3 | habitable_room_count 3 |
## Flats
| Elmhurst field | Value | Notes |
|---|---|---|
| Position of flat | **Ground Floor** | dwelling_type ground-floor flat |
| Floor | 0 | ground = storey 0 |
| Corridor | None | all-exposed |
## Dimensions
| Elmhurst field | Value | Notes |
|---|---|---|
| Floor area (lowest) | 72.99 m² | floor 0 |
| Room height | 2.36 m | room_height |
| Heat-loss perimeter | 36.04 m | heat_loss_perimeter |
| Party-wall length | **0** | not lodged |
## Walls
| Elmhurst field | Value | Notes |
|---|---|---|
| Construction | **Cavity** | wall_construction 4 |
| Insulation | **As Built** | measured U 0.24 W/m²K (U-known override not settable → accept band-M default; note divergence) |
## Roofs
| Elmhurst field | Value | Notes |
|---|---|---|
| Type | **Another dwelling above** | roof "(other premises above)" → no roof heat loss |
## Floors
| Elmhurst field | Value | Notes |
|---|---|---|
| Location / type | Ground floor / Solid | measured U 0.13 (accept band-M default) |
| Insulation | As built | — |
## Openings
| Elmhurst field | Value | Notes |
|---|---|---|
| Windows | total ~11.37 m² (4 windows), U 1.3, g 0.46 | glazing_type 7. Enter ONE combined band row ("Double … 2022" ≈ U1.4/g0.72) of 11.37 m² — `…known data` demands per-row frame-factor/source and fails validation. |
| Doors | **1, solid (uninsulated)** U 1.2 | sap_opening_types: 1 Solid Door, U 1.2 |
## Ventilation & Lighting
| Elmhurst field | Value | Notes |
|---|---|---|
| Ventilation | **Mechanical — decentralised MEV** (extract) | ventilation EXTRACT_OR_PIV_OUTSIDE. 🚩 engine models as extract loss not heat recovery. |
| Air Pressure Test | **Blower Door**, result **3.5** (AP50) | air_tightness "AP50 = 3.5 as tested"; Elmhurst (18)=AP50/20. Needs a certificate number to pass Recommendations validation. |
| Sheltered sides | (from built form) | — |
| Lighting | per cert low-energy % | sap_lighting |
## Space Heating
| Elmhurst field | Value | Notes |
|---|---|---|
| Main heating | Mains gas **combi** + radiators | fuel mains gas, PCDB 17929 (Vaillant combi), control 2110, category 2, NO cylinder → BGW condensing combi cascade |
| Flue | Balanced (room-sealed) | fan_flue_present; balanced flue |
| Secondary | None | — |
## Water Heating
| Elmhurst field | Value | Notes |
|---|---|---|
| Water heating | From main (901), gas | combi |
| Hot water cylinder | **None** | has_hot_water_cylinder False (combi) |
| Solar / WWHRS / FGHRS | None | — |
## Fields to clear in Elmhurst (do NOT map)
| Elmhurst field | Set to | Why absent |
|---|---|---|
| Conservatory | none | not lodged |
| Cylinder size/insulation | (gone — combi) | no cylinder |
| Main Heating 2 / Secondary | none | single combi system |
| PV / Wind / Hydro / WWHRS / FGHRS | none | none lodged |
| Room-in-roof / extensions | blank | single building part |

View file

@ -0,0 +1,456 @@
{
"der": 12.66,
"ter": 13.84,
"dfee": 36.3,
"dper": 69.44,
"tfee": 38.6,
"tper": 72.99,
"uprn": 10096028301,
"roofs": [
{
"description": {
"value": "(other premises above)",
"language": "1"
},
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
}
],
"walls": [
{
"description": {
"value": "Average thermal transmittance 0.24 W/m\u00b2K",
"language": "1"
},
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
}
],
"floors": [
{
"description": {
"value": "Average thermal transmittance 0.13 W/m\u00b2K",
"language": "1"
},
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
}
],
"status": "entered",
"tenure": "ND",
"windows": {
"description": {
"value": "High performance glazing",
"language": "1"
},
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
},
"lighting": {
"description": {
"value": "Good lighting efficiency",
"language": "1"
},
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
},
"postcode": "CT5 1TN",
"data_type": 2,
"hot_water": {
"description": {
"value": "From main system, waste water heat recovery",
"language": "1"
},
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
},
"post_town": "Whitstable",
"built_form": 1,
"created_at": "2025-06-09 10:49:27",
"living_area": 26.89,
"orientation": 3,
"region_code": 14,
"report_type": 3,
"sap_heating": {
"number_baths": 1,
"thermal_store": 1,
"shower_outlets": [
{
"shower_wwhrs": 2,
"shower_flow_rate": 9,
"shower_outlet_type": 3
}
],
"water_fuel_type": 1,
"water_heating_code": 901,
"instantaneous_wwhrs": {
"wwhrs_index_number1": 80137
},
"main_heating_details": [
{
"has_fghrs": "false",
"main_fuel_type": 1,
"heat_emitter_type": 1,
"is_flue_fan_present": "true",
"main_heating_number": 1,
"is_condensing_boiler": "true",
"main_heating_control": 2110,
"is_interlocked_system": "true",
"main_heating_category": 2,
"main_heating_fraction": 1,
"gas_or_oil_boiler_type": 2,
"main_heating_flue_type": 2,
"central_heating_pump_age": 2,
"main_heating_data_source": 1,
"main_heating_index_number": 17929,
"has_separate_delayed_start": "true",
"is_oil_pump_in_heated_space": "false",
"is_main_heating_hetas_approved": "false",
"is_central_heating_pump_in_heated_space": "true"
}
],
"has_hot_water_cylinder": "false",
"has_cylinder_thermostat": "false",
"has_fixed_air_conditioning": "false",
"secondary_heating_category": 1,
"is_immersion_for_summer_use": "false",
"is_hot_water_separately_timed": "false",
"is_heat_pump_assisted_by_immersion": "false"
},
"sap_version": 10.2,
"schema_type": "SAP-Schema-19.1.0",
"uprn_source": "Address Matched",
"country_code": "ENG",
"main_heating": [
{
"description": {
"value": "Boiler and radiators, mains gas",
"language": "1"
},
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 4
}
],
"sap_lighting": [
[
{
"lighting_power": 9,
"lighting_outlets": 6,
"lighting_efficacy": 80
}
]
],
"terrain_type": 2,
"air_tightness": {
"description": {
"value": "Air permeability [AP50] = 3.5 m\u00b3/h.m\u00b2 (as tested)",
"language": "1"
},
"energy_efficiency_rating": 4,
"environmental_efficiency_rating": 4
},
"dwelling_type": "Ground-floor flat",
"language_code": 1,
"property_type": 2,
"pv_connection": 1,
"address_line_1": "1 Ransome Way",
"assessment_date": "2025-06-09",
"assessment_type": "SAP",
"completion_date": "2025-06-09",
"inspection_date": "2025-06-09",
"sap_ventilation": {
"psv_count": 0,
"wall_type": 2,
"pressure_test": 1,
"wet_rooms_count": 2,
"air_permeability": 3.52,
"open_flues_count": 0,
"ventilation_type": 6,
"has_draught_lobby": "true",
"other_flues_count": 0,
"closed_flues_count": 0,
"extract_fans_count": 0,
"boilers_flues_count": 0,
"open_chimneys_count": 0,
"sheltered_sides_count": 2,
"blocked_chimneys_count": 0,
"kitchen_duct_fans_count": 0,
"kitchen_room_fans_count": 0,
"kitchen_wall_fans_count": 1,
"flueless_gas_fires_count": 0,
"mechanical_vent_duct_type": 1,
"non_kitchen_duct_fans_count": 0,
"non_kitchen_room_fans_count": 0,
"non_kitchen_wall_fans_count": 1,
"mechanical_ventilation_data_source": 1,
"mechanical_vent_system_index_number": 500769,
"is_mechanical_vent_approved_installer_scheme": "false"
},
"design_water_use": 1,
"sap_data_version": 10.2,
"sap_flat_details": {
"level": 1,
"storeys": 3
},
"total_floor_area": 73,
"transaction_type": 6,
"cold_water_source": 1,
"conservatory_type": 1,
"registration_date": "2025-06-09",
"sap_energy_source": {
"pv_arrays": [
{
"pitch": 3,
"peak_power": 0.38,
"orientation": 5,
"overshading": 1
}
],
"electricity_tariff": 1
},
"sap_opening_types": [
{
"name": "Solid Door",
"type": 1,
"u_value": 1.2,
"data_source": 2,
"glazing_type": 1,
"isargonfilled": "false"
},
{
"name": "Windows",
"type": 4,
"u_value": 1.3,
"data_source": 4,
"frame_factor": 1.0,
"glazing_type": 7,
"isargonfilled": "false",
"solar_transmittance": 0.46
}
],
"secondary_heating": {
"description": {
"value": "None",
"language": "1"
},
"energy_efficiency_rating": 0,
"environmental_efficiency_rating": 0
},
"lowest_storey_area": 72.99,
"lzc_energy_sources": [
11
],
"sap_building_parts": [
{
"sap_roofs": [
{
"name": "Party roof 1",
"u_value": 0,
"roof_type": 4,
"kappa_value": 30,
"total_roof_area": 72.99
}
],
"sap_walls": [
{
"name": "Walls (1)",
"u_value": 0.24,
"wall_type": 2,
"kappa_value": 60,
"total_wall_area": 68.76,
"is_curtain_walling": "false"
},
{
"name": "Walls (2)",
"u_value": 0.26,
"wall_type": 3,
"kappa_value": 110,
"total_wall_area": 16.3,
"is_curtain_walling": "false"
},
{
"name": "Internal Wall (1)",
"u_value": 0,
"wall_type": 5,
"kappa_value": 9,
"total_wall_area": 114.18,
"is_curtain_walling": "false"
}
],
"sap_openings": [
{
"name": "Front Door",
"type": "Solid Door",
"width": 2.15,
"height": 1,
"location": "Walls (1)",
"orientation": 3
},
{
"name": "Front Windows",
"type": "Windows",
"width": 4.77,
"height": 1,
"location": "Walls (1)",
"orientation": 3
},
{
"name": "RH Windows",
"type": "Windows",
"width": 4.39,
"height": 1,
"location": "Walls (1)",
"orientation": 1
},
{
"name": "Rear Window",
"type": "Windows",
"width": 0.62,
"height": 1,
"location": "Walls (1)",
"orientation": 7
},
{
"name": "LH Window",
"type": "Windows",
"width": 1.59,
"height": 1,
"location": "Walls (1)",
"orientation": 5
}
],
"construction_year": 2025,
"sap_thermal_bridges": {
"thermal_bridges": [
{
"length": 9.65,
"psi_value": 0.021,
"psi_value_source": 1,
"thermal_bridge_type": "E2"
},
{
"length": 8.16,
"psi_value": 0.017,
"psi_value_source": 1,
"thermal_bridge_type": "E3"
},
{
"length": 16.95,
"psi_value": 0.013,
"psi_value_source": 1,
"thermal_bridge_type": "E4"
},
{
"length": 29.14,
"psi_value": 0.054,
"psi_value_source": 1,
"thermal_bridge_type": "E5"
},
{
"length": 29.14,
"psi_value": 0.052,
"psi_value_source": 1,
"thermal_bridge_type": "E7"
},
{
"length": 11.61,
"psi_value": 0.046,
"psi_value_source": 1,
"thermal_bridge_type": "E16"
},
{
"length": 6.91,
"psi_value": 0.111,
"psi_value_source": 1,
"thermal_bridge_type": "E5"
},
{
"length": 6.91,
"psi_value": 0.056,
"psi_value_source": 1,
"thermal_bridge_type": "E7"
},
{
"length": 4.72,
"psi_value": -0.1,
"psi_value_source": 1,
"thermal_bridge_type": "E17"
}
],
"thermal_bridge_code": 5
},
"building_part_number": 1,
"sap_floor_dimensions": [
{
"storey": 0,
"u_value": 0.13,
"floor_type": 2,
"kappa_value": 75,
"storey_height": 2.36,
"heat_loss_area": 72.99,
"total_floor_area": 72.99
}
]
}
],
"user_interface_name": "Design SAP 10",
"windows_overshading": 2,
"heating_cost_current": {
"value": 218,
"currency": "GBP"
},
"co2_emissions_current": 0.8,
"energy_rating_average": 60,
"energy_rating_current": 85,
"lighting_cost_current": {
"value": 57,
"currency": "GBP"
},
"main_heating_controls": [
{
"description": {
"value": "Time and temperature zone control",
"language": "1"
},
"energy_efficiency_rating": 5,
"environmental_efficiency_rating": 5
}
],
"has_hot_water_cylinder": "false",
"heating_cost_potential": {
"value": 218,
"currency": "GBP"
},
"hot_water_cost_current": {
"value": 118,
"currency": "GBP"
},
"user_interface_version": "2.22.1",
"co2_emissions_potential": 0.8,
"energy_rating_potential": 85,
"gas_smart_meter_present": "false",
"lighting_cost_potential": {
"value": 57,
"currency": "GBP"
},
"schema_version_original": "SAP-Schema-19.1.0",
"hot_water_cost_potential": {
"value": 118,
"currency": "GBP"
},
"is_in_smoke_control_area": "unknown",
"seller_commission_report": "Y",
"energy_consumption_current": 59,
"has_fixed_air_conditioning": "false",
"is_dwelling_export_capable": "true",
"multiple_glazed_percentage": 100,
"calculation_software_version": "2.22.1",
"energy_consumption_potential": 59,
"environmental_impact_current": 90,
"current_energy_efficiency_band": "B",
"environmental_impact_potential": 90,
"electricity_smart_meter_present": "false",
"has_heated_separate_conservatory": "false",
"potential_energy_efficiency_band": "B",
"co2_emissions_current_per_floor_area": 10.7
}

View file

@ -883,6 +883,74 @@ class EpcPropertyDataMapper:
)
return roof_windows
@staticmethod
def from_sap_schema_19_1_0(schema: SapSchema17_1) -> EpcPropertyData:
"""Map a full-SAP `SAP-Schema-19.1.0` cert.
19.1.0 is a later full-SAP revision whose load-bearing fabric measured
`sap_opening_types`, construction-code `sap_building_parts`,
`sap_heating` / `sap_ventilation` is structurally identical to
`SAP-Schema-17.1`, so it parses with the 17.1 dataclass and reuses the
tested `from_sap_schema_17_1` mapper. This thin wrapper gives 19.1.0 its
own dispatch entry point: 19.1.0-only fields (e.g. `air_tightness`,
`windows_overshading`) are not yet consumed, and when one needs modelling
it is handled here rather than by widening the 17.1 mapper.
"""
return EpcPropertyDataMapper.from_sap_schema_17_1(schema)
@staticmethod
def from_sap_schema_17_0(schema: SapSchema17_1) -> EpcPropertyData:
"""Map a full-SAP `SAP-Schema-17.0` cert. Structurally identical to the
17.1 measured shape, so it parses with the 17.1 dataclass and reuses
`from_sap_schema_17_1`. Dedicated entry point per the one-mapper-per-
schema convention (note: distinct from RdSAP `from_rdsap_schema_17_0`)."""
return EpcPropertyDataMapper.from_sap_schema_17_1(schema)
@staticmethod
def from_sap_schema_18_0_0(schema: SapSchema17_1) -> EpcPropertyData:
"""Map a full-SAP `SAP-Schema-18.0.0` cert. Structurally identical to the
17.1 measured shape, so it parses with the 17.1 dataclass and reuses
`from_sap_schema_17_1`. Dedicated entry point per the one-mapper-per-
schema convention."""
return EpcPropertyDataMapper.from_sap_schema_17_1(schema)
@staticmethod
def from_sap_schema_16_0(data: Dict[str, Any]) -> EpcPropertyData:
"""Map a reduced-field `SAP-Schema-16.0` cert (see `from_sap_schema_16_2`)."""
return EpcPropertyDataMapper._from_sap_schema_16_x(data)
@staticmethod
def from_sap_schema_16_1(data: Dict[str, Any]) -> EpcPropertyData:
"""Map a reduced-field `SAP-Schema-16.1` cert (see `from_sap_schema_16_2`)."""
return EpcPropertyDataMapper._from_sap_schema_16_x(data)
@staticmethod
def from_sap_schema_16_2(data: Dict[str, Any]) -> EpcPropertyData:
"""Map a reduced-field `SAP-Schema-16.2` cert.
The SAP-Schema-16.x family is structurally RdSAP-17.1 (reduced fields,
`glazed_area` band, construction-code building parts) under a different
name plus a handful of renamed/omitted fields. Each revision gets a
dedicated mapper that normalises onto the RdSAP-17.1 shape via the shared
`_normalize_sap_schema_16_x` and reuses the tested `from_rdsap_schema_17_1`.
"""
return EpcPropertyDataMapper._from_sap_schema_16_x(data)
@staticmethod
def from_sap_schema_16_3(data: Dict[str, Any]) -> EpcPropertyData:
"""Map a reduced-field `SAP-Schema-16.3` cert (see `from_sap_schema_16_2`)."""
return EpcPropertyDataMapper._from_sap_schema_16_x(data)
@staticmethod
def _from_sap_schema_16_x(data: Dict[str, Any]) -> EpcPropertyData:
"""Shared body for the SAP-Schema-16.x dedicated mappers: normalise the
reduced-field doc onto the RdSAP-17.1 shape and reuse that mapper."""
from datatypes.epc.schema.rdsap_schema_17_1 import RdSapSchema17_1
return EpcPropertyDataMapper.from_rdsap_schema_17_1(
from_dict(RdSapSchema17_1, _normalize_sap_schema_16_x(data))
)
@staticmethod
def from_rdsap_schema_17_1(schema: RdSapSchema17_1) -> EpcPropertyData:
es = schema.sap_energy_source
@ -2578,27 +2646,36 @@ class EpcPropertyDataMapper:
mapped = EpcPropertyDataMapper.from_rdsap_schema_17_0(
from_dict(RdSapSchema17_0, data)
)
elif schema in ("SAP-Schema-17.1", "SAP-Schema-17.0", "SAP-Schema-18.0.0"):
# Full SAP (not RdSAP). SAP-Schema-17.0 / 18.0.0 are structurally
# identical to 17.1 (same measured sap_opening_types / building
# parts), so they parse with the 17.1 dataclass and reuse the same
# mapper. The common post-processing below (incl.
# _clear_basement_flag_when_system_built) is a no-op for full SAP
# (explicit wall types — no RdSAP code-6 basement ambiguity).
# Full-SAP family: each revision gets a dedicated mapper (see the
# per-schema methods) even where it currently delegates to the 17.1
# logic — so divergences land in one place rather than a shared tuple.
elif schema == "SAP-Schema-17.1":
mapped = EpcPropertyDataMapper.from_sap_schema_17_1(
from_dict(SapSchema17_1, data)
)
elif schema in ("SAP-Schema-16.2", "SAP-Schema-16.3", "SAP-Schema-16.0"):
# The SAP-Schema-16.x family is structurally RdSAP-17.1 (reduced
# fields, glazed_area band, construction-code building parts) under a
# different name + a handful of renamed/omitted fields — normalise
# onto the RdSAP-17.1 shape and reuse that tested mapper. See
# `_normalize_sap_schema_16_x`.
from datatypes.epc.schema.rdsap_schema_17_1 import RdSapSchema17_1
mapped = EpcPropertyDataMapper.from_rdsap_schema_17_1(
from_dict(RdSapSchema17_1, _normalize_sap_schema_16_x(data))
elif schema == "SAP-Schema-17.0":
mapped = EpcPropertyDataMapper.from_sap_schema_17_0(
from_dict(SapSchema17_1, data)
)
elif schema == "SAP-Schema-18.0.0":
mapped = EpcPropertyDataMapper.from_sap_schema_18_0_0(
from_dict(SapSchema17_1, data)
)
elif schema == "SAP-Schema-19.1.0":
mapped = EpcPropertyDataMapper.from_sap_schema_19_1_0(
from_dict(SapSchema17_1, data)
)
# SAP-Schema-16.x family: reduced-field (RdSAP-shaped) certs. Each gets a
# dedicated mapper that normalises onto the RdSAP-17.1 shape via the
# shared `_normalize_sap_schema_16_x` and reuses `from_rdsap_schema_17_1`.
elif schema == "SAP-Schema-16.0":
mapped = EpcPropertyDataMapper.from_sap_schema_16_0(data)
elif schema == "SAP-Schema-16.1":
mapped = EpcPropertyDataMapper.from_sap_schema_16_1(data)
elif schema == "SAP-Schema-16.2":
mapped = EpcPropertyDataMapper.from_sap_schema_16_2(data)
elif schema == "SAP-Schema-16.3":
mapped = EpcPropertyDataMapper.from_sap_schema_16_3(data)
else:
raise ValueError(f"Unsupported EPC schema: {schema!r}")

28
domain/elmhurst/README.md Normal file
View file

@ -0,0 +1,28 @@
# Elmhurst reference data
Lookup tables to support re-keying certs into the Elmhurst RdSAP entry tool
(see `scripts/hyde/` and the `expand-sap-accuracy-corpus` skill).
## `pcdb_gas_oil_boiler_codes.csv`
Every gas/oil boiler in the SAP Product Characteristics Database (PCDB Table
105), one row per boiler, so you can look up the **Ref No** to type into
Elmhurst's *PCDF boiler Reference* field (Space Heating → 🔍 search → Select).
Columns: `pcdb_id` (the Ref No), `brand`, `model`, `qualifier`, `type`
(Regular / Combi / Combi-storage / CPSU), `winter_eff_pct`, `summer_eff_pct`,
`output_kw_max`, `final_year`.
- Generated from `domain/sap10_calculator/tables/pcdb/data/pcdb_table_105_gas_oil_boilers.jsonl`.
- `type` is decoded from the raw PCDB row's boiler-type code (`raw[14]`:
`1`→Regular, `2`→Combi, `3`→Combi-storage, `4`→CPSU). This is the field that
decides whether Elmhurst wants a hot-water cylinder (Regular) or not (Combi).
- Find a boiler: `grep -i "ecotec pro,28" pcdb_gas_oil_boiler_codes.csv` or
filter by brand/model; the cert's lodged `main_heating_index_number` is the
`pcdb_id`.
**Note on entering it in Elmhurst:** the on-screen *PCDF boiler Reference* text
box is editable and will *display* a number you type, but typing alone does NOT
re-resolve the boiler — its type/efficiency/cylinder behaviour stays from the
previously-selected boiler. Only selecting via the 🔍 **search dialog** genuinely
sets the boiler. This CSV tells you which Ref to pick.

View file

@ -87,12 +87,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -9,7 +9,7 @@
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$RadioButtonDimensionsType",
"label": "Type",
"value": "Internal",
"disabled": true,
"disabled": false,
"autopostback": false,
"options": [
{
@ -29,13 +29,53 @@
}
]
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_WebUserControlDimensionsMain_TextBoxFloorArea1stFloor",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$WebUserControlDimensionsMain$TextBoxFloorArea1stFloor",
"label": "",
"value": "31.06",
"disabled": false,
"autopostback": true
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_WebUserControlDimensionsMain_TextBoxRoomHeight1stFloor",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$WebUserControlDimensionsMain$TextBoxRoomHeight1stFloor",
"label": "",
"value": "2.65",
"disabled": false,
"autopostback": true
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_WebUserControlDimensionsMain_TextBoxWallPerimeter1stFloor",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$WebUserControlDimensionsMain$TextBoxWallPerimeter1stFloor",
"label": "",
"value": "15.80",
"disabled": false,
"autopostback": true
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_WebUserControlDimensionsMain_TextBoxPartyWallLength1stFloor",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$WebUserControlDimensionsMain$TextBoxPartyWallLength1stFloor",
"label": "",
"value": "0.00",
"disabled": false,
"autopostback": true
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_WebUserControlDimensionsMain_TextBoxFloorAreaLowestFloor",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$WebUserControlDimensionsMain$TextBoxFloorAreaLowestFloor",
"label": "",
"value": "59.50",
"value": "31.06",
"disabled": false,
"autopostback": true
},
@ -55,7 +95,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_WebUserControlDimensionsMain_TextBoxWallPerimeterLowestFloor",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$WebUserControlDimensionsMain$TextBoxWallPerimeterLowestFloor",
"label": "",
"value": "13.86",
"value": "15.80",
"disabled": false,
"autopostback": true
},
@ -65,7 +105,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_WebUserControlDimensionsMain_TextBoxPartyWallLengthLowestFloor",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$WebUserControlDimensionsMain$TextBoxPartyWallLengthLowestFloor",
"label": "",
"value": "13.86",
"value": "0.00",
"disabled": false,
"autopostback": true
},
@ -154,12 +194,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -198,12 +198,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -184,12 +184,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -22,73 +22,13 @@
"disabled": false,
"autopostback": false
},
{
"tag": "input",
"type": "image",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_GridViewExtendedWidows_DeleteButton_1",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$GridViewExtendedWidows$ctl03$DeleteButton",
"label": "2",
"value": "",
"disabled": false,
"autopostback": false
},
{
"tag": "input",
"type": "image",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_GridViewExtendedWidows_CopyButton_1",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$GridViewExtendedWidows$ctl03$CopyButton",
"label": "2",
"value": "",
"disabled": false,
"autopostback": false
},
{
"tag": "input",
"type": "image",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_GridViewExtendedWidows_DeleteButton_2",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$GridViewExtendedWidows$ctl04$DeleteButton",
"label": "3",
"value": "",
"disabled": false,
"autopostback": false
},
{
"tag": "input",
"type": "image",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_GridViewExtendedWidows_CopyButton_2",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$GridViewExtendedWidows$ctl04$CopyButton",
"label": "3",
"value": "",
"disabled": false,
"autopostback": false
},
{
"tag": "input",
"type": "image",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_GridViewExtendedWidows_DeleteButton_3",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$GridViewExtendedWidows$ctl05$DeleteButton",
"label": "4",
"value": "",
"disabled": false,
"autopostback": false
},
{
"tag": "input",
"type": "image",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_GridViewExtendedWidows_CopyButton_3",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$GridViewExtendedWidows$ctl05$CopyButton",
"label": "4",
"value": "",
"disabled": false,
"autopostback": false
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_TextBoxExtWidth",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$TextBoxExtWidth",
"label": "",
"value": "1.44",
"value": "0.00",
"disabled": false,
"autopostback": true
},
@ -98,7 +38,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_TextBoxExtHeight",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$TextBoxExtHeight",
"label": "",
"value": "1.00",
"value": "0.00",
"disabled": false,
"autopostback": true
},
@ -108,7 +48,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_TextBoxExtArea",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$TextBoxExtArea",
"label": "",
"value": "1.44",
"value": "0.00",
"disabled": true,
"autopostback": false
},
@ -140,14 +80,14 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_DropDownListExtGlazing",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$DropDownListExtGlazing",
"label": "",
"value": "Double with unknown install date",
"value": "",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "",
"text": "",
"selected": false
"selected": true
},
{
"value": "Single glazing",
@ -172,7 +112,7 @@
{
"value": "Double with unknown install date",
"text": "Double with unknown install date",
"selected": true
"selected": false
},
{
"value": "Secondary glazing",
@ -216,89 +156,25 @@
}
]
},
{
"tag": "select",
"type": "select-one",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_DropDownListExtFrameType",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$DropDownListExtFrameType",
"label": "",
"value": "PVC",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "",
"text": "",
"selected": false
},
{
"value": "PVC",
"text": "PVC",
"selected": true
},
{
"value": "Wood",
"text": "Wood",
"selected": false
},
{
"value": "Metal",
"text": "Metal",
"selected": false
}
]
},
{
"tag": "select",
"type": "select-one",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_DropDownListExtGlazingGap",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$DropDownListExtGlazingGap",
"label": "",
"value": "12 mm",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "",
"text": "",
"selected": false
},
{
"value": "6 mm",
"text": "6 mm",
"selected": false
},
{
"value": "12 mm",
"text": "12 mm",
"selected": true
},
{
"value": "16 mm or more",
"text": "16 mm or more",
"selected": false
}
]
},
{
"tag": "select",
"type": "select-one",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_DropDownListExtBuildingPartId",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$DropDownListExtBuildingPartId",
"label": "",
"value": "Main",
"value": "",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "",
"text": "",
"selected": false
"selected": true
},
{
"value": "Main",
"text": "Main",
"selected": true
"selected": false
}
]
},
@ -308,24 +184,14 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_DropDownListExtLocation",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$DropDownListExtLocation",
"label": "",
"value": "External wall",
"value": "",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "",
"text": "",
"selected": false
},
{
"value": "External wall",
"text": "External wall",
"selected": true
},
{
"value": "Alternative wall 1",
"text": "Alternative wall 1",
"selected": false
}
]
},
@ -335,19 +201,19 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_DropDownListExtOrientation",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$DropDownListExtOrientation",
"label": "",
"value": "North",
"value": "",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "",
"text": "",
"selected": false
"selected": true
},
{
"value": "North",
"text": "North",
"selected": true
"selected": false
},
{
"value": "North East",
@ -397,7 +263,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_TextBoxExtUValue",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$TextBoxExtUValue",
"label": "",
"value": "2.80",
"value": "0.00",
"disabled": true,
"autopostback": true
},
@ -407,7 +273,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWindowsPanel_TextBoxExtGValue",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWindowsPanel$TextBoxExtGValue",
"label": "",
"value": "0.76",
"value": "0.00",
"disabled": true,
"autopostback": true
},
@ -420,7 +286,7 @@
"value": "on",
"disabled": false,
"autopostback": false,
"checked": true
"checked": false
},
{
"tag": "select",
@ -582,12 +448,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -8,7 +8,7 @@
"id": "ContentBody_ContentPlaceHolder1_DropDownListPropertyType1",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$DropDownListPropertyType1",
"label": "",
"value": "F Flat",
"value": "H House",
"disabled": false,
"autopostback": true,
"options": [
@ -20,7 +20,7 @@
{
"value": "H House",
"text": "H House",
"selected": false
"selected": true
},
{
"value": "B Bungalow",
@ -30,7 +30,7 @@
{
"value": "F Flat",
"text": "F Flat",
"selected": true
"selected": false
},
{
"value": "M Maisonette",
@ -44,13 +44,60 @@
}
]
},
{
"tag": "select",
"type": "select-one",
"id": "ContentBody_ContentPlaceHolder1_DropDownListPropertyType2",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$DropDownListPropertyType2",
"label": "",
"value": "E End-Terrace",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "",
"text": "",
"selected": false
},
{
"value": "D Detached",
"text": "D Detached",
"selected": false
},
{
"value": "S Semi-Detached",
"text": "S Semi-Detached",
"selected": false
},
{
"value": "M Mid-Terrace",
"text": "M Mid-Terrace",
"selected": false
},
{
"value": "E End-Terrace",
"text": "E End-Terrace",
"selected": true
},
{
"value": "EM Enclosed Mid-Terrace",
"text": "EM Enclosed Mid-Terrace",
"selected": false
},
{
"value": "EE Enclosed End-Terrace",
"text": "EE Enclosed End-Terrace",
"selected": false
}
]
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TextBoxStoreys",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TextBoxStoreys",
"label": "Storeys",
"value": "1",
"value": "2",
"disabled": false,
"autopostback": true
},
@ -60,7 +107,7 @@
"id": "ContentBody_ContentPlaceHolder1_TextBoxHabitableRooms",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TextBoxHabitableRooms",
"label": "Habitable Rooms",
"value": "3",
"value": "4",
"disabled": false,
"autopostback": false
},
@ -70,7 +117,7 @@
"id": "ContentBody_ContentPlaceHolder1_TextBoxHeatedHabitableRooms",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TextBoxHeatedHabitableRooms",
"label": "Heated Habitable Rooms",
"value": "3",
"value": "4",
"disabled": false,
"autopostback": true
},
@ -80,7 +127,7 @@
"id": "ContentBody_ContentPlaceHolder1_DropDownListDateBuiltMain",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$DropDownListDateBuiltMain",
"label": "Main Property",
"value": "L 2012-2022",
"value": "G 1983-1990",
"disabled": false,
"autopostback": true,
"options": [
@ -122,7 +169,7 @@
{
"value": "G 1983-1990",
"text": "G 1983-1990",
"selected": false
"selected": true
},
{
"value": "H 1991-1995",
@ -147,7 +194,7 @@
{
"value": "L 2012-2022",
"text": "L 2012-2022",
"selected": true
"selected": false
},
{
"value": "M 2023 onwards",
@ -968,12 +1015,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -228,12 +228,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -97,7 +97,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_WebUserControlRoofMain_DropDownListThickness",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$WebUserControlRoofMain$DropDownListThickness",
"label": "Insulation Thickness",
"value": "270 mm",
"value": "100 mm",
"disabled": false,
"autopostback": true,
"options": [
@ -129,7 +129,7 @@
{
"value": "100 mm",
"text": "100 mm",
"selected": false
"selected": true
},
{
"value": "125 mm",
@ -164,7 +164,7 @@
{
"value": "270 mm",
"text": "270 mm",
"selected": true
"selected": false
},
{
"value": "300 mm",
@ -273,12 +273,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -18,7 +18,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_TextBoxMainHeatingCode",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMainHeating1$WebUserControlMainHeating1$TextBoxMainHeatingCode",
"label": "Main Heating EES Code",
"value": "BGB",
"value": "BGW",
"disabled": false,
"autopostback": true
},
@ -114,7 +114,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_RadioButtonListFlueType",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMainHeating1$WebUserControlMainHeating1$RadioButtonListFlueType",
"label": "Flue Type",
"value": "Open",
"value": "Balanced",
"disabled": false,
"autopostback": true,
"options": [
@ -126,12 +126,12 @@
{
"value": "Balanced",
"text": "Balanced",
"selected": false
"selected": true
},
{
"value": "Open",
"text": "Open",
"selected": true
"selected": false
}
]
},
@ -335,7 +335,7 @@
"id": "ContentBody_ContentPlaceHolder1_DropDownListSecondaryHeatingPresent",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$DropDownListSecondaryHeatingPresent",
"label": "",
"value": "Yes",
"value": "No",
"disabled": false,
"autopostback": true,
"options": [
@ -347,25 +347,15 @@
{
"value": "No",
"text": "No",
"selected": false
"selected": true
},
{
"value": "Yes",
"text": "Yes",
"selected": true
"selected": false
}
]
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TextBoxSecondaryHeatingCode",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TextBoxSecondaryHeatingCode",
"label": "Secondary Heating EES Code",
"value": "RWM",
"disabled": false,
"autopostback": true
},
{
"tag": "input",
"type": "image",
@ -845,12 +835,6 @@
"onclick": null,
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMainHeating2_WebUserControlMainHeating2_ButtonMainHeatingCode"
},
{
"text": "",
"href": "javascript:__doPostBack('ctl00$ctl00$ContentBody$ContentPlaceHolder1$ButtonSecondaryHeatingCode','')",
"onclick": null,
"id": "ContentBody_ContentPlaceHolder1_ButtonSecondaryHeatingCode"
},
{
"text": "Boiler search",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$ContentPlaceHolder1$SelectBoilerDialog$HyperLinkActionBoilerSearch\", \"\", true, \"\", \"\", false, true))",
@ -911,12 +895,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -152,19 +152,19 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelAirPressureTest_DropDownListTestMethod",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelAirPressureTest$DropDownListTestMethod",
"label": "Test Method",
"value": "Not available",
"value": "Blower Door",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "Not available",
"text": "Not available",
"selected": true
"selected": false
},
{
"value": "Blower Door",
"text": "Blower Door",
"selected": false
"selected": true
},
{
"value": "Pulse",
@ -173,13 +173,33 @@
}
]
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelAirPressureTest_TextBoxTestResult",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelAirPressureTest$TextBoxTestResult",
"label": "Pressure Test Result",
"value": "3.59",
"disabled": false,
"autopostback": true
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelAirPressureTest_TextBoxTestCertificateNumber",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelAirPressureTest$TextBoxTestCertificateNumber",
"label": "Certificate Number",
"value": "APT-10093116330",
"disabled": false,
"autopostback": true
},
{
"tag": "input",
"type": "text",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelLighting_TextBoxLightsTotal",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelLighting$TextBoxLightsTotal",
"label": "",
"value": "6",
"value": "9",
"disabled": false,
"autopostback": true
},
@ -200,7 +220,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelLighting_TextBoxLedLightsTotal",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelLighting$TextBoxLedLightsTotal",
"label": "",
"value": "6",
"value": "0",
"disabled": false,
"autopostback": true
},
@ -210,7 +230,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelLighting_TextBoxCflLightsTotal",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelLighting$TextBoxCflLightsTotal",
"label": "",
"value": "0",
"value": "2",
"disabled": false,
"autopostback": true
},
@ -220,7 +240,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelLighting_TextBoxLelLightsTotal",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelLighting$TextBoxLelLightsTotal",
"label": "",
"value": "6",
"value": "2",
"disabled": true,
"autopostback": true
},
@ -230,7 +250,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelLighting_TextBoxIncandescentLights",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelLighting$TextBoxIncandescentLights",
"label": "",
"value": "0",
"value": "7",
"disabled": true,
"autopostback": false
},
@ -402,12 +422,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -8,7 +8,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_InnerTabContainerMain_TabPanelExternalWallMain_WebUserControlWallMain_DropDownListType",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$InnerTabContainerMain$TabPanelExternalWallMain$WebUserControlWallMain$DropDownListType",
"label": "Type",
"value": "TI Timber Frame",
"value": "CA Cavity",
"disabled": false,
"autopostback": true,
"options": [
@ -40,12 +40,12 @@
{
"value": "CA Cavity",
"text": "CA Cavity",
"selected": false
"selected": true
},
{
"value": "TI Timber Frame",
"text": "TI Timber Frame",
"selected": true
"selected": false
},
{
"value": "SY System Build",
@ -65,7 +65,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_InnerTabContainerMain_TabPanelExternalWallMain_WebUserControlWallMain_DropDownListInsulation",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$InnerTabContainerMain$TabPanelExternalWallMain$WebUserControlWallMain$DropDownListInsulation",
"label": "Insulation",
"value": "A As Built",
"value": "F Filled Cavity",
"disabled": false,
"autopostback": true,
"options": [
@ -74,6 +74,36 @@
"text": "",
"selected": false
},
{
"value": "E External",
"text": "E External",
"selected": false
},
{
"value": "F Filled Cavity",
"text": "F Filled Cavity",
"selected": true
},
{
"value": "FI Filled Cavity + Internal",
"text": "FI Filled Cavity + Internal",
"selected": false
},
{
"value": "FE Filled Cavity + External",
"text": "FE Filled Cavity + External",
"selected": false
},
{
"value": "UI Unfilled Cavity + Internal",
"text": "UI Unfilled Cavity + Internal",
"selected": false
},
{
"value": "UE Unfilled Cavity + External",
"text": "UE Unfilled Cavity + External",
"selected": false
},
{
"value": "I Internal",
"text": "I Internal",
@ -82,7 +112,7 @@
{
"value": "A As Built",
"text": "A As Built",
"selected": true
"selected": false
},
{
"value": "U Unknown",
@ -97,7 +127,7 @@
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMain_InnerTabContainerMain_TabPanelExternalWallMain_WebUserControlWallMain_TextBoxWallThickness",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelMain$InnerTabContainerMain$TabPanelExternalWallMain$WebUserControlWallMain$TextBoxWallThickness",
"label": "Wall Thickness [mm]",
"value": "400",
"value": "280",
"disabled": false,
"autopostback": true
},
@ -217,12 +247,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -21,154 +21,7 @@
"value": "on",
"disabled": true,
"autopostback": false,
"checked": true
},
{
"tag": "select",
"type": "select-one",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWaterHeating_DropDownListCylinderSize",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWaterHeating$DropDownListCylinderSize",
"label": "",
"value": "Large",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "",
"text": "",
"selected": false
},
{
"value": "No Access",
"text": "No Access",
"selected": false
},
{
"value": "Normal",
"text": "Normal",
"selected": false
},
{
"value": "Medium",
"text": "Medium",
"selected": false
},
{
"value": "Large",
"text": "Large",
"selected": true
},
{
"value": "Value known",
"text": "Value known",
"selected": false
}
]
},
{
"tag": "select",
"type": "select-one",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWaterHeating_DropDownListInsulated",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWaterHeating$DropDownListInsulated",
"label": "",
"value": "Foam",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "",
"text": "",
"selected": false
},
{
"value": "No Insulation",
"text": "No Insulation",
"selected": false
},
{
"value": "Jacket",
"text": "Jacket",
"selected": false
},
{
"value": "Foam",
"text": "Foam",
"selected": true
},
{
"value": "Measured Loss",
"text": "Measured Loss",
"selected": false
}
]
},
{
"tag": "select",
"type": "select-one",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWaterHeating_DropDownListInsulationThickness",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWaterHeating$DropDownListInsulationThickness",
"label": "",
"value": "120 mm",
"disabled": false,
"autopostback": true,
"options": [
{
"value": "",
"text": "",
"selected": false
},
{
"value": "None",
"text": "None",
"selected": false
},
{
"value": "12 mm",
"text": "12 mm",
"selected": false
},
{
"value": "25 mm",
"text": "25 mm",
"selected": false
},
{
"value": "38 mm",
"text": "38 mm",
"selected": false
},
{
"value": "50 mm",
"text": "50 mm",
"selected": false
},
{
"value": "80 mm",
"text": "80 mm",
"selected": false
},
{
"value": "120 mm",
"text": "120 mm",
"selected": true
},
{
"value": "160 mm",
"text": "160 mm",
"selected": false
}
]
},
{
"tag": "input",
"type": "checkbox",
"id": "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelWaterHeating_CheckBoxCylinderThermostat",
"name": "ctl00$ctl00$ContentBody$ContentPlaceHolder1$TabContainer$TabPanelWaterHeating$CheckBoxCylinderThermostat",
"label": "",
"value": "on",
"disabled": false,
"autopostback": false,
"checked": true
"checked": false
},
{
"tag": "input",
@ -811,12 +664,6 @@
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionConservatories_Link"
},
{
"text": "Flats",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionFlats$Link\", \"\", true, \"\", \"\", false, true))",
"onclick": "waitForAction(this);",
"id": "ContentBody_buttonActionFlats_Link"
},
{
"text": "Walls",
"href": "javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(\"ctl00$ctl00$ContentBody$buttonActionWalls$Link\", \"\", true, \"\", \"\", false, true))",

View file

@ -28,7 +28,7 @@ SESSION_DIR = HERE / ".elmhurst-session"
SAMPLE_DIR = (
HERE.parent.parent
/ "backend/epc_api/json_samples/real_life_examples"
/ "SAP-Schema-17.1/uprn_10092973954"
/ "SAP-Schema-17.0/uprn_10023444320"
)
ASSESSMENT_GUID = "B44A0DB4-4C08-4241-B818-86F060172105"
@ -85,6 +85,12 @@ def download_reports() -> int:
print(f"entering assessment {ASSESSMENT_GUID} ...", flush=True)
page.goto(ENTRY_URL, wait_until="networkidle", timeout=60_000)
# The Summary nav only fires AFTER the Recommendations validation gate has
# been visited — clicking Summary straight from the Address page is a no-op.
print("navigating via Recommendations ...", flush=True)
with page.expect_navigation(wait_until="networkidle", timeout=60_000):
page.click("#ContentBody_buttonActionRecommendations_Link", timeout=15_000)
page.wait_for_timeout(600)
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)

View file

@ -226,3 +226,113 @@ def delete_first_window(page: Page) -> None:
page.wait_for_timeout(200)
if window_row_count(page) < before:
break
# --- Heating dialogs (boiler search + cascade) ---------------------------
# Hard-won (campaign certs 100021943298 / 10096028301): every Elmhurst modal
# dialog (PCDF boiler search, the SelectHeatingDialog cascade for water-heating
# method and main-heating controls) sits ABOVE a `modalBackground` overlay that
# makes Playwright's actionability hit-test see the background "intercepting
# pointer events" — so element .click()/.fill()/.select_option() all fail even
# with force. The reliable pattern is:
# * open the dialog with a JS .click() on its button,
# * set <select> 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 <span>, 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)

View file

@ -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,
),
)