mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(water-heating): gate DHW separate-timing on programmer + boiler age (RdSAP 10 §10.5)
`_separately_timed_dhw` returned True for any boiler+cylinder+from-main
cert, applying the SAP 10.2 Table 2b note b) ×0.9 temperature-factor
reduction unconditionally. For the lpg-boiler "before" worksheet (pre-
1998 LPG boiler SAP code 115 + 210 L cylinder, NO cylinder thermostat,
control 2113 "Room thermostat and TRVs" — no programmer) this dropped
the (53) temperature factor to 0.702 (= 0.60 × 1.3 × 0.9) where the
worksheet lodges 0.78 (= 0.60 × 1.3), under-counting cylinder storage
loss (55) by ~119 kWh/yr and over-rating SAP by ~0.25.
RdSAP 10 §10.5 (PDF p.57) "Hot water separately timed":
No programmer, pre-1998 boiler → No
Programmer, pre-1998 boiler → Yes
Post-1998 boiler → Yes
DHW is therefore NOT separately timed only when a pre-1998 boiler is
paired with a no-programmer control. Add the two SAP 10.2 Table 4c(2) /
Table 4b lookups (controls without a programmer = {2101, 2103, 2111,
2113}; pre-1998 gas/LPG boilers 110-119 + oil 124/125/128) and return
False for that combination; every other boiler+cylinder cert keeps the
separately-timed default, so the change is confined to old low-control
stock and the heating corpus + goldens are unchanged.
Effect: the full chain (Summary PDF → extractor → mapper → cert_to_inputs
→ calculator) now reproduces the lpg-boiler worksheet's §11a unrounded
SAP -6.6499 at abs < 1e-4 (was -6.4013). Full regression suite green bar
the 3 pre-existing unrelated fails.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
90de1fc976
commit
5a74897fed
2 changed files with 62 additions and 1 deletions
|
|
@ -202,6 +202,28 @@ def test_summary_001431_lpg_boiler_maps_main_fuel_to_bottled_lpg() -> None:
|
|||
assert epc.sap_heating.water_heating_fuel == 3
|
||||
|
||||
|
||||
def test_summary_001431_lpg_boiler_full_chain_sap_matches_worksheet_pdf() -> None:
|
||||
# Arrange — the lpg-boiler "before" worksheet (P960-0001-001431):
|
||||
# pre-1998 LPG boiler (SAP code 115, eff 61%) + 210 L cylinder, NO
|
||||
# cylinder thermostat, control 2113 (room thermostat + TRVs, no
|
||||
# programmer). RdSAP 10 §10.5 (p.57) "Hot water separately timed":
|
||||
# a no-programmer + pre-1998 boiler is NOT separately timed, so the
|
||||
# Table 2b temperature factor (53) is 0.78 (= 0.60 × 1.3), not
|
||||
# 0.702 (× 0.9). Worksheet §11a lodges unrounded SAP -6.6499.
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_001431_LPG_PDF)
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||
|
||||
# Act
|
||||
result = calculate_sap_from_inputs(
|
||||
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
|
||||
)
|
||||
|
||||
# Assert
|
||||
worksheet_unrounded_sap = -6.6499
|
||||
assert abs(result.sap_score_continuous - worksheet_unrounded_sap) < 1e-4
|
||||
|
||||
|
||||
def test_summary_001431_topfloor_flat_classified_as_top_floor() -> None:
|
||||
# Arrange — the recommendation "after" Summary lodges §6.0 "Position
|
||||
# of flat in block of flats: Top Floor": floor "A Another dwelling
|
||||
|
|
|
|||
|
|
@ -5150,6 +5150,28 @@ _TABLE_4A_SOLID_FUEL_BOILER_CODES: Final[frozenset[int]] = frozenset(
|
|||
)
|
||||
|
||||
|
||||
# SAP 10.2 Table 4c(2) boiler controls (21xx) that carry NO programmer /
|
||||
# time switch: 2101 "No time or thermostatic control", 2103 "Room
|
||||
# thermostat only", 2111 "TRVs and bypass", 2113 "Room thermostat and
|
||||
# TRVs". Every other 21xx control includes a programmer (2102/2104/2105/
|
||||
# 2106 …) or time-and-temperature zone control (2110/2112). Used by the
|
||||
# RdSAP 10 §10.5 (PDF p.57) "Hot water separately timed" rule below.
|
||||
_BOILER_CONTROLS_WITHOUT_PROGRAMMER: Final[frozenset[int]] = frozenset(
|
||||
{2101, 2103, 2111, 2113}
|
||||
)
|
||||
|
||||
# SAP 10.2 Table 4b (PDF p.168) gas/LPG/biogas boilers lodged pre-1998
|
||||
# (fan-assisted flue 110-114 + balanced/open flue 115-119) plus the
|
||||
# pre-1998 liquid-fuel boilers (124 pre-1985, 125 1985-1997, 128 combi
|
||||
# pre-1998). Gas/LPG 101-109 and oil 126/127/129/130 are 1998-or-later.
|
||||
# Used by the RdSAP 10 §10.5 separate-timing rule: a 1998-or-later boiler
|
||||
# is always separately timed; a pre-1998 boiler only when a programmer
|
||||
# is present.
|
||||
_PRE_1998_BOILER_SAP_CODES: Final[frozenset[int]] = frozenset(
|
||||
set(range(110, 120)) | {124, 125, 128}
|
||||
)
|
||||
|
||||
|
||||
def _separately_timed_dhw(
|
||||
epc: EpcPropertyData, main: Optional[MainHeatingDetail],
|
||||
) -> bool:
|
||||
|
|
@ -5232,7 +5254,24 @@ def _separately_timed_dhw(
|
|||
# DHW is not separately timed.
|
||||
if epc.sap_heating.water_heating_code not in _WATER_INHERIT_FROM_MAIN_CODES:
|
||||
return False
|
||||
return bool(epc.has_hot_water_cylinder)
|
||||
if not epc.has_hot_water_cylinder:
|
||||
return False
|
||||
# RdSAP 10 §10.5 (PDF p.57) "Hot water separately timed":
|
||||
# No programmer, pre-1998 boiler → No
|
||||
# Programmer, pre-1998 boiler → Yes
|
||||
# Post-1998 boiler → Yes
|
||||
# i.e. DHW is NOT separately timed only when a pre-1998 boiler is
|
||||
# paired with a no-programmer control (Table 4c(2): room-thermostat-
|
||||
# only / TRV-only). Every other boiler+cylinder cert keeps the
|
||||
# separately-timed default — so the change is confined to old, low-
|
||||
# control stock (this lpg-boiler "before" worksheet: code 115 + 2113
|
||||
# → (53) temperature factor 0.78, not 0.702).
|
||||
if (
|
||||
main.main_heating_control in _BOILER_CONTROLS_WITHOUT_PROGRAMMER
|
||||
and main.sap_main_heating_code in _PRE_1998_BOILER_SAP_CODES
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _table_2b_note_b_multiplier_applies(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue