mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
slice S-B11: e7_eligible_main_codes on PriceTable; cert calibration adds 191-196
Hand-tracing cert 0800-1364 (Detached bungalow, code 191/direct-electric,
actual SAP 71, predicted 37) showed the cert assessor applies off-peak
rates to direct-electric main heating despite SAP 10.2 Table 12a
specifying 90% high-rate. Adds e7_eligible_main_codes to PriceTable so
each price source carries its own rule:
- SAP_10_2_SPEC_PRICES: {401-409, 421-425} (storage only, per Table 12a)
- CERT_CALIBRATION: {191-196, 401-409, 421-425} (empirically what
the cert software does)
100-cert parity probe:
MAE 4.99 → 4.66 (recovered to pre-S-B9 best state)
bias -1.03 → -0.70
within ±1: 23% → 24%
within ±3: 47% → 48%
within ±10: 93% → 94%
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
92727568a3
commit
737e5d6bf5
2 changed files with 39 additions and 23 deletions
|
|
@ -133,25 +133,41 @@ class PriceTable:
|
|||
"""Seam between the spec-correct SAP 10.2/10.3 Table 12 prices and
|
||||
the empirical cert-calibration prices used to parity-test against
|
||||
the corpus's lodged ratings. The cert assessor software diverges
|
||||
from spec on unit prices (see slice S-B9 commit); this struct lets
|
||||
the cert mapper switch between modes without touching the engine.
|
||||
from spec on unit prices AND on which heating codes pick up the
|
||||
off-peak rate (see slice S-B9 commit + S-B11 hand-trace).
|
||||
|
||||
`unit_price_p_per_kwh` accepts either an API fuel code or a Table 12
|
||||
code; implementations translate before lookup. `e7_low_rate_p_per_kwh`
|
||||
is the off-peak rate used for true storage-heater space heating, and
|
||||
is the off-peak rate used for E7-eligible space heating, and
|
||||
`standard_electricity_p_per_kwh` is the rate applied to lighting +
|
||||
pumps + fans regardless of main fuel.
|
||||
pumps + fans regardless of main fuel. `e7_eligible_main_codes` lists
|
||||
the SAP Table 4a main-heating codes that bill space heating at
|
||||
`e7_low_rate_p_per_kwh` — narrower under the spec (storage heaters
|
||||
only per Table 12a) than under cert calibration (the cert assessor
|
||||
appears to apply off-peak to direct-electric too).
|
||||
"""
|
||||
|
||||
unit_price_p_per_kwh: Callable[[Optional[int]], float]
|
||||
e7_low_rate_p_per_kwh: float
|
||||
standard_electricity_p_per_kwh: float
|
||||
e7_eligible_main_codes: frozenset[int]
|
||||
|
||||
|
||||
# SAP 10.2/10.3 spec-correct: per Table 12a, only true storage heaters
|
||||
# (401-409) and high-heat-retention storage (421-425) bill space heating
|
||||
# at the low rate. Direct-acting electric (191-196), heat pumps, and
|
||||
# underfloor heating bill 70-100% at the high rate, so they're not in
|
||||
# the off-peak set here.
|
||||
_SPEC_E7_ELIGIBLE_MAIN_CODES: Final[frozenset[int]] = frozenset(
|
||||
list(range(401, 410)) + list(range(421, 426))
|
||||
)
|
||||
|
||||
|
||||
SAP_10_2_SPEC_PRICES: Final[PriceTable] = PriceTable(
|
||||
unit_price_p_per_kwh=unit_price_p_per_kwh,
|
||||
e7_low_rate_p_per_kwh=9.40,
|
||||
standard_electricity_p_per_kwh=16.49,
|
||||
e7_eligible_main_codes=_SPEC_E7_ELIGIBLE_MAIN_CODES,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -167,18 +183,6 @@ _CONTROL_TYPE_BY_CODE: Final[dict[int, int]] = {
|
|||
}
|
||||
|
||||
|
||||
# SAP 10.2 Table 12a "high-rate fractions" — only true storage-type
|
||||
# electric heating systems bill space heating at the off-peak rate.
|
||||
# Storage heaters on 7h tariff have a 0% high-rate fraction (genuinely
|
||||
# all off-peak); high-heat-retention storage heaters likewise. Direct-
|
||||
# acting electric heating (codes 191-196), heat pumps, and underfloor
|
||||
# heating run 70-100% at the high rate — they were incorrectly grouped
|
||||
# into this set in slice S-B4. Hot water on these dwellings still
|
||||
# inherits the off-peak rate if the dwelling carries E7 (see
|
||||
# _hot_water_fuel_cost_gbp_per_kwh).
|
||||
_E7_SPACE_HEATING_CODES: Final[frozenset[int]] = frozenset(
|
||||
list(range(401, 410)) + list(range(421, 426))
|
||||
)
|
||||
|
||||
|
||||
def _dwelling_exposure(dwelling_type: Optional[str]) -> DwellingExposure:
|
||||
|
|
@ -355,14 +359,16 @@ def _fuel_cost_gbp_per_kwh(
|
|||
return prices.unit_price_p_per_kwh(_main_fuel_code(main)) * _PENCE_TO_GBP
|
||||
|
||||
|
||||
def _is_electric_storage_or_direct(main: Optional[MainHeatingDetail]) -> bool:
|
||||
"""RdSAP convention: electric storage heaters + direct-electric main
|
||||
systems bill space heating at the off-peak rate while hot water +
|
||||
lighting + pumps stay on the on-peak/standard rate."""
|
||||
def _is_e7_eligible(main: Optional[MainHeatingDetail], prices: PriceTable) -> bool:
|
||||
"""Whether this dwelling's main heating code bills space heating at
|
||||
the off-peak rate under the supplied price table's rules. SAP spec
|
||||
restricts this to true storage heaters; cert calibration extends to
|
||||
direct-electric (codes 191-196) where the cert assessor empirically
|
||||
applies off-peak even though Table 12a says 90% high-rate."""
|
||||
if main is None:
|
||||
return False
|
||||
code = main.sap_main_heating_code
|
||||
return code is not None and code in _E7_SPACE_HEATING_CODES
|
||||
return code is not None and code in prices.e7_eligible_main_codes
|
||||
|
||||
|
||||
def _space_heating_fuel_cost_gbp_per_kwh(
|
||||
|
|
@ -370,7 +376,7 @@ def _space_heating_fuel_cost_gbp_per_kwh(
|
|||
) -> float:
|
||||
"""Off-peak rate when the main heating is electric-storage (codes
|
||||
401-409 or 421-425), else the standard main-fuel rate."""
|
||||
if _is_electric_storage_or_direct(main):
|
||||
if _is_e7_eligible(main, prices):
|
||||
return prices.e7_low_rate_p_per_kwh * _PENCE_TO_GBP
|
||||
return _fuel_cost_gbp_per_kwh(main, prices)
|
||||
|
||||
|
|
@ -386,7 +392,7 @@ def _hot_water_fuel_cost_gbp_per_kwh(
|
|||
households typically run the immersion on the off-peak timer.
|
||||
Falls back to the main fuel when the cert doesn't lodge a separate
|
||||
water fuel."""
|
||||
is_e7 = _is_electric_storage_or_direct(main)
|
||||
is_e7 = _is_e7_eligible(main, prices)
|
||||
e7_low = prices.e7_low_rate_p_per_kwh
|
||||
if is_e7 and (
|
||||
water_heating_fuel is None
|
||||
|
|
|
|||
|
|
@ -99,10 +99,20 @@ def _build_cert_calibration_table():
|
|||
spec table_12. We expose a factory the caller uses to build the
|
||||
`PriceTable` value at validation-script init time."""
|
||||
from domain.sap.rdsap.cert_to_inputs import PriceTable
|
||||
|
||||
# Cert-calibration empirically extends the E7 off-peak set to
|
||||
# direct-electric codes (191-196) — the cert assessor applies the
|
||||
# off-peak rate to these even though SAP 10.2 Table 12a says 90%
|
||||
# high-rate. Confirmed by hand-tracing cert 0800-1364 (Detached
|
||||
# bungalow, code 191, actual SAP 71, predicted 37 before this fix).
|
||||
cert_calibration_e7_codes = frozenset(
|
||||
list(range(191, 197)) + list(range(401, 410)) + list(range(421, 426))
|
||||
)
|
||||
return PriceTable(
|
||||
unit_price_p_per_kwh=unit_price_p_per_kwh,
|
||||
e7_low_rate_p_per_kwh=E7_LOW_RATE_P_PER_KWH,
|
||||
standard_electricity_p_per_kwh=STANDARD_ELECTRICITY_P_PER_KWH,
|
||||
e7_eligible_main_codes=cert_calibration_e7_codes,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue