diff --git a/scripts/audit/anomalies.py b/scripts/audit/anomalies.py index 022f6fe27..8c341cd3d 100644 --- a/scripts/audit/anomalies.py +++ b/scripts/audit/anomalies.py @@ -26,6 +26,11 @@ be justified against the real distribution, not guessed. Read-only: this script never writes to the DB. """ +# Every check is reached through `_REGISTRY` via the `@check` decorator, never by +# name, so pyright sees each one as an unused function. That is the design, not a +# defect — suppress it for the module rather than tagging a dozen definitions. +# pyright: reportUnusedFunction=false + from __future__ import annotations import argparse @@ -42,6 +47,11 @@ from scripts.e2e_common import build_engine, load_env # A..G, A best — index is the rank (lower = better) for band comparisons. _BANDS = "ABCDEFG" +# The smallest SAP movement that counts as a real improvement. The effective +# baseline is a rounded integer, so anything at or under half a point is within +# the rounding the published band already absorbs — invisible on a certificate. +_MIN_REAL_SAP_GAIN = 0.5 + def _band_rank(band: Optional[str]) -> Optional[int]: if band is None or band not in _BANDS: @@ -189,7 +199,34 @@ def _already_meets_goal_with_works(a: PropertyAudit) -> Optional[str]: @check("costed-plan-without-real-gain", Severity.HIGH) def _costed_plan_without_real_gain(a: PropertyAudit) -> Optional[str]: - raise NotImplementedError + """Money was spent but the modelled post-retrofit SAP barely moves off the + effective baseline — works that buy no real improvement. + + Provenance: #1652 / #1655. This closes a coverage hole rather than adding a + new opinion: `plan-score-below-baseline` only fires on a *drop* beyond 0.5, + and `zero-works-post-differs` only on £0 plans, so a **costed** plan landing + flat against the baseline was caught by neither. It is what the 112 + "no real improvement" homes in portfolio 796 looked like — priced plans whose + post-retrofit SAP sat ~0.2 *below* the effective baseline — and what the + band-boundary defect (#1654) produced at scale: 630 homes each sold ~1 SAP + point of works toward a band they had already reached. + + The 0.5 threshold is the same "within rounding is not a real move" line the + published banding uses: the effective baseline is a rounded integer, so a + sub-half-point shift is not a change anyone can see on a certificate. Spend + that buys less than that is not a retrofit.""" + if a.effective_sap is None or a.post_sap is None: + return None + cost = a.cost_of_works or 0.0 + if cost <= 0.0: + return None + gain = a.post_sap - a.effective_sap + if gain > _MIN_REAL_SAP_GAIN: + return None + return ( + f"£{cost:.0f} of works for {gain:+.1f} SAP " + f"(effective {a.effective_sap:.1f} → post {a.post_sap:.1f})" + ) @check("plan-stops-short-of-goal", Severity.HIGH) @@ -555,11 +592,14 @@ def _load( rollups = { m["property_id"]: (m["solar_sap"], m["solar_bill"], m["n_measures"]) for m in ( - row._mapping for row in conn.execute(_ROLLUP_QUERY, params) + # `Row._mapping` is public API in SQLAlchemy 2.0; only the + # stub marks it protected. + row._mapping # pyright: ignore[reportPrivateUsage] + for row in conn.execute(_ROLLUP_QUERY, params) ) } for r in conn.execute(_QUERY, params): - m = r._mapping + m = r._mapping # pyright: ignore[reportPrivateUsage] solar_sap, solar_bill, n_measures = rollups.get(m["id"], (None, None, 0)) out.append( PropertyAudit(