"""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." ), )