mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Uninsulated cylinder (type 0) takes the SAP Table 2 jacket t=0 storage loss 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
cf50c53cad
commit
db488de43d
1 changed files with 86 additions and 0 deletions
|
|
@ -6365,6 +6365,92 @@ def test_loose_jacket_cylinder_computes_storage_loss_via_table2_loose_jacket_bra
|
||||||
assert got_jan_kwh > 36.9530 # loose jacket loses more than factory
|
assert got_jan_kwh > 36.9530 # loose jacket loses more than factory
|
||||||
|
|
||||||
|
|
||||||
|
def test_uninsulated_cylinder_computes_storage_loss_via_table2_jacket_t0() -> None:
|
||||||
|
"""SAP 10.2 Table 2 (PDF p.158): an UNINSULATED cylinder takes the
|
||||||
|
loose-jacket loss factor at t = 0 — L = 0.005 + 1.76 / 12.8 = 0.1425
|
||||||
|
kWh/L/day, the worst storage loss SAP knows. The EPB API lodges
|
||||||
|
cylinder_insulation_type=0 = no insulation (1 = factory, 2 = loose
|
||||||
|
jacket) with no thickness field. Before this fix
|
||||||
|
`_cylinder_storage_loss_override` returned None for type 0, so the
|
||||||
|
worksheet kept the zero-storage-loss combi default and the dwelling
|
||||||
|
OVER-RATED — the opposite of what "no insulation" means. Portfolio
|
||||||
|
814's audit (2026-07-03) found all 26 such properties over-rated
|
||||||
|
+1..+18 SAP vs lodged (avg +12.7 for type 0); same-lodged WD5 0DP
|
||||||
|
neighbours (pids 742215/742216, both lodged 54, identical building
|
||||||
|
parts) split effective 50 vs 69 because one lodged a 12 mm jacket and
|
||||||
|
the other lodged no insulation.
|
||||||
|
"""
|
||||||
|
# Arrange — identical to the loose-jacket storage-loss test but
|
||||||
|
# cylinder_insulation_type=0 (none) and no lodged thickness.
|
||||||
|
from domain.sap10_calculator.worksheet.water_heating import (
|
||||||
|
cylinder_storage_loss_factor_table_2,
|
||||||
|
cylinder_temperature_factor_table_2b,
|
||||||
|
cylinder_volume_factor_table_2a,
|
||||||
|
)
|
||||||
|
|
||||||
|
hp_main = MainHeatingDetail(
|
||||||
|
has_fghrs=False,
|
||||||
|
main_fuel_type=29,
|
||||||
|
heat_emitter_type=1,
|
||||||
|
emitter_temperature=1,
|
||||||
|
main_heating_control=2206,
|
||||||
|
main_heating_category=4,
|
||||||
|
sap_main_heating_code=None,
|
||||||
|
)
|
||||||
|
epc = make_minimal_sap10_epc(
|
||||||
|
total_floor_area_m2=_TYPICAL_TFA_M2,
|
||||||
|
habitable_rooms_count=4,
|
||||||
|
country_code="ENG",
|
||||||
|
has_hot_water_cylinder=True,
|
||||||
|
sap_building_parts=[make_building_part()],
|
||||||
|
sap_heating=make_sap_heating(
|
||||||
|
main_heating_details=[hp_main],
|
||||||
|
water_heating_code=901,
|
||||||
|
cylinder_size=3, # Medium → 160 L
|
||||||
|
cylinder_insulation_type=0, # no insulation
|
||||||
|
cylinder_insulation_thickness_mm=None,
|
||||||
|
cylinder_thermostat="Y",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
# Expected (56)m Jan from the Table 2 loose-jacket branch at t=0 —
|
||||||
|
# the SAP uninsulated-cylinder factor (same V / VF / TF as the
|
||||||
|
# loose-jacket test; only the thickness differs).
|
||||||
|
loss_factor = cylinder_storage_loss_factor_table_2(
|
||||||
|
insulation_type="loose_jacket", thickness_mm=0.0
|
||||||
|
)
|
||||||
|
vol_factor = cylinder_volume_factor_table_2a(160.0)
|
||||||
|
temp_factor = cylinder_temperature_factor_table_2b(
|
||||||
|
has_cylinder_thermostat=True, separately_timed_dhw=True
|
||||||
|
)
|
||||||
|
expected_jan_kwh = 160.0 * loss_factor * vol_factor * temp_factor * 31
|
||||||
|
|
||||||
|
# Act
|
||||||
|
wh_result, _ = _water_heating_worksheet_and_gains(
|
||||||
|
epc=epc,
|
||||||
|
water_efficiency_pct=1.7,
|
||||||
|
is_instantaneous=False,
|
||||||
|
primary_age="D",
|
||||||
|
pcdb_record=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert — non-None (was the zero-loss combi default) and equal to
|
||||||
|
# the loose-jacket branch at t=0; an uninsulated cylinder must lose
|
||||||
|
# MORE than the 50 mm loose-jacket case, never less.
|
||||||
|
assert wh_result is not None
|
||||||
|
got_jan_kwh = wh_result.solar_storage_monthly_kwh[0]
|
||||||
|
assert abs(got_jan_kwh - expected_jan_kwh) < 1e-4
|
||||||
|
jacket_50mm_jan_kwh = (
|
||||||
|
160.0
|
||||||
|
* cylinder_storage_loss_factor_table_2(
|
||||||
|
insulation_type="loose_jacket", thickness_mm=50.0
|
||||||
|
)
|
||||||
|
* vol_factor
|
||||||
|
* temp_factor
|
||||||
|
* 31
|
||||||
|
)
|
||||||
|
assert got_jan_kwh > jacket_50mm_jan_kwh
|
||||||
|
|
||||||
|
|
||||||
def test_no_water_heating_default_age_a_to_f_uses_12mm_loose_jacket_per_table_29() -> None:
|
def test_no_water_heating_default_age_a_to_f_uses_12mm_loose_jacket_per_table_29() -> None:
|
||||||
"""RdSAP 10 §10.7 + Table 29 (PDF p.55-56): when no water heating
|
"""RdSAP 10 §10.7 + Table 29 (PDF p.55-56): when no water heating
|
||||||
system is lodged, the default cylinder takes the age-band insulation,
|
system is lodged, the default cylinder takes the age-band insulation,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue