mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
S0380.233: PV self-consumption credited at Table 12a weighted import rate
SAP 10.2 Appendix M1 §6 (PDF p.94, lines 5510-5513): "apply the normal import electricity price to PV energy used within the dwelling and the 'electricity sold to grid, PV' price from Table 12 to the energy exported. In the case of the former, use a weighted average of high and low rates (Table 12a)." `_pv_dwelling_import_price_gbp_per_kwh` was returning the bare off-peak LOW rate (5.50 p/kWh on a 7-hour tariff) for the PV-used-in-dwelling credit. PV self-consumption displaces the dwelling's "all other uses" electricity (lighting / appliances / pumps), which on an off-peak tariff bills at the Table 12a Grid 2 ALL_OTHER_USES weighted blend, not the low rate. On simulated case 19 the worksheet (252)/(269) credits PV-used-in-dwelling at 14.3110 p/kWh = 0.90 × 15.29 + 0.10 × 5.50; we credited it at 5.50, under-crediting onsite PV by ~£0.088/kWh on every off-peak PV cert. Fix delegates to `_other_fuel_cost_gbp_per_kwh(tariff, prices)` (the same ALL_OTHER_USES rate): STANDARD tariff still returns the flat Table 32 code 30 13.19 p/kWh (golden cohort unchanged — all 2412 tests pass); off-peak returns the weighted high/low blend. Call sites now pass the resolved `_rdsap_tariff(epc)`. The now-unused `_off_peak_low_rate_gbp_per_kwh_via_meter_heuristic` (its only caller) is removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
212b0c92ab
commit
d4a8c02b54
2 changed files with 42 additions and 43 deletions
|
|
@ -2082,23 +2082,6 @@ def _off_peak_low_rate_gbp_per_kwh(tariff: Tariff) -> float:
|
|||
return low * _PENCE_TO_GBP
|
||||
|
||||
|
||||
def _off_peak_low_rate_gbp_per_kwh_via_meter_heuristic(meter_type: object) -> float:
|
||||
"""Off-peak low-rate £/kWh for callsites that detect off-peak via the
|
||||
`_is_off_peak_meter` heuristic (RdSAP meter code 3 = Unknown is
|
||||
treated as off-peak for electric end-uses; see _is_off_peak_meter
|
||||
docstring). When the meter resolves to a known off-peak tariff
|
||||
(codes 1/4/5), bills at that tariff's Table 32 low rate; when the
|
||||
meter resolves to STANDARD (codes 2 = Single, 3 = Unknown), falls
|
||||
back to the SEVEN_HOUR rate (5.50, Table 32 code 31). Codifies the
|
||||
heuristic that pre-S0380.138 was baked into the literal
|
||||
`prices.e7_low_rate_p_per_kwh` constant."""
|
||||
tariff = tariff_from_meter_type(meter_type)
|
||||
if tariff is Tariff.STANDARD:
|
||||
_high, low = _tariff_high_low_rates_p_per_kwh(Tariff.SEVEN_HOUR)
|
||||
return low * _PENCE_TO_GBP
|
||||
return _off_peak_low_rate_gbp_per_kwh(tariff)
|
||||
|
||||
|
||||
# Tariff → (high_rate_fuel_code, low_rate_fuel_code) for the SAP 10.2
|
||||
# Table 12d (CO2) / Table 12e (PE) monthly factors. Mirror of the
|
||||
# Table 32 cost-rates dict above: 7-hour and 10-hour tariffs split into
|
||||
|
|
@ -2660,30 +2643,25 @@ def _pv_export_credit_gbp_per_kwh() -> float:
|
|||
|
||||
|
||||
def _pv_dwelling_import_price_gbp_per_kwh(
|
||||
meter_type: object, prices: PriceTable
|
||||
tariff: Tariff, prices: PriceTable
|
||||
) -> float:
|
||||
"""PV dwelling-consumption price per kWh per SAP 10.2 Appendix M1 §6
|
||||
(p.94): "apply the normal import electricity price to PV energy used
|
||||
within the dwelling". Onsite-consumed PV displaces grid IMPORTS, so
|
||||
it bills at the standard electricity import tariff (Table 32 code 30
|
||||
under the RdSAP10 amendment per ADR-0010 §10 = 13.19 p/kWh — the
|
||||
same rate `_fuel_cost`'s `other_uses_p_per_kwh` already pays for
|
||||
lighting/pumps/fans, and crucially the same rate Table 32 code 60
|
||||
pays for the EXPORT credit. In Table 32 these collapse to a single
|
||||
13.19 p value, so the IMPORT/EXPORT split is mathematically
|
||||
equivalent to the legacy single-rate-EXPORT credit — but the
|
||||
distinction matters when an off-peak tariff lands: §6 then directs
|
||||
a weighted Table 12a high/low rate, deferred until the first off-
|
||||
peak cost cert ships."""
|
||||
if _is_off_peak_meter(meter_type, fuel_is_electric=True):
|
||||
# Off-peak weighted Table 12a rate (deferred — `_fuel_cost`
|
||||
# short-circuits Tariff != STANDARD before reaching this path).
|
||||
# Routes through the meter-heuristic helper so an Unknown-meter
|
||||
# cert (code 3 = "treat as off-peak for electric end-uses" per
|
||||
# _is_off_peak_meter) falls back to the SEVEN_HOUR low rate
|
||||
# rather than raising on STANDARD.
|
||||
return _off_peak_low_rate_gbp_per_kwh_via_meter_heuristic(meter_type)
|
||||
return table_32_unit_price_p_per_kwh(30) * _PENCE_TO_GBP
|
||||
(PDF p.94, lines 5510-5513): "apply the normal import electricity
|
||||
price to PV energy used within the dwelling … In the case of the
|
||||
former, use a weighted average of high and low rates (Table 12a)."
|
||||
|
||||
Onsite-consumed PV displaces the dwelling's "all other uses"
|
||||
electricity (lighting / appliances / pumps), so it bills at the same
|
||||
Table 12a Grid 2 ALL_OTHER_USES rate `_other_fuel_cost_gbp_per_kwh`
|
||||
derives — a STANDARD-tariff dwelling pays the flat Table 32 code 30
|
||||
13.19 p/kWh (unchanged from the legacy single-rate path), while an
|
||||
off-peak dwelling pays the weighted high/low blend (7-hour:
|
||||
0.90 × 15.29 + 0.10 × 5.50 = 14.311 p/kWh, matching worksheet
|
||||
(252)/(269) "PV used in dwelling" on case 19).
|
||||
|
||||
Pre-S0380.233 the off-peak branch returned the bare low rate
|
||||
(5.50 p/kWh), under-crediting onsite PV on every off-peak cert."""
|
||||
return _other_fuel_cost_gbp_per_kwh(tariff, prices)
|
||||
|
||||
|
||||
def _other_fuel_cost_gbp_per_kwh(
|
||||
|
|
@ -6137,9 +6115,7 @@ def _fuel_cost(
|
|||
pv_dwelling_kwh_per_yr=pv_dwelling_kwh_per_yr,
|
||||
pv_exported_kwh_per_yr=pv_exported_kwh_per_yr,
|
||||
pv_dwelling_import_price_gbp_per_kwh=(
|
||||
_pv_dwelling_import_price_gbp_per_kwh(
|
||||
epc.sap_energy_source.meter_type, prices
|
||||
)
|
||||
_pv_dwelling_import_price_gbp_per_kwh(_rdsap_tariff(epc), prices)
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -6976,7 +6952,7 @@ def cert_to_inputs(
|
|||
pv_generation_kwh_per_yr=_pv_generation_kwh_per_yr(epc, climate),
|
||||
pv_export_credit_gbp_per_kwh=_pv_export_credit_gbp_per_kwh(),
|
||||
pv_dwelling_import_price_gbp_per_kwh=_pv_dwelling_import_price_gbp_per_kwh(
|
||||
epc.sap_energy_source.meter_type, prices
|
||||
_rdsap_tariff(epc), prices
|
||||
),
|
||||
# SAP 10.2 Appendix M1 §3-4 PV split — the cascade applies
|
||||
# IMPORT PEF (Table 12) to the onsite portion and EXPORT PEF
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
|||
_main_floor_u_value, # pyright: ignore[reportPrivateUsage]
|
||||
_main_space_heating_high_rate_fraction, # pyright: ignore[reportPrivateUsage]
|
||||
_other_fuel_cost_gbp_per_kwh, # pyright: ignore[reportPrivateUsage]
|
||||
_pv_dwelling_import_price_gbp_per_kwh, # pyright: ignore[reportPrivateUsage]
|
||||
_pv_eligible_demand_monthly_kwh, # pyright: ignore[reportPrivateUsage]
|
||||
_primary_loss_applies, # pyright: ignore[reportPrivateUsage]
|
||||
_rdsap_extract_fans_default, # pyright: ignore[reportPrivateUsage]
|
||||
|
|
@ -1852,6 +1853,28 @@ def test_pv_eligible_demand_excludes_low_rate_main_space_heating() -> None:
|
|||
assert abs(included[m] - (base[m] + secondary[m] + main_1[m])) <= 1e-9
|
||||
|
||||
|
||||
def test_pv_dwelling_import_price_blends_high_low_on_off_peak() -> None:
|
||||
# Arrange — SAP 10.2 Appendix M1 §6 (PDF p.94, lines 5510-5513): PV
|
||||
# used in the dwelling is credited at "a weighted average of high and
|
||||
# low rates (Table 12a)". On a 7-hour tariff the ALL_OTHER_USES blend
|
||||
# is 0.90 × 15.29 + 0.10 × 5.50 = 14.311 p/kWh (worksheet case 19
|
||||
# (252) "PV used in dwelling" = 14.3110). STANDARD tariff has no
|
||||
# split → flat Table 32 code 30 = 13.19 p/kWh (unchanged).
|
||||
from domain.sap10_calculator.tables.table_12a import Tariff
|
||||
|
||||
# Act
|
||||
off_peak = _pv_dwelling_import_price_gbp_per_kwh(
|
||||
Tariff.SEVEN_HOUR, SAP_10_2_SPEC_PRICES
|
||||
)
|
||||
standard = _pv_dwelling_import_price_gbp_per_kwh(
|
||||
Tariff.STANDARD, SAP_10_2_SPEC_PRICES
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert abs(off_peak - 0.14311) <= 1e-6
|
||||
assert abs(standard - 0.1319) <= 1e-6
|
||||
|
||||
|
||||
def test_is_off_peak_meter_recognises_bare_18_hour_lodging() -> None:
|
||||
# Arrange — RdSAP 10 §17 page 85 row 10-2 lodges 18-hour meter
|
||||
# as the bare "18-hour" or "18 Hour" form (Elmhurst Summary §14.2
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue