"""Behaviour of ``_predict_epc``'s failure modes: an EPC-less Property that cannot be predicted must fail loudly with a *specific*, debuggable exception (carrying the Property's identity + the cause), not a single generic "not predictable" string. The per-property handler records ``str(exc)`` in the SubTask output, so these messages are the operator's only debugging signal. """ from typing import Callable, Optional, cast from unittest.mock import MagicMock import pytest from applications.modelling_e2e.errors import ( DegeneratePredictionError, NoSameTypeComparablesError, UnresolvedPropertyTypeError, ) from applications.modelling_e2e.handler import ( _predict_epc, # pyright: ignore[reportPrivateUsage] ) from datatypes.epc.domain.epc_property_data import ( BuildingPartIdentifier, EpcPropertyData, SapBuildingPart, ) from domain.epc_prediction.comparable_properties import ComparableProperty from domain.epc_prediction.epc_prediction import EpcPrediction from domain.epc_prediction.prediction_target import ( PredictionTarget, PredictionTargetAttributes, ) from repositories.property.override_backed_prediction_attributes_reader import ( OverrideBackedPredictionAttributesReader, ) class _FixedAttributesReader: """An attributes reader that returns one fixed PredictionTargetAttributes.""" def __init__(self, attributes: PredictionTargetAttributes) -> None: self._attributes = attributes def attributes_for(self, property_id: int) -> PredictionTargetAttributes: return self._attributes def _reader( attributes: PredictionTargetAttributes, ) -> OverrideBackedPredictionAttributesReader: return cast( OverrideBackedPredictionAttributesReader, _FixedAttributesReader(attributes) ) def _no_cohort() -> Callable[[str], list[ComparableProperty]]: return lambda _postcode: [] def _no_broaden() -> Callable[[PredictionTarget], list[ComparableProperty]]: return lambda _target: [] def _epc(property_type: Optional[str]) -> EpcPropertyData: epc: EpcPropertyData = object.__new__(EpcPropertyData) epc.property_type = property_type return epc def test_unresolved_property_type_raises_with_property_identity() -> None: # Arrange — no resolvable property_type (the contradictory/missing-override # case): build_prediction_target gates the Property out before any cohort. reader = _reader(PredictionTargetAttributes(property_type=None)) # Act / Assert with pytest.raises(UnresolvedPropertyTypeError) as excinfo: _predict_epc( property_id=733290, uprn=100021987320, postcode="SE4 1DX", portfolio_id=796, attributes_reader=reader, coordinates=None, cohort_for=_no_cohort(), broaden=_no_broaden(), predictor=EpcPrediction(), ) message = str(excinfo.value) assert "733290" in message assert "100021987320" in message assert "SE4 1DX" in message assert "796" in message def test_resolved_type_but_empty_cohort_raises_no_same_type_comparables() -> None: # Arrange — property_type resolves, but neither the own postcode nor the # broadened cohort yields a same-type comparable. reader = _reader(PredictionTargetAttributes(property_type="2")) # Act / Assert with pytest.raises(NoSameTypeComparablesError) as excinfo: _predict_epc( property_id=723681, uprn=100021935228, postcode="SE13 5RT", portfolio_id=796, attributes_reader=reader, coordinates=None, cohort_for=_no_cohort(), broaden=_no_broaden(), predictor=EpcPrediction(), ) assert excinfo.value.property_type == "2" assert excinfo.value.broadened is True assert "SE13 5RT" in str(excinfo.value) def test_cohort_found_but_prediction_has_no_main_part_raises_degenerate() -> None: # Arrange — a same-type cohort exists, but the synthesised EPC carries no # MAIN building part (the template was lodged with a null part identifier). reader = _reader(PredictionTargetAttributes(property_type="2")) cohort = [ComparableProperty(epc=_epc("2"), certificate_number="c1")] no_main: EpcPropertyData = object.__new__(EpcPropertyData) part: SapBuildingPart = object.__new__(SapBuildingPart) part.identifier = BuildingPartIdentifier.OTHER no_main.sap_building_parts = [part] predictor = MagicMock() predictor.predict.return_value = no_main # Act / Assert with pytest.raises(DegeneratePredictionError) as excinfo: _predict_epc( property_id=712977, uprn=100061743824, postcode="LS6 1AA", portfolio_id=796, attributes_reader=reader, coordinates=None, cohort_for=lambda _postcode: cohort, broaden=_no_broaden(), predictor=cast(EpcPrediction, predictor), ) assert excinfo.value.cohort_size == 1 assert "100061743824" in str(excinfo.value)