mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
§8 slice 2: 6 Elmhurst fixtures conform on (95)..(99)
Adds LINE_95_M_USEFUL_GAINS_W, LINE_97_M_HEAT_LOSS_RATE_W, LINE_98A_M_SPACE_HEATING_KWH, LINE_98C_M_TOTAL_SPACE_HEATING_KWH, LINE_98C_ANNUAL_KWH, LINE_99_PER_M2_KWH to each _elmhurst_worksheet_*.py fixture, plus an ALL_FIXTURES-parametrised end-to-end test. Tolerances vary by line ref per §5's per-line precedent: - (95) η × G → 5e-2 W per month - (97) H × ΔT → 5e-2 W per month - (98a)/(98c) → 1e-1 kWh per month - ∑(98c) annual → 1e-1 kWh - (99) per-m² → 5e-3 kWh Looser than §6/§7's flat 5e-3 W budget because §8 inputs (LINE_93, LINE_94, LINE_84) carry 4-d.p. display rounding from upstream worksheets, and §8's 0.024·31·(L−ηG) amplifies that rounding into the per-month kWh band. The orchestrator computes in full precision; tolerances reflect the fixture-pin precision floor, not physics error. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
9113f30aa8
commit
1f078af7db
7 changed files with 198 additions and 0 deletions
|
|
@ -346,3 +346,22 @@ LINE_94_M_UTILISATION_WHOLE: tuple[float, ...] = (
|
|||
0.9720, 0.9633, 0.9455, 0.9098, 0.8457, 0.7330,
|
||||
0.5633, 0.6168, 0.8187, 0.9256, 0.9627, 0.9745,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# §8 Space heating requirement — expected outputs
|
||||
# ============================================================================
|
||||
LINE_95_M_USEFUL_GAINS_W: tuple[float, ...] = (
|
||||
506.0790, 567.8843, 634.7414, 693.9770, 699.4312, 595.3705,
|
||||
436.3012, 437.1801, 518.5588, 513.1261, 484.1045, 483.5787,
|
||||
)
|
||||
LINE_97_M_HEAT_LOSS_RATE_W: tuple[float, ...] = (
|
||||
2956.1218, 2856.8816, 2570.5049, 2119.4574, 1594.2286, 1008.8825,
|
||||
577.3275, 616.0265, 1080.3274, 1738.5871, 2392.7150, 2948.0890,
|
||||
)
|
||||
LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
||||
1822.8319, 1538.2062, 1440.2081, 1026.3458, 665.7293, 0.0,
|
||||
0.0, 0.0, 0.0, 911.7430, 1374.1995, 1833.5957,
|
||||
)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -281,3 +281,22 @@ LINE_94_M_UTILISATION_WHOLE: tuple[float, ...] = (
|
|||
0.9833, 0.9781, 0.9667, 0.9426, 0.8931, 0.7917,
|
||||
0.6257, 0.6735, 0.8617, 0.9507, 0.9774, 0.9852,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# §8 Space heating requirement — expected outputs
|
||||
# ============================================================================
|
||||
LINE_95_M_USEFUL_GAINS_W: tuple[float, ...] = (
|
||||
590.8300, 645.1621, 698.8688, 740.0958, 732.9898, 628.3945,
|
||||
474.8614, 479.1612, 568.4900, 575.2972, 558.7756, 566.6199,
|
||||
)
|
||||
LINE_97_M_HEAT_LOSS_RATE_W: tuple[float, ...] = (
|
||||
2959.4740, 2848.9539, 2553.0985, 2078.0015, 1561.3993, 993.9559,
|
||||
595.9047, 632.0681, 1074.1362, 1720.1111, 2368.4759, 2924.2562,
|
||||
)
|
||||
LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
||||
1762.2712, 1480.9481, 1379.5469, 963.2921, 616.3366, 0.0,
|
||||
0.0, 0.0, 0.0, 851.7415, 1302.9842, 1754.0814,
|
||||
)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -310,3 +310,22 @@ LINE_94_M_UTILISATION_WHOLE: tuple[float, ...] = (
|
|||
0.9830, 0.9785, 0.9687, 0.9467, 0.9000, 0.8039,
|
||||
0.6441, 0.6959, 0.8745, 0.9534, 0.9772, 0.9847,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# §8 Space heating requirement — expected outputs
|
||||
# ============================================================================
|
||||
LINE_95_M_USEFUL_GAINS_W: tuple[float, ...] = (
|
||||
624.7214, 673.4519, 721.4221, 767.3929, 774.6171, 677.1408,
|
||||
516.2266, 513.9498, 591.7998, 597.6119, 588.5443, 600.6212,
|
||||
)
|
||||
LINE_97_M_HEAT_LOSS_RATE_W: tuple[float, ...] = (
|
||||
3470.4829, 3343.3744, 2995.0906, 2453.9817, 1839.7089, 1170.0720,
|
||||
687.2651, 730.6558, 1258.5840, 2031.0834, 2804.7962, 3458.8981,
|
||||
)
|
||||
LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
||||
2117.2466, 1794.1879, 1691.6094, 1214.3439, 792.4283, 0.0,
|
||||
0.0, 0.0, 0.0, 1066.5028, 1595.7014, 2126.5580,
|
||||
)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -327,3 +327,22 @@ LINE_94_M_UTILISATION_WHOLE: tuple[float, ...] = (
|
|||
0.9813, 0.9748, 0.9651, 0.9481, 0.9139, 0.8354,
|
||||
0.6865, 0.7123, 0.8636, 0.9441, 0.9737, 0.9835,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# §8 Space heating requirement — expected outputs
|
||||
# ============================================================================
|
||||
LINE_95_M_USEFUL_GAINS_W: tuple[float, ...] = (
|
||||
658.8087, 728.6249, 757.9288, 747.8850, 703.1606, 604.2371,
|
||||
481.2153, 495.1643, 607.8162, 650.0718, 633.5798, 629.6995,
|
||||
)
|
||||
LINE_97_M_HEAT_LOSS_RATE_W: tuple[float, ...] = (
|
||||
3164.2298, 3053.3605, 2735.3722, 2227.2636, 1667.5142, 1068.2279,
|
||||
651.0154, 692.3750, 1169.8152, 1866.6927, 2554.8108, 3138.0600,
|
||||
)
|
||||
LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
||||
1864.0333, 1562.2224, 1471.2179, 1065.1526, 717.4791, 0.0,
|
||||
0.0, 0.0, 0.0, 905.1659, 1383.2864, 1866.2202,
|
||||
)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -322,3 +322,22 @@ LINE_94_M_UTILISATION_WHOLE: tuple[float, ...] = (
|
|||
0.9735, 0.9659, 0.9524, 0.9261, 0.8752, 0.7738,
|
||||
0.6027, 0.6474, 0.8348, 0.9303, 0.9644, 0.9759,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# §8 Space heating requirement — expected outputs
|
||||
# ============================================================================
|
||||
LINE_95_M_USEFUL_GAINS_W: tuple[float, ...] = (
|
||||
579.5188, 638.5351, 682.1664, 707.0047, 692.2499, 591.2711,
|
||||
441.2650, 447.7583, 546.1761, 568.1093, 552.8782, 555.2620,
|
||||
)
|
||||
LINE_97_M_HEAT_LOSS_RATE_W: tuple[float, ...] = (
|
||||
3153.9991, 3044.0808, 2727.4470, 2230.2770, 1662.0453, 1042.5972,
|
||||
592.8375, 635.4462, 1131.3062, 1843.3753, 2548.3231, 3143.7625,
|
||||
)
|
||||
LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
||||
1915.4133, 1616.5267, 1521.6888, 1096.7561, 721.5278, 0.0,
|
||||
0.0, 0.0, 0.0, 948.7979, 1436.7203, 1925.8443,
|
||||
)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -294,3 +294,22 @@ LINE_94_M_UTILISATION_WHOLE: tuple[float, ...] = (
|
|||
0.9841, 0.9791, 0.9693, 0.9484, 0.9040, 0.8120,
|
||||
0.6598, 0.7062, 0.8755, 0.9540, 0.9783, 0.9858,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# §8 Space heating requirement — expected outputs
|
||||
# ============================================================================
|
||||
LINE_95_M_USEFUL_GAINS_W: tuple[float, ...] = (
|
||||
674.6949, 736.2594, 787.5631, 827.0143, 824.6046, 719.4386,
|
||||
557.6630, 557.3181, 645.0463, 654.9073, 639.2232, 646.7565,
|
||||
)
|
||||
LINE_97_M_HEAT_LOSS_RATE_W: tuple[float, ...] = (
|
||||
3539.0517, 3411.2290, 3059.3047, 2504.6246, 1886.7214, 1213.3943,
|
||||
735.3648, 778.4417, 1307.1906, 2082.2459, 2857.3815, 3515.5362,
|
||||
)
|
||||
LINE_98A_M_SPACE_HEATING_KWH: tuple[float, ...] = (
|
||||
2131.0814, 1797.5796, 1690.1757, 1207.8794, 790.2149, 0.0,
|
||||
0.0, 0.0, 0.0, 1061.9399, 1597.0740, 2134.3721,
|
||||
)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -7,13 +7,23 @@ negative or below 1 kWh/month per the Table 9c note.
|
|||
Reference: SAP 10.3 specification (13-01-2026) Table 9c (page 185).
|
||||
"""
|
||||
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
|
||||
from domain.sap.climate.appendix_u import external_temperature_c
|
||||
from domain.sap.worksheet.space_heating import (
|
||||
SpaceHeatingResult,
|
||||
monthly_heat_requirement_kwh,
|
||||
space_heating_monthly_kwh,
|
||||
)
|
||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
||||
|
||||
|
||||
_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)
|
||||
|
||||
|
||||
# Worksheet U985-0001-000490 (UK-avg weather, region 0) inputs for §8.
|
||||
|
|
@ -169,3 +179,77 @@ def test_sub_one_kwh_requirement_clamps_to_zero_per_table_9c_note() -> None:
|
|||
|
||||
# Assert
|
||||
assert result == 0.0
|
||||
|
||||
|
||||
def _section_8_inputs(fixture: ModuleType) -> dict[str, object]:
|
||||
"""Build space_heating_monthly_kwh kwargs from §1-§7 fixture pins.
|
||||
Mirrors cert_to_inputs flow at slice 3: (84)m total gains = LINE_84,
|
||||
(39)m HTC = LINE_37 + 0.33·V·LINE_25_M, T_internal from §7 LINE_93,
|
||||
η_whole from §7 LINE_94, ext from Appendix U region 0."""
|
||||
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_internal_temperature_c": fixture.LINE_93_M_ADJUSTED_MIT_C,
|
||||
"monthly_external_temperature_c": _UK_AVG_EXT_TEMP_C,
|
||||
"monthly_utilisation_factor": fixture.LINE_94_M_UTILISATION_WHOLE,
|
||||
"monthly_total_gains_w": fixture.LINE_84_M_TOTAL_GAINS_W,
|
||||
"total_floor_area_m2": fixture.LINE_4_TFA_M2,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fixture", ALL_FIXTURES, ids=[fixture_id(f) for f in ALL_FIXTURES])
|
||||
def test_space_heating_monthly_kwh_matches_elmhurst_worksheet_all_fixtures(
|
||||
fixture: ModuleType,
|
||||
) -> None:
|
||||
"""End-to-end §8 orchestrator against every Elmhurst conformance fixture.
|
||||
|
||||
Each fixture pins LINE_95_M (useful gains), LINE_97_M (heat loss rate),
|
||||
LINE_98A_M / LINE_98C_M (space heating req incl. summer clamp),
|
||||
LINE_98C_ANNUAL_KWH, LINE_99_PER_M2_KWH. Inputs flow from §1-§7 pins
|
||||
via `_section_8_inputs(fixture)` — same shape as cert_to_inputs at
|
||||
slice 3.
|
||||
|
||||
Asserts each per-month line ref. Tolerances vary by line ref because
|
||||
upstream fixture pins (LINE_93, LINE_94, LINE_84) are 4-d.p. display-
|
||||
rounded and the products / sums in §8 accumulate that rounding:
|
||||
- (95) η × G → 5e-2 W per month
|
||||
- (97) H × ΔT → 5e-2 W per month
|
||||
- (98a)/(98c) → 1e-1 kWh per month (driven by (97)−(95) propagation
|
||||
through 0.024·31 days amplifier ~×0.7; mixed-glazing
|
||||
fixtures 000474/000477/000487 compound worst)
|
||||
- ∑(98c) annual → 1e-1 kWh (12-month sum amplifies)
|
||||
- (99) per-m² → 5e-3 kWh (annual÷TFA brings the digit back)
|
||||
Same precedent as §5's (68) 5e-2 W tolerance — display-rounding floor,
|
||||
not physics imprecision (the orchestrator computes in full precision).
|
||||
"""
|
||||
# Arrange
|
||||
inputs = _section_8_inputs(fixture)
|
||||
|
||||
# Act
|
||||
result = space_heating_monthly_kwh(**inputs) # type: ignore[arg-type]
|
||||
|
||||
# Assert
|
||||
assert result.total_space_heating_kwh_per_yr == pytest.approx(
|
||||
fixture.LINE_98C_ANNUAL_KWH, abs=1e-1
|
||||
)
|
||||
assert result.space_heating_per_m2_kwh == pytest.approx(
|
||||
fixture.LINE_99_PER_M2_KWH, abs=5e-3
|
||||
)
|
||||
for m in range(12):
|
||||
assert result.useful_gains_monthly_w[m] == pytest.approx(
|
||||
fixture.LINE_95_M_USEFUL_GAINS_W[m], abs=5e-2
|
||||
), f"(95) month {m+1}"
|
||||
assert result.heat_loss_rate_monthly_w[m] == pytest.approx(
|
||||
fixture.LINE_97_M_HEAT_LOSS_RATE_W[m], abs=5e-2
|
||||
), f"(97) month {m+1}"
|
||||
assert result.space_heating_requirement_monthly_kwh[m] == pytest.approx(
|
||||
fixture.LINE_98A_M_SPACE_HEATING_KWH[m], abs=1e-1
|
||||
), f"(98a) month {m+1}"
|
||||
assert result.total_space_heating_monthly_kwh[m] == pytest.approx(
|
||||
fixture.LINE_98C_M_TOTAL_SPACE_HEATING_KWH[m], abs=1e-1
|
||||
), f"(98c) month {m+1}"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue