From afe1801273dc5d382e50160e0de3460ccb05ed82 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 30 Jun 2026 15:27:19 +0000 Subject: [PATCH] =?UTF-8?q?implausible-lodged-score=20check=20fires=20for?= =?UTF-8?q?=20bad=20upstream=20EPC=20data=20(lodged=20<=2013,=20gap=20>=3D?= =?UTF-8?q?=2015)=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- scripts/audit/anomalies.py | 24 +++++++++++++++++++++++- tests/scripts/test_audit_anomalies.py | 20 ++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/scripts/audit/anomalies.py b/scripts/audit/anomalies.py index 762fc30a5..b48b74015 100644 --- a/scripts/audit/anomalies.py +++ b/scripts/audit/anomalies.py @@ -191,7 +191,29 @@ def _effective_lodged_divergence(a: PropertyAudit) -> Optional[str]: @check("implausible-lodged-score", Severity.LOW) def _implausible_lodged_score(a: PropertyAudit) -> Optional[str]: - raise NotImplementedError + """Lodged SAP score is below 13 with a large effective-vs-lodged gap — the + lodged figure is implausible upstream EPC register data, not a modelling bug. + + Floor of 13 is justified against the portfolio-796 distribution: every lodged + score below 13 either matches a known Class C exemplar (SAP 1: pids 727752, + 727008, 731205) or shares the same bad-data fingerprint — implausibly low + lodged paired with a reasonable effective in band D/E (pids 710449 SAP 5, + 722562 SAP 7, 725377 SAP 9, 727457 SAP 10, 723364 SAP 12). SAP 13 and above + are left in scope as ambiguous. + + The gap guard (≥15) mirrors ``effective-lodged-divergence`` so the two checks + partition the same population without overlap.""" + if a.lodged_sap is None or a.effective_sap is None: + return None + if a.lodged_sap >= 13: + return None + gap = a.effective_sap - a.lodged_sap + if abs(gap) < 15: + return None + return ( + f"lodged SAP {a.lodged_sap:.0f} is implausible (< 13) — upstream EPC data," + f" not a modelling bug (effective {a.effective_sap:.0f}, Δ{gap:+.0f})" + ) @check("impossible-sap-over-100", Severity.HIGH) diff --git a/tests/scripts/test_audit_anomalies.py b/tests/scripts/test_audit_anomalies.py index 69cd25080..280b7c3a9 100644 --- a/tests/scripts/test_audit_anomalies.py +++ b/tests/scripts/test_audit_anomalies.py @@ -72,3 +72,23 @@ class TestImplausibleLodgedScore: # Assert assert result is not None assert "1" in result + + def test_silent_when_gap_below_threshold(self) -> None: + # Arrange — lodged=9, effective=5 (gap -4, below ±15): both low, not a divergence anomaly + audit = _make_audit(lodged_sap=9.0, effective_sap=5.0) + + # Act + result = _implausible_lodged_score(audit) + + # Assert + assert result is None + + def test_silent_when_lodged_sap_at_or_above_floor(self) -> None: + # Arrange — lodged=13 is on the floor boundary; effective=70 gives large gap + audit = _make_audit(lodged_sap=13.0, effective_sap=70.0) + + # Act + result = _implausible_lodged_score(audit) + + # Assert + assert result is None