mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
Pure compare_prediction (TDD): wall-construction classification hit + signed residuals on floor area, window count, total window area, building-parts count. Plus validate_epc_prediction.py (IO plumbing): drops each cert from its postcode cohort, predicts from the rest on guaranteed inputs only, aggregates the metrics, and reports SAP three ways (pred-calc vs lodged / vs calc-on-actual / vs the neighbour-mean baseline). Smoke run: wall 90.9%, floor-area mean|·| 42.6 m2 (a real signal — template-copied floor area is noisy), SAP pred-calc edges baseline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
"""Behaviour of the per-Property prediction comparison (ADR-0029): given a
|
||
predicted EpcPropertyData and the actual one, report the accuracy signals the
|
||
validation harness aggregates — classification matches on the key categoricals
|
||
and residuals on the geometry. Pure; SAP residual is computed in the runner
|
||
(it needs the calculator + lodged SAP).
|
||
"""
|
||
|
||
from typing import Optional
|
||
|
||
from datatypes.epc.domain.epc_property_data import (
|
||
EpcPropertyData,
|
||
SapBuildingPart,
|
||
SapWindow,
|
||
)
|
||
from domain.epc_prediction.prediction_comparison import compare_prediction
|
||
|
||
|
||
def _epc(
|
||
*,
|
||
wall_construction: int = 1,
|
||
floor_area: float = 80.0,
|
||
building_parts: int = 1,
|
||
windows: Optional[list[tuple[float, float]]] = None,
|
||
) -> EpcPropertyData:
|
||
epc: EpcPropertyData = object.__new__(EpcPropertyData)
|
||
epc.total_floor_area_m2 = floor_area
|
||
parts: list[SapBuildingPart] = []
|
||
for _ in range(building_parts):
|
||
part: SapBuildingPart = object.__new__(SapBuildingPart)
|
||
part.wall_construction = wall_construction
|
||
parts.append(part)
|
||
epc.sap_building_parts = parts
|
||
sap_windows: list[SapWindow] = []
|
||
for width, height in windows or []:
|
||
w: SapWindow = object.__new__(SapWindow)
|
||
w.window_width = width
|
||
w.window_height = height
|
||
sap_windows.append(w)
|
||
epc.sap_windows = sap_windows
|
||
return epc
|
||
|
||
|
||
def test_flags_a_correct_main_wall_construction_classification() -> None:
|
||
# Arrange — predicted and actual agree on cavity (1).
|
||
predicted = _epc(wall_construction=1)
|
||
actual = _epc(wall_construction=1)
|
||
|
||
# Act
|
||
comparison = compare_prediction(predicted, actual)
|
||
|
||
# Assert
|
||
assert comparison.wall_construction_correct is True
|
||
|
||
|
||
def test_flags_an_incorrect_main_wall_construction_classification() -> None:
|
||
# Arrange — predicted cavity (1), actual solid brick (2).
|
||
predicted = _epc(wall_construction=1)
|
||
actual = _epc(wall_construction=2)
|
||
|
||
# Act
|
||
comparison = compare_prediction(predicted, actual)
|
||
|
||
# Assert
|
||
assert comparison.wall_construction_correct is False
|
||
|
||
|
||
def test_reports_the_floor_area_residual_as_predicted_minus_actual() -> None:
|
||
# Arrange — predicted 90 m², actual 100 m² (a 10 m² under-prediction).
|
||
predicted = _epc(floor_area=90.0)
|
||
actual = _epc(floor_area=100.0)
|
||
|
||
# Act
|
||
comparison = compare_prediction(predicted, actual)
|
||
|
||
# Assert — signed residual, predicted − actual.
|
||
assert abs(comparison.floor_area_residual - (-10.0)) <= 1e-9
|
||
|
||
|
||
def test_reports_the_building_parts_count_residual() -> None:
|
||
# Arrange — predicted a single part; the actual has a main + an extension.
|
||
predicted = _epc(building_parts=1)
|
||
actual = _epc(building_parts=2)
|
||
|
||
# Act
|
||
comparison = compare_prediction(predicted, actual)
|
||
|
||
# Assert — predicted − actual.
|
||
assert comparison.building_parts_residual == -1
|
||
|
||
|
||
def test_reports_window_count_and_total_area_residuals() -> None:
|
||
# Arrange — predicted 2 windows (3 m² total); actual 1 window (1 m²).
|
||
predicted = _epc(windows=[(1.0, 1.0), (2.0, 1.0)])
|
||
actual = _epc(windows=[(1.0, 1.0)])
|
||
|
||
# Act
|
||
comparison = compare_prediction(predicted, actual)
|
||
|
||
# Assert
|
||
assert comparison.window_count_residual == 1
|
||
assert abs(comparison.total_window_area_residual - 2.0) <= 1e-9
|