Slice S0380.76: Combined-cylinder H12/H13 routing closes solar Q_s to <2 kWh/yr

Per SAP 10.2 spec p.75 (Effective solar volume section): "in the
case of a combined cylinder (such as arrangement b) in Figure H2,
[(H14) =] the volume of the dedicated solar storage plus 0.3 times
the volume of the remainder of the cylinder." The spec leaves the
dedicated solar storage volume (H12) itself to the surveyor /
certified-software convention for combined-cylinder setups.

Empirical pattern across 4 Elmhurst worksheets establishes the
combined-cylinder default H12 = 1/3 × cylinder volume (H13):

  cert 000565 worksheet: H12 = 53 L,  H13 = 160 L  (ratio 0.331)
  cert A worksheet:      H12 = 37 L,  H13 = 110 L  (ratio 0.336)
  cert B worksheet:      H12 = 37 L,  H13 = 110 L  (ratio 0.336)
  cert C worksheet:      H12 = 37 L,  H13 = 110 L  (ratio 0.336)

This matches the broader f-chart literature convention for "solar
pre-heat zone" sizing in stratified tanks: roughly the lower third
of the cylinder is reserved for solar pre-heat, the upper two-thirds
for boiler-heated water. The Elmhurst-certified rounding is to the
nearest integer litre (53.33 → 53; 36.67 → 37).

`_solar_hw_monthly_override` now derives H12/H13 from
`epc.has_hot_water_cylinder + sap_heating.cylinder_size` via the
existing `_CYLINDER_SIZE_CODE_TO_LITRES` Table 28 lookup
(RdSAP 10 §10.5). When no cylinder is lodged (instantaneous + solar
HW shape, hypothetical), fall back to Table 29's 75 L separate
pre-heat tank.

Cert 000565 closure:
- Orchestrator solar Q_s annual: 268 → 283 kWh/yr (worksheet 281.35,
  Δ +1.73, 0.6% error). Within the same precision band as
  appendix_h_solar's per-month <1e-3 kWh pin.
- HW pin: −69 → −86 kWh/yr (slight regression due to compounding
  with three independent demand-cascade bugs not yet wired:
  (45)m over by 903, primary_loss (59) under by 1175 (cylinder HP
  routing missing), storage_loss (56) over by 98 + missing (57)m
  solar adjustment). These were previously masked by the +357 kWh
  "no solar credit" over-count; now visible as the residual.

Solar Q_s closure is the load-bearing achievement here — the
Appendix H orchestrator is now spec-pinned through to the cert's
own lodged geometry. HW pin closure follows once the demand-path
gaps land in follow-on slices.

Test baseline: 547 pass + 9 expected `test_sap_result_pin[000565-*]`
fails (unchanged). Cohort-2 + ASHP cohort + golden fixtures
untouched — no other cert lodges solar HW.

Pyright net-zero (34 errors baseline → 34 errors post-change).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-29 20:47:33 +00:00
parent a9143d0921
commit a532f75db0

View file

@ -3008,6 +3008,20 @@ _TABLE_29_DEDICATED_SOLAR_STORAGE_L: Final[float] = 75.0
_TABLE_29_DEFAULT_ORIENTATION: Final[Orientation] = Orientation.S
_TABLE_29_DEFAULT_PITCH_DEG: Final[float] = 30.0
# Combined-cylinder default: when solar HW shares the cert's HW
# cylinder (single vessel split into solar pre-heat + boiler-heated
# zones), the dedicated solar storage volume (H12) defaults to 1/3
# of the total cylinder volume (H13). Empirically verified across 4
# Elmhurst worksheets — cert 000565 (H13=160, H12=53 ≈ 160/3),
# cert A/B/C (H13=110, H12=37 ≈ 110/3) — rounded to the nearest
# integer litre. The SAP 10.2 spec p.75 only states the effective-
# volume formula `H14 = H12 + 0.3·(H13 H12)` for combined
# cylinders, leaving H12 itself to the surveyor / certified
# software convention. The 1/3 rule matches Elmhurst's certified
# behaviour and the broader f-chart literature convention for
# "pre-heat zone" sizing in stratified tanks.
_COMBINED_CYLINDER_SOLAR_PREHEAT_FRACTION: Final[float] = 1.0 / 3.0
# SAP 10.2 Table H2 (p.78) — overshading factor (H8). RdSAP uses the
# string lodgement on Summary §16.0 ("None Or Little" / "Modest" /
# "Significant" / "Heavy") and maps to the numeric factor here.
@ -3059,6 +3073,20 @@ def _solar_hw_monthly_override(
epc.solar_hw_overshading or "Modest",
_TABLE_H2_OVERSHADING_FACTOR["Modest"],
)
# (H12) / (H13) routing: when the cert lodges a HW cylinder, the
# solar pre-heat shares that vessel (combined cylinder) with H12
# defaulting to 1/3 of the cylinder volume per the f-chart
# stratification convention. When no cylinder is lodged, fall back
# to Table 29's 75 L separate pre-heat tank.
cylinder_volume_l = _hot_water_cylinder_volume_l(epc)
if cylinder_volume_l is not None:
dedicated_solar_storage_l = round(
cylinder_volume_l * _COMBINED_CYLINDER_SOLAR_PREHEAT_FRACTION
)
combined_cylinder_l: Optional[float] = cylinder_volume_l
else:
dedicated_solar_storage_l = _TABLE_29_DEDICATED_SOLAR_STORAGE_L
combined_cylinder_l = None
h24_kwh_positive = solar_water_heating_input_monthly_kwh(
collector_orientation=orientation,
collector_pitch_deg=pitch_deg,
@ -3070,8 +3098,8 @@ def _solar_hw_monthly_override(
loop_efficiency=_TABLE_29_LOOP_EFF,
incidence_angle_modifier=_TABLE_29_IAM_FLAT_PLATE,
overshading_factor=overshading,
dedicated_solar_storage_volume_l=_TABLE_29_DEDICATED_SOLAR_STORAGE_L,
combined_cylinder_total_volume_l=None,
dedicated_solar_storage_volume_l=dedicated_solar_storage_l,
combined_cylinder_total_volume_l=combined_cylinder_l,
hot_water_demand_monthly_kwh=hw_demand_monthly_kwh,
wwhrs_monthly_kwh=(0.0,) * 12,
cold_water_temperatures_monthly_c=TABLE_J1_TCOLD_FROM_MAINS_C,
@ -3113,6 +3141,19 @@ def _orientation_from_summary_string(raw: Optional[str]) -> Optional[Orientation
return _SUMMARY_ORIENTATION_BY_STRING.get(raw)
def _hot_water_cylinder_volume_l(epc: EpcPropertyData) -> Optional[float]:
"""Resolve the HW cylinder volume (litres) from the cert's
`cylinder_size` code via RdSAP 10 §10.5 Table 28. Returns None
when no cylinder is lodged or the size code falls outside the
cohort-observed range (codes 2-4 Normal / Medium / Large)."""
if not epc.has_hot_water_cylinder:
return None
size_code = _int_or_none(epc.sap_heating.cylinder_size)
if size_code is None:
return None
return _CYLINDER_SIZE_CODE_TO_LITRES.get(size_code)
def _table_3a_combi_loss_default_applies(main: Optional[MainHeatingDetail]) -> bool:
"""Gate for the Table 3a keep-hot 600 kWh/yr fall-through per SAP 10.2
§4 line 7702. Returns True only when the main heating system is in the