§5 slice 5: (68) appliances_monthly_w — Appendix L13/L14/L16a

E_A = 207.8 × (TFA × N)^0.4714 (L13) chained through monthly factor
1 + 0.157 × cos(2π × (m - 1.78) / 12) (L14) then watts via × 1000 /
(24 × n_m) (L16a). Column A typical-gain form — 1.0× conversion. L16's
0.67× reduced form deferred (new-build DPER/TPER use).

Verified against Elmhurst U985-0001-000490 (68)m row to ≤5e-2 W
(display rounding from the (TFA × N)^0.4714 term).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-20 18:07:34 +00:00
parent a4d6321f21
commit 0bc9eac34c
2 changed files with 72 additions and 0 deletions

View file

@ -30,6 +30,12 @@ from dataclasses import dataclass
from math import cos, exp, pi
from typing import Final, Optional
_DAYS_PER_YEAR: Final[float] = 365.0
_APPLIANCES_E_A_COEFF: Final[float] = 207.8
_APPLIANCES_E_A_EXPONENT: Final[float] = 0.4714
_APPLIANCES_MONTHLY_AMPLITUDE: Final[float] = 0.157
_APPLIANCES_MONTHLY_PHASE: Final[float] = 1.78
_MONTHS_IN_YEAR: Final[int] = 12
_DAYS_PER_MONTH: Final[tuple[int, ...]] = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
@ -73,6 +79,38 @@ def cooking_monthly_w(*, n_occupants: float) -> tuple[float, ...]:
return tuple(gain for _ in range(_MONTHS_IN_YEAR))
def appliances_monthly_w(
*,
total_floor_area_m2: float,
n_occupants: float,
) -> tuple[float, ...]:
"""SAP 10.2 §5 line (68) — appliance gains in watts per month.
Appendix L equations L13, L14, L16a (Column A, typical gains):
E_A = 207.8 × (TFA × N)^0.4714 [kWh/yr]
E_A,m = E_A × [1 + 0.157 × cos(2π × (m - 1.78) / 12)] × n_m / 365 [kWh/mo]
G_A,m = E_A,m × 1000 / (24 × n_m) [W]
The cosine peaks ~end-of-January (m=1.78) and troughs ~end-of-July.
All electrical appliance energy stays as internal heat full 1.0×
conversion. Column B's L16 (0.67×) reduced form is for new-build
DPER/TPER only.
"""
e_a_annual = (
_APPLIANCES_E_A_COEFF
* (total_floor_area_m2 * n_occupants) ** _APPLIANCES_E_A_EXPONENT
)
monthly: list[float] = []
for m_idx, days in enumerate(_DAYS_PER_MONTH):
m = m_idx + 1
factor = 1.0 + _APPLIANCES_MONTHLY_AMPLITUDE * cos(
2.0 * pi * (m - _APPLIANCES_MONTHLY_PHASE) / _MONTHS_IN_YEAR
)
e_a_m_kwh = e_a_annual * factor * days / _DAYS_PER_YEAR
monthly.append(e_a_m_kwh * _KWH_TO_WH / (_HOURS_PER_DAY * days))
return tuple(monthly)
def water_heating_gains_monthly_w(
*,
heat_gains_from_water_heating_monthly_kwh: tuple[float, ...],

View file

@ -13,6 +13,7 @@ Table 5a + Appendix L (lighting/appliances/cooking) + Appendix J Table 1b
import pytest
from domain.sap.worksheet.internal_gains import (
appliances_monthly_w,
cooking_monthly_w,
losses_monthly_w,
metabolic_monthly_w,
@ -121,3 +122,36 @@ def test_water_heating_gains_bridge_converts_kwh_per_month_to_watts() -> None:
assert len(monthly) == 12
for m, (actual, exp) in enumerate(zip(monthly, expected_w)):
assert actual == pytest.approx(exp, abs=1e-3), f"month {m+1}"
def test_appliances_gains_match_appendix_l13_l14_l16a_for_000490() -> None:
"""SAP 10.2 Appendix L equations L13/L14/L16a:
E_A = 207.8 × (TFA × N)^0.4714 [kWh/yr]
E_A,m = E_A × [1 + 0.157 × cos(2π × (m - 1.78) / 12)] × n_m / 365 [kWh/mo]
G_A,m = E_A,m × 1000 / (24 × n_m) [W]
Verified against U985-0001-000490 worksheet (68)m row. For
TFA=66.06, N=2.1468:
E_A = 207.8 × 141.84^0.4714 2147.8 kWh/yr
Jan factor = 1 + 0.157 × cos(2π × -0.78/12) 1.1441
E_A,Jan 208.66 kWh G_A,Jan 280.46 W
matches worksheet 280.4965 to 0.04 W (display rounding from E_A^0.4714).
Table 5 Column A applies (rating + cooling); Column B's 0.67×
reduced-gain form (L16) is deferred to a future new-build slice.
"""
# Arrange
tfa = 66.06
n = 2.1468
expected_w = (
280.4965, 283.4071, 276.0723, 260.4574, 240.7463, 222.2207,
209.8445, 206.9338, 214.2686, 229.8835, 249.5946, 268.1202,
)
# Act
monthly = appliances_monthly_w(total_floor_area_m2=tfa, n_occupants=n)
# Assert
assert len(monthly) == 12
for m, (actual, exp) in enumerate(zip(monthly, expected_w)):
assert actual == pytest.approx(exp, abs=5e-2), f"month {m+1}"