mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
§8c slice 3: CalculatorInputs + MonthlyEntry + SapResult + cert_to_inputs wiring (atomic)
Full §8 mirror per Q9 grilling: CalculatorInputs.space_cooling_monthly_kwh (default (0,)*12), MonthlyEntry.space_cool_requirement_kwh, SapResult.space_cooling_kwh_per_yr. _solve_month indexes into the cooling tuple and calculate_sap_from_inputs sums the per-month entries. cert_to_inputs calls space_cooling_monthly_kwh with f_C=0 and cooling_gains=(0,)*12 — RdSAP convention since the cert never lodges cooled-area data and every `has_fixed_air_conditioning=False` cert collapses (107) to zero. The first cooling-enabled fixture needs a cooling_gains_from_cert helper + RdSAP cooled-area defaulting rule (deferred — SPEC_COVERAGE §8c row). Round-trip test pins inputs.space_cooling_monthly_kwh = (0,)*12, result.space_cooling_kwh_per_yr = 0.0, and every MonthlyEntry.space_cool_requirement_kwh = 0.0 for a typical SAP10 minimal cert. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
3b9fa936f0
commit
f37970666e
3 changed files with 61 additions and 4 deletions
|
|
@ -129,6 +129,12 @@ class CalculatorInputs:
|
|||
secondary_heating_fraction: float = 0.0
|
||||
secondary_heating_efficiency: float = 1.0
|
||||
secondary_heating_fuel_cost_gbp_per_kwh: float = 0.0
|
||||
# SAP10.2 (107)m — space cooling requirement kWh per month from §8c
|
||||
# orchestrator `space_cooling_monthly_kwh`. Includes spec Jun-Aug
|
||||
# inclusion mask + 1-kWh clamp. Default (0,)*12 for backwards
|
||||
# compatibility — every cert without `has_fixed_air_conditioning`
|
||||
# collapses cooling to zero.
|
||||
space_cooling_monthly_kwh: tuple[float, ...] = (0.0,) * 12
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -145,6 +151,7 @@ class MonthlyEntry:
|
|||
space_heat_requirement_kwh: float
|
||||
main_heating_fuel_kwh: float
|
||||
secondary_heating_fuel_kwh: float = 0.0
|
||||
space_cool_requirement_kwh: float = 0.0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -159,6 +166,7 @@ class SapResult:
|
|||
total_fuel_cost_gbp: float
|
||||
co2_kg_per_yr: float
|
||||
space_heating_kwh_per_yr: float
|
||||
space_cooling_kwh_per_yr: float
|
||||
main_heating_fuel_kwh_per_yr: float
|
||||
secondary_heating_fuel_kwh_per_yr: float
|
||||
hot_water_kwh_per_yr: float
|
||||
|
|
@ -210,6 +218,10 @@ def _solve_month(
|
|||
else 0.0
|
||||
)
|
||||
|
||||
# SAP 10.2 §8c — (107)m precomputed upstream by `space_cooling_monthly_kwh`
|
||||
# (includes Jun-Aug inclusion mask + post-f_C × f_intermittent clamp).
|
||||
q_cool = inputs.space_cooling_monthly_kwh[month - 1]
|
||||
|
||||
return MonthlyEntry(
|
||||
month=month,
|
||||
external_temp_c=t_ext,
|
||||
|
|
@ -221,6 +233,7 @@ def _solve_month(
|
|||
space_heat_requirement_kwh=q_heat,
|
||||
main_heating_fuel_kwh=fuel_main,
|
||||
secondary_heating_fuel_kwh=fuel_secondary,
|
||||
space_cool_requirement_kwh=q_cool,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -262,6 +275,7 @@ def calculate_sap_from_inputs(inputs: CalculatorInputs) -> SapResult:
|
|||
)
|
||||
|
||||
space_heating_kwh = sum(e.space_heat_requirement_kwh for e in monthly)
|
||||
space_cooling_kwh = sum(e.space_cool_requirement_kwh for e in monthly)
|
||||
main_fuel_kwh = sum(e.main_heating_fuel_kwh for e in monthly)
|
||||
secondary_fuel_kwh = sum(e.secondary_heating_fuel_kwh for e in monthly)
|
||||
delivered_fuel_kwh = (
|
||||
|
|
@ -377,6 +391,7 @@ def calculate_sap_from_inputs(inputs: CalculatorInputs) -> SapResult:
|
|||
total_fuel_cost_gbp=total_cost,
|
||||
co2_kg_per_yr=co2,
|
||||
space_heating_kwh_per_yr=space_heating_kwh,
|
||||
space_cooling_kwh_per_yr=space_cooling_kwh,
|
||||
main_heating_fuel_kwh_per_yr=main_fuel_kwh,
|
||||
secondary_heating_fuel_kwh_per_yr=secondary_fuel_kwh,
|
||||
hot_water_kwh_per_yr=inputs.hot_water_kwh_per_yr,
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ from domain.sap.worksheet.mean_internal_temperature import (
|
|||
mean_internal_temperature_monthly,
|
||||
)
|
||||
from domain.sap.worksheet.solar_gains import solar_gains_from_cert
|
||||
from domain.sap.worksheet.space_cooling import space_cooling_monthly_kwh
|
||||
from domain.sap.worksheet.space_heating import space_heating_monthly_kwh
|
||||
from domain.sap.worksheet.ventilation import (
|
||||
MechanicalVentilationKind,
|
||||
|
|
@ -928,18 +929,35 @@ def cert_to_inputs(
|
|||
# SAP10.2 §8 — compose (98c)m via the orchestrator. Reuses the per-month
|
||||
# HTC + total-gains tuples already computed for §7 and adds T_int + η
|
||||
# from the MIT result. Includes the Table 9c step 10 summer clamp.
|
||||
monthly_external_temp_c = tuple(
|
||||
external_temperature_c(_region_index(epc.region_code), m)
|
||||
for m in range(1, 13)
|
||||
)
|
||||
space_heating_result = space_heating_monthly_kwh(
|
||||
monthly_heat_transfer_coefficient_w_per_k=monthly_htc_w_per_k,
|
||||
monthly_internal_temperature_c=mit_result.adjusted_mean_internal_temp_monthly,
|
||||
monthly_external_temperature_c=tuple(
|
||||
external_temperature_c(_region_index(epc.region_code), m)
|
||||
for m in range(1, 13)
|
||||
),
|
||||
monthly_external_temperature_c=monthly_external_temp_c,
|
||||
monthly_utilisation_factor=mit_result.utilisation_factor_whole_monthly,
|
||||
monthly_total_gains_w=monthly_total_gains_w,
|
||||
total_floor_area_m2=dim.total_floor_area_m2,
|
||||
)
|
||||
|
||||
# SAP10.2 §8c — compose (107)m via the orchestrator. RdSAP convention:
|
||||
# `cooled_area_fraction = 0` always (the cert never lodges cooled-area
|
||||
# data) and `cooling_gains = (0,)*12` until a real cooling-gains-from-
|
||||
# cert helper lands. Both decisions deferred per SPEC_COVERAGE §8c row;
|
||||
# for `has_fixed_air_conditioning=False` certs the f_C=0 zeros (107)
|
||||
# regardless of gains so the stub is harmless.
|
||||
space_cooling_result = space_cooling_monthly_kwh(
|
||||
monthly_heat_transfer_coefficient_w_per_k=monthly_htc_w_per_k,
|
||||
monthly_external_temperature_c=monthly_external_temp_c,
|
||||
monthly_total_gains_w=(0.0,) * 12,
|
||||
total_floor_area_m2=dim.total_floor_area_m2,
|
||||
thermal_mass_parameter_kj_per_m2_k=_DEFAULT_THERMAL_MASS_PARAMETER_KJ_PER_M2_K,
|
||||
cooled_area_fraction=0.0,
|
||||
intermittency_factor=0.25,
|
||||
)
|
||||
|
||||
return CalculatorInputs(
|
||||
dimensions=dim,
|
||||
heat_transmission=ht,
|
||||
|
|
@ -962,6 +980,9 @@ def cert_to_inputs(
|
|||
# SAP10.2 (98c)m — total space heating kWh/month from §8 orchestrator
|
||||
# above (includes the spec Jun..Sep summer clamp).
|
||||
space_heating_monthly_kwh=space_heating_result.total_space_heating_monthly_kwh,
|
||||
# SAP10.2 (107)m — space cooling kWh/month from §8c orchestrator
|
||||
# above (includes Jun-Aug inclusion mask + 1-kWh clamp).
|
||||
space_cooling_monthly_kwh=space_cooling_result.space_cooling_monthly_kwh,
|
||||
region=_region_index(epc.region_code),
|
||||
control_type=control_type_value,
|
||||
responsiveness=responsiveness_value,
|
||||
|
|
|
|||
|
|
@ -292,6 +292,27 @@ def test_minimal_cert_round_trips_through_calculator_and_returns_sap_result() ->
|
|||
assert result.total_fuel_cost_gbp > 0
|
||||
|
||||
|
||||
def test_no_ac_cert_round_trips_to_zero_space_cooling_on_sap_result() -> None:
|
||||
"""RdSAP cert without a fixed air-conditioning system (the dominant
|
||||
case) must wire through cert_to_inputs → calculator with the §8c
|
||||
orchestrator producing all-zero cooling. SapResult.space_cooling_kwh_
|
||||
per_yr == 0.0 and every MonthlyEntry.space_cool_requirement_kwh == 0.0.
|
||||
"""
|
||||
# Arrange — has_fixed_air_conditioning is False by default on the
|
||||
# SAP10 minimal heating fixture (mirrors every Elmhurst fixture).
|
||||
epc = _typical_semi_detached_epc()
|
||||
assert epc.sap_heating.has_fixed_air_conditioning is False
|
||||
|
||||
# Act
|
||||
inputs = cert_to_inputs(epc)
|
||||
result = Sap10Calculator().calculate(epc)
|
||||
|
||||
# Assert
|
||||
assert inputs.space_cooling_monthly_kwh == (0.0,) * 12
|
||||
assert result.space_cooling_kwh_per_yr == 0.0
|
||||
assert all(entry.space_cool_requirement_kwh == 0.0 for entry in result.monthly)
|
||||
|
||||
|
||||
def test_calculator_always_uses_uk_average_weather_for_rating() -> None:
|
||||
# Arrange — SAP 10.2 Appendix U explicitly states: "Calculations for
|
||||
# ratings (SAP rating and environmental impact rating) are done with
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue