Model/tests/scripts/test_expired_prediction_pairs_harness.py
Khalim Conn-Kowlessar ee5bb6c4f2 Pairs harness telemetry: filter engagement + historic-vs-new agreement 🟩
Per pair, replay the age->fuel->TFA conditioning ladder over the plain
arm's cohort to record ENGAGED vs RELAXED per filter, and record whether
each historic stable attribute still agrees with the newly lodged cert
(the direct staleness measurement). Emitted as JSONL (--telemetry) and
aggregated into two diagnosis tables appended to the report.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 09:24:46 +00:00

148 lines
5.1 KiB
Python

"""Pure pair-selection and aggregation logic of the ADR-0054 pairs harness."""
from __future__ import annotations
import dataclasses
from typing import Optional
from datatypes.epc.domain.historic_epc import HistoricEpc
from domain.epc_prediction.prediction_comparison import PredictionComparison
from scripts.expired_prediction_pairs_harness import (
PairScore,
aggregate,
format_report,
latest_pre_2012_by_uprn,
)
def _hist(uprn: str, lodgement_date: str) -> HistoricEpc:
fields = {f.name: "" for f in dataclasses.fields(HistoricEpc)}
fields["uprn"] = uprn
fields["lodgement_date"] = lodgement_date
return HistoricEpc(**fields)
def _score(
hits: dict[str, Optional[bool]], sap_residual: Optional[float] = None
) -> PairScore:
return PairScore(
comparison=PredictionComparison(
categorical_hits=hits,
floor_area_residual=-4.0,
building_parts_residual=1,
window_count_residual=-2,
total_window_area_residual=3.5,
door_count_residual=0,
),
sap_residual=sap_residual,
)
def test_pairs_keep_only_the_latest_pre_2012_cert_per_uprn():
# Arrange — one UPRN lodged twice pre-2012, once post-2012; one UPRN-less row.
records = [
_hist("100", "2008-05-01"),
_hist("100", "2010-11-30"),
_hist("100", "2013-01-01"),
_hist("", "2009-01-01"),
]
# Act
by_uprn = latest_pre_2012_by_uprn(records)
# Assert — the 2010 cert wins; the 2013 one can never seed an expired pair.
assert set(by_uprn) == {"100"}
assert by_uprn["100"].lodgement_date == "2010-11-30"
def test_aggregate_matches_the_component_accuracy_shape():
# Arrange — two pairs; wall scored twice (1 hit), roof scored once (None
# means the actual lodges no value — out of the denominator, per ADR-0030).
scores = [
_score({"wall_construction": True, "roof_construction": None}, sap_residual=6.0),
_score({"wall_construction": False, "roof_construction": True}),
]
# Act
arm = aggregate(scores)
# Assert — classification (hits, applicable), all five residual components,
# and the SAP residual list (only the scored pair contributes).
assert arm.classification["wall_construction"] == (1, 2)
assert arm.classification["roof_construction"] == (1, 1)
assert arm.residuals["floor_area_m2"] == [-4.0, -4.0]
assert arm.residuals["building_parts"] == [1.0, 1.0]
assert arm.residuals["window_count"] == [-2.0, -2.0]
assert arm.residuals["total_window_area_m2"] == [3.5, 3.5]
assert arm.residuals["door_count"] == [0.0, 0.0]
assert arm.sap_residuals == [6.0]
def test_report_prints_both_arms_side_by_side():
# Arrange
plain = aggregate([_score({"wall_construction": False}, sap_residual=-8.0)])
conditioned = aggregate([_score({"wall_construction": True}, sap_residual=2.0)])
# Act
report = format_report(plain, conditioned, pairs=1)
# Assert — hit-rates, residuals and the SAP arm all present, side by side.
assert "| wall_construction | 0/1 (0%) | 1/1 (100%) |" in report
assert "| floor_area_m2 | 4.0 | 4.0 |" in report
assert "| mean abs | 8.0 | 2.0 |" in report
assert "1 pairs" in report
def test_ladder_simulation_engages_only_with_enough_matches():
# Arrange — a 6-strong base cohort: 5 band-C (engages at k=5), then within
# the band-C survivors only 2 on fuel 26 (relaxes), and 5 within ±5% of
# 100 m² (engages on the un-shrunk cohort).
from scripts.expired_prediction_pairs_harness import simulate_conditioning_ladder
from tests.domain.epc_prediction.test_comparable_properties import _comparable
base = [
_comparable(
property_type="0",
certificate_number=f"C{i}",
construction_age_band="C",
main_fuel=26 if i < 2 else 29,
total_floor_area_m2=100.0 + i,
)
for i in range(5)
] + [
_comparable(
property_type="0",
certificate_number="G0",
construction_age_band="G",
main_fuel=26,
total_floor_area_m2=100.0,
)
]
# Act
steps = simulate_conditioning_ladder(
base, age_band="C", main_fuel=26, total_floor_area_m2=100.0
)
# Assert — age engaged (5 matches), fuel relaxed (2 < 5 within band-C
# survivors), TFA engaged (all 5 survivors within the band).
age = steps["construction_age_band"]
fuel = steps["main_fuel"]
tfa = steps["total_floor_area"]
assert age is not None and age.engaged and age.matches == 5
assert fuel is not None and not fuel.engaged and fuel.matches == 2
assert tfa is not None and tfa.engaged and tfa.matches == 5
def test_ladder_simulation_skips_unresolved_attributes():
from scripts.expired_prediction_pairs_harness import simulate_conditioning_ladder
steps = simulate_conditioning_ladder(
[], age_band=None, main_fuel=None, total_floor_area_m2=None
)
assert steps == {
"construction_age_band": None,
"main_fuel": None,
"total_floor_area": None,
}