Fix cylinder_size/boiler_flue_type input errors for UPRN 100010086084 (SAP-15.0)

Both flagged mismatches were Elmhurst input errors (same silent-stale-value
contamination class as the earlier chimneys/wall-thickness bugs), not
parser or calculator bugs:

- cylinder_size: build_100010086084.py's water_heating() selected
  DropDownListCylinderSize by raw value "2", but this DOM's option values
  ARE their visible litre-band text (no "2" among them) — Playwright
  silently no-ops on a non-matching value, leaving a prior cert's "Medium
  (131-170L)" selection in place. Fixed to match by text ("Normal"), and
  added the missing mapper dict entries (Normal/Large litre-suffixed
  labels) to _ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10 (a real mapper-coverage
  gap — the calculator raises UnmappedElmhurstLabel rather than silently
  mis-mapping).

- boiler_flue_type: the boiler-code search dialog's combined "Balanced/Open
  Flue" Table 4b category doesn't drive the separate, independently
  selectable RadioButtonListFlueType field, which was left at an inherited
  "Balanced" from a prior cert. Fixed space_heating() to explicitly select
  "Open" to match the lodged gov-API code. This field isn't consumed by
  Sap10Calculator (ML/generator-only), so it had zero effect on the SAP
  score.

Re-downloaded elmhurst_summary.pdf/elmhurst_worksheet.pdf after the fixes
(Recommendations page confirmed clean). Elmhurst-PDF-inputs path moved
46->47 SAP from the cylinder-volume correction; gov-API SAP (53) and
Elmhurst's own worksheet (51) are unchanged. Full accuracy suites re-run
clean (67 passed, 67 skipped, 1 xfailed, same as before).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-07-10 09:11:41 +00:00
parent 798d19d6ff
commit e91e0fa33e
6 changed files with 72 additions and 16 deletions

View file

