mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
The neighbour SAP-divergence cohort was (postcode, property_type, built_form), which mixed electric- and gas-heated dwellings. Electricity scores materially lower in SAP than mains gas for identical fabric, so a mixed-fuel postcode produced cross-fuel false outliers — an electric dwelling flagged only for being electric among gas neighbours. Add the main-heating fuel class to the cohort key (electricity variants — 29/30 + off-peak 31-40 — collapse to one class; every other code is its own bucket). Now every flagged divergence is a same-fuel comparison worth investigating. On portfolio 814 this refined 20 raw divergences to 17 same-fuel ones while keeping the genuine within-fuel outliers (e.g. the all-electric WC2B 4AW flats at SAP 26/38 vs a cohort median of 67). Reaches the fuel via epc_main_heating_detail through the indexed property_id — still never touches the recommendation table. Surfaced by the portfolio-814 recommendation audit (Work item B, #1388). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
109 lines
3.6 KiB
Python
109 lines
3.6 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,
|
|
main_fuel_type: Optional[int] = 26, # default: mains gas
|
|
) -> 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,
|
|
main_fuel_type=main_fuel_type,
|
|
)
|
|
|
|
|
|
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 == []
|
|
|
|
def test_electric_and_gas_are_separate_cohorts(self) -> None:
|
|
# Arrange — same postcode/type/form, but an electric dwelling (fuel 29)
|
|
# scores a band below its gas neighbours (fuel 26). Electricity is
|
|
# inherently lower-SAP, so this is expected stock, not a divergence.
|
|
mixed = [
|
|
_n(1, effective_sap=70.0, main_fuel_type=26), # gas
|
|
_n(2, effective_sap=72.0, main_fuel_type=26), # gas
|
|
_n(3, effective_sap=52.0, main_fuel_type=29), # electric — its own cohort
|
|
]
|
|
|
|
# Act
|
|
result = find_divergences(mixed, min_gap=12.0)
|
|
|
|
# Assert — the electric singleton has no same-fuel peers, so nothing fires.
|
|
assert result == []
|
|
|
|
def test_electric_outlier_still_flagged_within_its_own_fuel_cohort(self) -> None:
|
|
# Arrange — three electric dwellings; one genuinely diverges. Keying on
|
|
# fuel must not blind the detector to real within-fuel outliers.
|
|
electric = [
|
|
_n(1, effective_sap=55.0, main_fuel_type=29),
|
|
_n(2, effective_sap=57.0, main_fuel_type=30), # 30 also electric-class
|
|
_n(3, effective_sap=30.0, main_fuel_type=29), # Δ-25 vs median
|
|
]
|
|
|
|
# Act
|
|
result = find_divergences(electric, min_gap=12.0)
|
|
|
|
# Assert
|
|
assert [d.property_id for d in result] == [3]
|