diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 20b66638..ec27d6fc 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -869,6 +869,7 @@ class EpcPropertyDataMapper: meter_type=_sap_17_1_meter_type( schema.sap_energy_source.electricity_tariff ), + photovoltaic_arrays=_sap_17_1_pv_arrays(schema), pv_battery_count=0, wind_turbines_count=schema.sap_energy_source.wind_turbines_count or 0, gas_smart_meter_present=False, @@ -2934,6 +2935,31 @@ def _sap_dwelling_on_mains_gas(schema: SapSchema17_1) -> bool: ) +def _sap_17_1_pv_arrays( + schema: SapSchema17_1, +) -> Optional[List[PhotovoltaicArray]]: + """Map a full-SAP cert's lodged PV (`sap_energy_source.pv_arrays`) to the + domain `PhotovoltaicArray` list the calculator's Appendix-M generation + credit reads. Without this the PV is dropped and an all-electric dwelling + that the array lifts to A/B is mis-modelled down a band or more. An array + with no peak power generates nothing, so it's skipped; orientation honours + the ND/NA sentinel (None ⇒ zero-generation array).""" + arrays = schema.sap_energy_source.pv_arrays + if not arrays: + return None + mapped = [ + PhotovoltaicArray( + peak_power=array.peak_power, + pitch=array.pitch if array.pitch is not None else 0, + overshading=array.overshading if array.overshading is not None else 0, + orientation=_pv_orientation(array.orientation), + ) + for array in arrays + if array.peak_power is not None + ] + return mapped or None + + def _sap_17_1_meter_type(electricity_tariff: Optional[int]) -> str: """Translate a full-SAP ``energy_tariff`` code into the RdSAP ``meter_type`` value the calculator's Table 12a tariff resolver consumes.