Model/domain
Khalim Conn-Kowlessar f9551355bb Slice S0380.79: (57)m solar storage adjustment + separately-timed-DHW cylinder default
Closes cert 000565 sap_score regression — 28 (Δ−1) → **29 ✓ EXACT**.
Continuous SAP 28.4735 → 28.5652 (Δ −0.035 → +0.057 vs worksheet
28.5087). Two coupled fixes that together close the (56)/(57)m
storage-loss gap per SAP 10.2 §4 + Table 2b.

## 1. (57)m solar storage adjustment — SAP 10.2 §4 line 7693 (p.137)

    If the vessel contains dedicated solar storage or dedicated
    WWHRS storage,
          (57)m = (56)m × [(47) - Vs] ÷ (47), else (57)m = (56)m
    where Vs is Vww from Appendix G3 or (H12) from Appendix H.

    Total heat required for water heating calculated for each month
          (62)m = 0.85 × (45)m + (46)m + (57)m + (59)m + (61)m

(62)m sums (57)m — the solar-adjusted storage loss — not raw
(56)m. The cascade's `_cylinder_storage_loss_override` was
passing (56)m straight through as `solar_storage_monthly_kwh_
override`, over-counting (62)m by Vs/V each month whenever solar
HW shares the cylinder. For cert 000565: V = 160 L, Vs = (H12) =
53 L per the combined-cylinder ⅓-volume convention (S0380.76);
(V − Vs)/V = 0.6688 (matches worksheet 50.7018/75.8157 = 0.6688).

Fix: when `epc.solar_water_heating` is True, return (57)m =
(56)m × (V − Vs)/V from `_cylinder_storage_loss_override`. The
combined-cylinder Vs derivation reuses the
`_COMBINED_CYLINDER_SOLAR_PREHEAT_FRACTION` constant established
by S0380.76 for the Appendix H orchestrator path.

## 2. separately_timed_dhw defaults True when a cylinder is lodged

SAP 10.2 Table 2b note b) (PDF p.159):

    Multiply Temperature Factor by 0.9 if there is separate time
    control of domestic hot water (boiler systems, warm air
    systems and heat pump systems)

RdSAP 10 Specification §3 default table "Hot water separately
timed" (PDF p.57):

    No programmer, pre-1998 boiler: - No
    Programmer, pre-1998 boiler: - Yes
    Post-1998 boiler: - Yes

When a hot-water cylinder is lodged, DHW is timed by its own
programmer / boost cycle regardless of which heat generator
(boiler, HP, or combi-acting-as-boiler) feeds it. Combi-only
dwellings (no cylinder) skip the multiplier — DHW is
instantaneous and shares the boiler's space-heating cycle.

The earlier `_separately_timed_dhw` heuristic gated only on
`main_heating_category == 4` (heat pumps), returning False for
boiler-family + cylinder combos. Cert 000565 (gas combi via
WHC 914 + 160 L cylinder + cyl-stat absent) fell through to TF
= 0.60 × 1.3 × 1.0 = 0.78; the worksheet uses 0.60 × 1.3 × 0.9
= 0.702. The 10% TF over-count drove +98 kWh/yr into (56)m before
compounding with the missing (57)m solar adjustment.

Fix: `_separately_timed_dhw(epc, main)` returns True when a
cylinder is lodged, in addition to the existing HP branch. Signature
gains `epc` so the helper can inspect `has_hot_water_cylinder`;
both call sites in `_primary_loss_override` and
`_cylinder_storage_loss_override` updated.

## Cert 000565 movements at HEAD (post-S0380.78 → post-this slice)

| Field                | Pre-slice  | Post-slice |  Worksheet | Pre-Δ   | Post-Δ  |
|----------------------|-----------:|-----------:|-----------:|--------:|--------:|
| **sap_score**        |       **28** |       **29** |       **29** |    −1   |  **✓ 0**  |
| sap_score_continuous |    28.4735 |    28.5652 |    28.5087 | −0.035  |  +0.057 |
| ecf                  |     5.3904 |     5.3810 |     5.3866 | +0.004  |  −0.006 |
| total_fuel_cost_gbp  |    4683.39 |    4675.23 |    4680.26 |  +3.13  |   −5.03 |
| co2_kg               |    6480.57 |    6388.80 |    6447.63 |  +33    |   −58.8 |
| hot_water_kwh        |    4014.64 |    3517.37 |    3755.03 | +259.6  |  −237.7 |
| space_heating_kwh    |   58792.99 |   58936.06 |   59008.35 | −215.4  |   −72.3 |
| main_heating_fuel    |   34584.11 |   34668.27 |   34710.79 | −126.7  |   −42.5 |

HW pin overshot −238 (down from +260) — within ~6% of the
worksheet, vs the +37% over-count three slices ago. Continuous
SAP residual flipped from Δ −0.035 to Δ +0.057, restoring integer
sap_score = 29 EXACT. The cumulative cert 000565 closure across
S0380.77/78/79:
  hot_water_kwh:   +1399  →  +260  →  −238   (84% closed)
  sap_score_cont:  +0.60  → −0.035 →  +0.057 (90% closed)
  ecf:             −0.06  → +0.004 →  −0.006 (90% closed)

## Cross-cohort impact — cert 0390 golden pin update

Golden cert `0390-2954-3640-2196-4175` (Firebird oil combi PCDF
9005 + 160 L cylinder + cyl-stat=Y, no solar) was previously
flagged at SAP residual −7 with the comment "traces to fabric
heat-loss / oil-fuel cost cascade rather than the §4 HW path".
That diagnosis was wrong: cert 0390's §4 HW cascade WAS
applying TF=0.60 instead of TF=0.54 for the (56)m storage loss,
contributing ~£20/yr cost over-count.

Per [[feedback-spec-floor-skepticism]] + [[feedback-golden-
residuals-near-zero]], the +1 SAP closure (53→54, residual
−7→−6) is the spec-correct outcome of applying RdSAP §3 default
"Programmer, pre-1998 boiler → Yes". Pin updated; revised notes
record the slice S0380.79 attribution.

## Tests

- `test_cylinder_storage_loss_applies_57m_solar_adjustment_per_sap_4_line_7693`
  (test_cert_to_inputs.py) — pins `solar_storage_monthly_kwh[0]` to
  worksheet (57)Jan = 50.7018 at abs=1e-4 and the 12-month sum to
  596.9725 at abs=1e-3, for cert-000565-shape inputs (gas combi +
  cylinder + solar HW + cyl-stat absent).
- Updated golden pin for cert 0390 per the cross-cohort impact note.

Test baseline: 548 → 550 pass + 9 expected `test_sap_result_pin
[000565-*]` fails (sap_score now PASSING; one fewer expected fail
than mid-slice). Pyright net-zero on touched files (46 baseline =
46 after).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:46:12 +00:00
..
addresses get rid of comments 2026-05-20 13:21:11 +00:00
sap10_calculator Slice S0380.79: (57)m solar storage adjustment + separately-timed-DHW cylinder default 2026-05-29 21:46:12 +00:00
sap10_ml Slice S0380.26: RdSAP10 §5.8 dry-lining adjustment on alt walls — closes cert 7700 -0.44 → +5e-5 2026-05-28 10:56:11 +00:00
tasks added postcode splitter rewrite to ddd 2026-05-19 16:35:09 +00:00
postcode.py get rid of comments 2026-05-20 13:21:11 +00:00