"""Assemble an EPC-less Property's PredictionTarget, with the eligibility gate (ADR-0031 slice-5d). A `PredictionTarget` needs the target's own known inputs: its postcode (to find the cohort), coordinates (to distance-weight it), and the Landlord-Override attributes that condition selection — `property_type` (the HARD cohort filter), plus optional `built_form` / `wall_construction`. `property_type` is required: a Property whose type is unknown is gated out (never sized from a mixed-type cohort), so the builder returns None and the caller skips prediction. """ from __future__ import annotations from dataclasses import dataclass from typing import Optional, Union from domain.geospatial.coordinates import Coordinates from domain.property.property import PropertyIdentity @dataclass(frozen=True) class PredictionTarget: """The known inputs for the Property whose EPC we are predicting — the fields guaranteed at ingestion (plus any Landlord Overrides, added as they're used). `built_form` is often but not always known. """ postcode: str property_type: str built_form: Optional[str] = None # A known Landlord Override (e.g. solid brick) conditions cohort selection — # matching comparables are emphasised while enough remain (ADR-0029). wall_construction: Optional[Union[int, str]] = None # The target Property's own coordinates (resolved from its UPRN), against # which neighbours are distance-weighted. None disables geo-weighting. coordinates: Optional[Coordinates] = None @dataclass(frozen=True) class PredictionTargetAttributes: """The target Property's own attributes resolved from Landlord Overrides, needed to find and condition its cohort. `property_type` is the code-space value the cohort EPCs carry (e.g. "2"); None means it could not be resolved, which gates the Property out of prediction.""" property_type: Optional[str] built_form: Optional[str] = None wall_construction: Optional[Union[int, str]] = None def build_prediction_target( identity: PropertyIdentity, coordinates: Optional[Coordinates], attributes: PredictionTargetAttributes, ) -> Optional[PredictionTarget]: """The PredictionTarget for an EPC-less Property, or None when ineligible — `property_type` is the hard cohort filter, so a Property whose type is unknown is gated out of prediction (ADR-0031) rather than sized from a mixed-type cohort.""" if attributes.property_type is None: return None return PredictionTarget( postcode=identity.postcode, property_type=attributes.property_type, built_form=attributes.built_form, wall_construction=attributes.wall_construction, coordinates=coordinates, )