Add tests for repredict_epc flag routing via stored predicted EPC 🟩

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel Roth 2026-06-26 10:15:40 +00:00
parent a940c94b33
commit 100a580119

View file

@ -1298,6 +1298,181 @@ def test_refetch_epc_true_always_calls_api_even_if_stored_epc_exists() -> None:
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# repredict_epc flag
# ---------------------------------------------------------------------------
def test_repredict_epc_false_with_stored_predicted_epc_skips_prediction() -> None:
"""repredict_epc=False + stored predicted EPC present: EpcPrediction.predict
is not called; the stored predicted EPC reaches run_modelling."""
# Arrange
mock_engine = _engine_mock([PROPERTY_ID], [UPRN], [POSTCODE])
stored_predicted = MagicMock()
from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier
mock_part = MagicMock()
mock_part.identifier = BuildingPartIdentifier.MAIN
stored_predicted.sap_building_parts = [mock_part]
mock_plan = _plan_mock()
mock_uow = MagicMock()
with ExitStack() as stack:
stack.enter_context(patch("applications.modelling_e2e.handler.os.environ", _ENV))
stack.enter_context(
patch("applications.modelling_e2e.handler._get_engine", return_value=mock_engine)
)
stack.enter_context(
patch("applications.modelling_e2e.handler.EpcClientService")
).return_value.get_by_uprn.return_value = None # no lodged EPC → prediction path
stack.enter_context(patch("applications.modelling_e2e.handler.GeospatialS3Repository"))
stack.enter_context(patch("applications.modelling_e2e.handler.GoogleSolarApiClient"))
stack.enter_context(
patch("applications.modelling_e2e.handler._spatial_for", return_value=None)
)
stack.enter_context(
patch("applications.modelling_e2e.handler._solar_insights_for", return_value=None)
)
stack.enter_context(
patch("applications.modelling_e2e.handler.overlays_from", return_value=[])
)
stack.enter_context(
patch("applications.modelling_e2e.handler.PropertyOverridesPostgresReader")
).return_value.overrides_for_many.return_value = {}
stack.enter_context(
patch("applications.modelling_e2e.handler.ScenarioPostgresRepository")
).return_value.get_many.return_value = [MagicMock()]
stack.enter_context(
patch("applications.modelling_e2e.handler.catalogue_snapshot_with_off_catalogue_overrides")
)
stack.enter_context(patch("applications.modelling_e2e.handler.Session"))
stack.enter_context(
patch("applications.modelling_e2e.handler.run_modelling", return_value=mock_plan)
)
mock_predictor = stack.enter_context(
patch("applications.modelling_e2e.handler.EpcPrediction")
).return_value
# Stored predicted EPC is present
stack.enter_context(
patch("applications.modelling_e2e.handler.EpcPostgresRepository")
).return_value.get_predicted_for_properties.return_value = {
PROPERTY_ID: stored_predicted
}
MockUoW = stack.enter_context(
patch("applications.modelling_e2e.handler.PostgresUnitOfWork")
)
MockUoW.return_value.__enter__.return_value = mock_uow
MockUoW.return_value.__exit__.return_value = False
# Act
_call_handler({**_BODY, "repredict_epc": False})
# Assert — EpcPrediction.predict never called; stored EPC persisted in predicted slot
mock_predictor.predict.assert_not_called()
mock_uow.epc.save.assert_called_once_with(
stored_predicted,
property_id=PROPERTY_ID,
portfolio_id=PORTFOLIO_ID,
source="predicted",
)
def test_repredict_epc_false_without_stored_predicted_epc_falls_back_to_live_prediction() -> None:
"""repredict_epc=False + no stored predicted EPC: handler falls back to live
prediction so the property is not silently skipped."""
# Arrange
mock_engine = _engine_mock([PROPERTY_ID], [UPRN], [POSTCODE])
mock_plan = _plan_mock()
mock_uow = MagicMock()
mock_predicted_epc = MagicMock()
from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier
mock_part = MagicMock()
mock_part.identifier = BuildingPartIdentifier.MAIN
mock_predicted_epc.sap_building_parts = [mock_part]
mock_comparables = MagicMock()
mock_comparables.members = [MagicMock()]
with ExitStack() as stack:
stack.enter_context(patch("applications.modelling_e2e.handler.os.environ", _ENV))
stack.enter_context(
patch("applications.modelling_e2e.handler._get_engine", return_value=mock_engine)
)
stack.enter_context(
patch("applications.modelling_e2e.handler.EpcClientService")
).return_value.get_by_uprn.return_value = None # no lodged EPC
stack.enter_context(patch("applications.modelling_e2e.handler.GeospatialS3Repository"))
stack.enter_context(patch("applications.modelling_e2e.handler.GoogleSolarApiClient"))
stack.enter_context(
patch("applications.modelling_e2e.handler._spatial_for", return_value=None)
)
stack.enter_context(
patch("applications.modelling_e2e.handler._solar_insights_for", return_value=None)
)
stack.enter_context(
patch("applications.modelling_e2e.handler.overlays_from", return_value=[])
)
stack.enter_context(
patch("applications.modelling_e2e.handler.PropertyOverridesPostgresReader")
).return_value.overrides_for_many.return_value = {}
from domain.epc_prediction.prediction_target import PredictionTargetAttributes
stack.enter_context(
patch("applications.modelling_e2e.handler.OverrideBackedPredictionAttributesReader")
).return_value.attributes_for.return_value = PredictionTargetAttributes(
property_type="2"
)
stack.enter_context(
patch("applications.modelling_e2e.handler.select_comparables")
).return_value = mock_comparables
mock_predictor = stack.enter_context(
patch("applications.modelling_e2e.handler.EpcPrediction")
).return_value
mock_predictor.predict.return_value = mock_predicted_epc
stack.enter_context(
patch("applications.modelling_e2e.handler.EpcComparablePropertiesRepository")
).return_value.candidates_for.return_value = [MagicMock()]
stack.enter_context(
patch("applications.modelling_e2e.handler.ScenarioPostgresRepository")
).return_value.get_many.return_value = [MagicMock()]
stack.enter_context(
patch("applications.modelling_e2e.handler.catalogue_snapshot_with_off_catalogue_overrides")
)
stack.enter_context(patch("applications.modelling_e2e.handler.Session"))
stack.enter_context(
patch("applications.modelling_e2e.handler.run_modelling", return_value=mock_plan)
)
# No stored predicted EPC
stack.enter_context(
patch("applications.modelling_e2e.handler.EpcPostgresRepository")
).return_value.get_predicted_for_properties.return_value = {}
MockUoW = stack.enter_context(
patch("applications.modelling_e2e.handler.PostgresUnitOfWork")
)
MockUoW.return_value.__enter__.return_value = mock_uow
MockUoW.return_value.__exit__.return_value = False
# Act
_call_handler({**_BODY, "repredict_epc": False})
# Assert — live prediction was used as fallback
mock_predictor.predict.assert_called_once()
mock_uow.epc.save.assert_called_once_with(
mock_predicted_epc,
property_id=PROPERTY_ID,
portfolio_id=PORTFOLIO_ID,
source="predicted",
)
# ---------------------------------------------------------------------------
# Dry-run
# ---------------------------------------------------------------------------
def test_dry_run_skips_all_db_writes() -> None:
"""dry_run=True: run_modelling executes but PostgresUnitOfWork is never
entered no DB writes occur for any property in the batch."""