"""Behaviour of the Component Accuracy leave-one-out scorer (ADR-0030): given loaded postcode cohorts, hold out each SAP 10.2 target, predict it from its all-vintage neighbours, and aggregate the per-component hits + residuals. Pure (no IO, no calculator) — corpus loading is the caller's job. """ from datetime import date from typing import Optional, Union from datatypes.epc.domain.epc_property_data import ( EpcPropertyData, MainHeatingDetail, SapBuildingPart, SapEnergySource, SapFloorDimension, SapHeating, ) from domain.epc_prediction.comparable_properties import ComparableProperty from domain.epc_prediction.validation import evaluate_component_accuracy def _comparable( *, certificate_number: str, address: str, sap_version: float, wall_construction: Union[int, str] = 1, registration_date: Optional[date] = None, ) -> ComparableProperty: """A ComparableProperty carrying a fully-populated opaque EpcPropertyData — every field the predictor + comparison read (the partial-instance idiom).""" epc: EpcPropertyData = object.__new__(EpcPropertyData) epc.sap_version = sap_version epc.postcode = "LS6 1AA" epc.property_type = "2" epc.built_form = "4" epc.total_floor_area_m2 = 80.0 epc.door_count = 2 epc.solar_water_heating = False epc.has_hot_water_cylinder = True part: SapBuildingPart = object.__new__(SapBuildingPart) part.wall_construction = wall_construction part.wall_insulation_type = 1 part.construction_age_band = "K" part.roof_construction = 1 part.roof_insulation_thickness = 100 part.sap_room_in_roof = None floor_dim: SapFloorDimension = object.__new__(SapFloorDimension) floor_dim.floor_construction = 1 floor_dim.floor_insulation = 1 part.sap_floor_dimensions = [floor_dim] epc.sap_building_parts = [part] epc.sap_windows = [] detail: MainHeatingDetail = object.__new__(MainHeatingDetail) detail.main_fuel_type = 20 detail.main_heating_category = 2 detail.main_heating_control = 2100 heating: SapHeating = object.__new__(SapHeating) heating.main_heating_details = [detail] heating.water_heating_fuel = 20 heating.water_heating_code = 901 heating.cylinder_insulation_type = 1 heating.secondary_heating_type = None epc.sap_heating = heating energy: SapEnergySource = object.__new__(SapEnergySource) energy.photovoltaic_supply = None energy.photovoltaic_arrays = None epc.sap_energy_source = energy return ComparableProperty( epc=epc, certificate_number=certificate_number, address=address, registration_date=registration_date, ) def test_scores_only_sap_10_2_targets() -> None: # Arrange — a cohort of two distinct addresses: one SAP 10.2, one older # (SAP 9.94). Only the 10.2 cert is a valid held-out target; the older one # is kept as source evidence (its components are still valid). cohort = [ _comparable( certificate_number="A", address="1 THE ROW", sap_version=10.2 ), _comparable( certificate_number="B", address="2 THE ROW", sap_version=9.94 ), ] # Act accuracy = evaluate_component_accuracy([cohort]) # Assert — exactly one target scored (the 10.2 cert), predicted from the # older neighbour; the older cert was never held out. assert accuracy.targets == 1 assert accuracy.rate("wall_construction") == 1.0 def test_aggregates_a_wall_classification_miss() -> None: # Arrange — the 10.2 target is solid brick (2); its only neighbour (the # source) is cavity (1), so the predicted mode misses the wall. cohort = [ _comparable( certificate_number="A", address="1 THE ROW", sap_version=10.2, wall_construction=2, ), _comparable( certificate_number="B", address="2 THE ROW", sap_version=10.2, wall_construction=1, ), ] # Act accuracy = evaluate_component_accuracy([cohort]) # Assert — both are 10.2 targets, and each is predicted from the other (the # opposite wall), so wall_construction is missed both times. assert accuracy.targets == 2 assert accuracy.rate("wall_construction") == 0.0