Map surveyed PasHub PV arrays into the Appendix M photovoltaic_arrays input 🟩

_pashub_pv_arrays mirrors _elmhurst_pv_arrays, reusing its format-agnostic
pitch/orientation/overshading converters (PasHub lodges "South East"
space-separated; normalise to the octant map's hyphenated keys). Wired into
the SapEnergySource construction so the calculator's Appendix M generation
path sees measured PV instead of silently zero credit (issue #1590 bug 1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-14 21:33:18 +00:00
parent 9a1422f6d7
commit 1282f81a72

View file

@ -153,6 +153,7 @@ from datatypes.epc.surveys.pashub_rdsap_site_notes import (
FloorMeasurement,
HeatingAndHotWater,
PasHubRdSapSiteNotes,
Renewables as PasHubRenewables,
RoofSpaceDetail,
Ventilation,
WaterUse,
@ -367,6 +368,7 @@ class EpcPropertyDataMapper:
wind_turbines_terrain_type=general.terrain_type,
electricity_smart_meter_present=general.electricity_smart_meter,
pv_connection=renewables.pv_connection,
photovoltaic_arrays=_pashub_pv_arrays(renewables),
photovoltaic_supply=(
PhotovoltaicSupply(
none_or_no_details=PhotovoltaicSupplyNoneOrNoDetails(
@ -6697,6 +6699,37 @@ _ELMHURST_PV_OVERSHADING_TO_RDSAP: Dict[str, int] = {
}
def _pashub_pv_arrays(
renewables: PasHubRenewables,
) -> Optional[List[PhotovoltaicArray]]:
"""Build the Appendix M cascade's input list from the PasHub Renewables
per-system PV block, mirroring `_elmhurst_pv_arrays` below and reusing its
format-agnostic converters. PasHub lodges the orientation space-separated
("South East") where the shared octant map keys hyphenated ("South-East"),
so normalise before the lookup. Returns None when no per-array detail is
lodged the PV-absent path the cascade already handles (issue #1590
bug 1: dropping this detail silently zeroed the PV credit)."""
if not renewables.pv_arrays:
return None
out: List[PhotovoltaicArray] = []
for arr in renewables.pv_arrays:
if arr.kwp is None or arr.kwp <= 0.0:
continue
out.append(
PhotovoltaicArray(
peak_power=arr.kwp,
pitch=_elmhurst_pv_pitch_code(
arr.pitch_degrees if arr.pitch_degrees is not None else 30
),
orientation=_elmhurst_orientation_int(
(arr.orientation or "").replace(" ", "-")
),
overshading=_elmhurst_pv_overshading_int(arr.overshading),
)
)
return out or None
def _elmhurst_pv_arrays(
renewables: ElmhurstRenewables,
) -> Optional[List[PhotovoltaicArray]]: