mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-22 08:48:38 +00:00
Electric immersion water heating (WHC 903) on an off-peak tariff billed 100% at the low rate, under-costing the dwelling and over-rating it (median +0.98 SAP across the off-peak WHC-903 API cohort, n=57). SAP 10.2 Table 12a "Immersion water heater" row (PDF p.191) routes the water-heating column to Table 13 (PDF p.197): the high-rate fraction is a function of cylinder volume V, assumed occupancy N (Appendix J Table 1b) and single-/dual-immersion. The remainder bills at the low rate. Table 13 Note 2 supplies exact equations equivalent to the rounded grid; `electric_dhw_high_rate_fraction` evaluates them (validated against the published 110 L grid cells). Per Note 1 the 10-hour equations cover any tariff with >=10 hours/day low-rate (so 18-/24-hour use that column). Immersion code mapping CONFIRMED 1=dual, 2=single via RdSAP 10 §10.5 (PDF p.54 — an immersion is "assumed dual" on a dual/off-peak meter) cross-checked against the API cohort (code 1 sits 3.6:1 on dual meters; code 2 on single meters). This INVERTS an earlier handover's unverified "1=single, 2=dual" note — the dual code carries Table 13's small fraction, matching the cohort over-rating direction; the single mapping overshot in a prototype. API SAP eval: 47.6% -> 48.6% within 0.5; <1.0 62.6% -> 63.8%; mean|err| 1.586 -> 1.561; 909 computed, 0 raises. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
3.8 KiB
Python
103 lines
3.8 KiB
Python
"""SAP 10.2 Table 13 — high-rate fraction for electric DHW heating.
|
|
|
|
Locks `electric_dhw_high_rate_fraction` against the published table grid
|
|
and the Note-2 clamp at
|
|
`domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf`,
|
|
page 197. Table 12a's "Immersion water heater" row (PDF p.191) routes
|
|
electric immersion DHW here.
|
|
|
|
The helper evaluates the Note-2 equations, which the spec offers as an
|
|
exact alternative to the rounded grid — the pins below check that the
|
|
equations reproduce the published 2-dp cells.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from domain.sap10_calculator.tables.table_12a import Tariff
|
|
from domain.sap10_calculator.tables.table_13 import (
|
|
electric_dhw_high_rate_fraction,
|
|
)
|
|
|
|
# Appendix J Table 1b occupancy N at a few total floor areas (m²) — the
|
|
# anchor for the V/N grid cells below. Computed from the same piecewise
|
|
# formula the §4 worksheet uses (water_heating.assumed_occupancy).
|
|
_N_AT_TFA_100 = 2.7395 # N(100 m²)
|
|
_N_AT_TFA_60 = 1.9816 # N(60 m²)
|
|
|
|
# Table 13 high-rate-fraction grid cells (PDF p.197), keyed by (floor
|
|
# area row, cylinder litres column, tariff, single?) → published value.
|
|
_GRID_TOL = 0.005 # the published grid is rounded to 2 dp
|
|
|
|
|
|
def test_table_13_dual_immersion_matches_published_grid() -> None:
|
|
# Arrange — SAP 10.2 Table 13 (PDF p.197), 110 L cylinder, dual
|
|
# immersion. Floor area 100 m² row: 7-hour = 0.18, 10-hour = 0.10.
|
|
|
|
# Act
|
|
seven = electric_dhw_high_rate_fraction(
|
|
cylinder_volume_l=110.0, occupancy_n=_N_AT_TFA_100,
|
|
single_immersion=False, tariff=Tariff.SEVEN_HOUR,
|
|
)
|
|
ten = electric_dhw_high_rate_fraction(
|
|
cylinder_volume_l=110.0, occupancy_n=_N_AT_TFA_100,
|
|
single_immersion=False, tariff=Tariff.TEN_HOUR,
|
|
)
|
|
|
|
# Assert
|
|
assert abs(seven - 0.18) <= _GRID_TOL
|
|
assert abs(ten - 0.10) <= _GRID_TOL
|
|
|
|
|
|
def test_table_13_single_immersion_matches_published_grid() -> None:
|
|
# Arrange — SAP 10.2 Table 13 (PDF p.197), 110 L cylinder, single
|
|
# immersion. Floor area 100 m² row: 7-hour = 0.61, 10-hour = 0.23.
|
|
# Single immersion carries a much larger high-rate fraction than dual.
|
|
|
|
# Act
|
|
seven = electric_dhw_high_rate_fraction(
|
|
cylinder_volume_l=110.0, occupancy_n=_N_AT_TFA_100,
|
|
single_immersion=True, tariff=Tariff.SEVEN_HOUR,
|
|
)
|
|
ten = electric_dhw_high_rate_fraction(
|
|
cylinder_volume_l=110.0, occupancy_n=_N_AT_TFA_100,
|
|
single_immersion=True, tariff=Tariff.TEN_HOUR,
|
|
)
|
|
|
|
# Assert
|
|
assert abs(seven - 0.61) <= _GRID_TOL
|
|
assert abs(ten - 0.23) <= _GRID_TOL
|
|
|
|
|
|
def test_table_13_large_cylinder_single_immersion_clamps_to_zero() -> None:
|
|
# Arrange — SAP 10.2 Table 13 Note 2 (PDF p.197): "If these formulae
|
|
# give a value less than zero, set the high-rate fraction to zero." A
|
|
# 210 L cylinder with single immersion on a 10-hour tariff falls below
|
|
# zero (the published 210 L 10-hour column is 0), so the helper clamps.
|
|
|
|
# Act
|
|
fraction = electric_dhw_high_rate_fraction(
|
|
cylinder_volume_l=210.0, occupancy_n=_N_AT_TFA_60,
|
|
single_immersion=True, tariff=Tariff.TEN_HOUR,
|
|
)
|
|
|
|
# Assert
|
|
assert fraction == 0.0
|
|
|
|
|
|
def test_table_13_eighteen_hour_uses_ten_hour_column() -> None:
|
|
# Arrange — SAP 10.2 Table 13 Note 1 (PDF p.197): the table applies
|
|
# "for tariffs providing at least 10 hours ... at the low rate", so an
|
|
# 18-hour tariff resolves to the 10-hour equations, not a separate
|
|
# column.
|
|
|
|
# Act
|
|
eighteen = electric_dhw_high_rate_fraction(
|
|
cylinder_volume_l=110.0, occupancy_n=_N_AT_TFA_100,
|
|
single_immersion=False, tariff=Tariff.EIGHTEEN_HOUR,
|
|
)
|
|
ten = electric_dhw_high_rate_fraction(
|
|
cylinder_volume_l=110.0, occupancy_n=_N_AT_TFA_100,
|
|
single_immersion=False, tariff=Tariff.TEN_HOUR,
|
|
)
|
|
|
|
# Assert
|
|
assert abs(eighteen - ten) <= 1e-9
|