mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
78 lines
3.3 KiB
Python
78 lines
3.3 KiB
Python
"""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
|
|
# Stable attributes observed on an expired Historic EPC condition selection
|
|
# the same way (ADR-0054): RdSAP Table S1 band letter, modern main_fuel
|
|
# code, and a ±5% floor-area band. None leaves each filter inactive.
|
|
construction_age_band: Optional[str] = None
|
|
main_fuel: Optional[int] = None
|
|
total_floor_area_m2: Optional[float] = None
|
|
# The roof FORM family ("pitched" / "flat" / "dwelling_above" /
|
|
# "premises_above") — the API's roof_construction codes group by form,
|
|
# so the filter matches families, never exact codes (ADR-0054 amendment).
|
|
roof_form: Optional[str] = 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,
|
|
)
|