@ -405,7 +405,7 @@ methodology) is still open — see comment in test_real_cert_sap_accuracy.py.
All 20 pinned as `RealCertExpectation`s to the engine's OWN observed output.
- [x] 🔧 100010359769 — SAP-15.0 · eng 68 (2011 lodged 68, not a target)
- [x] 🔧 100010359788 — SAP-15.0 · eng 72 (2011 lodged 73, not a target)
- [x] 🔍 100010086084 — SAP-15.0 · eng 53, Elmhurst worksheet 51 (2011 lodged 45, not a target) · FULL Elmhurst RdSAP10 rebuild REDONE 2026-07-10 — the prior 46/48 readings were CONTAMINATED (stale open-chimneys=2 and wall-thickness=280mm carried over from a PRIOR cert build in the shared Elmhurst session; the build script never explicitly zeroed `TextBoxOpenChimneys`). Fixed the build script (explicit chimney zeroing + wall thickness set to 220mm, since `CheckBoxWallThicknessUnknown` doesn't persist via Playwright and leaving it blank makes the Walls section invalid) and a real build-script bug (`ref.input_value()` truthiness check treated the string `"0"` as truthy, causing spurious navigation to Home.aspx that broke the SpaceHeating nav). Clean rebuild: eng 53 vs Elmhurst 51 — only a ~2pt gap now (was ~5-7pt). No mapper.py change was needed/made — the gov-API mapper's open_chimneys_count=0 was correct all along; the register itself lodges open_fireplaces_count=0. Remaining ~2pt gap not chased further: single-window-orientation approximation (South, max solar gain) plus untriaged minor divergences. See test_real_cert_sap_accuracy.py comment for full detail, incl. a possible Elmhurst-PDF-parser mismatch (cylinder size / boiler flue type) noted but out of scope.
- [x] 🔍 100010086084 — SAP-15.0 · eng 53, Elmhurst worksheet 51 (2011 lodged 45, not a target) · FULL Elmhurst RdSAP10 rebuild REDONE 2026-07-10 — the prior 46/48 readings were CONTAMINATED (stale open-chimneys=2 and wall-thickness=280mm carried over from a PRIOR cert build in the shared Elmhurst session; the build script never explicitly zeroed `TextBoxOpenChimneys`). Fixed the build script (explicit chimney zeroing + wall thickness set to 220mm, since `CheckBoxWallThicknessUnknown` doesn't persist via Playwright and leaving it blank makes the Walls section invalid) and a real build-script bug (`ref.input_value()` truthiness check treated the string `"0"` as truthy, causing spurious navigation to Home.aspx that broke the SpaceHeating nav). Clean rebuild: eng 53 vs Elmhurst 51 — only a ~2pt gap now (was ~5-7pt). No mapper.py change was needed/made — the gov-API mapper's open_chimneys_count=0 was correct all along; the register itself lodges open_fireplaces_count=0. Remaining ~2pt gap not chased further: single-window-orientation approximation (South, max solar gain) plus untriaged minor divergences. FOLLOW-UP (2026-07-10): root-caused + fixed the cylinder_size / boiler_flue_type mismatches, both (a) Elmhurst input errors (same silent-stale-value class as chimneys/wall-thickness) — build script's cylinder-size select assumed option VALUE==numeric code, but this DOM's option values are the visible litre-band text, so it silently no-opped and left a prior cert's "Medium" selected (fixed: match by text, "Normal"); the boiler-code dialog's combined "Balanced/Open Flue" category doesn't drive the separate FlueType field, which was left at a stale "Balanced" (fixed: explicit select to "Open"). Added mapper dict entries for the litre-suffixed cylinder labels (mapper.py `_ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10`). boiler_flue_type isn't consumed by the calculator at all (ML/generator-only), so zero SAP-score effect; cylinder fix moved Elmhurst-PDF-inputs SAP 46→47 only. gov-API SAP (53) and Elmhurst worksheet (51) unchanged. Full suite re-run clean (67 passed/67 skipped/1 xfailed). See test_real_cert_sap_accuracy.py comment for full detail.
- [x] 🔧 100010090369 — SAP-15.0 · eng 53 (2011 lodged 47, not a target) · ditto
- [x] 🔧 100010074470 — SAP-15.0 · eng 46 (2011 lodged 39, not a target) · ditto
- [x] 🔧 100010100813 — SAP-15.0 · eng 59 (2011 lodged 47, not a target) · ditto

View file

@ -7555,7 +7555,9 @@ _ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10: Dict[str, int] = {
# The live RdSAP10-Online tool renders the size band WITH its litre range
# suffix (observed on the SAP-Schema-15.0 UPRN 100010086084 build) rather
# than the bare label the cohort fixtures used above.
"Normal (Less than 130L)": 2,
"Medium (131-170L)": 3,
"Large (Greater than 171L)": 4,
}

View file

@ -5,7 +5,7 @@ UNKNOWN, not a specific mm value), pitched 300mm loft insulation, SOLID
ground floor uninsulated, mains-gas generic (no-PCDB) REGULAR boiler SAP
Table 4b code 115 (pre-1998, balanced/open flue, "BGL Pre 98 Wall mount
BF/OF") + programmer/room-thermostat control (2104/CBC) + radiators +
cylinder (size 2 = "Medium (131-170L)" on the live tool, foam, 25mm), gas
cylinder (size 2 = "Normal (Less than 130L)" on the live tool, foam, 25mm), gas
room-heater SECONDARY (603, approximated as RGB "Modern fire with open flue
post 1980" -- the cert gives no finer detail), TFA 68, single meter,
2-storey / 2 floor-dims (no extensions). Partial double glazing
@ -244,6 +244,15 @@ def space_heating(page):
E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingControls",
"^Boilers", "^Standard", "CBC Programmer and room thermostat")
print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value())
# Explicit flue type (BUG fixed): the "Balanced/Open Flue" boiler-code
# CATEGORY picked above is a combined Table 4b group name (BF/OF) that
# does NOT set the separate `RadioButtonListFlueType` select on this
# page -- that field kept its inherited/default "Balanced" from a
# prior cert (silent-stale-value class of bug, see docstring), which
# mismatches the lodged gov-API `boiler_flue_type=1` ("Open"). Set it
# explicitly to match.
E.set_select(page, f"{MH1B}RadioButtonListFlueType", "Open")
print("flue type:", page.locator(f"#{E.FP}{MH1B}RadioButtonListFlueType").input_value())
E.save_close(page)
@ -269,12 +278,18 @@ def water_heating(page):
E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode",
"From Space Heating", "From the primary heating system")
print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value())
# cylinder: size 2 (lodged cylinder_size code -- set_select matches by
# <option VALUE>, not visible text; value "2" renders as "Medium
# (131-170L)" on the live tool -- see the mapper fix for that label).
# foam insulation (type 1), 25mm (value happens to equal visible text
# here: "25 mm").
E.set_select(page, f"{WH}DropDownListCylinderSize", "2")
# cylinder: lodged cylinder_size code 2 = SAP10 "Normal" (110 L; see
# cert_to_inputs.py _CYLINDER_SIZE_CODE_TO_LITRES). BUG (fixed): this
# dropdown's <option VALUE> IS its visible text on this build ("No
# Access" / "Normal (Less than 130L)" / "Medium (131-170L)" / "Large
# (Greater than 171L)" / "Value known") -- passing the bare numeric
# code "2" to `set_select` silently matched nothing (Playwright
# select_option no-ops on a non-matching value) and left the field at
# whatever a PRIOR cert's build had set ("Medium (131-170L)"), the
# same silent-failure/stale-value contamination class as the wall-
# thickness gotcha documented in this file's docstring. Match by the
# real option text instead.
E.select_by_contains(page, f"{WH}DropDownListCylinderSize", "Normal")
E.set_select(page, f"{WH}DropDownListInsulated", "Foam")
E.set_select(page, f"{WH}DropDownListInsulationThickness", "25 mm")
print("cylinder size:", page.locator(f"#{E.FP}{WH}DropDownListCylinderSize").input_value())

View file

@ -1182,14 +1182,53 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = (
# — true multi-orientation entry does not reliably stick, see
# elmhurst_lib.py). South-facing (max solar gain) was used here as the
# fairer single-orientation bound.
# Also noted but NOT chased further (secondary to the primary gov-API vs
# Elmhurst-worksheet reconciliation, and outside this mapper): the
# Elmhurst-PDF ingestion path (`_map_elmhurst_*` in mapper.py) parses this
# rebuild's cylinder size as code 3 ("Large") though "Medium (131-170L)"
# (code 2) was selected in the tool, and boiler_flue_type as 2 though
# "Balanced/Open Flue" (code 1) was selected — possible PDF-parser
# mismatches worth a follow-up if the Elmhurst-PDF-inputs path is ever
# made a reconciliation target for this cert.
# UPDATE (2026-07-10, follow-up): the two flagged cylinder_size /
# boiler_flue_type mismatches were root-caused and fixed — both were (a)
# Elmhurst input errors, the same silent-stale-value contamination class
# as the chimneys/wall-thickness bugs above, NOT parser or mapper bugs:
# * cylinder_size: `water_heating()`'s `E.set_select(...,
# "DropDownListCylinderSize", "2")` assumed the <option> VALUE
# attribute was the bare numeric SAP code, but on this build's DOM
# the option VALUE *is* its visible text (`select_options()` dump:
# "No Access" / "Normal (Less than 130L)" / "Medium (131-170L)" /
# "Large (Greater than 171L)" / "Value known" — no "2" among them).
# Playwright's `select_option("2")` silently no-ops on a non-matching
# value, leaving the field at "Medium (131-170L)" inherited from a
# prior cert. Confirmed live: `.input_value()` read back "Medium
# (131-170L)" before the fix. Fixed the build script to match by
# visible text (`E.select_by_contains(..., "Normal")`) — verified
# `.input_value()` now reads "Normal (Less than 130L)", matching the
# lodged gov-API `cylinder_size=2` (Normal/110L per
# `cert_to_inputs.py` `_CYLINDER_SIZE_CODE_TO_LITRES`). Added the
# matching mapper dict entries for the litre-range-suffixed labels
# ("Normal (Less than 130L)": 2, "Large (Greater than 171L)": 4) next
# to the existing "Medium (131-170L)": 3 entry in
# `_ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10` (mapper.py) — a real
# mapper-coverage gap, since the calculator raises
# `UnmappedElmhurstLabel` rather than silently mis-mapping.
# * boiler_flue_type: the boiler-code SEARCH DIALOG's "Balanced/Open
# Flue" (matched via `"Balanced.*Open"`) is a combined Table 4b
# BOILER CATEGORY name (covers both flue types under one SAP code,
# 115) — it does NOT set the separate, independently-selectable
# `RadioButtonListFlueType` field on the SpaceHeating page (options:
# "Balanced" / "Open"). That field was left at its inherited/default
# "Balanced" from a prior cert (confirmed live via DOM dump: selected
# option was "Balanced" pre-fix), which the PDF/parser correctly read
# as code 2 — mismatching the lodged gov-API `boiler_flue_type=1`
# ("Open"). This field is NOT consumed by `Sap10Calculator`/
# `cert_to_inputs.py` (only by ML/recommendation-generator code), so
# it had zero effect on the SAP score either way — but fixed for data
# fidelity: `space_heating()` now explicitly sets
# `RadioButtonListFlueType` to "Open" after picking the boiler code.
# Both fixes applied, PDFs re-downloaded (Recommendations page confirmed
# clean beforehand). Elmhurst-PDF-inputs path moved 46 → 47 SAP (cylinder
# volume correction, `HW` 3436 kWh vs the prior smaller "Large" reading);
# this engine's gov-API SAP (53) and Elmhurst's own worksheet (51) are
# unaffected/unchanged by this follow-up — no calculator-input field for
# THIS reconciliation target actually depended on either mismatch.
# Full `test_real_cert_sap_accuracy.py` + `test_sap_accuracy_corpus.py`
# suites re-run clean after the mapper dict addition (67 passed, 67
# skipped, 1 xfailed — same as before the change).
RealCertExpectation(
schema="SAP-Schema-15.0",
sample="uprn_100010086084",