mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Slice S0380.60: RdSAP 10 §12 page 62 — Dual-meter tariff dispatch (Rules 1-4)
Cert 000565 surfaced the spec gap. Worksheet shows "Electricity
Tariff: 10 Hour Off Peak" while the Summary PDF only lodges
"Electricity meter type: Dual" — no separate tariff-hour field is
exported. Elmhurst SAP picks 10-hour because RdSAP 10 §12 page 62
contains a published inference algorithm:
> If the meter is dual 18-hour/24-hour it is 18-hour/24-hour tariff.
> Otherwise the choice between 7-hour and 10-hour is determined as
> follows.
> 1. If the main heating system (or main system if there are two)
> is an electric CPSU (192) it is 10-hour tariff.
> 2. Otherwise, if … electric storage heaters (401 to 409), or
> electric dry core or water storage boiler (193 or 195), or
> electric underfloor heating (421 or 422) — it is 7-hour tariff.
> 3. If that has not resolved it then if … direct-acting electric
> boiler (191), or heat pump (211 to 224, 521 to 524, or
> database), or electric room heaters — it is 10-hour tariff.
> 4. If none of the above applies it is 7-hour tariff.
Cert 000565 Main 1 SAP code 224 (ASHP) + Dual meter → Rule 3 →
10-hour. Matches the worksheet exactly.
New `rdsap_tariff_for_cert(meter_type, main_1_sap_code=...,
main_2_sap_code=..., main_1_is_heat_pump_database=...,
main_2_is_heat_pump_database=...)` implements the dispatch.
"or database" branch covers PCDB Table 362 heat-pump lodgements per
the spec's "or database" wording. Callers compute the boolean via
`heat_pump_record(main_heating_index_number) is not None`.
The pre-existing `tariff_from_meter_type(meter_type)` keeps its
contract for legacy call sites — returns SEVEN_HOUR as the Dual
default (the §12 Rule 4 fallback). Docstring updated to point at the
new helper for callers that need spec-correct dispatch.
Code sets (verbatim §12 page 62):
- `_RULE_1_CPSU_CODES` = {192}
- `_RULE_2_STORAGE_CODES` = {401..409, 193, 195, 421, 422} (NOT 423/424/425)
- `_RULE_3_TEN_HOUR_CODES` = {191, 211..224, 521..524}
- electric room heater codes (Table 4a 6xx) deferred with TODO until a
fixture surfaces them — Rule 4 fallback is correct in the interim
(electric room heater certs would currently get 7-hour, biasing
their cost residual; not on the active fixture front).
This commit is the FOUNDATIONAL change — no cost helpers are wired
to the new dispatch yet, so cohort/golden tests are unchanged
(354 pass + 10 expected 000565 fails). The next slice wires
`_space_heating_fuel_cost_gbp_per_kwh` / `_hot_water_fuel_cost_gbp_
per_kwh` / `_other_fuel_cost_gbp_per_kwh` to use the new dispatch +
Table 12a high-rate fractions for off-peak certs.
Spec source: `domain/sap10_calculator/docs/specs/RdSAP 10
Specification 10-06-2025.pdf` §12 page 62. Verified verbatim per
[[feedback-verify-handover-claims]] before implementing.
Pyright net-zero (0 / 0).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
71d9738749
commit
a12d373eaf
1 changed files with 91 additions and 2 deletions
|
|
@ -16,7 +16,7 @@ all consumption at the unit price.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Final
|
from typing import Final, Optional
|
||||||
|
|
||||||
|
|
||||||
class Table12aSystem(Enum):
|
class Table12aSystem(Enum):
|
||||||
|
|
@ -191,7 +191,14 @@ def other_use_high_rate_fraction(use: OtherUse, tariff: Tariff) -> float:
|
||||||
def tariff_from_meter_type(meter_type: object) -> Tariff:
|
def tariff_from_meter_type(meter_type: object) -> Tariff:
|
||||||
"""Resolve the RdSAP cert `meter_type` field to a Table 12a tariff
|
"""Resolve the RdSAP cert `meter_type` field to a Table 12a tariff
|
||||||
column. Unknown / missing → STANDARD (no off-peak split applied)
|
column. Unknown / missing → STANDARD (no off-peak split applied)
|
||||||
per the Q11b spec-faithful policy."""
|
per the Q11b spec-faithful policy.
|
||||||
|
|
||||||
|
NOTE: for a Dual meter the §12 dispatch (Rules 1-4 page 62)
|
||||||
|
requires the main heating SAP codes to choose between 7-hour and
|
||||||
|
10-hour. This helper returns the SEVEN_HOUR default for Dual —
|
||||||
|
callers that have access to the main heating codes should use
|
||||||
|
`rdsap_tariff_for_cert` instead.
|
||||||
|
"""
|
||||||
if meter_type is None:
|
if meter_type is None:
|
||||||
return Tariff.STANDARD
|
return Tariff.STANDARD
|
||||||
if isinstance(meter_type, int):
|
if isinstance(meter_type, int):
|
||||||
|
|
@ -202,3 +209,85 @@ def tariff_from_meter_type(meter_type: object) -> Tariff:
|
||||||
return Tariff.STANDARD
|
return Tariff.STANDARD
|
||||||
return _METER_INT_TO_TARIFF[code]
|
return _METER_INT_TO_TARIFF[code]
|
||||||
return Tariff.STANDARD
|
return Tariff.STANDARD
|
||||||
|
|
||||||
|
|
||||||
|
# RdSAP 10 §12 page 62 — SAP main heating code sets for the Dual-meter
|
||||||
|
# tariff dispatch. Each rule's set is taken verbatim from §12 Rules 1-3.
|
||||||
|
# Rule 1: Electric CPSU → 10-hour
|
||||||
|
_RULE_1_CPSU_CODES: Final[frozenset[int]] = frozenset({192})
|
||||||
|
# Rule 2: storage-based electric → 7-hour. The (421, 422) underfloor
|
||||||
|
# subset is explicit per §12 ("421 or 422, but not 424") — 423 / 425
|
||||||
|
# fall through to Rule 4 default unless a later spec amendment adds
|
||||||
|
# them.
|
||||||
|
_RULE_2_STORAGE_CODES: Final[frozenset[int]] = frozenset(
|
||||||
|
list(range(401, 410)) # electric storage heaters 401-409
|
||||||
|
+ [193, 195] # electric dry-core / water-storage boiler
|
||||||
|
+ [421, 422] # electric underfloor heating (424 excluded)
|
||||||
|
)
|
||||||
|
# Rule 3: direct-acting electric + heat pumps + electric room heaters
|
||||||
|
# → 10-hour. §12 lists "heat pump (211 to 224, 521 to 524, or
|
||||||
|
# database)" — the "database" branch fires when the cert lodges a
|
||||||
|
# PCDB Table 362 heat-pump index regardless of SAP code.
|
||||||
|
_RULE_3_TEN_HOUR_CODES: Final[frozenset[int]] = frozenset(
|
||||||
|
[191] # direct-acting electric boiler
|
||||||
|
+ list(range(211, 225)) # heat pumps 211-224
|
||||||
|
+ list(range(521, 525)) # warm-air heat pumps 521-524
|
||||||
|
# TODO: electric room heater codes (SAP Table 4a row 6xx for
|
||||||
|
# electric panel / radiant heaters) when a fixture surfaces them.
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def rdsap_tariff_for_cert(
|
||||||
|
meter_type: object,
|
||||||
|
*,
|
||||||
|
main_1_sap_code: Optional[int] = None,
|
||||||
|
main_2_sap_code: Optional[int] = None,
|
||||||
|
main_1_is_heat_pump_database: bool = False,
|
||||||
|
main_2_is_heat_pump_database: bool = False,
|
||||||
|
) -> Tariff:
|
||||||
|
"""RdSAP 10 §12 page 62 — full meter+heating tariff dispatch.
|
||||||
|
|
||||||
|
Single meter → STANDARD. Dual 18-hour / Dual 24-hour map straight
|
||||||
|
to their respective tariffs. Otherwise applies §12 Rules 1-4
|
||||||
|
where each rule considers BOTH main heating systems on multi-
|
||||||
|
main certs ("the main system or either main system if there are
|
||||||
|
two"):
|
||||||
|
|
||||||
|
Rule 1 Electric CPSU (192) → 10-hour
|
||||||
|
Rule 2 Storage / storage boiler / underfloor → 7-hour
|
||||||
|
(401-409, 193, 195, 421, 422)
|
||||||
|
Rule 3 Direct-acting electric boiler (191), → 10-hour
|
||||||
|
heat pump (211-224, 521-524, database),
|
||||||
|
electric room heaters
|
||||||
|
Rule 4 None of the above → 7-hour
|
||||||
|
(default for Dual + non-electric main)
|
||||||
|
|
||||||
|
`main_1_is_heat_pump_database` / `main_2_is_heat_pump_database`
|
||||||
|
signal the "or database" Rule 3 branch — the cert lodges a PCDB
|
||||||
|
Table 362 heat-pump record. Callers compute this via
|
||||||
|
`heat_pump_record(main_heating_index_number) is not None`.
|
||||||
|
|
||||||
|
Cert 000565 (Main 1 SAP code 224 ASHP + Dual meter) → Rule 3 →
|
||||||
|
TEN_HOUR, matching the worksheet's "10 Hour Off Peak" lodging.
|
||||||
|
"""
|
||||||
|
base = tariff_from_meter_type(meter_type)
|
||||||
|
# Non-Dual meters resolve straight from the meter type.
|
||||||
|
if base is not Tariff.SEVEN_HOUR:
|
||||||
|
return base
|
||||||
|
main_codes = {
|
||||||
|
c for c in (main_1_sap_code, main_2_sap_code) if c is not None
|
||||||
|
}
|
||||||
|
# Rule 1
|
||||||
|
if main_codes & _RULE_1_CPSU_CODES:
|
||||||
|
return Tariff.TEN_HOUR
|
||||||
|
# Rule 2 — checked BEFORE rule 3 per §12 ordering (storage takes
|
||||||
|
# precedence over the broader Rule 3 electric set).
|
||||||
|
if main_codes & _RULE_2_STORAGE_CODES:
|
||||||
|
return Tariff.SEVEN_HOUR
|
||||||
|
# Rule 3
|
||||||
|
if main_codes & _RULE_3_TEN_HOUR_CODES:
|
||||||
|
return Tariff.TEN_HOUR
|
||||||
|
if main_1_is_heat_pump_database or main_2_is_heat_pump_database:
|
||||||
|
return Tariff.TEN_HOUR
|
||||||
|
# Rule 4 — default
|
||||||
|
return Tariff.SEVEN_HOUR
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue