diff --git a/backend/documents_parser/tests/test_pashub_sap_accuracy.py b/backend/documents_parser/tests/test_pashub_sap_accuracy.py index b22c44b31..df8dd1d91 100644 --- a/backend/documents_parser/tests/test_pashub_sap_accuracy.py +++ b/backend/documents_parser/tests/test_pashub_sap_accuracy.py @@ -87,7 +87,9 @@ _MIN_WITHIN_HALF: float = 0.12 # Ratcheted 2.64 -> 2.58 by #1590 bug 1 (surveyed PV arrays now reach the # Appendix M input): observed MAE 2.571 — 507639151843's 13.5-SAP zero-PV-credit # under-rate closing. -_MAX_SAP_MAE: float = 2.58 +# Ratcheted 2.58 -> 2.55 by #1590 bug 3 (PV Connection label -> gov-API int: +# "Not connected" no longer silently credited): observed MAE 2.538. +_MAX_SAP_MAE: float = 2.55 _KNOWN_GAP_REASON = ( "pashub `from_site_notes` mapper does not int-code a main-heating " diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 638042178..abc3e31e2 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -367,7 +367,7 @@ class EpcPropertyDataMapper: is_dwelling_export_capable=general.dwelling_export_capable, wind_turbines_terrain_type=general.terrain_type, electricity_smart_meter_present=general.electricity_smart_meter, - pv_connection=renewables.pv_connection, + pv_connection=_pashub_pv_connection_code(renewables.pv_connection), photovoltaic_arrays=_pashub_pv_arrays(renewables), photovoltaic_supply=( PhotovoltaicSupply( @@ -6699,6 +6699,32 @@ _ELMHURST_PV_OVERSHADING_TO_RDSAP: Dict[str, int] = { } +# PasHub surveyed "PV Connection" label → the gov-API int enum the calculator's +# Appendix M credit gate keys off (`_pv_connected_to_dwelling_meter`): 1 = PV +# present but NOT connected to the dwelling's meter (zero credit — communal / +# separately-metered), 2 = connected (credited). The gate treats any non-int as +# "connected", so an unresolved label silently credits PV the dwelling doesn't +# get to count (issue #1590 bug 3). +_PASHUB_PV_CONNECTION_TO_GOV_API: Dict[str, int] = { + "Not connected to electricity meter": 1, + "Connected to dwellings electricity meter": 2, +} + + +def _pashub_pv_connection_code(connection_label: Optional[str]) -> Optional[int]: + """Resolve a PasHub surveyed PV Connection label to the gov-API int enum at + the mapper boundary (ADR-0015). Absent stays None (no PV lodged); a + non-empty label the lookup does not cover strict-raises + `UnmappedPasHubLabel` so the gap is fixed here, never silently credited + downstream.""" + if not connection_label: + return None + code = _PASHUB_PV_CONNECTION_TO_GOV_API.get(connection_label) + if code is None: + raise UnmappedPasHubLabel("PV connection", connection_label) + return code + + def _pashub_pv_arrays( renewables: PasHubRenewables, ) -> Optional[List[PhotovoltaicArray]]: