mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.170: Community heating mapper unblock (Table 12 dispatch)
Closes the 5 community-heating variants in the heating-systems corpus
(community heating 1/2/3/4/6 on property 001431). Pre-slice the
mapper returned `MainHeatingDetail.main_fuel_type=''` for every
community-heating cert because §14.0 lodges no Fuel Type — only EES
'COM' + a Table 4a heat-network SAP code (301/302/304). The cascade
strict-raised `MissingMainFuelType` per S0380.132. The actual fuel
that bills the cascade lives in the §14.1 Community Heating/Heat
Network block, which the extractor was skipping entirely.
SAP 10.2 Table 12 (PDF p.189) defines the heat-network fuel codes:
Boilers + Mains Gas → 51 (heat from boilers — mains gas)
Boilers + Mineral oil → 53 (heat from boilers — oil)
Boilers + Coal → 54 (heat from boilers — coal)
Boilers + Biomass → 43 (heat from boilers — biomass)
Combined Heat and Power → 48 (heat from CHP; fuel-agnostic)
Heat pump + Electricity → 41 (heat from electric heat pump)
Per spec text the upstream fuel determines the boiler-side code; CHP
is fuel-agnostic at the Table 12 cost / CO2 / PE level.
Three layers wired:
1. Survey schema — new `CommunityHeating` dataclass alongside
`MainHeating2` carrying the §14.1 fields (heating_type,
community_heat_source, community_fuel_type, heating_controls_ees,
heating_controls_sap, chp_fuel_factor). Mutually exclusive with
`main_heating_2` at the §14.1 level. Attached as
`MainHeating.community_heating: Optional[CommunityHeating] = None`.
2. Extractor — new `_extract_community_heating()` method bracketed by
"14.1 Community Heating/Heat Network" / "14.2 Meters". Returns
None on individually-heated dwellings (no Community Heat Source
lodged). Wired into `_extract_main_heating()`.
3. Mapper — new `_resolve_community_heating_fuel_code(heat_source,
fuel)` dispatch helper + `_ELMHURST_COMMUNITY_BOILER_FUEL_TO_TABLE_12`
constant for the boiler upstream-fuel split. Wired in
`_map_elmhurst_sap_heating` after the EES-code-to-fuel dispatch
and before the strict-raise on absent SAP code.
Per the standard slice workflow + [[feedback-aaa-test-convention]]:
- 5 new AAA tests in `test_community_heating_mapper_resolves_table_12_
fuel_code` parametrized over the 5 corpus variants, asserting the
mapper resolves the expected Table 12 code per variant.
- The existing parametrized residual-pin test in
`test_heating_systems_corpus_residual_matches_pin` picks up the
5 community-heating variants with cascade-side residuals pinned as
forcing functions for follow-up slices:
variant dSAP dcost dCO2 dPE
CH1 (Boilers/Gas) +0.59 -£14 -787 -3827
CH2 (CHP/Gas) +4.50 -£104 -1430 +1506
CH3 (HP/Elec) +0.59 -£14 +1614 +11879
CH4 (CHP/Oil) +4.50 -£104 -4397 +495
CH6 (CHP/Coal) -3.52 +£81 -2935 +7865
These reflect open cascade-side work (SAP 10.2 Appendix C CHP/
boiler heat-fraction split missing — cascade treats CHP+Boilers as
100% CHP; community-HP COP cascade missing — cascade doesn't divide
delivered heat by COP for Table 12 code 41; heat-network overall
CO2/PE blended-factor cascade missing — cascade doesn't compute
worksheet rows (386)/(486)). Pinned per [[feedback-zero-error-strict]];
follow-up slices close gaps and re-pin smaller residuals.
- `_BLOCKED_BY_MISSING_MAIN_FUEL_TYPE` tuple now empty; the
blocked-tier test pytest-skipped via `pytest.mark.skipif` with a
reason naming this slice.
Test baseline at HEAD: 921 pass + 1 skipped (was 916 + 0 at
predecessor 7e08e7af). Pyright net-zero on affected files
(elmhurst_site_notes.py, elmhurst_extractor.py, mapper.py,
test_heating_systems_corpus.py): 32 → 32.
Per [[feedback-spec-citation-in-commits]] the dispatch is grounded
in SAP 10.2 Table 12 (PDF p.189). Per
[[feedback-bigger-slices-for-uniform-work]] all 5 variants land in
one slice — the work is uniform (single Elmhurst label dict + single
dispatch helper) and the per-variant residuals surface together
because of cascade-side gaps, not mapper-side variation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
7e08e7af71
commit
9f0d23adc6
4 changed files with 237 additions and 6 deletions
|
|
@ -6,6 +6,7 @@ from datatypes.epc.surveys.elmhurst_site_notes import (
|
|||
AlternativeWall,
|
||||
BathsAndShowers,
|
||||
BuildingPartDimensions,
|
||||
CommunityHeating,
|
||||
ElmhurstSiteNotes,
|
||||
ExtensionPart,
|
||||
FloorDetails,
|
||||
|
|
@ -1239,6 +1240,7 @@ class ElmhurstSiteNotesExtractor:
|
|||
else None
|
||||
)
|
||||
main_heating_2 = self._extract_main_heating_2()
|
||||
community_heating = self._extract_community_heating()
|
||||
return MainHeating(
|
||||
heat_emitter=self._local_str(lines, "Heat Emitter"),
|
||||
fuel_type=self._local_str(lines, "Fuel Type"),
|
||||
|
|
@ -1254,6 +1256,7 @@ class ElmhurstSiteNotesExtractor:
|
|||
main_heating_ees=self._local_str(lines, "Main Heating EES Code"),
|
||||
secondary_heating_sap_code=secondary_code,
|
||||
main_heating_2=main_heating_2,
|
||||
community_heating=community_heating,
|
||||
)
|
||||
|
||||
def _extract_main_heating_2(self) -> Optional[MainHeating2]:
|
||||
|
|
@ -1304,6 +1307,38 @@ class ElmhurstSiteNotesExtractor:
|
|||
main_heating_sap_code=main_heating_sap_code,
|
||||
)
|
||||
|
||||
def _extract_community_heating(self) -> Optional[CommunityHeating]:
|
||||
"""§14.1 Community Heating/Heat Network block. Lodged in place of
|
||||
§14.1 Main Heating2 when the §14.0 Main Heating SAP code names a
|
||||
heat-network row (Table 4a 301/302/304). Returns None when no
|
||||
§14.1 Community Heating block is present on the cert.
|
||||
|
||||
The block carries the Community Heat Source (Boilers / CHP /
|
||||
Heat pump) + Community Fuel Type (Mains Gas / Electricity /
|
||||
Mineral oil or biodiesel / Coal) — together these resolve the
|
||||
Table 12 heat-network fuel code that bills the cascade. See
|
||||
`_resolve_community_heating_fuel_code` in the mapper.
|
||||
"""
|
||||
lines = self._section_lines(
|
||||
"14.1 Community Heating/Heat Network", "14.2 Meters",
|
||||
)
|
||||
# Absence of the §14.1 Community Heating block: no marker found
|
||||
# → `_section_lines` returns []. Lodgement convention also
|
||||
# leaves Community Heat Source empty on individually-heated
|
||||
# dwellings; treat both as "no community heating present".
|
||||
heat_source = self._local_str(lines, "Community Heat Source")
|
||||
if not lines or not heat_source:
|
||||
return None
|
||||
return CommunityHeating(
|
||||
heating_type=self._local_str(lines, "Heating Type"),
|
||||
pcdf_boiler_reference=self._local_val(lines, "PCDF Boiler Reference"),
|
||||
community_heat_source=heat_source,
|
||||
community_fuel_type=self._local_str(lines, "Community Fuel Type"),
|
||||
heating_controls_ees=self._local_str(lines, "Heating Controls EES"),
|
||||
heating_controls_sap=self._local_str(lines, "Heating Controls SAP"),
|
||||
chp_fuel_factor=self._local_val(lines, "CHP Fuel Factor"),
|
||||
)
|
||||
|
||||
def _extract_meters(self) -> Meters:
|
||||
return Meters(
|
||||
electricity_meter_type=self._str_val("Electricity meter type"),
|
||||
|
|
|
|||
|
|
@ -476,6 +476,50 @@ _EXPECTATIONS: tuple[_CorpusExpectation, ...] = (
|
|||
# cascade-side §A.2.2 efficiency or tariff-routing gap; pinned as
|
||||
# forcing function for follow-up.
|
||||
_CorpusExpectation(variant='no system', block='11a', expected_sap_resid=+1.1783, expected_cost_resid_gbp=-27.1485, expected_co2_resid_kg=-49.8272, expected_pe_resid_kwh=-562.4367),
|
||||
# Slice S0380.170 unblocked the 5 community-heating variants. Per
|
||||
# SAP 10.2 Table 12 (PDF p.189) the heat-network fuel code comes
|
||||
# from the §14.1 Community Heat Source × Community Fuel Type pair:
|
||||
# `Boilers × Mains Gas` → 51, `CHP × *` → 48 (fuel-agnostic),
|
||||
# `Heat pump × Electricity` → 41. New CommunityHeating dataclass
|
||||
# on `ElmhurstSiteNotes.main_heating` + extractor `_extract_
|
||||
# community_heating()` + mapper `_resolve_community_heating_fuel_
|
||||
# code(heat_source, fuel)` + dispatch wired before the strict-raise.
|
||||
#
|
||||
# CH1 (301 / Boilers / Mains Gas / code 51): cascade lands ~£14
|
||||
# under-cost; the gap is the missing electricity-for-heat-
|
||||
# distribution kWh stream not propagating to (340)/(342) at the
|
||||
# heat-network rate. CO2/PE residuals reflect the heat-network
|
||||
# overall CO2 / PE factor calc not yet matching Elmhurst's (386)/
|
||||
# (486) blended-factor cascade.
|
||||
#
|
||||
# CH2/CH4 (302 / CHP / fuel / code 48): cascade overshoots SAP by
|
||||
# +4.5 because it treats CHP+Boilers as 100% CHP at 2.97 p/kWh,
|
||||
# missing the SAP 10.2 Appendix C 35% CHP / 65% boiler heat-
|
||||
# fraction split for "Existing CHP (2015+), flexible operation".
|
||||
# The boiler-side fuel-code dispatch + CHP-credit emissions for
|
||||
# exported electricity (worksheet rows (464)/(466)) are the next
|
||||
# cascade-side work.
|
||||
#
|
||||
# CH3 (304 / Heat pump / Electricity / code 41): cascade SAP +0.59
|
||||
# (same as CH1 — both worksheet SAP=64.2427 with identical Block
|
||||
# 10b shapes). CO2/PE residuals are large because the cascade
|
||||
# doesn't yet divide by the community-HP COP — Table 12 code 41
|
||||
# carries electricity factors but the worksheet divides delivered
|
||||
# heat by COP first.
|
||||
#
|
||||
# CH6 (302 / CHP / Coal / code 48): same CHP split gap as CH2/CH4
|
||||
# but with upstream coal — cascade under-CO2 by ~2935 kg and
|
||||
# over-PE by ~7865 kWh because the boiler-side code-54 coal CO2/PE
|
||||
# factors are not applied.
|
||||
#
|
||||
# All 5 pinned as forcing functions for follow-up cascade work
|
||||
# (CHP heat-fraction split, community-HP COP cascade, heat-network
|
||||
# overall factor calc). Mapper-side closure complete.
|
||||
_CorpusExpectation(variant='community heating 1', block='11b', expected_sap_resid=+0.5915, expected_cost_resid_gbp=-13.6289, expected_co2_resid_kg=-787.2531, expected_pe_resid_kwh=-3827.1887),
|
||||
_CorpusExpectation(variant='community heating 2', block='11b', expected_sap_resid=+4.5018, expected_cost_resid_gbp=-103.7279, expected_co2_resid_kg=-1430.3212, expected_pe_resid_kwh=+1506.0355),
|
||||
_CorpusExpectation(variant='community heating 3', block='11b', expected_sap_resid=+0.5915, expected_cost_resid_gbp=-13.6289, expected_co2_resid_kg=+1613.7837, expected_pe_resid_kwh=+11878.7588),
|
||||
_CorpusExpectation(variant='community heating 4', block='11b', expected_sap_resid=+4.5018, expected_cost_resid_gbp=-103.7279, expected_co2_resid_kg=-4397.0794, expected_pe_resid_kwh=+494.6090),
|
||||
_CorpusExpectation(variant='community heating 6', block='11b', expected_sap_resid=-3.5201, expected_cost_resid_gbp=+81.1097, expected_co2_resid_kg=-2934.9021, expected_pe_resid_kwh=+7864.5950),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -495,16 +539,15 @@ _EXPECTATIONS: tuple[_CorpusExpectation, ...] = (
|
|||
# - Solid-fuel boilers (Table 4a 150-160, 600-636) ×10
|
||||
# - PCDB-lodged "Bulk LPG" mapper-dict gap ×1
|
||||
_BLOCKED_BY_MISSING_MAIN_FUEL_TYPE: tuple[str, ...] = (
|
||||
'community heating 1',
|
||||
'community heating 2',
|
||||
'community heating 3',
|
||||
'community heating 4',
|
||||
'community heating 6',
|
||||
# Slice S0380.133 unblocked all 10 solid-fuel variants via the
|
||||
# §14.0 EES-code-driven fuel derivation; they now appear in
|
||||
# `_EXPECTATIONS` above with their post-derivation residual pins.
|
||||
# Slice S0380.166 unblocked `pcdb 3` via `"Bulk LPG": 27` in the
|
||||
# Elmhurst label dict; it now lives in `_EXPECTATIONS` at ±0.0000.
|
||||
# Slice S0380.170 unblocked all 5 community-heating variants via
|
||||
# the new CommunityHeating extractor field + the §14.1 Heat
|
||||
# Source × Fuel Type → Table 12 fuel-code dispatch. They now
|
||||
# appear in `_EXPECTATIONS` with pinned cascade-side residuals.
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -674,9 +717,13 @@ def test_heating_systems_corpus_residual_matches_pin(
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not _BLOCKED_BY_MISSING_MAIN_FUEL_TYPE,
|
||||
reason="all blocked variants have been unblocked (latest: S0380.170)",
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"variant",
|
||||
_BLOCKED_BY_MISSING_MAIN_FUEL_TYPE,
|
||||
_BLOCKED_BY_MISSING_MAIN_FUEL_TYPE or ("__placeholder__",),
|
||||
ids=lambda v: v,
|
||||
)
|
||||
def test_heating_systems_corpus_blocked_variant_raises_missing_main_fuel_type(
|
||||
|
|
@ -703,3 +750,52 @@ def test_heating_systems_corpus_blocked_variant_raises_missing_main_fuel_type(
|
|||
# Act / Assert
|
||||
with pytest.raises(MissingMainFuelType):
|
||||
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
|
||||
|
||||
|
||||
# S0380.170 — Community heating mapper dispatch coverage tests.
|
||||
#
|
||||
# These focused tests document the per-variant resolution path
|
||||
# independently of the cascade. The parametrized `_EXPECTATIONS` test
|
||||
# above is the load-bearing assertion that the cascade lands at the
|
||||
# pinned residual; these unit tests assert the mapper's `main_fuel_type`
|
||||
# resolves to the correct Table 12 heat-network code per
|
||||
# `_resolve_community_heating_fuel_code(heat_source, fuel)`.
|
||||
_COMMUNITY_HEATING_EXPECTED_FUEL_CODES: tuple[tuple[str, int], ...] = (
|
||||
# (variant, SAP 10.2 Table 12 fuel code)
|
||||
('community heating 1', 51), # Boilers + Mains Gas
|
||||
('community heating 2', 48), # CHP + Mains Gas
|
||||
('community heating 3', 41), # Heat pump + Electricity
|
||||
('community heating 4', 48), # CHP + Mineral oil or biodiesel
|
||||
('community heating 6', 48), # CHP + Coal
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("variant", "expected_table_12_code"),
|
||||
_COMMUNITY_HEATING_EXPECTED_FUEL_CODES,
|
||||
ids=lambda v: v if isinstance(v, str) else str(v),
|
||||
)
|
||||
def test_community_heating_mapper_resolves_table_12_fuel_code(
|
||||
variant: str, expected_table_12_code: int,
|
||||
) -> None:
|
||||
# Arrange — community-heating Summary lodges §14.0 EES='COM' + a
|
||||
# Table 4a heat-network SAP code, with §14.0 Fuel Type empty. The
|
||||
# §14.1 Community Heating/Heat Network block carries the upstream
|
||||
# Heat Source + Fuel Type pair, which the mapper's
|
||||
# `_resolve_community_heating_fuel_code` translates to a SAP 10.2
|
||||
# Table 12 (PDF p.189) heat-network code per the dispatch:
|
||||
# Boilers + Mains Gas → 51
|
||||
# Combined Heat and Power → 48 (fuel-agnostic)
|
||||
# Heat pump + Electricity → 41
|
||||
summary_pdf, _ = _variant_paths(variant)
|
||||
pages = _summary_pdf_to_textract_style_pages(summary_pdf)
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
|
||||
# Act
|
||||
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||
|
||||
# Assert — Main 1 picks up the Table 12 fuel code derived from the
|
||||
# §14.1 Community Heat Source + Community Fuel Type pair.
|
||||
main_heating_details = epc.sap_heating.main_heating_details
|
||||
assert main_heating_details is not None and len(main_heating_details) >= 1
|
||||
assert main_heating_details[0].main_fuel_type == expected_table_12_code
|
||||
|
|
|
|||
|
|
@ -4226,6 +4226,50 @@ _ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE: Final[dict[str, int]] = {
|
|||
}
|
||||
|
||||
|
||||
# Elmhurst §14.1 "Community Fuel Type" labels mapped to the SAP 10.2
|
||||
# Table 12 heat-network boiler fuel code (PDF p.189). Used when
|
||||
# `community_heat_source == "Boilers"` — the upstream fuel determines
|
||||
# which 51-58 row applies. CHP is fuel-agnostic at the Table 12 cost /
|
||||
# CO2 / PE level (code 48 carries the same factors irrespective of
|
||||
# upstream fuel); Heat-pump networks always route to code 41.
|
||||
#
|
||||
# Spec-correct codes from SAP 10.2 Table 12:
|
||||
# 51 = heat from boilers — mains gas
|
||||
# 52 = heat from boilers — LPG
|
||||
# 53 = heat from boilers — oil
|
||||
# 54 = heat from boilers — coal
|
||||
# 43 = heat from boilers — biomass
|
||||
_ELMHURST_COMMUNITY_BOILER_FUEL_TO_TABLE_12: Final[dict[str, int]] = {
|
||||
"Mains Gas": 51,
|
||||
"Mineral oil or biodiesel": 53,
|
||||
"Coal": 54,
|
||||
"Biomass": 43,
|
||||
}
|
||||
|
||||
|
||||
def _resolve_community_heating_fuel_code(
|
||||
heat_source: str, community_fuel: str,
|
||||
) -> Optional[int]:
|
||||
"""Resolve the SAP 10.2 Table 12 (PDF p.189) heat-network fuel code
|
||||
from the §14.1 "Community Heat Source" + "Community Fuel Type"
|
||||
pair. Returns None when the heat-source string isn't recognised
|
||||
(mapper-coverage gap for a future fixture).
|
||||
|
||||
Dispatch table (verified against corpus block 10b/11b/12b/13b):
|
||||
- "Combined Heat and Power" → 48 (heat from CHP; fuel-agnostic)
|
||||
- "Heat pump" → 41 (heat from electric heat pump)
|
||||
- "Boilers" + upstream fuel → 51/52/53/54/43 per
|
||||
`_ELMHURST_COMMUNITY_BOILER_FUEL_TO_TABLE_12`
|
||||
"""
|
||||
if heat_source == "Combined Heat and Power":
|
||||
return 48
|
||||
if heat_source == "Heat pump":
|
||||
return 41
|
||||
if heat_source == "Boilers":
|
||||
return _ELMHURST_COMMUNITY_BOILER_FUEL_TO_TABLE_12.get(community_fuel)
|
||||
return None
|
||||
|
||||
|
||||
class UnmappedElmhurstLabel(ValueError):
|
||||
"""An Elmhurst Summary lodged a finite-enum label that the mapper
|
||||
does not yet know how to translate to the SAP10 cascade enum.
|
||||
|
|
@ -4679,6 +4723,22 @@ def _map_elmhurst_sap_heating(survey: ElmhurstSiteNotes) -> SapHeating:
|
|||
and mh.main_heating_ees in _ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE
|
||||
):
|
||||
main_fuel_int = _ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE[mh.main_heating_ees]
|
||||
# Community heating: §14.0 lodges EES='COM' + a Table 4a heat-network
|
||||
# SAP code (301/302/304) but no §14.0 Fuel Type. The §14.1 Community
|
||||
# Heating/Heat Network block carries the actual heat source (Boilers
|
||||
# / CHP / Heat pump) + upstream fuel (Mains Gas / Electricity /
|
||||
# Mineral oil or biodiesel / Coal) which together resolve the
|
||||
# Table 12 heat-network fuel code (PDF p.189, codes 41/43/48/51-58).
|
||||
# Cascade routes through `_is_heat_network_main` (which keys on the
|
||||
# SAP code) for the DLF and seasonal-efficiency overrides.
|
||||
if (
|
||||
main_fuel_int is None
|
||||
and mh.community_heating is not None
|
||||
):
|
||||
main_fuel_int = _resolve_community_heating_fuel_code(
|
||||
mh.community_heating.community_heat_source,
|
||||
mh.community_heating.community_fuel_type,
|
||||
)
|
||||
heat_emitter_int = _elmhurst_heat_emitter_int(
|
||||
mh.heat_emitter,
|
||||
main_floor=survey.floor,
|
||||
|
|
|
|||
|
|
@ -246,6 +246,41 @@ class MainHeating2:
|
|||
main_heating_sap_code: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class CommunityHeating:
|
||||
"""Elmhurst §14.1 "Community Heating/Heat Network" block. Lodged
|
||||
when the §14.0 Main Heating SAP code identifies a heat-network row
|
||||
(Table 4a 301-304). Mutually exclusive with `MainHeating2` at the
|
||||
§14.1 level (the extractor closes §14.0 at whichever §14.1 form
|
||||
appears first).
|
||||
|
||||
The §14.0 "Main Heating SAP Code" identifies the Table 4a category
|
||||
(301 = community boilers, 302 = CHP + boilers, 304 = community heat
|
||||
pump), but the fuel that ultimately bills the cascade comes from
|
||||
the Community Fuel Type field combined with the Community Heat
|
||||
Source. See SAP 10.2 Table 12 (PDF p.189) heat-network fuel codes:
|
||||
|
||||
- Boilers + Mains Gas → code 51
|
||||
- Boilers + Mineral oil → code 53
|
||||
- Boilers + Coal → code 54
|
||||
- Boilers + Biomass → code 43
|
||||
- Combined Heat and Power → code 48 (fuel-agnostic)
|
||||
- Heat pump + Electricity → code 41
|
||||
"""
|
||||
|
||||
heating_type: str = "" # "Space and Water Heating"
|
||||
pcdf_boiler_reference: Optional[str] = None
|
||||
community_heat_source: str = "" # "Boilers" / "Combined Heat and Power" / "Heat pump"
|
||||
community_fuel_type: str = "" # "Mains Gas" / "Electricity" / "Mineral oil or biodiesel" / "Coal"
|
||||
heating_controls_ees: str = ""
|
||||
heating_controls_sap: str = ""
|
||||
# SAP 10.2 Appendix C — CHP Fuel Factor lookup label. Drives the
|
||||
# CHP-vs-boiler heat-fraction split when `community_heat_source ==
|
||||
# "Combined Heat and Power"`. Absent on non-CHP networks (e.g.
|
||||
# CH1 boilers-only / CH3 heat-pump only).
|
||||
chp_fuel_factor: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class MainHeating:
|
||||
heat_emitter: str # e.g. "Radiators"
|
||||
|
|
@ -289,6 +324,11 @@ class MainHeating:
|
|||
# the §14.1 block is absent OR lodges only placeholder zeros (PCDB-
|
||||
# only certs). See `MainHeating2` docstring above.
|
||||
main_heating_2: Optional[MainHeating2] = None
|
||||
# §14.1 "Community Heating/Heat Network" block — Optional, lodged
|
||||
# in place of Main Heating2 when the §14.0 SAP code identifies a
|
||||
# heat-network row (Table 4a 301/302/304). Mutually exclusive with
|
||||
# `main_heating_2`. None on individually-heated dwellings.
|
||||
community_heating: Optional[CommunityHeating] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue