mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
S0380.190: derive gas-combi main fuel from §15.0 when §14.0 Fuel Type is empty
The newer Elmhurst Summary export lodges a gas combi as §14.0 "Fuel Type" empty + "Main Heating SAP Code" 104 (EES "BGW"), with no fuel string. The site-notes mapper left `main_fuel_type=''`, so `cert_to_inputs` raised `MissingMainFuelType` — blocking the whole gas-combi Summary path (reproduced on the simulated 001431 case). SAP 10.2 Table 4b (PDF p.168) rows 101-119 are "Gas boilers (including mains gas, LPG and biogas)": the code fixes the boiler type/efficiency but NOT the carrier, so 104 alone can't distinguish mains gas from LPG. The disambiguator is §15.0 "Water Heating Fuel Type" — a combi/boiler heats space + water from one appliance — exactly mirroring the existing liquid-fuel (codes 120-141) fallback. `_elmhurst_gas_boiler_main_fuel` adopts the §15.0 carrier only when the SAP code is in 101-119 AND §15.0 resolves to a gas/LPG fuel, so a regular boiler + electric immersion (§15.0 = "Electricity") still strict-raises rather than mis-billing gas as electric. 2291 passed (+1), 0 failed; pyright net-zero on both files. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e63d046b9d
commit
e43ff79c77
2 changed files with 97 additions and 0 deletions
|
|
@ -4143,6 +4143,59 @@ _LIQUID_FUEL_BOILER_SAP_MAIN_HEATING_CODES: Final[frozenset[int]] = (
|
|||
frozenset(range(120, 142))
|
||||
)
|
||||
|
||||
# SAP 10.2 Table 4b gas-boiler code range (PDF p.168). Rows 101-119 are
|
||||
# "Gas boilers (including mains gas, LPG and biogas)" — 101-109 are
|
||||
# 1998-or-later, 110-114 pre-1998 fan-assisted flue, 115-119 pre-1998
|
||||
# balanced/open flue. The code identifies the boiler TYPE/efficiency, not
|
||||
# the specific carrier: the same row applies to mains gas, bulk/bottled
|
||||
# LPG and biogas alike. The older Elmhurst export lodged §14.0 "Fuel
|
||||
# Type: Mains gas" explicitly, but the newer form leaves §14.0 "Fuel
|
||||
# Type" empty and lodges only the SAP code (e.g. 104 condensing combi,
|
||||
# EES "BGW"). For these, §15.0 "Water Heating Fuel Type" names the
|
||||
# carrier — a combi/boiler heats space + water from the one appliance —
|
||||
# so it disambiguates mains-gas-vs-LPG. Codes 120-141 (CPSU + range
|
||||
# cookers) are already covered by
|
||||
# `_LIQUID_FUEL_BOILER_SAP_MAIN_HEATING_CODES`.
|
||||
_GAS_BOILER_SAP_MAIN_HEATING_CODES: Final[frozenset[int]] = (
|
||||
frozenset(range(101, 120))
|
||||
)
|
||||
|
||||
# SAP10 main-fuel codes in the gas / LPG family — the only carriers a
|
||||
# Table 4b gas-boiler row (101-119) can have (mains gas, mains gas
|
||||
# community, bottled/bulk/special-condition LPG). Per
|
||||
# `_ELMHURST_MAIN_FUEL_TO_SAP10`: mains gas = 26, mains gas community =
|
||||
# 1, LPG bottled/bulk/special = 5/6/7, "Bulk LPG" = 27. The §15.0
|
||||
# water-heating-fuel derivation is gated on the resolved fuel being one
|
||||
# of these so it can't mis-assign electricity from a separate immersion
|
||||
# (where §15.0 lodges the immersion's fuel, not the boiler's) — that
|
||||
# case still strict-raises `MissingMainFuelType` to force a mapper fix.
|
||||
_GAS_LPG_MAIN_FUEL_CODES: Final[frozenset[int]] = frozenset({1, 5, 6, 7, 26, 27})
|
||||
|
||||
|
||||
def _elmhurst_gas_boiler_main_fuel(
|
||||
sap_main_heating_code: Optional[int],
|
||||
water_heating_fuel_code: Optional[int],
|
||||
) -> Optional[int]:
|
||||
"""Derive a gas/LPG main-fuel code for a Table 4b gas boiler whose
|
||||
§14.0 "Fuel Type" string is absent (newer Elmhurst export form).
|
||||
|
||||
Returns the §15.0 water-heating fuel code when, and only when, the
|
||||
SAP main-heating code is a Table 4b gas-boiler row (101-119) AND the
|
||||
§15.0 fuel resolves to a gas/LPG carrier — the same combi/boiler
|
||||
heats space + water, so §15.0 names the boiler's carrier. Returns
|
||||
None otherwise (non-gas-boiler code, or §15.0 lodges a non-gas fuel
|
||||
such as an electric immersion), leaving the caller to strict-raise.
|
||||
|
||||
Spec: SAP 10.2 Table 4b "Seasonal efficiency for gas and liquid fuel
|
||||
boilers" (PDF p.168) — rows 101-119 are gas-family boilers.
|
||||
"""
|
||||
if (
|
||||
sap_main_heating_code in _GAS_BOILER_SAP_MAIN_HEATING_CODES
|
||||
and water_heating_fuel_code in _GAS_LPG_MAIN_FUEL_CODES
|
||||
):
|
||||
return water_heating_fuel_code
|
||||
return None
|
||||
|
||||
|
||||
# Elmhurst §14.0 "Main Heating EES Code" → Table 32 main fuel code.
|
||||
# Empirically derived from the heating-systems corpus at
|
||||
|
|
@ -4770,6 +4823,19 @@ def _map_elmhurst_sap_heating(survey: ElmhurstSiteNotes) -> SapHeating:
|
|||
and mh.main_heating_sap_code in _LIQUID_FUEL_BOILER_SAP_MAIN_HEATING_CODES
|
||||
):
|
||||
main_fuel_int = water_heating_fuel
|
||||
# Gas / LPG boilers: SAP 10.2 Table 4b codes 101-119 (PDF p.168)
|
||||
# identify a gas-family boiler but not the specific carrier (mains
|
||||
# gas vs LPG vs biogas). The newer Elmhurst export leaves §14.0
|
||||
# "Fuel Type" empty and lodges only the SAP code (e.g. 104 condensing
|
||||
# combi, EES "BGW"); the §15.0 "Water Heating Fuel Type" names the
|
||||
# carrier because the same combi/boiler heats space + water. Adopt it
|
||||
# only when it resolves to a gas/LPG fuel, so a regular boiler paired
|
||||
# with an electric immersion (where §15.0 lodges "Electricity") still
|
||||
# strict-raises rather than mis-billing the gas boiler as electric.
|
||||
if main_fuel_int is None:
|
||||
main_fuel_int = _elmhurst_gas_boiler_main_fuel(
|
||||
mh.main_heating_sap_code, water_heating_fuel,
|
||||
)
|
||||
# Solid-fuel main heating: SAP code rows 150-160 (open / closed
|
||||
# room heaters with boiler) and 600-636 (independent solid-fuel
|
||||
# boilers) cover multiple distinct fuels under a single Table 4a
|
||||
|
|
|
|||
|
|
@ -2120,6 +2120,37 @@ def test_elmhurst_main_fuel_to_sap10_maps_bio_liquid_water_heating_labels() -> N
|
|||
assert _ELMHURST_MAIN_FUEL_TO_SAP10["Bio-liquid HVO from used cooking oil"] == 71
|
||||
|
||||
|
||||
def test_elmhurst_gas_boiler_main_fuel_derives_carrier_from_water_heating() -> None:
|
||||
# Arrange — SAP 10.2 Table 4b (PDF p.168) rows 101-119 are "Gas
|
||||
# boilers (including mains gas, LPG and biogas)". The code identifies
|
||||
# the boiler type/efficiency, NOT the carrier. The newer Elmhurst
|
||||
# export leaves §14.0 "Fuel Type" empty and lodges only the SAP code
|
||||
# (e.g. 104 condensing combi); the §15.0 "Water Heating Fuel Type"
|
||||
# names the carrier because the same combi/boiler heats space + water.
|
||||
from datatypes.epc.domain.mapper import (
|
||||
_elmhurst_gas_boiler_main_fuel, # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
|
||||
# Act / Assert — combi (104) + §15.0 mains gas (26) → mains gas.
|
||||
assert _elmhurst_gas_boiler_main_fuel(104, 26) == 26
|
||||
# Regular condensing (102) + §15.0 bulk LPG (27) → bulk LPG.
|
||||
assert _elmhurst_gas_boiler_main_fuel(102, 27) == 27
|
||||
# Boundary codes of the 101-119 gas-boiler range resolve too.
|
||||
assert _elmhurst_gas_boiler_main_fuel(101, 26) == 26
|
||||
assert _elmhurst_gas_boiler_main_fuel(119, 5) == 5 # bottled LPG
|
||||
# §15.0 lodges a separate electric immersion's fuel (30), NOT the
|
||||
# gas boiler's carrier → no derivation; caller strict-raises.
|
||||
assert _elmhurst_gas_boiler_main_fuel(104, 30) is None
|
||||
# Non-gas-boiler SAP code (224 = air-source heat pump) → None even
|
||||
# when §15.0 names a gas fuel (the HP doesn't burn it).
|
||||
assert _elmhurst_gas_boiler_main_fuel(224, 26) is None
|
||||
# Liquid-fuel boiler range (120-141) is owned by the separate
|
||||
# `_LIQUID_FUEL_BOILER_SAP_MAIN_HEATING_CODES` branch → None here.
|
||||
assert _elmhurst_gas_boiler_main_fuel(120, 26) is None
|
||||
# No SAP code lodged → None.
|
||||
assert _elmhurst_gas_boiler_main_fuel(None, 26) is None
|
||||
|
||||
|
||||
def test_elmhurst_main_heating_ees_maps_no_system_code_to_electricity() -> None:
|
||||
# Arrange — SAP 10.2 §A.2.2 (PDF p.189 area) "When no main heating
|
||||
# system is identified, the calculation is for the assumed system
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue