diff --git a/domain/sap10_ml/tests/_fixtures.py b/domain/sap10_ml/tests/_fixtures.py index f92a4995..d3fae7e2 100644 --- a/domain/sap10_ml/tests/_fixtures.py +++ b/domain/sap10_ml/tests/_fixtures.py @@ -267,6 +267,7 @@ def make_minimal_sap10_epc( sap_heating: Optional[SapHeating] = None, photovoltaic_arrays: Optional[list[PhotovoltaicArray]] = None, photovoltaic_supply_percent_roof_area: Optional[int] = None, + pv_connection: Optional[int] = None, mains_gas: bool = True, electricity_smart_meter_present: bool = False, gas_smart_meter_present: bool = False, @@ -308,6 +309,7 @@ def make_minimal_sap10_epc( sap_energy_source=SapEnergySource( mains_gas=mains_gas, meter_type="Single", + pv_connection=pv_connection, pv_battery_count=pv_battery_count, wind_turbines_count=wind_turbines_count, gas_smart_meter_present=gas_smart_meter_present, diff --git a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py index 2adeea02..b643bf0d 100644 --- a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py +++ b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py @@ -40,6 +40,7 @@ from domain.sap10_ml.tests._fixtures import ( make_floor_dimension, make_main_heating_detail, make_minimal_sap10_epc, + make_pv_array, make_sap_heating, make_window, ) @@ -8387,3 +8388,38 @@ def test_heat_pump_water_scop_not_applied_to_separate_immersion_dhw() -> None: # heat pump or a gas boiler (the HP water SCOP does not apply to it). assert hp_fuel > 0.0 assert abs(hp_fuel - boiler_fuel) <= 1e-6 + + +def test_pv_credited_only_when_connected_to_dwelling_meter_per_pv_connection() -> None: + # RdSAP 10 §11.1 / SAP 10.2 Appendix M: PV-generated electricity is + # included in a dwelling's assessment ONLY IF the array is connected to + # the dwelling's own electricity meter; an unconnected (communal / + # separately-metered) array contributes nothing to the dwelling's energy + # cost, CO2 or primary energy. The gov-API `sap_energy_source.pv_connection` + # enum encodes this: 0 = no PV, 1 = present but NOT connected, 2 = connected. + # + # Validated on the RdSAP-21.0.1 corpus (57 PV certs): every pv_connection=1 + # cert reconciles BETTER without the credit (MAE 4.48 -> 1.22, 0/5 need it), + # while pv_connection=2 certs need it (MAE 0.98 vs 10.29 without). Accredited + # Elmhurst proof: an identical dwelling rates SAP 87 with "Connected to + # Dwelling = Yes" (credit -£167) vs SAP 74 with "No" (credit £0). + array = [make_pv_array(peak_power=3.0)] + + def _gen(pv_connection: int) -> float: + epc = make_minimal_sap10_epc( + dwelling_type="Mid-terrace house", + total_floor_area_m2=70.0, + habitable_rooms_count=3, + country_code="ENG", + photovoltaic_arrays=array, + is_dwelling_export_capable=True, + pv_connection=pv_connection, + ) + return cert_to_inputs(epc).pv_generation_kwh_per_yr + + # pv_connection=2 (connected to the dwelling's meter) → PV serves the + # dwelling and is credited. + assert _gen(2) > 0.0 + # pv_connection=1 (present but NOT connected to the dwelling's meter) → + # the array contributes nothing to this dwelling's SAP. + assert _gen(1) == 0.0