Model/backend/documents_parser
Khalim Conn-Kowlessar 7dceeff24b Slice S0380.147: Appendix D Eq D1 — Table 4b non-PCDB boilers (winter/summer monthly cascade)
SAP 10.2 Appendix D §D2.1 (2) Equation (D1) (PDF p.57):

  If the boiler provides both space and water heating, and the summer
  seasonal efficiency is lower than the winter seasonal efficiency,
  the efficiency is a combination of winter and summer seasonal
  efficiencies according to the relative proportion of heat needed
  from the boiler for space and water heating in the month concerned:

              Q_space + Q_water
  η_water,m = ───────────────────────────────
              Q_space/η_winter + Q_water/η_summer

  where Q_space (kWh/month) is the quantity calculated at (98c)m
  multiplied by (204) or by (205);
        Q_water (kWh/month) is the quantity calculated at (64)m;
        η_winter and η_summer are the winter and summer seasonal
        efficiencies (from Table 4b).

Pre-slice the cascade only wired Eq D1 for PCDB-tested boilers (the
`pcdb_record` branch in `_apply_water_efficiency`). For non-PCDB
Table 4b boilers (`sap_main_heating_code` 101-141) where the cert
lodges no `main_heating_index_number`, the cascade fell through to
the scalar `water_efficiency_pct` divisor — which resolved via WHC
901 inherit to Table 4b WINTER eff (wrong direction; spec wants the
monthly Eq D1 blend).

This slice:

  - Adds `domain/sap10_calculator/tables/table_4b.py` with the full
    41-row Table 4b (winter, summer) pair dict for codes 101-141
    verbatim from SAP 10.2 PDF p.168 (Table 4b).
  - Refactors `_apply_water_efficiency` parameter from
    `pcdb_record: Optional[GasOilBoilerRecord]` to
    `eq_d1_winter_summer_pct: Optional[tuple[float, float]]` —
    decouples the Eq D1 input from the PCDB record so a Table 4b
    fallback can populate it without faking a PCDB record.
  - Resolves Eq D1 inputs at the call site with priority order:
        1. PCDB Table 105 winter/summer (existing path)
        2. SAP 10.2 Table 4b (PDF p.168) winter/summer when PCDB
           absent + WHC=901 (`_WHC_FROM_MAIN_HEATING`, the spec form
           of "boiler provides both space and water heating").
    §9.4.11 -5pp interlock applies symmetrically to both columns of
    whichever (winter, summer) tuple is resolved.

Oil 1 cert worksheet (217)m verified Jan 81.83 / Apr 81.42 / May
79.94 / Jun-Sep 72.00 / Dec 81.86 — exact back-solve to Eq D1 with
Table 4b code 127 (winter 84, summer 72). Annual HW fuel (219) =
Σ (64)m × 100 / (217)m = 3638.99 kWh/yr ≡ cascade post-slice.

Cascade impact:

  Heating-systems corpus (worksheet-pinned, oil 1 only on pin grid):
    oil 1  SAP +1.76 → +1.18  (Δ -0.59)
           cost -£40.60 → -£27.12  (Δ +£13.48)
           CO2  -129.22 → -55.36   (Δ +73.86 kg/yr)
           PE   -590.02 → -275.52  (Δ +314.50 kWh/yr)
    Remaining oil 1 residual is Table 4f auxiliary energy (cascade
    pumps_fans 130 kWh vs worksheet 265 kWh — missing the oil-boiler
    pump 100 kWh + CH pump 130 vs ws 165). Follow-up slice.

  Golden fixtures (cert-pinned, integer-rounded PE):
    cert 0240 (dual oil combi 130, no cylinder): PE +0.05 → +1.02
    cert 6035 (gas combi 104, no cylinder):      PE +46.10 → +47.29
    Both shifts reflect spec-correct Eq D1 now firing for non-PCDB
    combi-no-cylinder configs. The pre-slice near-zero pin on cert
    0240 was masking offsetting cascade gaps (likely Table 4f
    auxiliary energy and/or dual-main Q_space split per (98c)m ×
    (204) which the cascade currently treats as full demand).

Following [[reference-unmapped-sap-code]] discipline, the new Table
4b dict is the canonical spec-source — `domain.sap10_ml.sap_
efficiencies._SPACE_EFF_BY_CODE` still carries the winter column for
the ML feature cascade and is left in place per the sap10_ml
deprecation plan (separate migration).

Test:
  test_sap_appendix_d_eq_d1_water_efficiency_monthly_for_non_pcdb_
  table_4b_boiler_with_cylinder — asserts cert 1431 oil 1 HW fuel
  annual = 3638.99 ± 1.0 kWh/yr (matches worksheet (219)).

Extended handover suite: 890 pass, 0 fail. Pyright net-zero (44=44).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 08:22:46 +00:00
..
handler address JTK review comments 2026-04-20 15:11:17 +00:00
tests Slice S0380.147: Appendix D Eq D1 — Table 4b non-PCDB boilers (winter/summer monthly cascade) 2026-06-01 08:22:46 +00:00
__init__.py Map to RdSapSiteNotes from site notes JSON 🟥 2026-04-16 13:54:03 +00:00
db_writer.py include updating epc_property_data to pashub to ara workflow 2026-04-29 09:55:14 +00:00
elmhurst_extractor.py Slice S0380.140: §4 cylinder storage loss — extractor picks up §16 thermostat lodging + Table 2b note b restricts ×0.9 to boiler/warm-air/HP systems 2026-05-31 19:03:58 +00:00
extractor.py Handle wall thickness "Unmeasurable" 🟩 2026-04-30 16:41:16 +00:00
local_runner.py update local runner to work for elmhurst 2026-04-24 14:01:36 +00:00
parser.py load ecmk site notes to db 2026-04-29 11:20:47 +00:00
pdf.py update local runner to work for elmhurst 2026-04-24 14:01:36 +00:00