mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
sap score and elmhirst mapper optimsaiation
This commit is contained in:
parent
37b0a38425
commit
3044c70202
25 changed files with 405 additions and 23 deletions
|
|
@ -180,12 +180,23 @@ Table 32 unit costs, p/kWh (`domain/sap10_calculator/tables/table_32.py`):
|
|||
**`main_fuel_type` / `water_heating_fuel` 29 = off-peak (7-hour) electricity** →
|
||||
Elmhurst Electricity meter type = **Dual-rate / Economy 7 (7-hour)**, NOT Single.
|
||||
|
||||
⚠️ **Known over-rating bug:** the engine prices **100% of off-peak space heating
|
||||
AND hot water at the 5.50p low rate** (`inputs.space_heating_fuel_cost_gbp_per_kwh`
|
||||
= 0.055), instead of the SAP **Table 12a high/low split** (a portion at the 15.29p
|
||||
high rate). This under-costs all-electric Economy-7 dwellings and inflates the SAP
|
||||
score. Always surface this in the output's "Known divergences". Canonical case:
|
||||
UPRN 10002468137 — lodged 55, engine 62.
|
||||
✅ **Economy-7 high/low split — FIXED (PR #1217).** The engine now applies the SAP
|
||||
**Table 12a Grid 1** (space) + **Table 13** (immersion DHW) high/low split rather
|
||||
than pricing 100% at the 5.50p low rate. Electric STORAGE heaters legitimately get
|
||||
a 0.00 SH high-rate fraction (100% low — spec value, not a bug); immersion HW takes
|
||||
the cylinder-volume/occupancy/single-dual Table 13 blend (applied in
|
||||
`cert_to_inputs._hot_water_fuel_cost_gbp_per_kwh` when volume + occupancy +
|
||||
single/dual are all resolved; absent any of them it still falls back to 100% low —
|
||||
a rarer edge). Verified: canonical UPRN 10002468137 engine 60.92 = Elmhurst 61 to
|
||||
the penny (its lodged 55 is the OLD SAP-2012 schema, not comparable); UPRN
|
||||
10022893721 engine 79 = lodged 79, Elmhurst (Dual meter) 81.
|
||||
|
||||
⚠️ **When building in Elmhurst you MUST set the Economy-7 meter** (`main_fuel_type`
|
||||
/ `water_heating_fuel` 29 = off-peak 7-hour → Electricity meter type **Dual**, NOT
|
||||
Single). Elmhurst silently defaults to Single/Standard and prices at the 13.19p
|
||||
standard rate, collapsing the worksheet SAP ~13 points — which can masquerade as an
|
||||
engine "over-rating". The control is a hidden Meters sub-tab on the SpaceHeating
|
||||
page (`TabPanelMeters_RadioButtonListElectricityType`).
|
||||
|
||||
## Water Heating
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,74 @@ score the property would get unchanged. Elmhurst is the accredited ground truth;
|
|||
its Input Summary (parsed back to `EpcPropertyData`) exposes mapper holes, and its
|
||||
worksheet exposes calculator holes.
|
||||
|
||||
## ⏩ Resume in a fresh context (autonomous run)
|
||||
|
||||
If the user says "continue" / "next" / "keep going through the worklist", run this
|
||||
loop **continuously without asking between certs** — report only `eng X / elm Y`
|
||||
per cert and tick the worklist line as each is pinned. The build automation is
|
||||
fully working (see `scripts/hyde/elmhurst_lib.py` helpers + the `build_<uprn>.py`
|
||||
templates); most certs build unattended end-to-end.
|
||||
|
||||
**State (2026-06-19):** pinned this campaign — SAP-17.1 cohort + RdSAP-17.1/18.0
|
||||
(older) plus: **16.1** `100021943298` (76/75), **19.1.0** `10096028301` (82/82),
|
||||
**16.3** `44012843` (79/78), **17.0** `10023444324` (80/80) + `10023444320`
|
||||
(81/82), **RdSAP-20.0.0** `10090844932` (78/77), **16.2** `100090182288` (69/71,
|
||||
semi house). Latest run (2026-06-19): **16.2** `100021985993` (74/72, end-terrace
|
||||
bungalow), **17.1** `10091568921` (82/80, full-SAP end-terrace house combi 17615)
|
||||
+ `10093718424` (81/80, semi sibling), **RdSAP-18.0** `10022893721` (79/81, first
|
||||
NON-BOILER cert — electric storage heaters + immersion; storage-heater automation
|
||||
now SOLVED incl. the Economy-7 Dual-meter step, see banked findings; engine 79 =
|
||||
lodged, NO bug), **RdSAP-21.0.1** `10023443426` (76/79, native schema, combi house;
|
||||
engine 76 = lodged EXACTLY; Elmhurst +3 = omitted-secondary build gap). Next `[ ]`:
|
||||
`10093412452` (SAP-17.1), then `10090343335` (17.0) / `10093115480` (17.1) /
|
||||
`68151071` (RdSAP-17.0). Skip `100020933699` (user said skip), `[⛔]` (NOT
|
||||
MAPPABLE), `[⚠]` (flagged engine bugs: MVHR / heat-pump fuel-39).
|
||||
|
||||
**Per-UPRN recipe** (all commands `DISPLAY=:99`, cwd `scripts/hyde`; run
|
||||
`bash scripts/hyde/start_viewer.sh` once; creds in `.elmhurst-creds.json`; shared
|
||||
assessment GUID `B44A0DB4-4C08-4241-B818-86F060172105`):
|
||||
1. `PYTHONPATH=/workspaces/model python scripts/fetch_real_life_epc_sample.py <uprn>`
|
||||
then scope: dwelling_type/built_form, age band, walls/roof/floor descriptions,
|
||||
heating `main_heating_index_number`/category/`has_hot_water_cylinder`, window
|
||||
total area (`sum(sap_windows w*h)`), party_wall_length, lighting %, MEV/AP50.
|
||||
2. **Copy the closest `build_<uprn>.py` template** and adjust values:
|
||||
- combi flat → `build_100021943298.py`; regular-boiler+cylinder flat →
|
||||
`build_44012843.py`; full-SAP combi flat (MEV+AP50) → `build_10096028301.py`
|
||||
/ `build_10023444324.py` (+party wall) / `build_10023444320.py` (mid-floor);
|
||||
- combi house → `build_10090844932.py` (end-terrace, party wall) /
|
||||
`build_100090182288.py` (semi, no party wall).
|
||||
Adjust: property type/built-form, band (`_pick` by year, e.g. "1950"/"2012"/
|
||||
"2023"), two-floor dims + party wall, wall insulation, roof, floor, window m²,
|
||||
doors, lighting, boiler PCDB ref + search query, MEV/AP50 if present.
|
||||
3. Run pages: `for p in property_description [flats] dimensions walls roofs floors
|
||||
openings ventilation; do … build_<uprn>.py $p; done` (one Save&Close each,
|
||||
~1 min/page; flats only for Flat property type). Then a window-verify/fix
|
||||
snippet (re-add the combined window if the grid shows 0.00), then
|
||||
`build_<uprn>.py space_heating` and `water_heating`.
|
||||
4. Heating uses the `elmhurst_lib` helpers: `E.select_boiler(page, "<model>",
|
||||
"<pcdb_id>")` (look up id/type in `domain/elmhurst/pcdb_gas_oil_boiler_codes.csv`;
|
||||
the lodged `main_heating_index_number` IS the id); control
|
||||
`E.set_heating_dialog(page, "…ButtonMainHeatingControls", "^Boilers",
|
||||
"^Standard", "CBE Programmer, room thermostat and TRVs")` (=2106); water
|
||||
`E.set_heating_dialog(page, "…ButtonWaterHeatingCode", "From Space Heating",
|
||||
"From the primary heating system")`; combi → `E.clear_hot_water_cylinder(page)`.
|
||||
5. Download: edit `elmhurst_download.py` `SAMPLE_DIR` to the cert's
|
||||
`<schema>/uprn_<uprn>` dir; first confirm Recommendations is clean (parse
|
||||
`[id*=ContentPlaceHolder1] a` link text — Summary silently redirects to Address
|
||||
until zero errors); `python scripts/hyde/elmhurst_download.py` (retry once; the
|
||||
nav goes Address→Recommendations→Summary).
|
||||
6. `PYTHONPATH=/workspaces/model python scripts/compare_epc_paths.py <uprn>` →
|
||||
read **"gov-API inputs → SAP"** (engine) and **"Elmhurst's OWN engine
|
||||
(worksheet …)"** (Elmhurst ground truth). Target ≤0.5–1.
|
||||
7. **Pin** the engine value: add a `RealCertExpectation(schema, sample=uprn_<uprn>,
|
||||
cert_num, sap_score=<engine>)` in `tests/.../test_real_cert_sap_accuracy.py`;
|
||||
run `…::test_real_cert_sap_score`.
|
||||
8. Tick the worklist line `[x] … · eng X / elm Y (lodged Z) · PINNED …`. Next cert.
|
||||
|
||||
See **Banked findings** below for the modal-dialog mechanics (all already encoded
|
||||
in the helpers). New schema not mappable → add a dedicated `from_*_schema_*`
|
||||
mapper first (per-schema convention) + guard with the RdSAP-21.0.1 corpus gauge.
|
||||
|
||||
## The loop (one UPRN)
|
||||
|
||||
1. **Pick** the first `[ ]` UPRN in [worklist.md](worklist.md).
|
||||
|
|
@ -93,6 +161,91 @@ Pattern: `with E.session() as (ctx,page): E.goto(...); E.set_text/set_select(...
|
|||
lines 17/18). This drove the first campaign mapper fix — see Banked findings.
|
||||
|
||||
## Banked findings (fold new ones in here as the corpus grows)
|
||||
- **MAPPER GAP — cylinder insulation thickness dropped (RdSAP-17.0+):** the mapper
|
||||
carries `cylinder_size` + `cylinder_insulation_type` but NOT
|
||||
`cylinder_insulation_thickness` → `EpcPropertyData.sap_heating.cylinder_
|
||||
insulation_thickness_mm` stays None even when the cert lodges it (e.g. 50 mm).
|
||||
The engine then assumes a poorly-insulated cylinder → over-counts HW cylinder
|
||||
loss → under-rates. Confirmed uprn_68151071 (raw 50 mm → mapped None; engine HW
|
||||
3446 vs Elmhurst 2911 kWh; engine 68 vs lodged 70 / Elmhurst 71). FIX: map the
|
||||
thickness in the RdSAP per-schema mapper; check blast radius (any pinned cylinder
|
||||
cert may shift) + regress the RdSAP-21.0.1 corpus gauge. Leverage point — likely
|
||||
improves every cylinder-with-lodged-thickness cert. Flagged, not yet fixed.
|
||||
- **Party-wall type `_pick` gotcha:** matching `"filled"` ALSO matches "Cavity
|
||||
masonry **UN**filled" (CU, U≈0.5) — the wrong type for a cert whose party wall is
|
||||
U≈0. Match `"masonry filled"` to hit CF (Cavity masonry filled, U≈0). Affects
|
||||
cavity builds (10090844932 / 10091568921 / 10093718424 / 10093412452 used the
|
||||
loose `"filled"` and may have got CU in Elmhurst — only the Elmhurst cross-check,
|
||||
not the pinned engine value which is validated against lodged). Fixed for
|
||||
uprn_10093115480.
|
||||
- **Shared-assessment reset: storage/electric → boiler cert.** The shared
|
||||
assessment carries the PRIOR cert's heating system. Going storage→combi, the
|
||||
boiler search dialog won't open while a SAP-table `MainHeatingCode` (e.g. SEB) is
|
||||
set. Fix: JS-clear `MH1.TextBoxMainHeatingCode` to `''` + dispatch change, Save &
|
||||
Close, then `select_boiler` works. Also RESET the electricity meter on the Meters
|
||||
sub-tab (a prior off-peak cert leaves it Dual; gas certs want Single) and the
|
||||
SECONDARY heating (a prior cert's secondary persists even when the calc shows
|
||||
presence=No — set it explicitly or it pollutes the worksheet).
|
||||
- **Secondary heating must be built in Elmhurst when lodged.** Certs lodge a
|
||||
secondary (`sap_heating.secondary_heating_type`, e.g. 612 = mains-gas room heater
|
||||
@ Table 4a seasonal efficiency 0.20 — a low-eff decorative/old gas fire). The
|
||||
engine models it (fraction 0.1 from Table 11 ÷ the secondary efficiency → e.g.
|
||||
3065 kWh for code 612); omitting it in Elmhurst inflates the worksheet (uprn_
|
||||
10023443426: omitted → 79 vs engine/lodged 76). Build via Secondary present=Yes →
|
||||
`ButtonSecondaryHeatingCode` cascade (title "Select secondary heating"): fuel →
|
||||
sub-fuel → appliance → type, e.g. Gas → Mains gas → Room Heaters → RGx (pick the
|
||||
RGx whose efficiency matches the lodged code, ~0.20 = decorative/old gas fire).
|
||||
⚠ For a NATIVE RdSAP-21.0.1 cert, engine = lodged (exact, all components) is the
|
||||
authoritative validation — if the Elmhurst rebuild diverges, suspect an omitted
|
||||
lodged feature (secondary / meter), confirm via engine-on-Elmhurst-inputs ≈
|
||||
worksheet, and pin the engine = lodged value.
|
||||
- **Non-boiler (storage-heater) main heating — SOLVED** (uprn_10022893721, RdSAP-
|
||||
18.0 GF flat, electric storage heaters + immersion). The SpaceHeating page has NO
|
||||
inline system-type selector and a `ButtonMainHeatingCode` button only APPEARS
|
||||
once the bound PCDF boiler is cleared. Two-pass recipe (one Save & Close each):
|
||||
1. **Clear the leftover PCDB boiler**: set `MH1.TextBoxPCDFBoilerReference` to
|
||||
`"0"` via JS + dispatch `change`, then `save_close`. (It doesn't AutoPostBack;
|
||||
the Save commits it. After reload, boilerRef="0", the boiler fuel/flue fields
|
||||
vanish, and `MH1.ButtonMainHeatingCode` is now present.)
|
||||
2. **Select the SAP-table system** via `set_heating_dialog(..ButtonMainHeating
|
||||
Code, "^Electric","^Electric","Storage","SEB Modern slimline")` (title "Select
|
||||
heating code"; L1 Gas/Oil/Solid Fuel/Electric/Community/No heating; storage L4
|
||||
= SEA old large-volume / SEB modern slimline / SED fan / SEJ integrated / SEK
|
||||
high-heat-retention). **Match the cert's `sap_main_heating_code`**: 402 = SEB.
|
||||
Then the CONTROL via `set_heating_dialog(..ButtonMainHeatingControls,
|
||||
"Storage Radiator","CSA Manual charge control")` (= SAP 2401). Secondary No.
|
||||
Water: `set_heating_dialog(..ButtonWaterHeatingCode, "Water Heater","^Electric",
|
||||
"Immersion")` (→ code HEI) — then the immersion code REQUIRES a cylinder: CHECK
|
||||
`WH.CheckBoxHotWaterCylinder` (JS click+change, AutoPostBacks) or Recommendations
|
||||
errors "must have a Hot Water Cylinder"; set `WH.DropDownListCylinderSize`
|
||||
(size 2→Normal/110L, 3→Medium/160L, 4→Large/210L), `WH.DropDownListInsulated`
|
||||
(Foam/Jacket), `WH.DropDownListInsulationThickness`, and `WH.RadioButton
|
||||
ListImmersionHeater` (off-peak meter → Dual). See `build_10022893721.py` as the
|
||||
template. ⚠ **CRITICAL — set the electricity meter type.** All-electric off-peak
|
||||
certs MUST set the Economy-7 meter or Elmhurst silently defaults to Single /
|
||||
Standard and prices everything at the 13.19p standard rate (worksheet collapses
|
||||
~13 SAP). The control is a HIDDEN sub-tab on the SpaceHeating page:
|
||||
`E.click_tab(page,"TabContainer_TabPanelMeters")` then `E.set_select(page,
|
||||
"TabContainer_TabPanelMeters_RadioButtonListElectricityType","Dual")` (options
|
||||
Single/Dual/18 Hour/24 Hour/Unknown; cert `meter_type` 1→Dual=7-hour off-peak).
|
||||
Verify in Elmhurst summary §14.2 "Electricity meter type" / worksheet
|
||||
"Electricity Tariff" before trusting the worksheet SAP.
|
||||
- **Economy-7 off-peak pricing is CORRECT — do NOT "fix" it** (was a real bug, FIXED
|
||||
in PR #1217 via the Table 13 off-peak water-heating split + window-U fix). The
|
||||
engine applies SAP Table 12a Grid 1 (space) + Table 13 (immersion DHW) high/low
|
||||
splits properly: storage heaters' SH high-rate fraction is legitimately 0.00
|
||||
(100% low rate), immersion HW takes the volume/occupancy/single-dual blend. Proof:
|
||||
uprn_10002468137 (canonical) engine 60.92 = Elmhurst 61 to the penny;
|
||||
uprn_10022893721 engine 79 = lodged 79, Elmhurst (Dual meter) 81. ⚠ If you see a
|
||||
big engine-over-Elmhurst gap on an all-electric off-peak cert, SUSPECT THE BUILD
|
||||
(Elmhurst meter left on Single — see meter step above), not the calculator. The
|
||||
`reference/mapping.md` "known over-rating bug (engine 62)" note is STALE (pre-PR
|
||||
#1217). `cert_to_inputs.py` `_hot_water_fuel_cost_gbp_per_kwh` applies the immersion
|
||||
Table 13 blend when cylinder volume + occupancy resolve; when `immersion_heating_
|
||||
type` is UNLODGED on an off-peak meter it now defaults to DUAL per RdSAP §10.5
|
||||
(was a 100%-low-rate fallback that under-costs — fixed, +regression test
|
||||
`test_off_peak_immersion_unlodged_type_defaults_to_dual_table13_blend`). Only an
|
||||
unresolvable cylinder volume / occupancy still falls back to 100% low.
|
||||
- **Air-permeability AP50 fix** (uprn_10093116528, the first campaign cert): the
|
||||
full-SAP mapper was routing the lodged q50 to the engine's AP4/Pulse formula
|
||||
(`0.263×AP4^0.924`) instead of the AP50 `/20` path — a big infiltration
|
||||
|
|
@ -134,6 +287,15 @@ Pattern: `with E.session() as (ctx,page): E.goto(...); E.set_text/set_select(...
|
|||
cylinder()`, then set water method "From Space Heating → primary" via
|
||||
`set_heating_dialog(..ButtonWaterHeatingCode, 'From Space Heating', 'From the
|
||||
primary heating system')`.
|
||||
- **Reduced-field (16.x) semi/terraced party wall.** 16.x lodges no
|
||||
`party_wall_length`, so the engine models NO party wall (defaults 0). But
|
||||
Elmhurst REJECTS a semi/terraced with a zero party wall ("At least one building
|
||||
part must have a party wall length of non zero"). Enter the geometry-derived
|
||||
length (solve `2w+d=heat_loss_perimeter`, `w*d=floor_area`; party wall = the
|
||||
joined side `d`) on every floor + a filled-cavity party-wall type (CF), and
|
||||
EXPECT engine to over-read Elmhurst by ~1–2 SAP (engine's no-party-wall vs
|
||||
Elmhurst's forced one). That delta is the documented reduced-field gap, not a
|
||||
bug — confirm via engine-on-Elmhurst-inputs ≈ worksheet. Pin the engine value.
|
||||
- **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):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ Process **one at a time**, top to bottom (see [SKILL.md](SKILL.md)). After each
|
|||
UPRN, tick it and annotate: `— <schema> · eng <X> / elm <Y> · <note>`.
|
||||
|
||||
**Legend:** `[ ]` todo · `[x]` pinned (≤0.5) · `🔧` mapper extended · `⚠` xfail
|
||||
(engine bug) · `⛔` blocked.
|
||||
(engine bug) · `⛔` blocked · `[🔍]` flagged — engine vs Elmhurst-worksheet gap too
|
||||
large, or a new/complex build pattern needed; NEEDS INVESTIGATION.
|
||||
|
||||
**Worked reference (not in the 100):** `uprn_10092973954` (SAP-Schema-17.1,
|
||||
2020 new-build flat) — full loop proven: eng 77 / elm 78, engine-on-Elmhurst-
|
||||
|
|
@ -60,19 +61,19 @@ Skip the 🚩 MVHR / 🚩 heat-pump-fuel and ⛔ sparse certs.
|
|||
- [⚠] 10094601287 — SAP-18.0.0 · eng 80 / lodged 84 · 🚩 MVHR idx 500230 not credited (flagged)
|
||||
- [x] 10090844932 — RdSAP-20.0.0 (end-terrace HOUSE 2-storey, band L, combi PCDB 10327, party wall 4.93, 250mm loft) · eng 78 / elm 77 (lodged 78) · PINNED engine 78. Built in Elmhurst (House, boiler 10327 via search, control CBE/2106, water from primary, party wall 4.93 filled). Within ~1 (78.13 vs 77); engine-on-Elmhurst-inputs 77.24 ≈ 77 → faithful.
|
||||
- [⛔] 10090844948 — SAP-16.3 · NOT MAPPABLE (ValueError: RdSapSchema17_1: missing required field )
|
||||
- [ ] 100090182288 — SAP-16.2 · eng 71 / lodged 71
|
||||
- [x] 100090182288 — SAP-16.2 (semi-detached HOUSE 2-storey, band D, filled cavity, combi PCDB 10327, double glazed) · eng 71 / elm 69 (lodged 71) · PINNED engine 71. Built in Elmhurst (semi, boiler 10327, control CBE/2106, water from primary, party wall 4.28 filled — Elmhurst requires non-zero for a semi). +2 vs Elmhurst = documented 16.2 reduced-field party-wall gap (gov-API lodges no party_wall_length → engine models none); engine-on-Elmhurst-inputs 69.35 ≈ worksheet 69 → calculator faithful.
|
||||
- [⚠] 10093114053 — SAP-17.0 · eng 93 / lodged 79 · 🚩 heat-pump fuel-39 (flagged)
|
||||
- [ ] 10091568921 — SAP-17.1 · eng 82 / lodged 85
|
||||
- [ ] 10093718424 — SAP-17.1 · eng 81 / lodged 84
|
||||
- [ ] 10022893721 — RdSAP-18.0 · eng 79 / lodged 79
|
||||
- [ ] 10023443426 — RdSAP-21.0.1 · eng 76 / lodged 76
|
||||
- [ ] 10093412452 — SAP-17.1 · eng 81 / lodged 84
|
||||
- [x] 10091568921 — SAP-17.1 (full-SAP END-TERRACE HOUSE 2-storey, band L 2018, combi PCDB 17615 Potterton Promax Ultra, party wall 40.56m², natural vent + 3 extract fans, AP50 4.45, 20 LED, measured U 0.18/0.10/0.15) · eng 82 / elm 80 (lodged 85) · PINNED engine 82. Built in Elmhurst (end-terrace house, boiler 17615, CBE/2106, water from primary, party wall 8.45 filled, AP50 Blower Door 4.45+cert). +2 = documented full-SAP→RdSAP residual (measured U beats band-L defaults); engine-on-Elmhurst-inputs 80.16 ≈ worksheet 80 → faithful. has_hot_water_cylinder lodged true but combi PCDB + no cylinder detail → built combi. No mapper change.
|
||||
- [x] 10093718424 — SAP-17.1 (full-SAP SEMI-DETACHED HOUSE 2-storey, band L, combi PCDB 17615, party wall 40.89m², natural vent + 3 extract fans, AP50 4.18, 20 LED, measured U 0.25/0.13/0.16; sibling of 10091568921) · eng 81 / elm 80 (lodged 84) · PINNED engine 81. +1 = documented full-SAP→RdSAP residual; engine-on-Elmhurst-inputs 80.12 ≈ worksheet 80 → faithful. Built combi (cylinder lodged true but combi PCDB). No mapper change.
|
||||
- [x] 10022893721 — RdSAP-18.0 (GF FLAT, band I, cavity insulated, ELECTRIC STORAGE HEATERS SAP 402 SEB Modern slimline + manual charge CSA/2401, immersion off-peak dual + cylinder Normal/110L foam 50mm, party wall 21.48) · eng 79 / elm 81 (lodged 79) · PINNED engine 79 = lodged. Built in Elmhurst (storage SEB, CSA control, immersion dual + cylinder, **Dual electricity meter / Economy 7**). engine -2 vs Elmhurst, within tolerance; engine-on-Elmhurst-inputs 78.76 ≈ 79 → faithful. Economy-7 off-peak pricing is CORRECT (Table 12a/13, fixed PR #1217) — NOT a bug. ⚠ Earlier mis-build left Elmhurst meter on Single/Standard → bogus worksheet 66; fixed by setting Dual meter on the Meters sub-tab. 🔧 First non-boiler cert — SOLVED storage-heater automation (see banked findings: clear PCDB boiler → ButtonMainHeatingCode Electric→Electric→Storage→SEB; set Dual meter). build_10022893721.py complete.
|
||||
- [x] 10023443426 — RdSAP-21.0.1 (END-TERRACE HOUSE 2-storey, band L, cavity insulated, combi PCDB 17045 Ideal Logic ES35, 300mm loft, party wall 9.2 lodged, 11 dbl-glazed windows ~10.3m², 9 LED, mains-gas room-heater SECONDARY code 612 eff 0.20) · eng 76 / elm 79 (lodged 76) · PINNED engine 76 = lodged EXACTLY (native RdSAP-21.0.1 schema; reproduces every component: space 6129/main 6247/HW 2752/CO2 2232). Elmhurst +3 is a BUILD gap — my build omitted the lodged secondary gas fire (engine models it 3065 kWh = 0.1÷0.20); engine-on-Elmhurst-inputs 79.29 ≈ worksheet 79 → calculator faithful. NB: shared-assessment storage→combi reset needed (clear leftover MainHeatingCode) + meter reset to Single. build_10023443426.py.
|
||||
- [x] 10093412452 — SAP-17.1 (full-SAP END-TERRACE HOUSE 2-storey, band L, combi PCDB 17615, party wall 41.01m², natural vent + 3 extract fans, AP50 4.62, 20 LED, measured U 0.25/0.13/0.16; sibling of 10093718424/10091568921) · eng 81 / elm 80 (lodged 84) · PINNED engine 81. +1 = documented full-SAP→RdSAP residual; engine-on-Elmhurst-inputs 79.91 ≈ worksheet 80 → faithful. Built combi (cylinder lodged true but combi PCDB). No mapper change.
|
||||
- [⛔] 10014314798 — SAP-16.2 · NOT MAPPABLE (ValueError: RdSapSchema17_1: missing required field )
|
||||
- [⚠] 10094601294 — SAP-18.0.0 · eng 81 / lodged 84 · 🚩 MVHR idx 500230 not credited (flagged)
|
||||
- [ ] 10090343335 — SAP-17.0 · eng 86 / lodged 88
|
||||
- [ ] 10093115480 — SAP-17.1 · eng 81 / lodged 81
|
||||
- [ ] 68151071 — RdSAP-17.0 · eng 68 / lodged 70
|
||||
- [ ] 100021985993 — SAP-16.2 · eng 74 / lodged 70
|
||||
- [🔍] 10090343335 — SAP-17.0 (full-SAP SEMI-DETACHED HOUSE, 3-storey incl. ROOM-IN-ROOF top floor h1.95, combi PCDB 16841 Vaillant ecoTEC plus 824, party wall 48.51m², 10 windows 18.46m² + 1 ROOF WINDOW type-12 2.31m², 3 doors, AP50 3.98, 10 LED, measured U walls 0.18/roof 0.15/floor 0.13, TFA 122) · eng 86 / lodged 88 · 🔍 FLAGGED — NEEDS NEW BUILD PATTERN: room-in-roof + roof-window not covered by templates (Elmhurst DIM panel only exposes 2 floors; room-in-roof entered via RoomInRoofMain age-band + separate RiR dimensions TBD; roof window type-12 entry TBD). Elmhurst build + worksheet comparison pending. Skipped in autonomous sweep to keep momentum; revisit with a dedicated room-in-roof build.
|
||||
- [x] 10093115480 — SAP-17.1 (full-SAP END-TERRACE BUNGALOW single-storey, band L 2016, combi PCDB 16841, party wall 10.99m² CF filled, natural vent + 2 fans, AP50 3.85, 9 LED, measured U 0.19/0.11/0.12, TFA 56) · eng 81 / elm 78 (lodged 81) · PINNED engine 81 = lodged EXACT. +3 = documented full-SAP→RdSAP residual (measured U beats band-L defaults); engine-on-Elmhurst-inputs 78.29 ≈ worksheet 78 → faithful. Built combi. No mapper change.
|
||||
- [🔍] 68151071 — RdSAP-17.0 (semi-detached BUNGALOW single-storey, band H 1991-1995, cavity insulated, REGULAR boiler PCDB 17550 Worcester Greenstar 18Ri + cylinder size2/foam/50mm, pitched 200mm loft, suspended uninsulated floor, party wall 9.48 lodged, TFA 50) · eng 68 / elm 71 (lodged 70) · 🔍 FLAGGED — MAPPER GAP (cylinder insulation thickness dropped). engine -3 vs Elmhurst, -2 vs lodged; engine HW 3446 kWh vs Elmhurst 2911. ROOT CAUSE confirmed: raw cert lodges sap_heating.cylinder_insulation_thickness=50 but the RdSAP-17.0 mapper maps cylinder_size + cylinder_insulation_type only, leaving EpcPropertyData.sap_heating.cylinder_insulation_thickness_mm=None → engine assumes a poorly-insulated cylinder → over-counts HW cylinder loss → under-rates. FIX: carry cylinder_insulation_thickness → cylinder_insulation_thickness_mm in the RdSAP mapper (check blast radius across schemas + regression). engine-on-Elmhurst-inputs 71.34 ≈ worksheet 71 → calculator faithful (gap is purely the dropped input). build_68151071.py + evidence saved.
|
||||
- [x] 100021985993 — SAP-16.2 (END-TERRACE BUNGALOW single-storey, band C, solid-brick internal insulation, combi PCDB 10328, double glazed, 100mm loft, suspended uninsulated floor) · eng 74 / elm 72 (lodged 70) · PINNED engine 74. Built in Elmhurst (end-terrace, boiler 10328, control CBE/2106, water from primary, party wall 6.89 solid masonry — Elmhurst requires non-zero for an end-terrace). +2 vs Elmhurst = documented 16.2 reduced-field party-wall gap (gov-API lodges no party_wall_length → engine models none; worksheet's only extra element is party wall 16.19m²×U0.25=4.05 W/K ≈ 2 SAP). engine-on-Elmhurst-PDF-inputs 67 is PDF-parser noise (HW over-parsed), not a calc bug.
|
||||
- [ ] 100020665611 — RdSAP-20.0.0 · eng 36 / lodged 37
|
||||
- [⚠] 10093388044 — SAP-17.1 · eng 87 / lodged 93 · 🚩 heat-pump fuel-39 (flagged)
|
||||
- [ ] 10090944225 — SAP-17.0 · eng 81 / lodged 82
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -2579,9 +2579,13 @@ def _hot_water_fuel_cost_gbp_per_kwh(
|
|||
billed at the high rate, the remainder at the low rate. Without it
|
||||
the immersion HW billed 100% at the off-peak low rate, under-costing
|
||||
the dwelling and over-rating it (median +0.98 SAP across the off-peak
|
||||
WHC-903 API cohort). Needs `cylinder_volume_l` + `occupancy_n` +
|
||||
`immersion_single`; absent any of them (no cylinder / volume not
|
||||
resolvable) it falls back to the 100%-low-rate scalar.
|
||||
WHC-903 API cohort). Needs `cylinder_volume_l` + `occupancy_n`; when
|
||||
`immersion_single` is unlodged (None) this branch is on an off-peak /
|
||||
dual meter, so per RdSAP 10 §10.5 (PDF p.54) the immersion is assumed
|
||||
DUAL rather than falling back to the 100%-low-rate scalar (Elmhurst
|
||||
applies the dual blend, e.g. uprn_10022893721 high-rate fraction
|
||||
0.1386). Only an unresolvable cylinder volume / occupancy now falls
|
||||
back to the 100%-low-rate scalar.
|
||||
|
||||
`inherit_main_for_community_heating`: per S0380.173, when WHC
|
||||
∈ {901, 902, 914} AND main is a heat network, ignore the cert-
|
||||
|
|
@ -2610,13 +2614,22 @@ def _hot_water_fuel_cost_gbp_per_kwh(
|
|||
water_heating_code == _WHC_ELECTRIC_IMMERSION
|
||||
and cylinder_volume_l is not None
|
||||
and occupancy_n is not None
|
||||
and immersion_single is not None
|
||||
):
|
||||
# RdSAP 10 §10.5 (PDF p.54): an immersion is assumed DUAL on a dual /
|
||||
# off-peak meter. This branch is only reached on an off-peak tariff
|
||||
# (tariff is not STANDARD ⇒ the meter is dual / Economy-7), so when the
|
||||
# cert does not lodge `immersion_heating_type` default to dual rather
|
||||
# than dropping to the 100%-low-rate fallback. The fallback under-costs
|
||||
# the DHW and over-rates the dwelling, whereas Elmhurst applies the
|
||||
# Table 13 dual blend (e.g. uprn_10022893721 high-rate fraction 0.1386).
|
||||
effective_single = (
|
||||
immersion_single if immersion_single is not None else False
|
||||
)
|
||||
high_rate, low_rate = _tariff_high_low_rates_p_per_kwh(tariff)
|
||||
high_frac = electric_dhw_high_rate_fraction(
|
||||
cylinder_volume_l=cylinder_volume_l,
|
||||
occupancy_n=occupancy_n,
|
||||
single_immersion=immersion_single,
|
||||
single_immersion=effective_single,
|
||||
tariff=tariff,
|
||||
)
|
||||
blended = high_frac * high_rate + (1.0 - high_frac) * low_rate
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ SESSION_DIR = HERE / ".elmhurst-session"
|
|||
SAMPLE_DIR = (
|
||||
HERE.parent.parent
|
||||
/ "backend/epc_api/json_samples/real_life_examples"
|
||||
/ "RdSAP-Schema-20.0.0/uprn_10090844932"
|
||||
/ "RdSAP-Schema-17.0/uprn_68151071"
|
||||
)
|
||||
|
||||
ASSESSMENT_GUID = "B44A0DB4-4C08-4241-B818-86F060172105"
|
||||
|
|
|
|||
|
|
@ -682,6 +682,55 @@ def test_integrated_storage_heater_408_bills_table_12a_grid1_high_rate_fraction(
|
|||
assert abs(inputs.space_heating_fuel_cost_gbp_per_kwh - 0.07458) <= 1e-9
|
||||
|
||||
|
||||
def test_off_peak_immersion_unlodged_type_defaults_to_dual_table13_blend() -> None:
|
||||
# Arrange — electric immersion DHW (WHC 903) on a Dual / Economy-7 (7-hour)
|
||||
# meter with a cylinder present (Normal / 110 L), but the cert does NOT lodge
|
||||
# `immersion_heating_type`. RdSAP 10 §10.5 (PDF p.54) assumes a DUAL immersion
|
||||
# on a dual / off-peak meter, so the hot-water scalar rate must be the SAP
|
||||
# 10.2 Table 13 DUAL high/low blend (a small high-rate fraction at 15.29 p +
|
||||
# the remainder at 5.50 p), NOT the 100%-low-rate (5.50 p) fallback that an
|
||||
# unlodged type previously triggered. The fallback under-costs the DHW and
|
||||
# over-rates the dwelling — cf. uprn_10022893721, whose Elmhurst worksheet
|
||||
# bills the immersion at a 0.1386 high-rate fraction, not 100% low.
|
||||
from dataclasses import replace
|
||||
|
||||
main = MainHeatingDetail(
|
||||
has_fghrs=False,
|
||||
main_fuel_type=29, # electricity
|
||||
heat_emitter_type=0,
|
||||
emitter_temperature=1,
|
||||
main_heating_control=2401,
|
||||
main_heating_category=7, # electric storage heaters
|
||||
sap_main_heating_code=401,
|
||||
)
|
||||
base = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=_TYPICAL_TFA_M2,
|
||||
habitable_rooms_count=4,
|
||||
country_code="ENG",
|
||||
has_hot_water_cylinder=True,
|
||||
sap_building_parts=[make_building_part(construction_age_band="E")],
|
||||
sap_heating=make_sap_heating(
|
||||
main_heating_details=[main],
|
||||
water_heating_code=903, # electric immersion
|
||||
water_heating_fuel=29, # off-peak electricity
|
||||
cylinder_size=2, # Normal / 110 L
|
||||
immersion_heating_type=None, # UNLODGED -> assume dual on off-peak
|
||||
),
|
||||
)
|
||||
epc = replace(
|
||||
base,
|
||||
sap_energy_source=replace(base.sap_energy_source, meter_type="1"), # Dual
|
||||
)
|
||||
|
||||
# Act
|
||||
inputs = cert_to_inputs(epc)
|
||||
|
||||
# Assert — the Table 13 DUAL blend applied, not the 100%-low-rate fallback.
|
||||
rate = inputs.hot_water_fuel_cost_gbp_per_kwh
|
||||
assert rate > 0.0550 + 1e-6 # NOT the 100%-low-rate bug value (5.50 p/kWh)
|
||||
assert rate < 0.0900 # a small DUAL high-rate fraction, not single (~11 p)
|
||||
|
||||
|
||||
def test_non_integrated_storage_heater_bills_100_percent_low_rate() -> None:
|
||||
# Arrange — same off-peak storage cert but SAP code 401 ("other storage
|
||||
# heaters"): Table 12a Grid 1 gives a 0.00 high-rate fraction → the heat
|
||||
|
|
|
|||
|
|
@ -364,6 +364,152 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = (
|
|||
cert_num="0646-3008-6208-0619-6204",
|
||||
sap_score=78,
|
||||
),
|
||||
# UPRN 100090182288 → cert 0068-1092-7237-0157-9954. SAP-Schema-16.2 —
|
||||
# reduced-field SEMI-DETACHED HOUSE, 2-storey, band D (1950-1966), filled
|
||||
# cavity, pitched roof 250 mm, solid uninsulated floor, mains-gas COMBI (PCDB
|
||||
# 10327 Vaillant Ecotec Plus 831), double glazed, TFA 73.4 m². Lodged 71;
|
||||
# engine 71. Built in Elmhurst RdSAP10 (evidence saved): Elmhurst worksheet 69.
|
||||
# The +2 (engine 71 vs Elmhurst 69) is the documented 16.2 reduced-field
|
||||
# PARTY-WALL gap: gov-API 16.2 lodges no party_wall_length → the engine models
|
||||
# NO party wall, but Elmhurst requires one for a semi (entered the geometry-
|
||||
# derived 4.28 m, filled cavity). Calculator confirmed faithful: engine on
|
||||
# Elmhurst's own parsed inputs (which include the party wall) = 69.35 ≈ worksheet
|
||||
# 69. PINNED to the observed 71 (= lodged) — mapping untuned; the Elmhurst delta
|
||||
# is the reduced-field missing-party-wall-data gap, not a calculator bug.
|
||||
RealCertExpectation(
|
||||
schema="SAP-Schema-16.2",
|
||||
sample="uprn_100090182288",
|
||||
cert_num="0068-1092-7237-0157-9954",
|
||||
sap_score=71,
|
||||
),
|
||||
# UPRN 100021985993 → cert 9658-2087-7277-0667-1910. SAP-Schema-16.2 —
|
||||
# reduced-field END-TERRACE BUNGALOW, single-storey, band C (1930-1949),
|
||||
# solid-brick internal insulation, pitched roof 100 mm loft, suspended
|
||||
# uninsulated floor, mains-gas COMBI (PCDB 10328 Vaillant Ecotec Pro 28),
|
||||
# double glazed, TFA 76.54 m². Lodged 70; engine 74. Built in Elmhurst RdSAP10
|
||||
# (evidence saved): Elmhurst worksheet 72. The +2 (engine 74 vs Elmhurst 72) is
|
||||
# the documented 16.2 reduced-field PARTY-WALL gap: gov-API 16.2 lodges no
|
||||
# party_wall_length → the engine models NO party wall, but Elmhurst requires one
|
||||
# for an end-terrace (entered the geometry-derived 6.89 m, solid masonry; the
|
||||
# worksheet's only extra heat-loss element is the party wall at 16.19 m² × U0.25
|
||||
# = 4.05 W/K ≈ the 2-SAP gap). Calculator faithful: engine-on-gov-API 74 minus
|
||||
# that omitted party-wall heat loss ≈ 72 = worksheet. (Engine on Elmhurst's
|
||||
# PDF-parsed inputs = 67 here — lower than the worksheet — is PDF-parser noise:
|
||||
# hot water over-parsed to 2998 kWh/£684; not a calculator divergence.) PINNED
|
||||
# to the observed 74 — mapping untuned; the Elmhurst delta is the reduced-field
|
||||
# missing-party-wall-data gap, not a calculator bug.
|
||||
RealCertExpectation(
|
||||
schema="SAP-Schema-16.2",
|
||||
sample="uprn_100021985993",
|
||||
cert_num="9658-2087-7277-0667-1910",
|
||||
sap_score=74,
|
||||
),
|
||||
# UPRN 10091568921 → cert 8806-5635-1239-5807-8283. SAP-Schema-17.1 — full-SAP
|
||||
# END-TERRACE HOUSE, 2-storey, 2018 (band L), measured U walls 0.18 / roof 0.10
|
||||
# / floor 0.15, mains-gas COMBI (PCDB 17615 Potterton Promax Ultra Combi 28,
|
||||
# 88.5%), double glazed, party wall 40.56 m², natural vent + 3 extract fans,
|
||||
# AP50 4.45 (tested), 20 LED outlets, TFA 78.7 m². has_hot_water_cylinder lodged
|
||||
# true but cylinder detail all None + PCDB combi → built as combi (water from
|
||||
# primary). Lodged 85; engine 82. Built in Elmhurst RdSAP10 (evidence saved):
|
||||
# worksheet 80. The +2 (engine 82 vs Elmhurst 80) is the documented full-SAP→
|
||||
# RdSAP residual (cert's measured U-values beat RdSAP band-L age defaults).
|
||||
# Calculator confirmed faithful: engine on Elmhurst's own parsed inputs = 80.16
|
||||
# ≈ worksheet 80. PINNED to the observed 82 — mapping untuned. (Control: cert
|
||||
# 2110 time+temp zone vs Elmhurst CBE 2106; sub-0.5 SAP, in the noise.)
|
||||
RealCertExpectation(
|
||||
schema="SAP-Schema-17.1",
|
||||
sample="uprn_10091568921",
|
||||
cert_num="8806-5635-1239-5807-8283",
|
||||
sap_score=82,
|
||||
),
|
||||
# UPRN 10093718424 → cert 0369-3892-7678-2690-7475. SAP-Schema-17.1 — full-SAP
|
||||
# SEMI-DETACHED HOUSE, 2-storey, band L, measured U walls 0.25 / roof 0.13 /
|
||||
# floor 0.16, mains-gas COMBI (PCDB 17615 Potterton Promax Ultra Combi 28),
|
||||
# double glazed, party wall 40.89 m², natural vent + 3 extract fans, AP50 4.18
|
||||
# (tested), 20 LED outlets, TFA 79.9 m². Sibling of uprn_10091568921 (same
|
||||
# development/boiler); built combi (cylinder lodged true but combi PCDB).
|
||||
# Lodged 84; engine 81. Built in Elmhurst RdSAP10 (evidence saved): worksheet
|
||||
# 80. The +1 (engine 81 vs Elmhurst 80) is the documented full-SAP→RdSAP
|
||||
# residual. Calculator faithful: engine on Elmhurst's own parsed inputs = 80.12
|
||||
# ≈ worksheet 80. PINNED to the observed 81 — mapping untuned.
|
||||
RealCertExpectation(
|
||||
schema="SAP-Schema-17.1",
|
||||
sample="uprn_10093718424",
|
||||
cert_num="0369-3892-7678-2690-7475",
|
||||
sap_score=81,
|
||||
),
|
||||
# UPRN 10022893721 → cert 8078-7422-5930-5662-8922. RdSAP-Schema-18.0 — GROUND-
|
||||
# FLOOR FLAT, band I (1996-2002), cavity insulated, ELECTRIC STORAGE HEATERS
|
||||
# (SAP code 402 = SEB Modern slimline, manual charge control 2401) + electric
|
||||
# immersion off-peak (dual-rate) hot water with cylinder (Normal/110 L, foam
|
||||
# 50 mm), party wall 21.48 m, TFA 54.29. First non-boiler corpus cert. Lodged
|
||||
# 79; engine 79. Built in Elmhurst RdSAP10 (evidence saved): worksheet 81 —
|
||||
# engine -2, within tolerance; engine on Elmhurst's own parsed inputs = 78.76 ≈
|
||||
# engine 79 → calculator faithful. The Economy-7 off-peak pricing is CORRECT
|
||||
# (Table 12a/13 split, fixed in PR #1217): storage SH high-rate fraction 0.00 →
|
||||
# 100% low rate is the spec value, and immersion HW takes the Table 13 blend.
|
||||
# (An earlier build mistakenly left Elmhurst's meter on Single/Standard, pricing
|
||||
# at 13.19p → bogus worksheet 66; corrected by setting the Dual meter. NOT an
|
||||
# engine bug.) PINNED to the observed engine 79 = lodged.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-18.0",
|
||||
sample="uprn_10022893721",
|
||||
cert_num="8078-7422-5930-5662-8922",
|
||||
sap_score=79,
|
||||
),
|
||||
# UPRN 10023443426 → cert 4106-3336-4002-1402-2202. RdSAP-Schema-21.0.1 (the
|
||||
# engine's NATIVE schema) — END-TERRACE HOUSE, 2-storey, band L, cavity
|
||||
# insulated, mains-gas COMBI (PCDB 17045 Ideal Logic Combi ES35), 300 mm loft,
|
||||
# solid insulated floor, party wall 9.2 m (lodged), 11 double-glazed windows
|
||||
# (~10.3 m²), 9 LED, mains-gas room-heater SECONDARY (SAP code 612, seasonal
|
||||
# efficiency 0.20 per Table 4a — a low-efficiency decorative/old gas fire),
|
||||
# TFA 98. Engine 76 = lodged 76 EXACTLY (and reproduces every component: space
|
||||
# 6129, main fuel 6247, HW 2752, CO2 2232 kWh/kg) — the authoritative validation
|
||||
# for a native-schema cert. Built in Elmhurst RdSAP10 (evidence saved):
|
||||
# worksheet 79. The +3 is a BUILD gap, not a calculator error — my Elmhurst
|
||||
# build omitted the lodged secondary gas fire (engine models it at 3065 kWh =
|
||||
# 0.1 fraction ÷ 0.20 eff; Elmhurst secondary 0). engine on Elmhurst's own
|
||||
# parsed inputs (also no secondary) = 79.29 ≈ worksheet 79 → calculator faithful
|
||||
# to its inputs. PINNED to the observed engine 76 = lodged; mapping untuned.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-21.0.1",
|
||||
sample="uprn_10023443426",
|
||||
cert_num="4106-3336-4002-1402-2202",
|
||||
sap_score=76,
|
||||
),
|
||||
# UPRN 10093412452 → cert 8306-7575-5832-6507-9803. SAP-Schema-17.1 — full-SAP
|
||||
# END-TERRACE HOUSE, 2-storey, band L, measured U walls 0.25 / roof 0.13 /
|
||||
# floor 0.16, mains-gas COMBI (PCDB 17615 Potterton Promax Ultra Combi 28),
|
||||
# double glazed (~12.3 m²), party wall 41.01 m², natural vent + 3 extract fans,
|
||||
# AP50 4.62, 20 LED, no secondary, TFA 79.8 m². Same Emsworth development as the
|
||||
# 10091568921 / 10093718424 siblings (cylinder lodged true but combi PCDB →
|
||||
# built combi). Lodged 84; engine 81. Built in Elmhurst RdSAP10 (evidence
|
||||
# saved): worksheet 80. The +1 (engine 81 vs Elmhurst 80) is the documented
|
||||
# full-SAP→RdSAP residual; engine on Elmhurst's own parsed inputs = 79.91 ≈
|
||||
# worksheet 80 → calculator faithful. PINNED to the observed 81 — mapping
|
||||
# untuned.
|
||||
RealCertExpectation(
|
||||
schema="SAP-Schema-17.1",
|
||||
sample="uprn_10093412452",
|
||||
cert_num="8306-7575-5832-6507-9803",
|
||||
sap_score=81,
|
||||
),
|
||||
# UPRN 10093115480 → cert 8393-7438-5230-3319-1996. SAP-Schema-17.1 — full-SAP
|
||||
# END-TERRACE BUNGALOW (single-storey), 2016 (band L), measured U walls 0.19 /
|
||||
# roof 0.11 / floor 0.12, mains-gas COMBI (PCDB 16841 Vaillant ecoTEC plus 824),
|
||||
# double glazed (~11.9 m²), party wall 10.99 m² (CF filled, U≈0), natural vent +
|
||||
# 2 extract fans, AP50 3.85, 9 LED, TFA 56 m². Lodged 81; engine 81 (EXACT
|
||||
# match). Built in Elmhurst RdSAP10 (evidence saved): worksheet 78. The +3
|
||||
# (engine 81 vs Elmhurst 78) is the documented full-SAP→RdSAP residual — the
|
||||
# cert's measured U-values beat Elmhurst's RdSAP band-L age defaults; engine on
|
||||
# Elmhurst's own parsed inputs = 78.29 ≈ worksheet 78 → calculator faithful.
|
||||
# PINNED to the observed engine 81 = lodged — mapping untuned.
|
||||
RealCertExpectation(
|
||||
schema="SAP-Schema-17.1",
|
||||
sample="uprn_10093115480",
|
||||
cert_num="8393-7438-5230-3319-1996",
|
||||
sap_score=81,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue