mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
144 lines
5.2 KiB
Python
144 lines
5.2 KiB
Python
"""Specific, debuggable failures for the modelling_e2e prediction path.
|
|
|
|
When an EPC-less Property cannot be modelled the per-property failure handler
|
|
records ``str(exc)`` in the child SubTask's outputs (see ``handler.run_subtask``).
|
|
A single generic "not predictable" string conflated three unrelated causes —
|
|
unresolved property_type, an empty same-type cohort, and a degenerate
|
|
prediction — so the output could not tell an operator *which* one fired or which
|
|
data to fix. Each cause is its own exception carrying the Property's identity
|
|
plus the cause-specific context needed to debug it from the output alone.
|
|
|
|
All subclass ``ValueError`` so the existing ``except Exception`` per-property
|
|
boundary (and any ``except ValueError`` callers) keep catching them.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
|
|
class PropertyNotModellableError(ValueError):
|
|
"""Base: an EPC-less Property could not be predicted. Subclasses name the
|
|
specific cause. Carries the Property's identity so the failed SubTask output
|
|
identifies exactly which Property (and portfolio) to investigate."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
property_id: int,
|
|
uprn: int,
|
|
postcode: str,
|
|
portfolio_id: int,
|
|
cause: str,
|
|
) -> None:
|
|
self.property_id = property_id
|
|
self.uprn = uprn
|
|
self.postcode = postcode
|
|
self.portfolio_id = portfolio_id
|
|
self.cause = cause
|
|
super().__init__(
|
|
f"cannot model property_id={property_id} uprn={uprn} "
|
|
f"postcode={postcode!r} portfolio_id={portfolio_id}: {cause}"
|
|
)
|
|
|
|
|
|
class UnresolvedPropertyTypeError(PropertyNotModellableError):
|
|
"""No lodged EPC and the Property's ``property_type`` could not be resolved,
|
|
so the hard same-type cohort filter cannot fire. Almost always a missing or
|
|
contradictory Landlord Override (e.g. a House/Bungalow lodged with an
|
|
"another dwelling above" roof is skipped by the override build), or a
|
|
``property_type`` value with no gov-EPC code."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
property_id: int,
|
|
uprn: int,
|
|
postcode: str,
|
|
portfolio_id: int,
|
|
property_type: Optional[str],
|
|
built_form: Optional[str],
|
|
) -> None:
|
|
self.property_type = property_type
|
|
self.built_form = built_form
|
|
super().__init__(
|
|
property_id=property_id,
|
|
uprn=uprn,
|
|
postcode=postcode,
|
|
portfolio_id=portfolio_id,
|
|
cause=(
|
|
"no lodged EPC and property_type could not be resolved "
|
|
f"(property_type={property_type!r}, built_form={built_form!r}), "
|
|
"so no same-type cohort can be selected. Usually a missing or "
|
|
"contradictory Landlord Override (e.g. a House/Bungalow with an "
|
|
"'another dwelling above' roof is skipped by the override build). "
|
|
"Resolve this Property's property_type override to model it."
|
|
),
|
|
)
|
|
|
|
|
|
class NoSameTypeComparablesError(PropertyNotModellableError):
|
|
"""``property_type`` resolved, but no same-type comparable was found in the
|
|
Property's own postcode or — after broadening — the nearby-postcode cohort,
|
|
so it cannot be sized from a mixed-type cohort (ADR-0031)."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
property_id: int,
|
|
uprn: int,
|
|
postcode: str,
|
|
portfolio_id: int,
|
|
property_type: str,
|
|
broadened: bool,
|
|
) -> None:
|
|
self.property_type = property_type
|
|
self.broadened = broadened
|
|
scope = (
|
|
"its own postcode or the broadened nearby-postcode cohort"
|
|
if broadened
|
|
else "its own postcode"
|
|
)
|
|
super().__init__(
|
|
property_id=property_id,
|
|
uprn=uprn,
|
|
postcode=postcode,
|
|
portfolio_id=portfolio_id,
|
|
cause=(
|
|
f"no lodged EPC; property_type={property_type!r} resolved but no "
|
|
f"same-type comparable was found in {scope}. Cannot size the "
|
|
"Property from a mixed-type cohort."
|
|
),
|
|
)
|
|
|
|
|
|
class DegeneratePredictionError(PropertyNotModellableError):
|
|
"""A same-type cohort was found, but the synthesised EPC carried no MAIN
|
|
building part — every comparable seeding the structure was lodged with a
|
|
null/unrecognised part identifier — so the SAP calculation has no main
|
|
dwelling to anchor on."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
property_id: int,
|
|
uprn: int,
|
|
postcode: str,
|
|
portfolio_id: int,
|
|
property_type: str,
|
|
cohort_size: int,
|
|
) -> None:
|
|
self.property_type = property_type
|
|
self.cohort_size = cohort_size
|
|
super().__init__(
|
|
property_id=property_id,
|
|
uprn=uprn,
|
|
postcode=postcode,
|
|
portfolio_id=portfolio_id,
|
|
cause=(
|
|
f"no lodged EPC; predicted from a {cohort_size}-member "
|
|
f"property_type={property_type!r} cohort, but the synthesised EPC "
|
|
"had no MAIN building part (the structural template was lodged "
|
|
"with a null part identifier). Cannot anchor the SAP calculation."
|
|
),
|
|
)
|