mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(pv): credit PV only when connected to the dwelling's meter 🟩
Gate PV generation/credit in cert_to_inputs on gov-API pv_connection:
credit only when ==2 ('connected'); ==1 ('present but not connected to the
dwelling's meter') contributes zero to the dwelling's cost/CO2/PE per
RdSAP 10 §11.1 / SAP 10.2 Appendix M. Non-int (None / site-notes str) keeps
the credit-if-array behaviour, so the Elmhurst/Summary + synthetic paths are
unchanged (no regression).
Corpus: all 5 pv_connection=1 PV certs move inside ±0.5 (e.g. 100051118081
+6.5→+0.5); MAE 0.760→0.740, within-0.5 73.8→74.3%, no regression
(pv_connection=2 certs keep their credit).
Also corrects a now-load-bearing latent bug: the solar-recommendation
overlay tagged recommended arrays pv_connection=1 ('not connected') — which
the new gate would zero. A new install connects to the dwelling's meter, so
it must be 2; pinned by the overlay test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
775232b4e7
commit
8606cab5f0
3 changed files with 60 additions and 4 deletions
|
|
@ -49,9 +49,13 @@ _BATTERY_CAPACITY_KWH = 5.0
|
|||
# Watts → kilowatts for peak-power.
|
||||
_WATTS_PER_KW = 1000.0
|
||||
# The dwelling's PV connects to its own meter (the after-cert §19 "Connected to
|
||||
# the dwelling's meter: Yes"). Non-load-bearing for the SAP cascade; carried for
|
||||
# fidelity. 1 = connected, the modal install case.
|
||||
_PV_CONNECTED_TO_DWELLING = 1
|
||||
# the dwelling's meter: Yes"). LOAD-BEARING: `cert_to_inputs` credits PV to the
|
||||
# dwelling's SAP only when `pv_connection == 2` ("connected"); value 1 means
|
||||
# "present but NOT connected" and zeroes the credit. gov-API enum (corpus- and
|
||||
# Elmhurst-validated): 0 = no PV, 1 = not connected, 2 = connected (the modal
|
||||
# install case, 52 vs 5 on the RdSAP-21.0.1 corpus). A newly-installed
|
||||
# recommended array is connected to the dwelling's own meter.
|
||||
_PV_CONNECTED_TO_DWELLING = 2
|
||||
|
||||
# A roof plane within this many degrees of due north (0°/360°, Google compass
|
||||
# convention) is dropped: it generates little and is not worth panelling. The
|
||||
|
|
|
|||
|
|
@ -3156,6 +3156,42 @@ def _pv_array_generation_kwh_per_yr(
|
|||
return _PV_MODULE_EFFICIENCY_FACTOR * array.peak_power * s * z
|
||||
|
||||
|
||||
# gov-API `sap_energy_source.pv_connection` enum (RdSAP 10 §11.1 /
|
||||
# SAP 10.2 Appendix M — "PV is included in the dwelling's assessment only
|
||||
# if connected to the dwelling's electricity meter"):
|
||||
# 0 = no PV
|
||||
# 1 = PV present but NOT connected to the dwelling's own meter
|
||||
# 2 = PV connected to the dwelling's own meter
|
||||
# Validated on the RdSAP-21.0.1 corpus (57 PV certs): pv_connection=1 certs
|
||||
# reconcile to the lodged SAP only WITHOUT a credit (MAE 4.48→1.22, 0/5 need
|
||||
# it); pv_connection=2 certs need it (MAE 0.98 vs 10.29 without). Accredited
|
||||
# Elmhurst proof: identical dwelling = SAP 87 connected vs SAP 74 not.
|
||||
_PV_CONNECTION_CONNECTED_TO_DWELLING_METER: Final[int] = 2
|
||||
|
||||
|
||||
def _pv_connected_to_dwelling_meter(epc: EpcPropertyData) -> bool:
|
||||
"""Whether a lodged PV array may be credited to this dwelling's SAP, i.e.
|
||||
whether it is connected to the dwelling's own electricity meter.
|
||||
|
||||
Keyed on the gov-API integer `pv_connection`: only value 2 ("connected")
|
||||
earns a credit; value 1 ("present but not connected" — a communal /
|
||||
separately-metered array) contributes nothing to the dwelling's energy
|
||||
cost, CO2 or primary energy, per RdSAP 10 §11.1 / SAP 10.2 Appendix M.
|
||||
|
||||
A non-integer `pv_connection` (None, or the site-notes `str` form which
|
||||
does not yet capture the connection flag) is NOT a determinate
|
||||
"not connected" signal, so it preserves the existing credit-if-array
|
||||
behaviour — no regression on the Elmhurst/Summary path or synthetic
|
||||
CalculatorInputs. The Elmhurst extractor parses "Connected to the
|
||||
dwelling's meter" today only as a parse stop-token; capturing its value
|
||||
is a follow-up that would let this gate apply to that path too.
|
||||
"""
|
||||
pv_connection = epc.sap_energy_source.pv_connection
|
||||
if isinstance(pv_connection, int):
|
||||
return pv_connection == _PV_CONNECTION_CONNECTED_TO_DWELLING_METER
|
||||
return True
|
||||
|
||||
|
||||
def _pv_generation_kwh_per_yr(
|
||||
epc: EpcPropertyData,
|
||||
climate: "int | PostcodeClimate",
|
||||
|
|
@ -3170,7 +3206,13 @@ def _pv_generation_kwh_per_yr(
|
|||
roof area" PV figure (no detailed kWp): synthesize a single PV
|
||||
array with kWp = 0.12 × PV area, South orientation, 30° pitch,
|
||||
Modest overshading.
|
||||
|
||||
Returns 0 when the array is not connected to the dwelling's own meter
|
||||
(`_pv_connected_to_dwelling_meter` — gov-API `pv_connection=1`), per
|
||||
RdSAP 10 §11.1 / SAP 10.2 Appendix M.
|
||||
"""
|
||||
if not _pv_connected_to_dwelling_meter(epc):
|
||||
return 0.0
|
||||
arrays = epc.sap_energy_source.photovoltaic_arrays
|
||||
if not arrays:
|
||||
arrays = _synthesize_pv_arrays_from_percent_roof_area(epc)
|
||||
|
|
@ -3217,7 +3259,13 @@ def _pv_monthly_generation_kwh(
|
|||
) -> tuple[float, ...]:
|
||||
"""SAP 10.2 Appendix M1 §2 (p.92) — monthly E_PV summed across all
|
||||
PV arrays. Annual sum matches `_pv_generation_kwh_per_yr` to
|
||||
float precision."""
|
||||
float precision.
|
||||
|
||||
Returns all-zero when the array is not connected to the dwelling's own
|
||||
meter (`_pv_connected_to_dwelling_meter`), so the §10a cost split and the
|
||||
CO2 / PE cascades all see no PV — mirroring the annual helper's gate."""
|
||||
if not _pv_connected_to_dwelling_meter(epc):
|
||||
return (0.0,) * 12
|
||||
arrays = epc.sap_energy_source.photovoltaic_arrays
|
||||
if not arrays:
|
||||
arrays = _synthesize_pv_arrays_from_percent_roof_area(epc)
|
||||
|
|
|
|||
|
|
@ -89,6 +89,10 @@ def test_each_option_overlay_installs_per_segment_arrays_and_ensures_export() ->
|
|||
assert overlay is not None
|
||||
assert overlay.is_dwelling_export_capable is True
|
||||
assert overlay.pv_diverter_present is True
|
||||
# A newly-installed recommended array is connected to the dwelling's own
|
||||
# meter, so it must be tagged pv_connection=2 ("connected") — the value
|
||||
# the SAP cascade credits. (1 = present-but-not-connected → zero credit.)
|
||||
assert overlay.pv_connection == 2
|
||||
arrays = overlay.photovoltaic_arrays
|
||||
assert arrays is not None and len(arrays) >= 1
|
||||
assert all(isinstance(a, PhotovoltaicArray) for a in arrays)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue