mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +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 enum import Enum
|
||||
from typing import Final
|
||||
from typing import Final, Optional
|
||||
|
||||
|
||||
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:
|
||||
"""Resolve the RdSAP cert `meter_type` field to a Table 12a tariff
|
||||
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:
|
||||
return Tariff.STANDARD
|
||||
if isinstance(meter_type, int):
|
||||
|
|
@ -202,3 +209,85 @@ def tariff_from_meter_type(meter_type: object) -> Tariff:
|
|||
return Tariff.STANDARD
|
||||
return _METER_INT_TO_TARIFF[code]
|
||||
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