From 43938126384aaf71811abdaf4f5321c40590bdf3 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 29 Jun 2026 21:51:09 +0000 Subject: [PATCH] =?UTF-8?q?Carry=20full-SAP=20lodged=20PV=20arrays=20throu?= =?UTF-8?q?gh=20to=20the=20domain=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc/domain/tests/test_from_sap_schema.py | 18 ++++++++++++++++++ datatypes/epc/schema/sap_schema_17_1.py | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/datatypes/epc/domain/tests/test_from_sap_schema.py b/datatypes/epc/domain/tests/test_from_sap_schema.py index de23ded0..574a1a61 100644 --- a/datatypes/epc/domain/tests/test_from_sap_schema.py +++ b/datatypes/epc/domain/tests/test_from_sap_schema.py @@ -137,6 +137,24 @@ class TestFromSapSchema17_1RebaselineFields: assert result.assessment_type == "SAP" +class TestFullSapPhotovoltaics: + """Full-SAP certs lodge measured PV under `sap_energy_source.pv_arrays`; + the mapper must carry it to the domain `photovoltaic_arrays` so the + Appendix-M generation credit isn't silently dropped.""" + + def test_maps_the_lodged_pv_array(self) -> None: + # sap_17_1_house.json lodges one 1.62 kWp array (pitch 3, orientation 6, + # overshading 1). + schema = from_dict(SapSchema17_1, load("sap_17_1_house.json")) + + result = EpcPropertyDataMapper.from_sap_schema_17_1(schema) + + arrays = result.sap_energy_source.photovoltaic_arrays + assert arrays is not None + assert len(arrays) == 1 + assert arrays[0].peak_power == 1.62 + + class TestFullSapElectricityTariffTranslation: """The full-SAP `energy_tariff` code space differs from the RdSAP `meter_type` one the calculator reads, so the mapper must translate it.""" diff --git a/datatypes/epc/schema/sap_schema_17_1.py b/datatypes/epc/schema/sap_schema_17_1.py index 5cad983c..e4f41d9e 100644 --- a/datatypes/epc/schema/sap_schema_17_1.py +++ b/datatypes/epc/schema/sap_schema_17_1.py @@ -107,6 +107,20 @@ class SapVentilation: mechanical_vent_duct_type: Optional[int] = None +@dataclass +class SapPvArray: + """One measured photovoltaic array lodged on a full-SAP cert (under + `sap_energy_source.pv_arrays`): peak power (kWp), pitch, SAP octant + orientation (1-8), overshading code, and the connection type. Mirrors the + domain `PhotovoltaicArray` the calculator's Appendix M generation uses.""" + + peak_power: Optional[float] = None + pitch: Optional[int] = None + orientation: Optional[int] = None + overshading: Optional[int] = None + pv_connection: Optional[int] = None + + @dataclass class SapEnergySource: """Electricity tariff, on-site generation and lighting. Lighting outlet @@ -118,6 +132,7 @@ class SapEnergySource: wind_turbine_terrain_type: Optional[int] = None fixed_lighting_outlets_count: Optional[int] = None low_energy_fixed_lighting_outlets_count: Optional[int] = None + pv_arrays: Optional[List[SapPvArray]] = None @dataclass