mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 25b: 000487 §4 closure (7/8) — has_electric_shower routes Nbath
Closes §4 LINE_43 + LINE_44/45/46/61/62/64 for 000487 (7 of 8 fails).
LINE_65 still fails — needs Appendix J step 8 (electric-shower kWh
derivation from cert) to land before LINE_65 heat gains close.
Spec citation: SAP10.2 Appendix J (p.81) step 2a: `Nbath = 0.13N + 0.19
if shower also present; = 0.35N + 0.50 if no shower present`. The
"shower also present" branch fires when ANY shower is lodged — mixer OR
electric — per the implicit reading that step 1a's Noutlets includes
electric showers in the count.
Changes:
- SapHeating gains `electric_shower_count` + `mixer_shower_count`.
- `water_heating_from_cert` gains `has_electric_shower: bool = False`;
combined with mixer-flow-rate presence to drive `has_shower`.
- `_mixer_shower_flow_rates_from_cert` honors `mixer_shower_count`
(default 1 vented when unlodged — preserves legacy behaviour).
- `_has_electric_shower_from_cert` new helper.
- `water_heating_section_from_cert` plumbs `has_electric_shower`
through bootstrap + final call (and the internal cert_to_inputs path).
- 000487 fixture: `electric_shower_count=1, mixer_shower_count=0`.
§4 per-fixture:
fixture | LINE_42 | LINE_43 | LINE_44-46 | LINE_61-65
000474 | ✓ | ✓ | ✓ | ✓ (9/9)
000477 | ✓ | ✓ | ✓ | ✗ LINE_61/62/64/65 (slice 25c)
000480 | ✓ | ✓ | ✓ | ✓ (9/9)
000487 | ✓ | ✓ | ✓ | ✓ except LINE_65 (8/9)
000490 | ✓ | ✓ | ✓ | ✓ (9/9)
000516 | ✓ | ✓ | ✓ | ✓ (9/9)
Scoreboard:
section_cascade_pins: 279 → 286 PASS (+7)
e2e SapResult: 32 → 32 PASS (unchanged — LINE_65 cascade still
open, blocks downstream §5 LINE_72/73 + §6 LINE_84 + §7 + downstream)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
015144361a
commit
ca56fdee5b
6 changed files with 63 additions and 17 deletions
|
|
@ -141,6 +141,18 @@ class SapHeating:
|
|||
# SAP10 hot-water demand inputs from sap_heating.
|
||||
number_baths: Optional[int] = None
|
||||
number_baths_wwhrs: Optional[int] = None
|
||||
# Per SAP10.2 Appendix J (p.81) step 1a: Noutlets includes electric
|
||||
# showers in the count for Nshower; step 2a routes Nbath through the
|
||||
# "shower also present" branch (0.13N + 0.19) when ANY shower is
|
||||
# lodged — including electric. Modelled separately from mixer outlets
|
||||
# because electric showers don't draw warm water from the system.
|
||||
electric_shower_count: Optional[int] = None
|
||||
# PCDF mixer-shower lodgement (count of outlets that DO draw warm
|
||||
# water from the main HW system). When set, overrides the heuristic
|
||||
# default of 1 vented outlet @ 7 L/min used by `_mixer_shower_flow_
|
||||
# rates_from_cert`. Most certs lodge only count; the standard
|
||||
# vented-system flow rate from Table J4 (7 L/min) is the default.
|
||||
mixer_shower_count: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -133,14 +133,15 @@ Two test files contain the strict pins:
|
|||
Total: **169 PASS / 83 FAIL** across the strict pins. 4 of 6 fixtures fully
|
||||
close §1+§2+§4. 000487 is the worst (RR fixture defect propagates everywhere).
|
||||
|
||||
(Post-slice-25a: section_cascade_pins 279 PASS / 33 FAIL, e2e SapResult
|
||||
32 PASS / 40 FAIL. §3 + §5 + §6 + §7 (mostly) pinned. §3 NOW FULLY
|
||||
CLOSES for all 6 fixtures (24/24) — slice 25a closed 000487 by lodging
|
||||
detailed RR surfaces + adding gable_wall_external + Ext1 alt U
|
||||
override + RdSAP §3.8 roof-area-as-max rule + half-up rounding.
|
||||
Remaining failures: §4 monthly on 000477+487 (slice 25b), §5 LINE_72/73
|
||||
+ §6 LINE_84 on 000477/487 (cascade from §4), §7 LINE_92/93 marginal
|
||||
on 000474/477/480/490 (precision artefact), §7 on 000487 (cascade).)
|
||||
(Post-slice-25b: section_cascade_pins 286 PASS / 26 FAIL, e2e SapResult
|
||||
32 PASS / 40 FAIL. §3 fully closes for all 6 fixtures (24/24). §4 closes
|
||||
8 of 9 for 000487 — only LINE_65 (heat gains from WH) still fails
|
||||
because the §4 cascade doesn't yet derive (64a) electric-shower kWh
|
||||
from the cert (Appendix J step 8). Remaining cascade failures: §4 on
|
||||
000477 (combi loss precision, slice 25c) + §4 LINE_65 on 000487
|
||||
(electric shower derivation), §5/§6 LINE_72/73/84 on 000477+487
|
||||
(cascade from §4), §7 LINE_92/93 marginal on 000474/477/480/490
|
||||
(precision artefact), §7 on 000487 (cascade from §4 LINE_65).)
|
||||
|
||||
### B.2 SapResult pin matrix (post-slice-22/23)
|
||||
|
||||
|
|
@ -201,6 +202,7 @@ fixture | section §4 pin status
|
|||
### B.5 Recent slices (in reverse order — newest first)
|
||||
|
||||
```
|
||||
Slice 25b: 000487 §4 closure (7/8) — has_electric_shower + mixer/electric counts on SapHeating, Appendix J step 2a fix
|
||||
Slice 25a: 000487 §3 closure — detailed RR + gable_wall_external + Ext1 alt U=1.9 + §3.8 max-floor roof + half-up rounding
|
||||
Slice 26c: §7 mean internal temp cascade pin (60 cases, 44 PASS) — LINE_85..94
|
||||
Slice 26b: §6 solar gains cascade pin (12 cases, 10 PASS) + SapRoofWindow solar attrs + plumb to §6 cascade
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ def make_sap_heating(
|
|||
secondary_fuel_type: Optional[int] = None,
|
||||
secondary_heating_type: Optional[int] = None,
|
||||
number_baths: Optional[int] = None,
|
||||
electric_shower_count: Optional[int] = None,
|
||||
mixer_shower_count: Optional[int] = None,
|
||||
) -> SapHeating:
|
||||
"""Build a SapHeating with SAP10 API defaults."""
|
||||
return SapHeating(
|
||||
|
|
@ -115,6 +117,8 @@ def make_sap_heating(
|
|||
secondary_fuel_type=secondary_fuel_type,
|
||||
secondary_heating_type=secondary_heating_type,
|
||||
number_baths=number_baths,
|
||||
electric_shower_count=electric_shower_count,
|
||||
mixer_shower_count=mixer_shower_count,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -718,12 +718,14 @@ def water_heating_section_from_cert(
|
|||
if main is not None and main.main_heating_index_number is not None
|
||||
else None
|
||||
)
|
||||
has_electric_shower = _has_electric_shower_from_cert(epc)
|
||||
bootstrap = water_heating_from_cert(
|
||||
epc=epc,
|
||||
mixer_shower_flow_rates_l_per_min=_mixer_shower_flow_rates_from_cert(epc),
|
||||
has_bath=_has_bath_from_cert(epc),
|
||||
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
||||
low_water_use=False,
|
||||
has_electric_shower=has_electric_shower,
|
||||
)
|
||||
combi_loss_override = pcdb_combi_loss_override(
|
||||
pcdb_main,
|
||||
|
|
@ -737,6 +739,7 @@ def water_heating_section_from_cert(
|
|||
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
||||
low_water_use=False,
|
||||
combi_loss_monthly_kwh_override=combi_loss_override,
|
||||
has_electric_shower=has_electric_shower,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -950,16 +953,30 @@ def _mixer_shower_flow_rates_from_cert(
|
|||
) -> tuple[float, ...]:
|
||||
"""Pull mixer-shower flow rates from the cert.
|
||||
|
||||
The cert lodges flow rate per shower outlet (Elmhurst worksheets
|
||||
show "Vented hot water system, 7.00"). The domain model doesn't
|
||||
surface that field yet; until it does, every cert defaults to the
|
||||
SAP10.2 Table J4 "Vented hot water system" row at 7 L/min — the
|
||||
existing-dwelling minimum and what both validation fixtures
|
||||
(000474 + 000490) actually lodge. Combi-pumped showers (11 L/min)
|
||||
or electric showers (handled via (64a)m, not here) will need
|
||||
proper shower-outlet-type plumbing in a later slice.
|
||||
When `sap_heating.mixer_shower_count` is lodged, use that count
|
||||
of vented mixers @ Table J4's 7 L/min row. When None, default to a
|
||||
single vented outlet — the modal RdSAP lodging. Combi-pumped
|
||||
showers (11 L/min) need a richer cert surface in a future slice.
|
||||
"""
|
||||
return (_SHOWER_FLOW_VENTED_L_PER_MIN,)
|
||||
count = (
|
||||
epc.sap_heating.mixer_shower_count
|
||||
if epc.sap_heating is not None
|
||||
else None
|
||||
)
|
||||
if count is None:
|
||||
count = 1
|
||||
return tuple(_SHOWER_FLOW_VENTED_L_PER_MIN for _ in range(max(0, count)))
|
||||
|
||||
|
||||
def _has_electric_shower_from_cert(epc: EpcPropertyData) -> bool:
|
||||
"""True iff cert lodges ≥ 1 instantaneous electric shower.
|
||||
|
||||
Electric showers don't draw warm water from the main HW system but
|
||||
count in `Noutlets` for SAP10.2 Appendix J (p.81) step 1a and route
|
||||
Nbath through the "shower also present" branch in step 2a (0.13N +
|
||||
0.19 instead of 0.35N + 0.50). Defaults False when unlodged."""
|
||||
n = epc.sap_heating.electric_shower_count if epc.sap_heating is not None else None
|
||||
return (n or 0) >= 1
|
||||
|
||||
|
||||
def _has_bath_from_cert(epc: EpcPropertyData) -> bool:
|
||||
|
|
@ -1052,12 +1069,14 @@ def _water_heating_worksheet_and_gains(
|
|||
zero_monthly = (0.0,) * 12
|
||||
if epc.total_floor_area_m2 is None:
|
||||
return None, zero_monthly
|
||||
has_electric_shower = _has_electric_shower_from_cert(epc)
|
||||
bootstrap = water_heating_from_cert(
|
||||
epc=epc,
|
||||
mixer_shower_flow_rates_l_per_min=_mixer_shower_flow_rates_from_cert(epc),
|
||||
has_bath=_has_bath_from_cert(epc),
|
||||
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
||||
low_water_use=False,
|
||||
has_electric_shower=has_electric_shower,
|
||||
)
|
||||
combi_loss_override = pcdb_combi_loss_override(
|
||||
pcdb_record,
|
||||
|
|
@ -1071,6 +1090,7 @@ def _water_heating_worksheet_and_gains(
|
|||
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
||||
low_water_use=False,
|
||||
combi_loss_monthly_kwh_override=combi_loss_override,
|
||||
has_electric_shower=has_electric_shower,
|
||||
)
|
||||
return wh_result, wh_result.heat_gains_monthly_kwh
|
||||
|
||||
|
|
|
|||
|
|
@ -172,6 +172,12 @@ def build_epc() -> EpcPropertyData:
|
|||
# radiant heaters" → SAP code 691. Table 11 fraction 0.10.
|
||||
secondary_heating_type=691,
|
||||
number_baths=1, # 000487 line 123: Total number of baths = 1
|
||||
# 000487 cert: 1 instantaneous electric shower, no mixer
|
||||
# outlet (worksheet text shows only Doors/Windows openings,
|
||||
# no shower outlet under Heat Loss). Drives Appendix J step 2a
|
||||
# to use Nbath = 0.13N + 0.19 ("shower also present").
|
||||
electric_shower_count=1,
|
||||
mixer_shower_count=0,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -595,6 +595,7 @@ def water_heating_from_cert(
|
|||
low_water_use: bool,
|
||||
combi_loss_monthly_kwh_override: Optional[tuple[float, ...]] = None,
|
||||
electric_shower_monthly_kwh_override: Optional[tuple[float, ...]] = None,
|
||||
has_electric_shower: bool = False,
|
||||
) -> WaterHeatingResult:
|
||||
"""SAP 10.2 §4 orchestrator — chain every line ref from (42) through
|
||||
(65) for a combi-gas dwelling with optional PCDB-backed combi loss.
|
||||
|
|
@ -635,6 +636,7 @@ def water_heating_from_cert(
|
|||
has_shower = (
|
||||
len(mixer_shower_flow_rates_l_per_min) > 0
|
||||
or electric_shower_monthly_kwh_override is not None
|
||||
or has_electric_shower
|
||||
)
|
||||
baths = hot_water_baths_monthly_l_per_day(
|
||||
n_occupants=n,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue