"""First Run end-to-end with NO database — in-memory fakes only. The same `AraFirstRunPipeline` the Postgres integration test drives, but wired against a `FakeUnitOfWork` instead of a `PostgresUnitOfWork`: Ingestion -> Baseline -> Modelling run start-to-finish, hand off through in-memory repos, and produce an inspectable multi-measure Plan without a `Session` ever being opened. This is the harness the owner runs to sense-check recommendations interactively. """ from __future__ import annotations import dataclasses from dataclasses import dataclass from pathlib import Path from typing import Any, Optional from datatypes.epc.domain.epc import Epc from datatypes.epc.domain.epc_property_data import EpcPropertyData from domain.modelling.scenario import Scenario from domain.property.property import Property, PropertyIdentity from domain.property_baseline.rebaseliner import StubRebaseliner from domain.sap10_calculator.calculator import Sap10Calculator from orchestration.ara_first_run_pipeline import AraFirstRunPipeline from orchestration.ingestion_orchestrator import IngestionOrchestrator from orchestration.modelling_orchestrator import ModellingOrchestrator from orchestration.property_baseline_orchestrator import PropertyBaselineOrchestrator from repositories.fuel_rates.fuel_rates_static_file_repository import ( FuelRatesStaticFileRepository, ) from repositories.geospatial.geospatial_repository import GeospatialRepository from repositories.product.product_json_repository import ProductJsonRepository from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( build_epc as _build_uninsulated_cavity_and_floor_epc, ) from tests.orchestration.fakes import ( FakeEpcRepo, FakePlanRepository, FakePropertyRepo, FakeScenarioRepository, FakeUnitOfWork, ) _CATALOGUE = Path(__file__).resolve().parent / "fixtures/product_catalogue.json" @dataclass class _Command: portfolio_id: int property_ids: list[int] scenario_ids: list[int] class _FetcherReturning: def __init__(self, epc: EpcPropertyData) -> None: self._epc = epc def get_by_uprn(self, uprn: int) -> Optional[EpcPropertyData]: return self._epc class _NoCoordinates(GeospatialRepository): def coordinates_for(self, uprn: int): # type: ignore[no-untyped-def] return None # skip the solar leg class _UnusedSolarFetcher: def get_building_insights( self, longitude: float, latitude: float ) -> dict[str, Any]: # pragma: no cover return {} def _uninsulated_lodged_epc() -> EpcPropertyData: # 000490: an uninsulated cavity wall + suspended floor (loft already 300mm), # so the wall + floor Generators fire and the ventilation Dependency follows. # The calculator fixture carries no lodged recorded-performance, so we fill it # in (as a real lodged EPC would) — it already carries the RHI block — so the # Baseline stage can run inside the full pipeline. epc = _build_uninsulated_cavity_and_floor_epc() return dataclasses.replace( epc, energy_rating_current=57, current_energy_efficiency_band=Epc.D, co2_emissions_current=3.0, energy_consumption_current=300, ) def test_first_run_produces_a_multi_measure_plan_without_a_database() -> None: # Arrange — an in-memory Property (no EPC yet; Ingestion supplies it), a # default Increasing-EPC Scenario, and a file-backed product catalogue. epc_repo = FakeEpcRepo() plan_repo = FakePlanRepository() property_repo = FakePropertyRepo( { 10: Property( identity=PropertyIdentity( portfolio_id=1, postcode="A0 0AA", address="1 Some Street", uprn=12345, ) ) }, epc_repo=epc_repo, ) unit: FakeUnitOfWork = FakeUnitOfWork( property=property_repo, epc=epc_repo, scenario=FakeScenarioRepository( { 7: Scenario( id=7, goal="Increasing EPC", goal_value="C", budget=None, is_default=True, ) } ), product=ProductJsonRepository(_CATALOGUE), plan=plan_repo, ) pipeline = AraFirstRunPipeline( ingestion=IngestionOrchestrator( unit_of_work=lambda: unit, epc_fetcher=_FetcherReturning(_uninsulated_lodged_epc()), geospatial_repo=_NoCoordinates(), solar_fetcher=_UnusedSolarFetcher(), ), baseline=PropertyBaselineOrchestrator( unit_of_work=lambda: unit, rebaseliner=StubRebaseliner(), fuel_rates=FuelRatesStaticFileRepository(), ), modelling=ModellingOrchestrator( unit_of_work=lambda: unit, calculator=Sap10Calculator(), fuel_rates=FuelRatesStaticFileRepository(), ), ) # Act — the whole First Run, no Session ever opened. pipeline.run(_Command(portfolio_id=1, property_ids=[10], scenario_ids=[7])) # Assert — a Plan was persisted in memory for (property 10, scenario 7), # with at least one Plan Measure and a post-retrofit SAP no worse than baseline. plan = plan_repo.saved[(10, 7)] assert len(plan.measures) >= 1 assert plan.post_sap_continuous >= plan.baseline.sap_continuous