From 56b36ce7f09e64278726e329d744ebc3422f21dd Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 14 Jul 2026 21:41:04 +0000 Subject: [PATCH] =?UTF-8?q?Resolve=20the=20PasHub=20PV=20Connection=20labe?= =?UTF-8?q?l=20to=20the=20gov-API=20credit-gate=20int=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Not connected to electricity meter" -> 1 (zero Appendix M credit), "Connected to dwellings electricity meter" -> 2; the raw string fell through the credit gate's non-int branch to True, crediting a separately-metered array (issue #1590 bug 3, fixture 499516101839 -6.9 SAP). Harness MAE 2.571 -> 2.538; ceiling ratcheted to 2.55. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../tests/test_pashub_sap_accuracy.py | 4 ++- datatypes/epc/domain/mapper.py | 28 ++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) 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]]: