mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-22 08:48:38 +00:00
The cascade lumped a dwelling with two main heating systems into one: `space_heating_fuel_monthly_kwh` hard-coded (203)=0 (a documented scope-A placeholder) and the calculator's per-month fuel read only main_1, so the full §8 space-heat demand billed against system 1's efficiency. Simulated case 6 (one oil boiler feeding radiators 51% + underfloor 49%) exposed it: main fuel ≈ demand/eff1 instead of the worksheet's (211)+(213) per-system split. Implements the SAP 10.2 §9a two-main model: (204) = (202) × (1 − (203)) → system 1 share of total heat (205) = (202) × (203) → system 2 share of total heat (211)m = (98c)m × (204) × 100 / (206) (213)m = (98c)m × (205) × 100 / (207) (203) = the second system's lodged `main_heating_fraction`; (207) = its own seasonal efficiency via the new per-detail `_main_heating_detail_ efficiency` (the core of `_main_heating_efficiency`, now reused for system 2). Calculator `_solve_month` aggregates main_1 + main_2 into `main_heating_fuel_kwh`. Cost (§10a 241), CO2 (§12 262) and PE (§13 276) main_2 paths were already wired and now activate. Site-notes gap also fixed: §14.1 Main Heating2 omits the "Fuel Type" cell when the second system shares Main 1's fuel (case 6: one oil boiler, two emitters). `_map_elmhurst_main_heating_2` now inherits Main 1's resolved fuel as a fallback. Blast radius: only dual-main certs. 0240 (2× oil code 130, identical Eq-D1 efficiency) is unchanged — its split collapses to the lumped total. Suite: 2355 passed, 1 skipped. New code: 0 pyright errors. NOTE: case 6 is not yet fully pinnable end-to-end — its two systems have DIFFERENT efficiencies (radiators 55°C → 79%, underfloor 35°C → 84%), a flow-temperature boiler-efficiency adjustment not yet modelled, and its dual-system auxiliary pumps ((230c)+(230d)=356) differ from the cascade. Both are separate follow-on features; this slice is the §9a fuel split. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
117 lines
5 KiB
Python
117 lines
5 KiB
Python
"""SAP 10.2 §9a Energy requirements — individual heating systems.
|
||
|
||
Spec lines 7909-7953 (worksheet block §9a). Composes the per-system fuel
|
||
kWh from the §8 space-heating tuple (98c)m, the Table 11 secondary
|
||
fraction (201), and the per-system efficiencies (206)/(207)/(208). The
|
||
formula for the main system 1 line ref (211)m is:
|
||
|
||
(211)m = (98c)m × (204) × 100 / (206)
|
||
|
||
where (204) = (202) × (1 − (203)) and (202) = 1 − (201). Single-main
|
||
case ((203) = 0) collapses (204) to (202), so (211)m = (98c)m × (202) ×
|
||
100 / (206). Same shape for secondary (215)m and main 2 (213)m.
|
||
|
||
Two-main split ((203) > 0) is implemented: (211)m = (98c)m × (204) ×
|
||
100 / (206) for system 1 and (213)m = (98c)m × (205) × 100 / (207) for
|
||
system 2, where (204) = (202) × (1 − (203)) and (205) = (202) × (203).
|
||
Cooling-fuel (209)/(221) remains a zero-branch placeholder.
|
||
|
||
Reference: SAP 10.2 specification (14-03-2025) §9a (lines 7909-7953).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class EnergyRequirementsResult:
|
||
"""SAP 10.2 §9a worksheet line refs (201)..(221).
|
||
|
||
Populated lines: (201)-(208), (211)m/(211), (213)m/(213) (two-main
|
||
split), (215)m/(215). Cooling-fuel line refs ((209), (221)) are
|
||
zero-branch placeholders until the first fixed-AC cert lands.
|
||
"""
|
||
|
||
# Fractions (Table 11)
|
||
secondary_heating_fraction: float # (201)
|
||
main_heating_total_fraction: float # (202) = 1 - (201)
|
||
main_2_of_main_fraction: float # (203)
|
||
main_1_of_total_fraction: float # (204) = (202) × (1 - (203))
|
||
main_2_of_total_fraction: float # (205) = (202) × (203)
|
||
# Efficiencies (%)
|
||
main_1_efficiency_pct: float # (206)
|
||
main_2_efficiency_pct: float # (207)
|
||
secondary_efficiency_pct: float # (208)
|
||
cooling_seer: float # (209)
|
||
# Per-month fuel (kWh)
|
||
main_1_fuel_monthly_kwh: tuple[float, ...] # (211)m
|
||
main_2_fuel_monthly_kwh: tuple[float, ...] # (213)m
|
||
secondary_fuel_monthly_kwh: tuple[float, ...] # (215)m
|
||
# Annual totals (kWh)
|
||
main_1_fuel_kwh_per_yr: float # (211)
|
||
main_2_fuel_kwh_per_yr: float # (213)
|
||
secondary_fuel_kwh_per_yr: float # (215)
|
||
cooling_fuel_kwh_per_yr: float # (221)
|
||
|
||
|
||
def space_heating_fuel_monthly_kwh(
|
||
*,
|
||
space_heating_monthly_kwh: tuple[float, ...],
|
||
secondary_heating_fraction: float,
|
||
main_heating_efficiency_pct: float,
|
||
secondary_heating_efficiency_pct: float,
|
||
main_2_of_main_fraction: float = 0.0,
|
||
main_2_efficiency_pct: float = 0.0,
|
||
) -> EnergyRequirementsResult:
|
||
"""SAP 10.2 §9a orchestrator — produce (201)..(221) line refs.
|
||
|
||
Single-main certs leave `main_2_of_main_fraction` = 0, collapsing
|
||
(204) to (202) and zeroing (213)m. Dual-main certs (cert 0240 /
|
||
simulated case 6) pass (203) = fraction of main heating from main
|
||
system 2 and (207) = main system 2 efficiency; the §8 space-heat
|
||
demand then splits (204)=(202)×(1−(203)) to system 1 and
|
||
(205)=(202)×(203) to system 2, each at its own efficiency. Cooling-
|
||
fuel (Table 10c SEER) remains a zero-branch placeholder.
|
||
"""
|
||
fraction_201 = secondary_heating_fraction
|
||
fraction_202 = 1.0 - fraction_201
|
||
fraction_203 = main_2_of_main_fraction
|
||
fraction_204 = fraction_202 * (1.0 - fraction_203)
|
||
fraction_205 = fraction_202 * fraction_203
|
||
|
||
main_1_eff = main_heating_efficiency_pct
|
||
main_2_eff = main_2_efficiency_pct
|
||
secondary_eff = secondary_heating_efficiency_pct
|
||
|
||
main_1_fuel_monthly = tuple(
|
||
q * fraction_204 * 100.0 / main_1_eff if main_1_eff > 0 else 0.0
|
||
for q in space_heating_monthly_kwh
|
||
)
|
||
main_2_fuel_monthly = tuple(
|
||
q * fraction_205 * 100.0 / main_2_eff if main_2_eff > 0 else 0.0
|
||
for q in space_heating_monthly_kwh
|
||
)
|
||
secondary_fuel_monthly = tuple(
|
||
q * fraction_201 * 100.0 / secondary_eff if secondary_eff > 0 else 0.0
|
||
for q in space_heating_monthly_kwh
|
||
)
|
||
|
||
return EnergyRequirementsResult(
|
||
secondary_heating_fraction=fraction_201,
|
||
main_heating_total_fraction=fraction_202,
|
||
main_2_of_main_fraction=fraction_203,
|
||
main_1_of_total_fraction=fraction_204,
|
||
main_2_of_total_fraction=fraction_205,
|
||
main_1_efficiency_pct=main_1_eff,
|
||
main_2_efficiency_pct=main_2_eff,
|
||
secondary_efficiency_pct=secondary_eff,
|
||
cooling_seer=0.0,
|
||
main_1_fuel_monthly_kwh=main_1_fuel_monthly,
|
||
main_2_fuel_monthly_kwh=main_2_fuel_monthly,
|
||
secondary_fuel_monthly_kwh=secondary_fuel_monthly,
|
||
main_1_fuel_kwh_per_yr=sum(main_1_fuel_monthly),
|
||
main_2_fuel_kwh_per_yr=sum(main_2_fuel_monthly),
|
||
secondary_fuel_kwh_per_yr=sum(secondary_fuel_monthly),
|
||
cooling_fuel_kwh_per_yr=0.0,
|
||
)
|