mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(pv): zero exported PV when dwelling is not export-capable
SAP 10.2 Appendix M1 (PDF p.94): "EPV,ex,m = 0 if the PV system is not connected to an export-capable meter." The cascade computed the β-split export stream regardless of `is_dwelling_export_capable`, so a non-export- capable dwelling was credited the full PV export — in the §10a COST it credits at the Table 32 import rate (13.19 p/kWh), which dominates the rating. On 7 Wybourn Terrace S2 5BJ the PE (144 vs lodged 151) and CO2 (27 vs 29) already matched, yet the phantom export cost credit pulled SAP from ~73 to 92.1 (+19). Zero `epv_exported_monthly_kwh` after the Appendix-G4 diverter adjustment when not export-capable; the onsite (EPV,dw) consumption and the diverter HW reduction are unchanged. Not-export-capable PV cohort (corpus, 4 certs): 7 Wybourn +19.1 -> +6.5, 4 Lime Ave +11.1 -> +0.4, 8 Hatherleigh +7.6 -> -0.2, Flat 5 ~-0.4. Gauge 66.1% -> 66.9%, MAE 1.124 -> 1.039. Floor 0.64 -> 0.65 / ceiling 1.18 -> 1.08. Worksheet harness 47/47 0 diverge (Summary certs carry export-capable meters). 1 AAA test, pyright net-zero. Found by auditing the worst over-rater without a worksheet: PE/CO2-match + cost-miss localised it to the PV export credit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
94275d07cc
commit
9ee3821138
3 changed files with 56 additions and 7 deletions
|
|
@ -7612,6 +7612,22 @@ def cert_to_inputs(
|
|||
epv_exported_monthly_kwh=adjusted_export_monthly_kwh,
|
||||
)
|
||||
|
||||
# SAP 10.2 Appendix M1 (PDF p.94): "EPV,ex,m = 0 if the PV system is not
|
||||
# connected to an export-capable meter." A non-export-capable dwelling
|
||||
# earns no export payment — only the onsite β consumption (EPV,dw)
|
||||
# offsets demand. Zero the exported stream so the §10a cost, CO2 and PE
|
||||
# export credits all drop out; the dwelling (onsite) portion and any
|
||||
# diverter HW reduction above are unchanged. (Without this the cascade
|
||||
# credited the full export — e.g. cert at 7 Wybourn Terrace S2 5BJ
|
||||
# over-rated +19 SAP: PE/CO2 matched the lodged figures but the export
|
||||
# cost credit alone pulled the rating from ~73 to 92.)
|
||||
if not epc.sap_energy_source.is_dwelling_export_capable:
|
||||
pv_split = PhotovoltaicSplit(
|
||||
beta_monthly=pv_split.beta_monthly,
|
||||
epv_dwelling_monthly_kwh=pv_split.epv_dwelling_monthly_kwh,
|
||||
epv_exported_monthly_kwh=(0.0,) * 12,
|
||||
)
|
||||
|
||||
# SAP 10.2 §12.4.4 overrides — when summer immersion applies (back-
|
||||
# boiler combo + cylinder + WHC from main heating), the HW cost /
|
||||
# CO2 / PE factors are kWh-weighted blends of the winter boiler fuel
|
||||
|
|
|
|||
|
|
@ -1226,6 +1226,38 @@ def test_pv_export_credit_input_reports_rdsap10_table_32_rate() -> None:
|
|||
assert abs(inputs.pv_export_credit_gbp_per_kwh - 0.1319) <= 1e-6
|
||||
|
||||
|
||||
def test_pv_not_export_capable_zeroes_exported_kwh() -> None:
|
||||
# Arrange — two identical PV dwellings (same array), one connected to an
|
||||
# export-capable meter and one not. SAP 10.2 Appendix M1 (PDF p.94):
|
||||
# "EPV,ex,m = 0 if the PV system is not connected to an export-capable
|
||||
# meter." A non-export-capable dwelling earns no export — only the
|
||||
# onsite β consumption offsets demand — so its exported kWh must be 0,
|
||||
# while the export-capable twin exports a positive amount. The onsite
|
||||
# (dwelling) portion is identical for both.
|
||||
capable = _typical_semi_detached_epc()
|
||||
capable.sap_energy_source.photovoltaic_arrays = [
|
||||
PhotovoltaicArray(peak_power=3.0, pitch=2, orientation=5, overshading=1),
|
||||
]
|
||||
capable.sap_energy_source.is_dwelling_export_capable = True
|
||||
not_capable = _typical_semi_detached_epc()
|
||||
not_capable.sap_energy_source.photovoltaic_arrays = [
|
||||
PhotovoltaicArray(peak_power=3.0, pitch=2, orientation=5, overshading=1),
|
||||
]
|
||||
not_capable.sap_energy_source.is_dwelling_export_capable = False
|
||||
|
||||
# Act
|
||||
cap_inputs = cert_to_inputs(capable)
|
||||
nocap_inputs = cert_to_inputs(not_capable)
|
||||
|
||||
# Assert — exported kWh zeroed when not export-capable; onsite unchanged.
|
||||
assert (cap_inputs.pv_exported_kwh_per_yr or 0.0) > 0.0
|
||||
assert (nocap_inputs.pv_exported_kwh_per_yr or 0.0) == 0.0
|
||||
assert abs(
|
||||
(cap_inputs.pv_dwelling_kwh_per_yr or 0.0)
|
||||
- (nocap_inputs.pv_dwelling_kwh_per_yr or 0.0)
|
||||
) <= 1e-9
|
||||
|
||||
|
||||
def test_pv_generation_differentiates_arrays_by_orientation() -> None:
|
||||
# Arrange — two single-array PVs at the same kWp / pitch / overshading,
|
||||
# one facing South and one facing North. Per SAP10.2 Appendix M
|
||||
|
|
|
|||
|
|
@ -41,11 +41,12 @@ _CORPUS = Path(
|
|||
)
|
||||
|
||||
# Measured floors/ceilings over the fixed corpus at HEAD (1000 certs, 0 skips).
|
||||
# Current: SAP within-0.5 = 66.1%, SAP MAE = 1.124 (RdSAP 10 §10.5 present-
|
||||
# but-unsized cylinder -> Table 28 Normal 110 L default, this slice — a
|
||||
# correctness fix: 7 certs that silently dropped storage loss + Table 13;
|
||||
# marginal on the headline. Prior slices: Table 12a code-408 0.20 storage
|
||||
# high-rate fraction; heat-network Table 4c(3) flat-rate charging factor).
|
||||
# Current: SAP within-0.5 = 66.9%, SAP MAE = 1.039 (SAP 10.2 Appendix M1
|
||||
# EPV,ex=0 for non-export-capable PV, this slice: 7 Wybourn +19.1 -> +6.5,
|
||||
# 4 Lime Ave +11.1 -> +0.4, 8 Hatherleigh +7.6 -> -0.2 — PE/CO2 matched
|
||||
# lodged but the export cost credit alone was over-rating). Prior slices:
|
||||
# unsized-cylinder Table 28 110 L; Table 12a code-408 0.20 storage fraction;
|
||||
# heat-network Table 4c(3) flat-rate charging factor.
|
||||
# CO2 MAE = 0.28 t/yr (signed +0.17 — a systematic over-estimate, see below).
|
||||
# PE MAE = 14.7 kWh/m2/yr (signed +9.1).
|
||||
#
|
||||
|
|
@ -66,8 +67,8 @@ _CORPUS = Path(
|
|||
# energy were 5% high; actual SAP bias is +0.145).
|
||||
# So closing demand over-estimates lifts BOTH the SAP gauge and PE/CO2; there is
|
||||
# no one-slice factor fix. RATCHET any ceiling up when a slice tightens it.
|
||||
_MIN_WITHIN_HALF_SAP = 0.64
|
||||
_MAX_SAP_MAE = 1.18
|
||||
_MIN_WITHIN_HALF_SAP = 0.65
|
||||
_MAX_SAP_MAE = 1.08
|
||||
_MAX_CO2_MAE_TONNES = 0.35 # t CO2 / yr vs co2_emissions_current
|
||||
_MAX_PE_PER_M2_MAE = 16.0 # kWh / m2 / yr vs energy_consumption_current
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue