refetch_solar=False models with stored Solar insights and never calls Google 🟥

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-07-02 14:19:31 +00:00
parent e08048244a
commit c1a558d2b5

View file

@ -124,6 +124,18 @@ def _baseline_orchestrator() -> Iterator[MagicMock]:
yield orchestrator
@pytest.fixture(autouse=True)
def _solar_repository() -> Iterator[MagicMock]:
"""Patch the stored-Solar repository for every test — the handler reads
stored insights through it whatever refetch_solar says. Default: no stored
rows; solar tests override get_many with real payloads."""
with patch(
"applications.modelling_e2e.handler.SolarPostgresRepository"
) as repository:
repository.return_value.get_many.return_value = {}
yield repository
# ---------------------------------------------------------------------------
# Solar throttle: per-container call gap resolved from env (quota/N safety)
# ---------------------------------------------------------------------------
@ -208,6 +220,96 @@ def test_trigger_body_new_flags_default_to_true() -> None:
assert result.refetch_solar is True
# ---------------------------------------------------------------------------
# refetch_solar semantics — False skips the LIVE fetch only, never the measure
# ---------------------------------------------------------------------------
def test_refetch_solar_false_models_with_stored_solar_and_never_calls_google(
_solar_repository: MagicMock,
) -> None:
"""refetch_solar=False means "don't pay for a Google call", NOT "model
without solar": the stored Solar insights are read from the DB and handed to
the Modelling stage, and the live Google client is never invoked.
Provenance: portfolio 814's 2026-07-02 re-model ran with refetch_solar=False
and every plan lost its solar candidates (pids 742071/742077/742397/742399
houses stopped short of goal B with bill-negative ASHP-only plans) even
though the `solar` table held healthy payloads for their UPRNs."""
# Arrange
stored_insights = {"solarPotential": {"maxArrayPanelsCount": 58}}
mock_engine = _engine_mock([PROPERTY_ID], [UPRN], [POSTCODE])
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 = MagicMock()
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)
)
mock_live_fetch = 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"))
mock_run_modelling = stack.enter_context(
patch(
"applications.modelling_e2e.handler.run_modelling",
return_value=_plan_mock(),
)
)
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
_solar_repository.return_value.get_many.return_value = {
UPRN: stored_insights
}
# Act — _BODY carries refetch_solar=False
_call_handler(_BODY)
# Assert — the stored insights reach the Modelling stage unchanged, and the
# paid Google leg is never exercised.
assert (
mock_run_modelling.call_args.kwargs["solar_insights"] == stored_insights
)
mock_live_fetch.assert_not_called()
# ---------------------------------------------------------------------------
# Child SubTask creation
# ---------------------------------------------------------------------------