mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
§4 slice 9: line (65)m heat gains from water heating
(65)m = 0.25 × [0.85 × (45)m + (61)m + (64a)m]
+ 0.80 × [(46)m + (57)m + (59)m]
First bracket recovers 25% of delivered-heat losses (hot water at the
tap + combi cycling + electric-shower waste heat); second bracket
recovers 80% of pipe-side losses (distribution + solar storage +
primary circuit) since pipework typically sits inside the heated
envelope. Per spec footnote on xlsx row 302, callers should zero (57)m
when the hot water store is OUTSIDE the heated space (e.g. communal
heat networks).
Validated against both Elmhurst fixtures to <1e-3 kWh:
000490 Jan: 0.25×(0.85×187.86 + 50.96 + 0) + 0.80×(28.18 + 0 + 0)
= 0.25×210.64 + 0.80×28.18 = 52.66 + 22.54 = 75.20 ✓
000474 Jan: 0.25×(0.85×174.40 + 28.72 + 0) + 0.80×(26.16 + 0 + 0)
= 0.25×176.96 + 0.80×26.16 = 44.24 + 20.93 = 65.17 ✓
LINE_64A_M and LINE_65_M lodged on both fixtures.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
feef819814
commit
43da3ea064
4 changed files with 76 additions and 0 deletions
|
|
@ -215,3 +215,8 @@ LINE_63B_M_PV_DIVERTER_KWH: tuple[float, ...] = (0.0,) * 12
|
|||
LINE_63C_M_SOLAR_KWH: tuple[float, ...] = (0.0,) * 12
|
||||
LINE_63D_M_FGHRS_KWH: tuple[float, ...] = (0.0,) * 12
|
||||
LINE_64_M_OUTPUT_FROM_WH_KWH: tuple[float, ...] = LINE_62_M_TOTAL_WH_KWH
|
||||
LINE_64A_M_ELECTRIC_SHOWER_KWH: tuple[float, ...] = (0.0,) * 12
|
||||
LINE_65_M_HEAT_GAINS_FROM_WH_KWH: tuple[float, ...] = (
|
||||
65.1690, 57.4789, 60.7300, 52.6612, 50.5342, 44.9825,
|
||||
44.0196, 46.0942, 46.9564, 53.0172, 57.1669, 64.3662,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -201,3 +201,8 @@ LINE_63B_M_PV_DIVERTER_KWH: tuple[float, ...] = (0.0,) * 12 # no PV diverter
|
|||
LINE_63C_M_SOLAR_KWH: tuple[float, ...] = (0.0,) * 12 # no solar HW
|
||||
LINE_63D_M_FGHRS_KWH: tuple[float, ...] = (0.0,) * 12 # no FGHRS
|
||||
LINE_64_M_OUTPUT_FROM_WH_KWH: tuple[float, ...] = LINE_62_M_TOTAL_WH_KWH # no reductions
|
||||
LINE_64A_M_ELECTRIC_SHOWER_KWH: tuple[float, ...] = (0.0,) * 12 # mixer showers only
|
||||
LINE_65_M_HEAT_GAINS_FROM_WH_KWH: tuple[float, ...] = (
|
||||
75.2034, 66.4381, 70.4305, 61.5896, 59.4711, 53.3391,
|
||||
52.4705, 54.6991, 55.4582, 62.1383, 66.4342, 74.3403,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from domain.sap.worksheet.water_heating import (
|
|||
combi_loss_monthly_kwh_table_3a_keep_hot_time_clock,
|
||||
distribution_loss_monthly_kwh,
|
||||
energy_content_of_hot_water_monthly_kwh,
|
||||
heat_gains_from_water_heating_monthly_kwh,
|
||||
hot_water_baths_monthly_l_per_day,
|
||||
hot_water_mixer_showers_monthly_l_per_day,
|
||||
hot_water_other_uses_monthly_l_per_day,
|
||||
|
|
@ -554,6 +555,35 @@ def test_output_from_water_heater_clamps_to_zero_when_renewables_exceed_demand()
|
|||
assert all(v == pytest.approx(0.0, abs=1e-9) for v in monthly)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fixture", (_w000474, _w000490), ids=("000474", "000490"))
|
||||
def test_heat_gains_from_water_heating_matches_elmhurst_line_65(fixture) -> None: # type: ignore[no-untyped-def]
|
||||
"""SAP10.2 §4 line (65)m heat gains released into the heated space:
|
||||
(65)m = 0.25 × [0.85 × (45)m + (61)m + (64a)m]
|
||||
+ 0.8 × [(46)m + (57)m + (59)m]
|
||||
|
||||
The first bracket is delivered-heat losses (energy hot from the tap +
|
||||
combi cycling losses + electric-shower waste heat) at 25% recovery;
|
||||
the second is pipe-side losses (distribution + solar storage +
|
||||
primary circuit) at 80% recovery. Per spec footnote (57)m is only
|
||||
included when the hot water store is inside the dwelling (heat
|
||||
networks excluded) — both fixtures have (57)m=0 so the conditional
|
||||
doesn't bite.
|
||||
"""
|
||||
# Arrange / Act
|
||||
monthly = heat_gains_from_water_heating_monthly_kwh(
|
||||
energy_content_monthly_kwh=fixture.LINE_45_M_HW_ENERGY_CONTENT_KWH,
|
||||
distribution_loss_monthly_kwh=fixture.LINE_46_M_DISTRIBUTION_LOSS_KWH,
|
||||
solar_storage_monthly_kwh=fixture.LINE_57_M_SOLAR_STORAGE_KWH,
|
||||
primary_loss_monthly_kwh=fixture.LINE_59_M_PRIMARY_LOSS_KWH,
|
||||
combi_loss_monthly_kwh=fixture.LINE_61_M_COMBI_LOSS_KWH,
|
||||
electric_shower_monthly_kwh=fixture.LINE_64A_M_ELECTRIC_SHOWER_KWH,
|
||||
)
|
||||
|
||||
# Assert
|
||||
for m, (actual, exp) in enumerate(zip(monthly, fixture.LINE_65_M_HEAT_GAINS_FROM_WH_KWH)):
|
||||
assert actual == pytest.approx(exp, abs=1e-3), f"month {m+1}"
|
||||
|
||||
|
||||
def test_assumed_occupancy_floor_at_n_eq_1_for_small_dwellings() -> None:
|
||||
"""Appendix J piecewise definition: TFA ≤ 13.9 m² → N=1 exactly. A
|
||||
tiny studio flat at the boundary is the most common trigger."""
|
||||
|
|
|
|||
|
|
@ -302,6 +302,42 @@ def output_from_water_heater_monthly_kwh(
|
|||
)
|
||||
|
||||
|
||||
def heat_gains_from_water_heating_monthly_kwh(
|
||||
*,
|
||||
energy_content_monthly_kwh: tuple[float, ...],
|
||||
distribution_loss_monthly_kwh: tuple[float, ...],
|
||||
solar_storage_monthly_kwh: tuple[float, ...],
|
||||
primary_loss_monthly_kwh: tuple[float, ...],
|
||||
combi_loss_monthly_kwh: tuple[float, ...],
|
||||
electric_shower_monthly_kwh: tuple[float, ...],
|
||||
) -> tuple[float, ...]:
|
||||
"""SAP 10.2 §4 line (65)m heat gains released into the heated space:
|
||||
(65)m = 0.25 × [0.85 × (45)m + (61)m + (64a)m]
|
||||
+ 0.80 × [(46)m + (57)m + (59)m]
|
||||
|
||||
First bracket: delivered-heat losses (hot water at the tap + combi
|
||||
cycling losses + electric-shower waste heat) at 25 % recovery into
|
||||
the dwelling. Second bracket: pipe-side losses (distribution +
|
||||
solar storage + primary circuit) at 80 % recovery — these run
|
||||
through the heated envelope so most of the loss heats it.
|
||||
|
||||
Per spec footnote at xlsx row 302, include (57)m only if the hot
|
||||
water store is in the dwelling. Callers should pass zero for
|
||||
out-of-dwelling stores (e.g. communal heat networks).
|
||||
"""
|
||||
return tuple(
|
||||
0.25 * (0.85 * e + c + es) + 0.80 * (d + s + p)
|
||||
for e, d, s, p, c, es in zip(
|
||||
energy_content_monthly_kwh,
|
||||
distribution_loss_monthly_kwh,
|
||||
solar_storage_monthly_kwh,
|
||||
primary_loss_monthly_kwh,
|
||||
combi_loss_monthly_kwh,
|
||||
electric_shower_monthly_kwh,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _days_weighted_average(monthly: tuple[float, ...]) -> float:
|
||||
"""Σ value[m] × n_m / 365 — used by Appendix J equations J4 and J9."""
|
||||
return sum(v * d for v, d in zip(monthly, _DAYS_IN_MONTH)) / _DAYS_IN_YEAR
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue