From 0786921357f2e35e15be8d56c2f656b86ec6ed0b Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 28 May 2026 23:58:35 +0000 Subject: [PATCH] Slice S0380.62: wire Table 32 standing charges into the off-peak cost fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cascade's `additional_standing_charges_gbp(main_fuel_code, water_heating_fuel_code, tariff)` function (table_32.py:178) was already producing the right values — for cert 000565 it returns £143 (£120 mains gas standing + £23 10-hour high-rate electricity standing per Table 32 page 95). But the value only landed in `FuelCostResult.additional_standing_charges_gbp` inside `_fuel_cost`, which returns `_ZERO_FUEL_COST_FOR_OFF_PEAK` for non-STANDARD tariff. The calculator then falls back to the inline cost math (scalar fuel-cost × kWh) which had no standing-charge component → £143 was silently dropped from the off-peak cost cascade. New `CalculatorInputs.standing_charges_gbp: float = 0.0` field carries the standing-charge total into the fallback path. The inline cost summation adds it before max-clamp + PV credit. STANDARD-tariff certs route via `fuel_cost.additional_standing_ charges_gbp` (set inside `_fuel_cost`) and the calculator ignores this scalar on that path — no double-count. `cert_to_inputs` populates the new field unconditionally; the value is just zero on standard-tariff certs (Table 12 note (a) gates standing-charge inclusion regardless). Cert 000565 cascade impact: - standing_charges_gbp = £143.00 ✓ (exact match to worksheet line 251) - total_fuel_cost_gbp: Δ −310 → −167 (46% reduction) - sap_score_continuous: Δ +3.61 → +1.91 (47% reduction) - co2_kg_per_yr: Δ unchanged (standing charges don't bill CO2) Cohort regression check: 427 pass + 10 expected 000565 fails. The 14 existing Elmhurst fixtures + JSON fixtures all have meter_type= None → STANDARD → standing routes via FuelCostResult unchanged. Spec source: RdSAP 10 Table 32 page 95 standing-charge column; SAP 10.2 Table 12 note (a) inclusion gating. Pyright net-zero on both files (0 / 34). Co-Authored-By: Claude Opus 4.7 --- domain/sap10_calculator/calculator.py | 8 ++++++++ domain/sap10_calculator/rdsap/cert_to_inputs.py | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/domain/sap10_calculator/calculator.py b/domain/sap10_calculator/calculator.py index 45dbb921..ff634c8f 100644 --- a/domain/sap10_calculator/calculator.py +++ b/domain/sap10_calculator/calculator.py @@ -300,6 +300,13 @@ class CalculatorInputs: fuel_cost: FuelCostResult = field( default_factory=lambda: _ZERO_FUEL_COST_RESULT ) + # Table 32 standing charges (electric off-peak high-rate code + + # mains gas) — added to `total_cost` when the calculator's off- + # peak fallback path fires. STANDARD-tariff certs route through + # `fuel_cost.additional_standing_charges_gbp` instead and ignore + # this field. cert_to_inputs sets this via `additional_standing_ + # charges_gbp(main_fuel_code, water_heating_fuel_code, tariff)`. + standing_charges_gbp: float = 0.0 @dataclass(frozen=True) @@ -518,6 +525,7 @@ def calculate_sap_from_inputs(inputs: CalculatorInputs) -> SapResult: + hot_water_cost + pumps_fans_cost + lighting_cost + + inputs.standing_charges_gbp - pv_credit, ) ecf = energy_cost_factor(total_cost_gbp=total_cost, total_floor_area_m2=tfa) diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index db1ac05f..69b46f15 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -3405,6 +3405,15 @@ def cert_to_inputs( other_fuel_cost_gbp_per_kwh=_other_fuel_cost_gbp_per_kwh( _rdsap_tariff(epc), prices ), + # Table 32 standing charges for the off-peak fallback path. + # STANDARD-tariff certs route via `fuel_cost.additional_ + # standing_charges_gbp` (set inside `_fuel_cost`) and the + # calculator ignores this scalar on that path. + standing_charges_gbp=additional_standing_charges_gbp( + main_fuel_code=_main_fuel_code(main), + water_heating_fuel_code=_water_heating_fuel_code(epc), + tariff=_rdsap_tariff(epc), + ), co2_factor_kg_per_kwh=_co2_factor_kg_per_kwh(main), # SAP10.2 Table 12d (p.194) per-end-use effective CO2 factors. For # electricity end-uses Σ(kWh_m × CO2_m) / Σ(kWh_m) replaces the