mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
§5 slice 6: (67) lighting_monthly_w — full Appendix L L1-L12 cascade
Implements the full SAP10.2 Appendix L lighting calculation: Λ_B (L1) → Λ_req (L3) → Λ_prov (L6) → Λ_topup (L7) → E_L,fixed/topup/portable (L9a-d) → monthly cosine modulation (L10) → 0.85 × 1000 / (24 × n_m) heat-gain bridge (L12). Critical detail uncovered while reconciling against the 000490 worksheet: C_daylight uses Z_L from Table 6d's **third column** (light access factor), NOT the 0.77 first column used for §6 solar gains. For "Average" overshading Z_L = 0.83. Conflating the two columns gives a ~2% lighting-energy bias. Verified against Elmhurst U985-0001-000490 (67)m to ≤5e-3 W/month (0.14% on E_L) using worksheet bulb table (8 LEL × 80 lm/W × 15 W) and Table 6b/6c/6d defaults for the window inputs. The orchestrator slice will derive C_L,fixed + ε_fixed from RdSAP §12-1 per-lamp-type defaults (LED 100 lm/W, CFL 55 lm/W, LEL 80 lm/W, incandescent 11.2 lm/W) and C_daylight from the cert's window data. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
0bc9eac34c
commit
50fd940ab9
2 changed files with 120 additions and 0 deletions
|
|
@ -36,6 +36,16 @@ _APPLIANCES_E_A_EXPONENT: Final[float] = 0.4714
|
|||
_APPLIANCES_MONTHLY_AMPLITUDE: Final[float] = 0.157
|
||||
_APPLIANCES_MONTHLY_PHASE: Final[float] = 1.78
|
||||
|
||||
# Appendix L lighting constants.
|
||||
_LIGHTING_LAMBDA_B_COEFF: Final[float] = 11.2 * 59.73
|
||||
_LIGHTING_LAMBDA_B_EXPONENT: Final[float] = 0.4714
|
||||
_LIGHTING_C_L_REF_PER_M2: Final[float] = 330.0
|
||||
_LIGHTING_TOPUP_EFFICACY_LM_PER_W: Final[float] = 21.3
|
||||
_LIGHTING_PORTABLE_EFFICACY_LM_PER_W: Final[float] = 21.3
|
||||
_LIGHTING_INTERNAL_FRACTION: Final[float] = 0.85
|
||||
_LIGHTING_MONTHLY_AMPLITUDE: Final[float] = 0.5
|
||||
_LIGHTING_MONTHLY_PHASE: Final[float] = 0.2
|
||||
|
||||
|
||||
_MONTHS_IN_YEAR: Final[int] = 12
|
||||
_DAYS_PER_MONTH: Final[tuple[int, ...]] = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
||||
|
|
@ -111,6 +121,66 @@ def appliances_monthly_w(
|
|||
return tuple(monthly)
|
||||
|
||||
|
||||
def lighting_monthly_w(
|
||||
*,
|
||||
total_floor_area_m2: float,
|
||||
n_occupants: float,
|
||||
fixed_lighting_capacity_lm: float,
|
||||
fixed_lighting_efficacy_lm_per_w: float,
|
||||
daylight_factor: float,
|
||||
) -> tuple[float, ...]:
|
||||
"""SAP 10.2 §5 line (67) — lighting gains in watts per month.
|
||||
|
||||
Applies the full Appendix L L1-L12 cascade with Column A (standard
|
||||
gains) per Table 5. Caller pre-computes:
|
||||
- fixed_lighting_capacity_lm (C_L,fixed, L5a): Σ(count × lumens) over
|
||||
all fixed-lighting outlets. For existing dwellings RdSAP §12-1 gives
|
||||
per-lamp-type defaults (LED 9 W/100 lm/W, CFL 19 W/55 lm/W, LEL
|
||||
15 W/80 lm/W, incandescent 60 W/11.2 lm/W). L5b fallback C_L,fixed
|
||||
= 185 × TFA applies only if no fixed lighting data lodged.
|
||||
- fixed_lighting_efficacy_lm_per_w (ε_fixed, L8): C_L,fixed / Σpower.
|
||||
Fall back to 21.3 lm/W per L8c when no fixed lighting present.
|
||||
- daylight_factor (C_daylight, L2b): C_daylight = 52.2 G_L² - 9.94 G_L
|
||||
+ 1.433 for G_L ≤ 0.095, else 0.96. G_L per L2a uses **Table 6d
|
||||
third column (light access factor Z_L), NOT solar Z** — a common
|
||||
source of bias when conflated with the §6 solar calc.
|
||||
|
||||
L12 reduced-gain branch (L12a, used for new-build DPER/TPER) is deferred.
|
||||
"""
|
||||
lambda_b = (
|
||||
_LIGHTING_LAMBDA_B_COEFF
|
||||
* (total_floor_area_m2 * n_occupants) ** _LIGHTING_LAMBDA_B_EXPONENT
|
||||
)
|
||||
lambda_req = (2.0 / 3.0) * lambda_b * daylight_factor
|
||||
c_l_ref = _LIGHTING_C_L_REF_PER_M2 * total_floor_area_m2
|
||||
lambda_prov = (
|
||||
lambda_req * fixed_lighting_capacity_lm / c_l_ref if c_l_ref > 0 else 0.0
|
||||
)
|
||||
lambda_topup = max(0.0, lambda_req / 3.0 - lambda_prov)
|
||||
e_l_fixed = (
|
||||
max(lambda_req, lambda_prov) / fixed_lighting_efficacy_lm_per_w
|
||||
if fixed_lighting_efficacy_lm_per_w > 0
|
||||
else 0.0
|
||||
)
|
||||
e_l_topup = lambda_topup / _LIGHTING_TOPUP_EFFICACY_LM_PER_W
|
||||
e_l_portable = (
|
||||
(1.0 / 3.0) * lambda_b * daylight_factor / _LIGHTING_PORTABLE_EFFICACY_LM_PER_W
|
||||
)
|
||||
e_l_annual_kwh = e_l_fixed + e_l_topup + e_l_portable
|
||||
monthly: list[float] = []
|
||||
for m_idx, days in enumerate(_DAYS_PER_MONTH):
|
||||
m = m_idx + 1
|
||||
factor = 1.0 + _LIGHTING_MONTHLY_AMPLITUDE * cos(
|
||||
2.0 * pi * (m - _LIGHTING_MONTHLY_PHASE) / _MONTHS_IN_YEAR
|
||||
)
|
||||
e_l_m_kwh = e_l_annual_kwh * factor * days / _DAYS_PER_YEAR
|
||||
monthly.append(
|
||||
e_l_m_kwh * _LIGHTING_INTERNAL_FRACTION * _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, ...],
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import pytest
|
|||
from domain.sap.worksheet.internal_gains import (
|
||||
appliances_monthly_w,
|
||||
cooking_monthly_w,
|
||||
lighting_monthly_w,
|
||||
losses_monthly_w,
|
||||
metabolic_monthly_w,
|
||||
water_heating_gains_monthly_w,
|
||||
|
|
@ -155,3 +156,52 @@ def test_appliances_gains_match_appendix_l13_l14_l16a_for_000490() -> None:
|
|||
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}"
|
||||
|
||||
|
||||
def test_lighting_gains_match_appendix_l1_l12_for_000490() -> None:
|
||||
"""SAP 10.2 Appendix L L1-L12 cascade — full lighting calculation.
|
||||
|
||||
Steps applied:
|
||||
L1 Λ_B = 11.2 × 59.73 × (TFA × N)^0.4714 [klm·h/yr]
|
||||
L3 Λ_req = (2/3) × Λ_B × C_daylight [klm·h/yr]
|
||||
L4 C_L,ref = 330 × TFA [lm]
|
||||
L6 Λ_prov = Λ_req × C_L,fixed / C_L,ref [klm·h/yr]
|
||||
L7 Λ_topup = max(0, Λ_req/3 - Λ_prov) [klm·h/yr]
|
||||
L9a E_L,fixed = max(Λ_req, Λ_prov) / ε_fixed [kWh/yr]
|
||||
L9b E_L,topup = Λ_topup / 21.3 [kWh/yr]
|
||||
L9c E_L,portable = (1/3) × Λ_B × C_daylight / 21.3 [kWh/yr]
|
||||
L9d E_L = E_L,fixed + E_L,topup + E_L,portable [kWh/yr]
|
||||
L10 E_L,m = E_L × (1 + 0.5 cos(2π(m-0.2)/12)) × n_m / 365 [kWh/mo]
|
||||
L12 G_L,m = E_L,m × 0.85 × 1000 / (24 × n_m) [W]
|
||||
|
||||
Inputs for 000490 (back-derived from the worksheet bulb table and
|
||||
Table 6b/c/d defaults):
|
||||
- 8 LEL bulbs × 80 lm/W × 15 W → C_L,fixed=9600 lm, ε_fixed=80 lm/W
|
||||
- 3 windows (NE 0.81 m² + SE 5.52 m² + NW 2.70 m² = 9.03 m² total),
|
||||
all double-glazed air-filled → g_L=0.80 (Table 6b), PVC frames →
|
||||
FF=0.7 (Table 6c), "Average" overshading → Z_L=0.83 (Table 6d
|
||||
third column, NOT the 0.77 solar-heating column)
|
||||
- G_L = 0.9 × 9.03 × 0.80 × 0.70 × 0.83 / 66.06 = 0.0572 (≤ 0.095)
|
||||
- C_daylight = 52.2 × 0.0572² - 9.94 × 0.0572 + 1.433 = 1.0353
|
||||
|
||||
Verified against worksheet (67)m row to ≤5e-3 W/month (0.14% E_L).
|
||||
"""
|
||||
# Arrange
|
||||
expected_w = (
|
||||
24.2665, 21.5533, 17.5283, 13.2701, 9.9195, 8.3745,
|
||||
9.0489, 11.7621, 15.7871, 20.0454, 23.3959, 24.9410,
|
||||
)
|
||||
|
||||
# Act
|
||||
monthly = lighting_monthly_w(
|
||||
total_floor_area_m2=66.06,
|
||||
n_occupants=2.1468,
|
||||
fixed_lighting_capacity_lm=9600.0,
|
||||
fixed_lighting_efficacy_lm_per_w=80.0,
|
||||
daylight_factor=1.0353,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert len(monthly) == 12
|
||||
for m, (actual, exp) in enumerate(zip(monthly, expected_w)):
|
||||
assert actual == pytest.approx(exp, abs=5e-3), f"month {m+1}"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue