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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-14 21:31:08 +00:00
parent bc58cf4930
commit 9a1422f6d7
2 changed files with 63 additions and 0 deletions

View file

@ -74,6 +74,54 @@ class TestRoofConstructionTypeCoding:
assert extension_part.roof_construction_type == "Flat"
class TestPhotovoltaicArrayMapping:
"""A surveyed per-system PV block ("PV 1: kWp value: 2.43 kWp", pitch 30°,
orientation "South East", overshading "None or very little") must reach
`sap_energy_source.photovoltaic_arrays` the ONLY field the calculator's
Appendix M generation path reads. Dropping it silently zeroes the PV credit
(issue #1590 bug 1; fixture 507639151843 under-rates by 13.6 SAP).
"""
def test_surveyed_pv_array_reaches_photovoltaic_arrays(self) -> None:
# Arrange — the survey's PV block: 2.43 kWp, 30°, SE, no overshading.
data = load("pashub_rdsap_site_notes_example1.json")
data["renewables"]["photovoltaic_array"] = True
data["renewables"]["pv_arrays"] = [
{
"kwp": 2.43,
"pitch_degrees": 30,
"orientation": "South East",
"overshading": "None or very little",
}
]
survey = from_dict(PasHubRdSapSiteNotes, data)
# Act
result = EpcPropertyDataMapper.from_site_notes(survey)
# Assert — SAP codes: pitch 30° → 2, "South East" → octant 4,
# "None or very little" → 1 (ZPV=1.0).
arrays = result.sap_energy_source.photovoltaic_arrays
assert arrays is not None
assert len(arrays) == 1
assert arrays[0].peak_power == 2.43
assert arrays[0].pitch == 2
assert arrays[0].orientation == 4
assert arrays[0].overshading == 1
def test_no_pv_block_keeps_arrays_absent(self) -> None:
# Arrange — the fixture as lodged (no PV).
survey = from_dict(
PasHubRdSapSiteNotes, load("pashub_rdsap_site_notes_example1.json")
)
# Act
result = EpcPropertyDataMapper.from_site_notes(survey)
# Assert
assert result.sap_energy_source.photovoltaic_arrays is None
class TestRoofInsulationNoneCoding:
"""A surveyed roof-space "Insulation At: None" is the assessor explicitly
recording ZERO loft insulation `from_site_notes` must lodge an explicit

View file

@ -222,6 +222,19 @@ class Conservatories:
has_conservatory: bool
@dataclass
class PvArrayDetail:
"""One surveyed PV array from the Renewables section's per-system block
("PV {n}: kWp value / Photovoltaic Pitch / Orientation / Overshading").
Raw survey labels for orientation/overshading coding to SAP ints happens
at the mapper boundary (ADR-0015)."""
kwp: Optional[float] = None
pitch_degrees: Optional[int] = None
orientation: Optional[str] = None
overshading: Optional[str] = None
@dataclass
class Renewables:
wind_turbines: bool
@ -231,6 +244,8 @@ class Renewables:
hydro: bool
pv_connection: Optional[str] = None
percent_roof_covered_pv: Optional[int] = None
pv_diverter_present: Optional[bool] = None
pv_arrays: Optional[List[PvArrayDetail]] = None
@dataclass