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.
|
# SAP10 hot-water demand inputs from sap_heating.
|
||||||
number_baths: Optional[int] = None
|
number_baths: Optional[int] = None
|
||||||
number_baths_wwhrs: 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
|
@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
|
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).
|
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
|
(Post-slice-25b: section_cascade_pins 286 PASS / 26 FAIL, e2e SapResult
|
||||||
32 PASS / 40 FAIL. §3 + §5 + §6 + §7 (mostly) pinned. §3 NOW FULLY
|
32 PASS / 40 FAIL. §3 fully closes for all 6 fixtures (24/24). §4 closes
|
||||||
CLOSES for all 6 fixtures (24/24) — slice 25a closed 000487 by lodging
|
8 of 9 for 000487 — only LINE_65 (heat gains from WH) still fails
|
||||||
detailed RR surfaces + adding gable_wall_external + Ext1 alt U
|
because the §4 cascade doesn't yet derive (64a) electric-shower kWh
|
||||||
override + RdSAP §3.8 roof-area-as-max rule + half-up rounding.
|
from the cert (Appendix J step 8). Remaining cascade failures: §4 on
|
||||||
Remaining failures: §4 monthly on 000477+487 (slice 25b), §5 LINE_72/73
|
000477 (combi loss precision, slice 25c) + §4 LINE_65 on 000487
|
||||||
+ §6 LINE_84 on 000477/487 (cascade from §4), §7 LINE_92/93 marginal
|
(electric shower derivation), §5/§6 LINE_72/73/84 on 000477+487
|
||||||
on 000474/477/480/490 (precision artefact), §7 on 000487 (cascade).)
|
(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)
|
### 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)
|
### 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 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 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
|
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_fuel_type: Optional[int] = None,
|
||||||
secondary_heating_type: Optional[int] = None,
|
secondary_heating_type: Optional[int] = None,
|
||||||
number_baths: Optional[int] = None,
|
number_baths: Optional[int] = None,
|
||||||
|
electric_shower_count: Optional[int] = None,
|
||||||
|
mixer_shower_count: Optional[int] = None,
|
||||||
) -> SapHeating:
|
) -> SapHeating:
|
||||||
"""Build a SapHeating with SAP10 API defaults."""
|
"""Build a SapHeating with SAP10 API defaults."""
|
||||||
return SapHeating(
|
return SapHeating(
|
||||||
|
|
@ -115,6 +117,8 @@ def make_sap_heating(
|
||||||
secondary_fuel_type=secondary_fuel_type,
|
secondary_fuel_type=secondary_fuel_type,
|
||||||
secondary_heating_type=secondary_heating_type,
|
secondary_heating_type=secondary_heating_type,
|
||||||
number_baths=number_baths,
|
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
|
if main is not None and main.main_heating_index_number is not None
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
has_electric_shower = _has_electric_shower_from_cert(epc)
|
||||||
bootstrap = water_heating_from_cert(
|
bootstrap = water_heating_from_cert(
|
||||||
epc=epc,
|
epc=epc,
|
||||||
mixer_shower_flow_rates_l_per_min=_mixer_shower_flow_rates_from_cert(epc),
|
mixer_shower_flow_rates_l_per_min=_mixer_shower_flow_rates_from_cert(epc),
|
||||||
has_bath=_has_bath_from_cert(epc),
|
has_bath=_has_bath_from_cert(epc),
|
||||||
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
||||||
low_water_use=False,
|
low_water_use=False,
|
||||||
|
has_electric_shower=has_electric_shower,
|
||||||
)
|
)
|
||||||
combi_loss_override = pcdb_combi_loss_override(
|
combi_loss_override = pcdb_combi_loss_override(
|
||||||
pcdb_main,
|
pcdb_main,
|
||||||
|
|
@ -737,6 +739,7 @@ def water_heating_section_from_cert(
|
||||||
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
||||||
low_water_use=False,
|
low_water_use=False,
|
||||||
combi_loss_monthly_kwh_override=combi_loss_override,
|
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, ...]:
|
) -> tuple[float, ...]:
|
||||||
"""Pull mixer-shower flow rates from the cert.
|
"""Pull mixer-shower flow rates from the cert.
|
||||||
|
|
||||||
The cert lodges flow rate per shower outlet (Elmhurst worksheets
|
When `sap_heating.mixer_shower_count` is lodged, use that count
|
||||||
show "Vented hot water system, 7.00"). The domain model doesn't
|
of vented mixers @ Table J4's 7 L/min row. When None, default to a
|
||||||
surface that field yet; until it does, every cert defaults to the
|
single vented outlet — the modal RdSAP lodging. Combi-pumped
|
||||||
SAP10.2 Table J4 "Vented hot water system" row at 7 L/min — the
|
showers (11 L/min) need a richer cert surface in a future slice.
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
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:
|
def _has_bath_from_cert(epc: EpcPropertyData) -> bool:
|
||||||
|
|
@ -1052,12 +1069,14 @@ def _water_heating_worksheet_and_gains(
|
||||||
zero_monthly = (0.0,) * 12
|
zero_monthly = (0.0,) * 12
|
||||||
if epc.total_floor_area_m2 is None:
|
if epc.total_floor_area_m2 is None:
|
||||||
return None, zero_monthly
|
return None, zero_monthly
|
||||||
|
has_electric_shower = _has_electric_shower_from_cert(epc)
|
||||||
bootstrap = water_heating_from_cert(
|
bootstrap = water_heating_from_cert(
|
||||||
epc=epc,
|
epc=epc,
|
||||||
mixer_shower_flow_rates_l_per_min=_mixer_shower_flow_rates_from_cert(epc),
|
mixer_shower_flow_rates_l_per_min=_mixer_shower_flow_rates_from_cert(epc),
|
||||||
has_bath=_has_bath_from_cert(epc),
|
has_bath=_has_bath_from_cert(epc),
|
||||||
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
||||||
low_water_use=False,
|
low_water_use=False,
|
||||||
|
has_electric_shower=has_electric_shower,
|
||||||
)
|
)
|
||||||
combi_loss_override = pcdb_combi_loss_override(
|
combi_loss_override = pcdb_combi_loss_override(
|
||||||
pcdb_record,
|
pcdb_record,
|
||||||
|
|
@ -1071,6 +1090,7 @@ def _water_heating_worksheet_and_gains(
|
||||||
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
cold_water_temps_c=TABLE_J1_TCOLD_FROM_MAINS_C,
|
||||||
low_water_use=False,
|
low_water_use=False,
|
||||||
combi_loss_monthly_kwh_override=combi_loss_override,
|
combi_loss_monthly_kwh_override=combi_loss_override,
|
||||||
|
has_electric_shower=has_electric_shower,
|
||||||
)
|
)
|
||||||
return wh_result, wh_result.heat_gains_monthly_kwh
|
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.
|
# radiant heaters" → SAP code 691. Table 11 fraction 0.10.
|
||||||
secondary_heating_type=691,
|
secondary_heating_type=691,
|
||||||
number_baths=1, # 000487 line 123: Total number of baths = 1
|
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,
|
low_water_use: bool,
|
||||||
combi_loss_monthly_kwh_override: Optional[tuple[float, ...]] = None,
|
combi_loss_monthly_kwh_override: Optional[tuple[float, ...]] = None,
|
||||||
electric_shower_monthly_kwh_override: Optional[tuple[float, ...]] = None,
|
electric_shower_monthly_kwh_override: Optional[tuple[float, ...]] = None,
|
||||||
|
has_electric_shower: bool = False,
|
||||||
) -> WaterHeatingResult:
|
) -> WaterHeatingResult:
|
||||||
"""SAP 10.2 §4 orchestrator — chain every line ref from (42) through
|
"""SAP 10.2 §4 orchestrator — chain every line ref from (42) through
|
||||||
(65) for a combi-gas dwelling with optional PCDB-backed combi loss.
|
(65) for a combi-gas dwelling with optional PCDB-backed combi loss.
|
||||||
|
|
@ -635,6 +636,7 @@ def water_heating_from_cert(
|
||||||
has_shower = (
|
has_shower = (
|
||||||
len(mixer_shower_flow_rates_l_per_min) > 0
|
len(mixer_shower_flow_rates_l_per_min) > 0
|
||||||
or electric_shower_monthly_kwh_override is not None
|
or electric_shower_monthly_kwh_override is not None
|
||||||
|
or has_electric_shower
|
||||||
)
|
)
|
||||||
baths = hot_water_baths_monthly_l_per_day(
|
baths = hot_water_baths_monthly_l_per_day(
|
||||||
n_occupants=n,
|
n_occupants=n,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue