Slice S0380.14: add 'Large' → cylinder_size=4 (closes cert 9418 Daikin)

🎯 Closes the 7th and final ASHP cohort cert. Summary path now
mirrors the API path's complete cohort closure at the ±0.07 spec
precision floor.

Cert 9418-3062-8205-3566-7200 (Summary_000902.pdf): Daikin Altherma
EDLQ05CAV3 (PCDB 102421 — distinct from the rest of the cohort's
Mitsubishi 104568), end-terrace house, TWO 1.64 kWp PV arrays (N+S),
210 L cylinder, `heating_duration_code='24'` (continuous heating).
Worksheet "SAP value" lodges 84.6305.

Single-line fix to
`_ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10`:
  +    "Large": 4,
extending Slice S0380.6's "Medium" → 3 mapping to also cover the
"Large" cylinder. Without it `_elmhurst_cylinder_size_code('Large',
True)` returned None → cascade routed off the HP-with-cylinder HW
path → HW kWh under by 466 (Summary 1404 vs API 1871 vs
worksheet-implied 1871 via (64)/(216) divide).

Forcing function: cert 9418 first-attempt Summary SAP closes from
Δ +2.5973 (lookup miss) to Δ **+0.0296** — within ±0.07. The PV
multi-array Slice S0380.9 work was already sufficient for cert
9418's two-array PV layout (1.64 kWp N + 1.64 kWp S surfaced
correctly first-try).

ASHP cohort closure: 7/7 at spec floor:
  cert  Δ vs worksheet
  0380  +0.0594
  0350  +0.0458
  2225  +0.0441
  2636  +0.0323
  3800  +0.0442
  9285  +0.0502
  9418  +0.0296  ← this slice
  ───────────────
  mean  +0.0437

Identical disposition to the API path's cohort closure at slice
102f (commit c0086660). Both paths now sit at the documented
Appendix N3.6 PSR-interpolation precision floor.

Added two tests:
- `test_summary_9418_large_cylinder_routes_to_code_4` — unit-level
  pin on the new mapping.
- `test_summary_9418_full_chain_sap_within_spec_floor_of_worksheet`
  — chain test at ±0.07.

Pyright net-zero on both edited files (mapper.py 32 baseline).

Regression suite: 686 pass + 10 fail (= handover baseline 669 + 10
+ 19 new GREEN tests across Slices S0380.2..S0380.14).

Spec refs:
- SAP 10.2 Table 2a — cylinder volume factor (52) keyed on volume_l;
  210 L = 0.8x range factor (vs 160 L = 0.9086).
- BRE PCDB Table 362 — Daikin EDLQ05CAV3 (id 102421) is the cohort's
  second HP record alongside Mitsubishi PUZ-WM50VHA (id 104568).
- Cert 9418 worksheet `dr87-0001-000902.pdf` "Cylinder Volume 210.00".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-27 21:52:15 +00:00
parent 7f099d986a
commit f878bf51a3
2 changed files with 50 additions and 4 deletions

View file

@ -64,6 +64,7 @@ _SUMMARY_000901_PDF = _FIXTURES / "Summary_000901.pdf" # cert 3800
_SUMMARY_000904_PDF = _FIXTURES / "Summary_000904.pdf" # cert 9285
_SUMMARY_000900_PDF = _FIXTURES / "Summary_000900.pdf" # cert 2225
_SUMMARY_000898_PDF = _FIXTURES / "Summary_000898.pdf" # cert 2636
_SUMMARY_000902_PDF = _FIXTURES / "Summary_000902.pdf" # cert 9418
# GOV.UK EPB API JSON for cert 001479 — the API-path counterpart of the
# Summary_001479.pdf fixture. Together they drive the API ≡ Summary
@ -816,6 +817,50 @@ def test_summary_2636_full_chain_sap_within_spec_floor_of_worksheet() -> None:
assert abs(result.sap_score_continuous - worksheet_unrounded_sap) < _ASHP_COHORT_CHAIN_TOLERANCE
def test_summary_9418_large_cylinder_routes_to_code_4() -> None:
# Arrange — cert 9418-3062-8205-3566-7200's Summary §15.1 lodges
# "Cylinder Size: Large". The dr87 worksheet lodges "Cylinder
# Volume 210.00" L, and the cascade lookup
# `_CYLINDER_SIZE_CODE_TO_LITRES = {3: 160.0, 4: 210.0}` maps code
# 4 → 210 L. Cert 9418 is the first cohort cert to exercise the
# "Large" cylinder lodging (every other cohort cert is "Medium").
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000902_PDF)
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
# Act
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
# Assert
assert epc.sap_heating.cylinder_size == 4
def test_summary_9418_full_chain_sap_within_spec_floor_of_worksheet() -> None:
# Arrange — cert 9418-3062-8205-3566-7200 (Summary_000902.pdf):
# **Daikin EDLQ05CAV3 ASHP** (PCDB index 102421 — distinct from
# the rest of the cohort's Mitsubishi 104568), end-terrace house
# with TWO 1.64 kWp PV arrays (N + S), 210 L cylinder.
# `heating_duration_code='24'` per Table N4 (continuous heating).
# Worksheet "SAP value" lodges 84.6305.
#
# Closes the cohort: the final ASHP cert. The only Summary-mapper
# gap was the missing "Large" → 4 mapping in
# `_ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10` (Slice S0380.14, this
# commit) — multi-array PV + Large-cylinder were the variants
# cert 9418 uniquely exercises.
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000902_PDF)
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
# Act
result = calculate_sap_from_inputs(
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
)
# Assert — ±0.07 ASHP-cohort spec-floor tolerance.
worksheet_unrounded_sap = 84.6305
assert abs(result.sap_score_continuous - worksheet_unrounded_sap) < _ASHP_COHORT_CHAIN_TOLERANCE
def test_summary_3800_full_chain_sap_within_spec_floor_of_worksheet() -> None:
# Arrange — cert 3800-8515-0922-3398-3563 (Summary_000901.pdf /
# dr87-0001-000901.pdf) is the third ASHP cohort cert to close on

View file

@ -3375,12 +3375,13 @@ _ELMHURST_GAS_BOILER_FUEL_TYPES: frozenset[str] = frozenset({
# Elmhurst Summary §15.1 "Cylinder Size" labels mapped to the SAP10
# cascade enum that `domain/sap10_calculator/rdsap/cert_to_inputs.py`
# `_CYLINDER_SIZE_CODE_TO_LITRES` keys ({3: 160.0, 4: 210.0}). Only the
# "Medium" lodging is exercised by the cohort (cert 0380); other size
# labels (Small / Large / Very Large) are deferred until a fixture
# exercises them.
# `_CYLINDER_SIZE_CODE_TO_LITRES` keys ({3: 160.0, 4: 210.0}). Exercised
# by the cohort: "Medium" (cert 0380 et al — 160 L) and "Large" (cert
# 9418 — 210 L). "Small" and "Very Large" labels are deferred until a
# fixture exercises them.
_ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10: Dict[str, int] = {
"Medium": 3,
"Large": 4,
}