mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
§8c slice 2: 6-fixture ALL_FIXTURES conformance (all-zero) with shared template constants
Shared SECTION_8C_ALL_ZERO_MONTHLY / SECTION_8C_ETA_LOSS_ALL_ONE / SECTION_8C_INTERMITTENCY_MONTHLY constants live in _elmhurst_fixtures.py; each of the 6 fixtures references them via plain attributes plus SECTION_8C_COOLED_AREA_FRACTION = 0.0 and the per-line LINE_103/106/107/108 + LINE_107_ANNUAL_KWH pins. (100), (102), (104) values depend on H × (24−T_e) per fixture and are not pinned here — the algebra is exercised by the synthetic-positive leaf/orchestrator tests in slice 1. First cooling-enabled cert will need a fixture pinning those lines; deferred per Q10 grilling decision. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
cf28eec44d
commit
3b9fa936f0
8 changed files with 210 additions and 0 deletions
|
|
@ -12,6 +12,18 @@ this directory and append it here.
|
|||
|
||||
from types import ModuleType
|
||||
|
||||
|
||||
# §8c shared template constants — every Elmhurst fixture has
|
||||
# `has_fixed_air_conditioning=False` so f_C = 0 and (107), (108) collapse
|
||||
# to zero. (101) η_loss = 1.0 every month because γ = G/L = 0 when cooling
|
||||
# gains are zero. (106) carries the spec Table 10b f_intermittent = 0.25
|
||||
# value only in the Jun-Aug inclusion window per the worksheet form.
|
||||
SECTION_8C_ALL_ZERO_MONTHLY: tuple[float, ...] = (0.0,) * 12
|
||||
SECTION_8C_ETA_LOSS_ALL_ONE: tuple[float, ...] = (1.0,) * 12
|
||||
SECTION_8C_INTERMITTENCY_MONTHLY: tuple[float, ...] = (
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.25, 0.25, 0.0, 0.0, 0.0, 0.0,
|
||||
)
|
||||
|
||||
from domain.sap.worksheet.tests import (
|
||||
_elmhurst_worksheet_000474 as w000474,
|
||||
_elmhurst_worksheet_000477 as w000477,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@ from domain.sap.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
|||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||
|
||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||
)
|
||||
|
||||
_WC_CAVITY = 4
|
||||
|
||||
|
||||
|
|
@ -365,3 +371,20 @@ LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
|||
LINE_98C_M_TOTAL_SPACE_HEATING_KWH: tuple[float, ...] = LINE_98A_M_SPACE_HEATING_KWH
|
||||
LINE_98C_ANNUAL_KWH: float = 10612.8595
|
||||
LINE_99_PER_M2_KWH: float = 186.879
|
||||
|
||||
# ============================================================================
|
||||
# §8c Space cooling requirement — expected outputs
|
||||
# ============================================================================
|
||||
# `has_fixed_air_conditioning=False` for this cert; cert_to_inputs sets f_C=0
|
||||
# and cooling gains = (0,)*12 so (107) and (108) collapse to zero. (101) η_loss
|
||||
# collapses to 1.0 every month because γ = G/L = 0 → spec γ≤0 branch. (100),
|
||||
# (102), (104) values depend on H × (24 − T_e) per fixture so they are not
|
||||
# asserted in the §8c ALL_FIXTURES test (covered by the synthetic-positive
|
||||
# test in `test_space_cooling.py`).
|
||||
SECTION_8C_COOLED_AREA_FRACTION: float = 0.0
|
||||
LINE_101_M_UTILISATION_FACTOR_LOSS = SECTION_8C_ETA_LOSS_ALL_ONE
|
||||
LINE_103_M_COOLING_GAINS_W = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_106_M_INTERMITTENCY_FACTOR = SECTION_8C_INTERMITTENCY_MONTHLY
|
||||
LINE_107_M_SPACE_COOLING_KWH = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_107_ANNUAL_KWH: float = 0.0
|
||||
LINE_108_PER_M2_KWH: float = 0.0
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ from domain.sap.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
|||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||
|
||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||
)
|
||||
|
||||
_WC_CAVITY = 4
|
||||
|
||||
|
||||
|
|
@ -300,3 +306,20 @@ LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
|||
LINE_98C_M_TOTAL_SPACE_HEATING_KWH: tuple[float, ...] = LINE_98A_M_SPACE_HEATING_KWH
|
||||
LINE_98C_ANNUAL_KWH: float = 10111.2020
|
||||
LINE_99_PER_M2_KWH: float = 130.3326
|
||||
|
||||
# ============================================================================
|
||||
# §8c Space cooling requirement — expected outputs
|
||||
# ============================================================================
|
||||
# `has_fixed_air_conditioning=False` for this cert; cert_to_inputs sets f_C=0
|
||||
# and cooling gains = (0,)*12 so (107) and (108) collapse to zero. (101) η_loss
|
||||
# collapses to 1.0 every month because γ = G/L = 0 → spec γ≤0 branch. (100),
|
||||
# (102), (104) values depend on H × (24 − T_e) per fixture so they are not
|
||||
# asserted in the §8c ALL_FIXTURES test (covered by the synthetic-positive
|
||||
# test in `test_space_cooling.py`).
|
||||
SECTION_8C_COOLED_AREA_FRACTION: float = 0.0
|
||||
LINE_101_M_UTILISATION_FACTOR_LOSS = SECTION_8C_ETA_LOSS_ALL_ONE
|
||||
LINE_103_M_COOLING_GAINS_W = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_106_M_INTERMITTENCY_FACTOR = SECTION_8C_INTERMITTENCY_MONTHLY
|
||||
LINE_107_M_SPACE_COOLING_KWH = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_107_ANNUAL_KWH: float = 0.0
|
||||
LINE_108_PER_M2_KWH: float = 0.0
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ from domain.sap.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
|||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||
|
||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||
)
|
||||
|
||||
_WC_CAVITY = 4
|
||||
|
||||
|
||||
|
|
@ -329,3 +335,20 @@ LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
|||
LINE_98C_M_TOTAL_SPACE_HEATING_KWH: tuple[float, ...] = LINE_98A_M_SPACE_HEATING_KWH
|
||||
LINE_98C_ANNUAL_KWH: float = 12398.5783
|
||||
LINE_99_PER_M2_KWH: float = 146.8852
|
||||
|
||||
# ============================================================================
|
||||
# §8c Space cooling requirement — expected outputs
|
||||
# ============================================================================
|
||||
# `has_fixed_air_conditioning=False` for this cert; cert_to_inputs sets f_C=0
|
||||
# and cooling gains = (0,)*12 so (107) and (108) collapse to zero. (101) η_loss
|
||||
# collapses to 1.0 every month because γ = G/L = 0 → spec γ≤0 branch. (100),
|
||||
# (102), (104) values depend on H × (24 − T_e) per fixture so they are not
|
||||
# asserted in the §8c ALL_FIXTURES test (covered by the synthetic-positive
|
||||
# test in `test_space_cooling.py`).
|
||||
SECTION_8C_COOLED_AREA_FRACTION: float = 0.0
|
||||
LINE_101_M_UTILISATION_FACTOR_LOSS = SECTION_8C_ETA_LOSS_ALL_ONE
|
||||
LINE_103_M_COOLING_GAINS_W = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_106_M_INTERMITTENCY_FACTOR = SECTION_8C_INTERMITTENCY_MONTHLY
|
||||
LINE_107_M_SPACE_COOLING_KWH = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_107_ANNUAL_KWH: float = 0.0
|
||||
LINE_108_PER_M2_KWH: float = 0.0
|
||||
|
|
|
|||
|
|
@ -112,6 +112,12 @@ def build_epc() -> EpcPropertyData:
|
|||
# ============================================================================
|
||||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
||||
|
||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||
)
|
||||
|
||||
INTERMITTENT_FANS: int = 1
|
||||
HAS_SUSPENDED_TIMBER_FLOOR: bool = True
|
||||
SUSPENDED_TIMBER_FLOOR_SEALED: bool = False
|
||||
|
|
@ -346,3 +352,20 @@ LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
|||
LINE_98C_M_TOTAL_SPACE_HEATING_KWH: tuple[float, ...] = LINE_98A_M_SPACE_HEATING_KWH
|
||||
LINE_98C_ANNUAL_KWH: float = 10834.7778
|
||||
LINE_99_PER_M2_KWH: float = 132.828
|
||||
|
||||
# ============================================================================
|
||||
# §8c Space cooling requirement — expected outputs
|
||||
# ============================================================================
|
||||
# `has_fixed_air_conditioning=False` for this cert; cert_to_inputs sets f_C=0
|
||||
# and cooling gains = (0,)*12 so (107) and (108) collapse to zero. (101) η_loss
|
||||
# collapses to 1.0 every month because γ = G/L = 0 → spec γ≤0 branch. (100),
|
||||
# (102), (104) values depend on H × (24 − T_e) per fixture so they are not
|
||||
# asserted in the §8c ALL_FIXTURES test (covered by the synthetic-positive
|
||||
# test in `test_space_cooling.py`).
|
||||
SECTION_8C_COOLED_AREA_FRACTION: float = 0.0
|
||||
LINE_101_M_UTILISATION_FACTOR_LOSS = SECTION_8C_ETA_LOSS_ALL_ONE
|
||||
LINE_103_M_COOLING_GAINS_W = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_106_M_INTERMITTENCY_FACTOR = SECTION_8C_INTERMITTENCY_MONTHLY
|
||||
LINE_107_M_SPACE_COOLING_KWH = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_107_ANNUAL_KWH: float = 0.0
|
||||
LINE_108_PER_M2_KWH: float = 0.0
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ from domain.sap.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
|||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||
|
||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||
)
|
||||
|
||||
_WC_CAVITY = 4
|
||||
|
||||
|
||||
|
|
@ -341,3 +347,20 @@ LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
|||
LINE_98C_M_TOTAL_SPACE_HEATING_KWH: tuple[float, ...] = LINE_98A_M_SPACE_HEATING_KWH
|
||||
LINE_98C_ANNUAL_KWH: float = 11183.2752
|
||||
LINE_99_PER_M2_KWH: float = 169.2897
|
||||
|
||||
# ============================================================================
|
||||
# §8c Space cooling requirement — expected outputs
|
||||
# ============================================================================
|
||||
# `has_fixed_air_conditioning=False` for this cert; cert_to_inputs sets f_C=0
|
||||
# and cooling gains = (0,)*12 so (107) and (108) collapse to zero. (101) η_loss
|
||||
# collapses to 1.0 every month because γ = G/L = 0 → spec γ≤0 branch. (100),
|
||||
# (102), (104) values depend on H × (24 − T_e) per fixture so they are not
|
||||
# asserted in the §8c ALL_FIXTURES test (covered by the synthetic-positive
|
||||
# test in `test_space_cooling.py`).
|
||||
SECTION_8C_COOLED_AREA_FRACTION: float = 0.0
|
||||
LINE_101_M_UTILISATION_FACTOR_LOSS = SECTION_8C_ETA_LOSS_ALL_ONE
|
||||
LINE_103_M_COOLING_GAINS_W = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_106_M_INTERMITTENCY_FACTOR = SECTION_8C_INTERMITTENCY_MONTHLY
|
||||
LINE_107_M_SPACE_COOLING_KWH = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_107_ANNUAL_KWH: float = 0.0
|
||||
LINE_108_PER_M2_KWH: float = 0.0
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ from domain.sap.worksheet.solar_gains import Orientation, RoofWindowInput, Roofl
|
|||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||
|
||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||
)
|
||||
|
||||
_WC_CAVITY = 4
|
||||
_WC_SOLID_BRICK = 3 # party walls — RdSAP10 maps to U=0.0 (solid masonry)
|
||||
|
||||
|
|
@ -313,3 +319,20 @@ LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
|||
LINE_98C_M_TOTAL_SPACE_HEATING_KWH: tuple[float, ...] = LINE_98A_M_SPACE_HEATING_KWH
|
||||
LINE_98C_ANNUAL_KWH: float = 12410.3170
|
||||
LINE_99_PER_M2_KWH: float = 137.0700
|
||||
|
||||
# ============================================================================
|
||||
# §8c Space cooling requirement — expected outputs
|
||||
# ============================================================================
|
||||
# `has_fixed_air_conditioning=False` for this cert; cert_to_inputs sets f_C=0
|
||||
# and cooling gains = (0,)*12 so (107) and (108) collapse to zero. (101) η_loss
|
||||
# collapses to 1.0 every month because γ = G/L = 0 → spec γ≤0 branch. (100),
|
||||
# (102), (104) values depend on H × (24 − T_e) per fixture so they are not
|
||||
# asserted in the §8c ALL_FIXTURES test (covered by the synthetic-positive
|
||||
# test in `test_space_cooling.py`).
|
||||
SECTION_8C_COOLED_AREA_FRACTION: float = 0.0
|
||||
LINE_101_M_UTILISATION_FACTOR_LOSS = SECTION_8C_ETA_LOSS_ALL_ONE
|
||||
LINE_103_M_COOLING_GAINS_W = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_106_M_INTERMITTENCY_FACTOR = SECTION_8C_INTERMITTENCY_MONTHLY
|
||||
LINE_107_M_SPACE_COOLING_KWH = SECTION_8C_ALL_ZERO_MONTHLY
|
||||
LINE_107_ANNUAL_KWH: float = 0.0
|
||||
LINE_108_PER_M2_KWH: float = 0.0
|
||||
|
|
|
|||
|
|
@ -5,12 +5,16 @@ Reference: SAP 10.2 specification (14-03-2025) Tables 10a/10b (page 186).
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
|
||||
from domain.sap.climate.appendix_u import external_temperature_c
|
||||
from domain.sap.worksheet.space_cooling import (
|
||||
space_cooling_monthly_kwh,
|
||||
utilisation_factor_loss,
|
||||
)
|
||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
||||
|
||||
|
||||
_FULLY_INACTIVE_GAINS_WINTER_TE_C: float = -10.0
|
||||
|
|
@ -18,6 +22,12 @@ _JULY_TE_C: float = 22.0
|
|||
_JULY_GAINS_W: float = 200.0
|
||||
_CONSTANT_H_W_PER_K: float = 100.0
|
||||
_TMP_FOR_A_EQUALS_THREE: float = 108.0 # → τ=30 → a=3 → γ=1 closed-form η=0.75
|
||||
_UK_AVG_EXT_TEMP_C: tuple[float, ...] = tuple(
|
||||
external_temperature_c(0, m) for m in range(1, 13)
|
||||
)
|
||||
_VENTILATION_HLC_COEFF_W_PER_M3_K: float = 0.33 # SAP10.2 (38)
|
||||
_DEFAULT_THERMAL_MASS_PARAMETER_KJ_PER_M2_K: float = 250.0
|
||||
_TABLE_10B_INTERMITTENCY_FACTOR: float = 0.25
|
||||
|
||||
|
||||
def test_utilisation_factor_loss_returns_one_for_negative_gamma() -> None:
|
||||
|
|
@ -202,3 +212,53 @@ def test_space_cooling_monthly_kwh_collapses_to_zero_when_f_cool_zero() -> None:
|
|||
assert result.space_cooling_monthly_kwh == (0.0,) * 12
|
||||
assert result.space_cooling_per_m2_kwh == 0.0
|
||||
assert result.cooled_area_fraction == 0.0
|
||||
|
||||
|
||||
def _section_8c_inputs(fixture: ModuleType) -> dict[str, object]:
|
||||
"""Build space_cooling_monthly_kwh kwargs from §1-§3 fixture pins.
|
||||
Mirrors cert_to_inputs flow at slice 3: H from (37) + 0.33·V·(25)m, T_e
|
||||
from Appendix U region 0, cooling gains = (0,)*12 (no AC), f_C = 0,
|
||||
TMP default 250 kJ/m²K."""
|
||||
return {
|
||||
"monthly_heat_transfer_coefficient_w_per_k": tuple(
|
||||
fixture.LINE_37_TOTAL_FABRIC_HEAT_LOSS_W_PER_K
|
||||
+ _VENTILATION_HLC_COEFF_W_PER_M3_K
|
||||
* fixture.LINE_5_VOLUME_M3
|
||||
* fixture.LINE_25_EFFECTIVE_ACH[m]
|
||||
for m in range(12)
|
||||
),
|
||||
"monthly_external_temperature_c": _UK_AVG_EXT_TEMP_C,
|
||||
"monthly_total_gains_w": (0.0,) * 12,
|
||||
"total_floor_area_m2": fixture.LINE_4_TFA_M2,
|
||||
"thermal_mass_parameter_kj_per_m2_k": _DEFAULT_THERMAL_MASS_PARAMETER_KJ_PER_M2_K,
|
||||
"cooled_area_fraction": fixture.SECTION_8C_COOLED_AREA_FRACTION,
|
||||
"intermittency_factor": _TABLE_10B_INTERMITTENCY_FACTOR,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fixture", ALL_FIXTURES, ids=[fixture_id(f) for f in ALL_FIXTURES])
|
||||
def test_space_cooling_monthly_kwh_matches_elmhurst_worksheet_all_fixtures(
|
||||
fixture: ModuleType,
|
||||
) -> None:
|
||||
"""End-to-end §8c orchestrator against every Elmhurst fixture. Each
|
||||
fixture has `has_fixed_air_conditioning=False` so f_C=0 collapses (107)
|
||||
and (108) to zero. (101) η_loss = 1.0 every month (γ=0 from zero cooling
|
||||
gains). (100), (102), (104) depend on H × (24−T_e) per fixture and are
|
||||
not asserted here — covered by the synthetic-positive leaf/orchestrator
|
||||
tests above. The first cooling-enabled cert lands a separate fixture
|
||||
pinning (100)..(104) per its T_e profile (deferred — see SPEC_COVERAGE
|
||||
§8c row)."""
|
||||
# Arrange
|
||||
inputs = _section_8c_inputs(fixture)
|
||||
|
||||
# Act
|
||||
result = space_cooling_monthly_kwh(**inputs) # type: ignore[arg-type]
|
||||
|
||||
# Assert
|
||||
assert result.cooled_area_fraction == fixture.SECTION_8C_COOLED_AREA_FRACTION
|
||||
assert result.utilisation_factor_loss_monthly == fixture.LINE_101_M_UTILISATION_FACTOR_LOSS
|
||||
assert result.cooling_gains_monthly_w == fixture.LINE_103_M_COOLING_GAINS_W
|
||||
assert result.intermittency_factor_monthly == fixture.LINE_106_M_INTERMITTENCY_FACTOR
|
||||
assert result.space_cooling_monthly_kwh == fixture.LINE_107_M_SPACE_COOLING_KWH
|
||||
assert result.space_cooling_kwh_per_yr == fixture.LINE_107_ANNUAL_KWH
|
||||
assert result.space_cooling_per_m2_kwh == fixture.LINE_108_PER_M2_KWH
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue