Model/tests/scripts/test_neighbour_divergence.py
Jun-te Kim 003defcf55 Add find-weird-recommendations skill + its two detectors
A focused sibling to audit-ara-portfolio: that skill audits baselines/plans/SAP;
this one audits the *recommendations themselves* — why a measure was or wasn't
offered. Motivated by the portfolio-814 review (Khalim's HHRSH-on-community-
heating, missing-HHRSH, missing-secondary-heating-removal, and a neighbour split).

Adds:
- .claude/skills/find-weird-recommendations/SKILL.md — scan -> neighbour scan ->
  live re-model deep-dive -> root-cause -> codify, with a seeded known-bug
  catalogue and the query-safety rules inherited from audit-ara-portfolio.
- scripts/audit/anomalies.py: new `plan-stops-short-of-goal` HIGH check — the
  default plan ends below the goal band on an unlimited-budget scenario (the
  deterministic worklist for "why didn't this get recommended X"). Adds
  scenario_budget to the bundle/query so budget-capped scenarios are excluded.
- scripts/audit/neighbour_divergence.py: groups a portfolio by (postcode,
  property_type, built_form) and flags effective-SAP outliers vs the cohort
  median. Never touches the 26m-row recommendation table, so it is safe
  portfolio-wide.
- Tests for both (12 passing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:06:03 +00:00

76 lines
2.2 KiB
Python

from typing import Optional
from scripts.audit.neighbour_divergence import Neighbour, find_divergences
def _n(
property_id: int,
*,
effective_sap: Optional[float],
postcode: str = "AB1 2CD",
property_type: str = "Flat",
built_form: str = "Mid-Terrace",
floor_area_m2: float = 60.0,
) -> Neighbour:
return Neighbour(
property_id=property_id,
uprn=None,
postcode=postcode,
property_type=property_type,
built_form=built_form,
floor_area_m2=floor_area_m2,
effective_sap=effective_sap,
effective_band=None,
)
class TestFindDivergences:
def test_flags_the_outlier_neighbour(self) -> None:
# Arrange — three identical-cohort flats, one sits a clear band below.
cohort = [
_n(1, effective_sap=70.0),
_n(2, effective_sap=72.0),
_n(3, effective_sap=50.0), # Δ-20 vs median 70
]
# Act
result = find_divergences(cohort, min_gap=12.0)
# Assert — only the outlier is flagged.
assert [d.property_id for d in result] == [3]
def test_silent_when_cohort_agrees(self) -> None:
# Arrange — neighbours within a few SAP points of each other.
cohort = [_n(1, effective_sap=70.0), _n(2, effective_sap=68.0)]
# Act
result = find_divergences(cohort, min_gap=12.0)
# Assert
assert result == []
def test_singletons_never_flagged(self) -> None:
# Arrange — one flat here, one house there: neither has a peer to diverge from.
lonely = [
_n(1, effective_sap=30.0, property_type="Flat"),
_n(2, effective_sap=90.0, property_type="House"),
]
# Act
result = find_divergences(lonely, min_gap=12.0)
# Assert
assert result == []
def test_different_postcode_is_a_different_cohort(self) -> None:
# Arrange — same type/form but different postcodes: not neighbours.
split = [
_n(1, effective_sap=40.0, postcode="AB1 2CD"),
_n(2, effective_sap=80.0, postcode="ZZ9 9ZZ"),
]
# Act
result = find_divergences(split, min_gap=12.0)
# Assert
assert result == []