mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Cohort residual slice 3: Table 4f gas-combi pumps_fans = 160 kWh/yr
Replaces the static `_DEFAULT_PUMPS_FANS_KWH_PER_YR = 130` for gas-combi main heating systems with the SAP10.2 Table 4f cascade value: 115 kWh/yr (230c central heating pump, post-2013 install) + 45 kWh/yr (230e main heating flue fan, balanced/condensing) = 160. Selection keyed by `main.main_heating_category` — currently only category 2 (Gas-fired boilers); other categories fall back to the legacy 130 sentinel pending the next fixture exercising them. Adds `_PUMPS_FANS_KWH_BY_MAIN_CATEGORY` lookup. Both `CalculatorInputs. pumps_fans_kwh_per_yr` and the `_fuel_cost(...)` pumps_fans arg now share the same per-cert value. E2E pins: new parametrized test `test_elmhurst_end_to_end_pumps_fans_kwh_matches_u985_worksheet` asserts `result.pumps_fans_kwh_per_yr == 160` at abs=1e-3 for the 2 e2e fixtures (000474, 000490). Impact on 000490: cost £803.62 → £807.58 (PDF £807.54, Δ +£0.04 ≈ 0%); continuous SAP 57.77 → 57.57 (PDF 57.40, Δ +0.17 — was +0.38). SAP integer still 58 vs PDF 57 — remaining residual is the SAP rating constants (rating.py uses SAP 10.3 deflator 0.36 / slope 16.21/120.5; PDF lodges SAP 10.2 0.42 / 13.95/121) — next slice. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
af6fcfb190
commit
b536b46ab4
2 changed files with 46 additions and 2 deletions
|
|
@ -118,6 +118,15 @@ _LIVING_AREA_FRACTION_MIN: Final[float] = 0.13
|
|||
_PENCE_TO_GBP: Final[float] = 0.01
|
||||
_DEFAULT_THERMAL_MASS_PARAMETER_KJ_PER_M2_K: Final[float] = 250.0
|
||||
_DEFAULT_PUMPS_FANS_KWH_PER_YR: Final[float] = 130.0
|
||||
# SAP10.2 Table 4f cascade — annual pumps + fans electricity by main
|
||||
# heating system category. The Elmhurst gas-combi cohort lodges 115
|
||||
# (230c central heating pump, post-2013 install) + 45 (230e main
|
||||
# heating flue fan, balanced/condensing) = 160 kWh/yr. Heat pumps,
|
||||
# warm-air, oil/biomass, electric storage etc. use different rows
|
||||
# (Table 4f spec lines 7905-8076) — deferred until a fixture exercises.
|
||||
_PUMPS_FANS_KWH_BY_MAIN_CATEGORY: Final[dict[int, float]] = {
|
||||
2: 160.0, # Gas-fired boilers (115 pump + 45 flue fan)
|
||||
}
|
||||
# SAP10.2 Table 6d note 1: "average or unknown" overshading is the
|
||||
# default for existing dwellings. RdSAP doesn't lodge a per-dwelling
|
||||
# overshading code so §5 always uses AVERAGE → Z_L = 0.83.
|
||||
|
|
@ -1033,6 +1042,10 @@ def cert_to_inputs(
|
|||
main_code = main.sap_main_heating_code if main is not None else None
|
||||
main_category = main.main_heating_category if main is not None else None
|
||||
main_fuel = _main_fuel_code(main)
|
||||
pumps_fans_kwh = _PUMPS_FANS_KWH_BY_MAIN_CATEGORY.get(
|
||||
main_category if main_category is not None else -1,
|
||||
_DEFAULT_PUMPS_FANS_KWH_PER_YR,
|
||||
)
|
||||
primary_age = (
|
||||
epc.sap_building_parts[0].construction_age_band if epc.sap_building_parts else None
|
||||
)
|
||||
|
|
@ -1287,7 +1300,7 @@ def cert_to_inputs(
|
|||
thermal_mass_parameter_kj_per_m2_k=_DEFAULT_THERMAL_MASS_PARAMETER_KJ_PER_M2_K,
|
||||
main_heating_efficiency=eff,
|
||||
hot_water_kwh_per_yr=hw_kwh,
|
||||
pumps_fans_kwh_per_yr=_DEFAULT_PUMPS_FANS_KWH_PER_YR,
|
||||
pumps_fans_kwh_per_yr=pumps_fans_kwh,
|
||||
lighting_kwh_per_yr=lighting_kwh,
|
||||
space_heating_fuel_cost_gbp_per_kwh=_space_heating_fuel_cost_gbp_per_kwh(
|
||||
main, epc.sap_energy_source.meter_type, prices
|
||||
|
|
@ -1320,7 +1333,7 @@ def cert_to_inputs(
|
|||
main=main,
|
||||
energy_requirements_result=energy_requirements_result,
|
||||
hot_water_kwh=hw_kwh,
|
||||
pumps_fans_kwh=_DEFAULT_PUMPS_FANS_KWH_PER_YR,
|
||||
pumps_fans_kwh=pumps_fans_kwh,
|
||||
lighting_kwh=lighting_kwh,
|
||||
cooling_kwh=energy_requirements_result.cooling_fuel_kwh_per_yr,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -217,6 +217,37 @@ def test_elmhurst_end_to_end_lighting_kwh_per_yr_matches_u985_worksheet(
|
|||
assert result.lighting_kwh_per_yr == pytest.approx(expected_kwh, abs=1e-4)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture, expected_kwh",
|
||||
[
|
||||
(_w000474, 160.0),
|
||||
(_w000490, 160.0),
|
||||
],
|
||||
ids=["000474", "000490"],
|
||||
)
|
||||
def test_elmhurst_end_to_end_pumps_fans_kwh_matches_u985_worksheet(
|
||||
fixture: object, expected_kwh: float
|
||||
) -> None:
|
||||
"""Component-level pin on `SapResult.pumps_fans_kwh_per_yr` for the
|
||||
e2e fixtures. PDF (231) for both 000474 + 000490: 115 (central
|
||||
heating pump (230c)) + 45 (main heating flue fan (230e)) = 160.
|
||||
|
||||
Pre-fix `cert_to_inputs` hardcoded 130 kWh/yr via
|
||||
`_DEFAULT_PUMPS_FANS_KWH_PER_YR`. The shortfall (-30 kWh × elec
|
||||
price = -£4) was the dominant remaining residual on 000490 after
|
||||
Appendix L + secondary heating + ventilation closures — pushed
|
||||
continuous SAP +0.38 over PDF → integer 58 vs 57.
|
||||
"""
|
||||
# Arrange
|
||||
epc = fixture.build_epc() # type: ignore[attr-defined]
|
||||
|
||||
# Act
|
||||
result = Sap10Calculator().calculate(epc)
|
||||
|
||||
# Assert
|
||||
assert result.pumps_fans_kwh_per_yr == pytest.approx(expected_kwh, abs=1e-3)
|
||||
|
||||
|
||||
def test_elmhurst_000490_end_to_end_secondary_heating_fuel_kwh_matches_u985_worksheet() -> None:
|
||||
"""Component-level e2e pin on `SapResult.secondary_heating_fuel_kwh_per_yr`
|
||||
for 000490 — cert lodges secondary heating system "Electricity Electric
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue