mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
§4 slice 1: assumed_occupancy (worksheet line (42), Appendix J)
First slice of the §4 worksheet-driven rewrite (xlsx rows 207-304). New module `domain/sap/worksheet/water_heating.py` lands the line-ref mapped functions; subsequent slices append below. `assumed_occupancy(tfa)` implements the SAP10.2 Appendix J Table 1b piecewise formula. Validated against: - canonical xlsx worked example (TFA Q23 → N U209) - Elmhurst U985-0001-000474 (TFA 56.79 → N 1.8896) - Elmhurst U985-0001-000490 (TFA 66.06 → N 2.1468) - boundary case TFA ≤ 13.9 (N=1 floor) The legacy `domain.ml.demand._default_occupants_sap_j` mirror stays in place until the §4 worksheet rewrite is complete; both sources will be reconciled in a later slice once dependent callers move over. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
d90827446a
commit
aff678e8eb
4 changed files with 119 additions and 0 deletions
|
|
@ -181,3 +181,6 @@ LINE_37_TOTAL_FABRIC_HEAT_LOSS_W_PER_K: float = 232.1169
|
|||
WINDOW_TOTAL_AREA_M2: float = 11.72
|
||||
WINDOW_AVG_RAW_U_VALUE: float = 2.37
|
||||
DOOR_COUNT: int = 2 # cascade default 1.85 m²/door → 3.70 m² matches worksheet
|
||||
|
||||
# §4 Water heating energy requirements
|
||||
LINE_42_OCCUPANCY: float = 1.8896
|
||||
|
|
|
|||
|
|
@ -169,3 +169,6 @@ LINE_37_TOTAL_FABRIC_HEAT_LOSS_W_PER_K: float = 236.6211
|
|||
WINDOW_TOTAL_AREA_M2: float = 9.03
|
||||
WINDOW_AVG_RAW_U_VALUE: float = 2.8
|
||||
DOOR_COUNT: int = 2 # cascade default 1.85 m²/door → 3.70 m² matches worksheet
|
||||
|
||||
# §4 Water heating energy requirements
|
||||
LINE_42_OCCUPANCY: float = 2.1468
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
"""Tests for SAP 10.2 §4 — water heating energy requirements.
|
||||
|
||||
Worksheet line refs land in `domain.sap.worksheet.water_heating`. Each
|
||||
test asserts a single line-ref output against the canonical xlsx worked
|
||||
example and/or Elmhurst conformance fixtures.
|
||||
|
||||
Reference: SAP 10.2 specification §4 + Appendix J; canonical xlsx rows
|
||||
207–304 (sheet `NonRegionalWeather`).
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from domain.sap.worksheet.tests import (
|
||||
_elmhurst_worksheet_000474 as _w000474,
|
||||
_elmhurst_worksheet_000490 as _w000490,
|
||||
)
|
||||
from domain.sap.worksheet.tests._xlsx_loader import load_cells
|
||||
from domain.sap.worksheet.water_heating import assumed_occupancy
|
||||
|
||||
|
||||
def test_assumed_occupancy_matches_canonical_xlsx_worked_example() -> None:
|
||||
"""SAP10.2 §4 line (42), Appendix J Table 1b. Canonical xlsx cell U209
|
||||
holds the worked-example occupancy for TFA=158.99 (Q23). The formula:
|
||||
|
||||
N = 1 + 1.76 × (1 − exp(−0.000349 × (TFA − 13.9)²))
|
||||
+ 0.0013 × (TFA − 13.9) for TFA > 13.9
|
||||
N = 1 for TFA ≤ 13.9
|
||||
|
||||
must reproduce 2.9475 W (matching the worksheet to 4 d.p.).
|
||||
"""
|
||||
# Arrange — TFA + expected N from the canonical worksheet.
|
||||
cells = load_cells("NonRegionalWeather", ("Q23", "U209"))
|
||||
tfa = cells["Q23"]
|
||||
expected_n = cells["U209"]
|
||||
|
||||
# Act
|
||||
n = assumed_occupancy(tfa)
|
||||
|
||||
# Assert
|
||||
assert n == pytest.approx(expected_n, abs=1e-4)
|
||||
|
||||
|
||||
def test_assumed_occupancy_matches_elmhurst_worksheet_000474() -> None:
|
||||
"""Mid-terrace TFA=56.79 m² → N=1.8896 per Elmhurst U985-0001-000474
|
||||
line (42)."""
|
||||
# Arrange / Act
|
||||
n = assumed_occupancy(_w000474.LINE_4_TFA_M2)
|
||||
|
||||
# Assert
|
||||
assert n == pytest.approx(_w000474.LINE_42_OCCUPANCY, abs=1e-4)
|
||||
|
||||
|
||||
def test_assumed_occupancy_matches_elmhurst_worksheet_000490() -> None:
|
||||
"""End-terrace TFA=66.06 m² → N=2.1468 per Elmhurst U985-0001-000490
|
||||
line (42)."""
|
||||
# Arrange / Act
|
||||
n = assumed_occupancy(_w000490.LINE_4_TFA_M2)
|
||||
|
||||
# Assert
|
||||
assert n == pytest.approx(_w000490.LINE_42_OCCUPANCY, abs=1e-4)
|
||||
|
||||
|
||||
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."""
|
||||
# Arrange / Act / Assert
|
||||
assert assumed_occupancy(13.9) == pytest.approx(1.0, abs=1e-9)
|
||||
assert assumed_occupancy(10.0) == pytest.approx(1.0, abs=1e-9)
|
||||
assert assumed_occupancy(0.0) == pytest.approx(1.0, abs=1e-9)
|
||||
44
packages/domain/src/domain/sap/worksheet/water_heating.py
Normal file
44
packages/domain/src/domain/sap/worksheet/water_heating.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"""SAP 10.2 §4 — water heating energy requirements.
|
||||
|
||||
Worksheet line refs (xlsx rows 207-304, sheet `NonRegionalWeather`):
|
||||
(42) assumed occupancy N from Appendix J Table 1b
|
||||
(42a)m monthly hot water usage for mixer showers
|
||||
(42b)m monthly hot water usage for baths
|
||||
(42c)m monthly hot water usage for other uses
|
||||
(43) annual average hot water usage (litres/day)
|
||||
(44)m daily hot water usage by month
|
||||
(45)m energy content of monthly hot water demand
|
||||
(46)m distribution loss = 0.15 × (45)m
|
||||
(47)–(56) storage volume + water storage / HIU loss
|
||||
(57) dedicated solar storage adjustment
|
||||
(58)–(61) primary loss + combi loss
|
||||
(62)m total monthly water heat requirement
|
||||
(63a)–(63d) WWHRS / PV-diverter / Solar / FGHRS reductions
|
||||
(64)m output from water heater
|
||||
(65)m heat gains from water heating
|
||||
|
||||
Reference: SAP 10.2 specification §4 (pages 22-31) + Appendix J (pages
|
||||
84-90); canonical xlsx worked example at the repo root.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from math import exp
|
||||
from typing import Final
|
||||
|
||||
|
||||
_OCCUPANCY_TFA_FLOOR_M2: Final[float] = 13.9
|
||||
|
||||
|
||||
def assumed_occupancy(total_floor_area_m2: float) -> float:
|
||||
"""SAP 10.2 §4 line (42) / Appendix J Table 1b.
|
||||
|
||||
Piecewise occupancy by total floor area:
|
||||
TFA ≤ 13.9 m² : N = 1
|
||||
TFA > 13.9 m² : N = 1 + 1.76 × (1 − exp(−0.000349 × (TFA − 13.9)²))
|
||||
+ 0.0013 × (TFA − 13.9)
|
||||
"""
|
||||
if total_floor_area_m2 <= _OCCUPANCY_TFA_FLOOR_M2:
|
||||
return 1.0
|
||||
x = total_floor_area_m2 - _OCCUPANCY_TFA_FLOOR_M2
|
||||
return 1.0 + 1.76 * (1.0 - exp(-0.000349 * x * x)) + 0.0013 * x
|
||||
Loading…
Add table
Reference in a new issue