mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
slice 20a.1: route ventilation through predicted_space_heating_kwh (v2.7.1)
v20a added ventilation_heat_loss_w_per_k as a standalone feature but never connected it to the HLC inside predicted_space_heating_kwh, so the downstream physics aggregates (predicted_ecf, predicted_total_fuel_cost, predicted_log10_ecf — the top-10 model features) never saw the infiltration signal. Importance for ventilation_heat_loss_w_per_k was rank 58/196 (importance 30) vs envelope's rank 21 (86). Adds the ventilation column to the envelope-conduction HLC before applying HDH and efficiency, so chimney + draught-proofing signals flow through the physics aggregates the model actually uses. Default 0 keeps backwards compatibility.
This commit is contained in:
parent
4d838bb03c
commit
244f4555ac
4 changed files with 54 additions and 6 deletions
|
|
@ -70,17 +70,21 @@ def predicted_space_heating_kwh(
|
|||
envelope_heat_loss_w_per_k: float,
|
||||
region_code: Optional[str],
|
||||
seasonal_efficiency_main: float,
|
||||
ventilation_heat_loss_w_per_k: float = 0.0,
|
||||
) -> float:
|
||||
"""Annual delivered space-heating kWh.
|
||||
|
||||
delivered_kWh = HLC * HDH_region * 1e-3 / efficiency
|
||||
where HLC is W/K, HDH is K*hours/year (so the product is Wh, /1000 = kWh,
|
||||
/efficiency converts useful demand to delivered fuel).
|
||||
where HLC = envelope (conduction + bridging) + ventilation (infiltration),
|
||||
HDH is K*hours/year. SAP10.2 §5 routes both losses through the same demand
|
||||
pipeline. Ventilation defaults to 0 for back-compat with pre-slice-20a
|
||||
callers.
|
||||
"""
|
||||
if envelope_heat_loss_w_per_k <= 0 or seasonal_efficiency_main <= 0:
|
||||
hlc = envelope_heat_loss_w_per_k + ventilation_heat_loss_w_per_k
|
||||
if hlc <= 0 or seasonal_efficiency_main <= 0:
|
||||
return 0.0
|
||||
hdh = _hdh_for_region(region_code)
|
||||
useful_kwh = envelope_heat_loss_w_per_k * hdh * 1e-3
|
||||
useful_kwh = hlc * hdh * 1e-3
|
||||
return useful_kwh / seasonal_efficiency_main
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,49 @@ def test_predicted_space_heating_falls_back_to_uk_average_when_region_unknown()
|
|||
assert result > 0.0
|
||||
|
||||
|
||||
def test_predicted_space_heating_adds_ventilation_to_envelope_hlc() -> None:
|
||||
# Arrange — SAP10.2 HLC = envelope (conduction) + ventilation (infiltration);
|
||||
# demand scales with HLC, so adding 50 W/K of ventilation to a 200 W/K
|
||||
# envelope should give the same kWh as a 250 W/K envelope alone.
|
||||
|
||||
# Act
|
||||
combined = predicted_space_heating_kwh(
|
||||
envelope_heat_loss_w_per_k=200.0,
|
||||
region_code="1",
|
||||
seasonal_efficiency_main=0.84,
|
||||
ventilation_heat_loss_w_per_k=50.0,
|
||||
)
|
||||
equivalent = predicted_space_heating_kwh(
|
||||
envelope_heat_loss_w_per_k=250.0,
|
||||
region_code="1",
|
||||
seasonal_efficiency_main=0.84,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert combined == pytest.approx(equivalent, rel=0.001)
|
||||
|
||||
|
||||
def test_predicted_space_heating_default_ventilation_zero_preserves_envelope_only_behaviour() -> None:
|
||||
# Arrange — back-compat: callers that don't pass ventilation get the
|
||||
# original envelope-only result.
|
||||
|
||||
# Act
|
||||
envelope_only = predicted_space_heating_kwh(
|
||||
envelope_heat_loss_w_per_k=200.0,
|
||||
region_code="1",
|
||||
seasonal_efficiency_main=0.84,
|
||||
)
|
||||
explicit_zero = predicted_space_heating_kwh(
|
||||
envelope_heat_loss_w_per_k=200.0,
|
||||
region_code="1",
|
||||
seasonal_efficiency_main=0.84,
|
||||
ventilation_heat_loss_w_per_k=0.0,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert envelope_only == pytest.approx(explicit_zero, rel=0.001)
|
||||
|
||||
|
||||
def test_predicted_space_heating_scotland_higher_than_thames() -> None:
|
||||
# Arrange — same HLC, same efficiency; Scotland's HDH > Thames's.
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ def test_transform_advertises_version_and_target_columns() -> None:
|
|||
|
||||
# Assert
|
||||
assert isinstance(schema, TransformSchema)
|
||||
assert schema.transform_version == "2.7.0"
|
||||
assert schema.transform_version == "2.7.1"
|
||||
assert schema.transform_version == EpcMlTransform.VERSION
|
||||
assert set(schema.target_columns.keys()) == set(_EXPECTED_TARGET_DTYPES.keys())
|
||||
for target_name, expected_dtype in _EXPECTED_TARGET_DTYPES.items():
|
||||
|
|
|
|||
|
|
@ -913,7 +913,7 @@ class EpcMlTransform:
|
|||
Version 0.1.0 — schema contract only; feature columns added in subsequent slices.
|
||||
"""
|
||||
|
||||
VERSION: str = "2.7.0"
|
||||
VERSION: str = "2.7.1"
|
||||
|
||||
def schema(self) -> TransformSchema:
|
||||
"""The cross-repo ML data contract.
|
||||
|
|
@ -999,6 +999,7 @@ class EpcMlTransform:
|
|||
envelope_heat_loss_w_per_k=envelope_w_per_k,
|
||||
region_code=epc.region_code,
|
||||
seasonal_efficiency_main=space_eff,
|
||||
ventilation_heat_loss_w_per_k=ventilation_w_per_k,
|
||||
)
|
||||
cylinder_size_val = heating_aggregates.get("cylinder_size")
|
||||
cylinder_ins_thk = heating_aggregates.get("cylinder_insulation_thickness_mm")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue