diff --git a/repositories/scenario_export/__init__.py b/repositories/scenario_export/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/repositories/scenario_export/scenario_export_postgres_repository.py b/repositories/scenario_export/scenario_export_postgres_repository.py new file mode 100644 index 000000000..ff8326627 --- /dev/null +++ b/repositories/scenario_export/scenario_export_postgres_repository.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import Sequence + +from sqlmodel import Session + +from domain.scenario_export.scenario_sheet import PropertyScenarioData +from repositories.scenario_export.scenario_export_repository import ( + ScenarioExportRepository, +) + + +class ScenarioExportPostgresRepository(ScenarioExportRepository): + """Reads Scenario Export rows from Postgres (ADR-0065). Hand-rolled SQL over + the live tables (the ``uploaded_file`` repository precedent), referencing only + the mirrored columns; returns the domain read-model, never ORM rows.""" + + def __init__(self, session: Session) -> None: + self._session = session + + def rows_for( + self, + *, + portfolio_id: int, + scenario_id: int, + property_ids: Sequence[int], + ) -> list[PropertyScenarioData]: + raise NotImplementedError diff --git a/repositories/scenario_export/scenario_export_repository.py b/repositories/scenario_export/scenario_export_repository.py new file mode 100644 index 000000000..dfc9cb113 --- /dev/null +++ b/repositories/scenario_export/scenario_export_repository.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Sequence + +from domain.scenario_export.scenario_sheet import PropertyScenarioData + + +class ScenarioExportRepository(ABC): + """Reads the persisted data one Scenario Export sheet needs (ADR-0065).""" + + @abstractmethod + def rows_for( + self, + *, + portfolio_id: int, + scenario_id: int, + property_ids: Sequence[int], + ) -> list[PropertyScenarioData]: + """The export rows for the Properties in ``property_ids`` that have a + default Plan under the Scenario — identity, override-resolved + Effective-EPC descriptive fields, the Plan's post-works figures, and the + selected measures. A Property with no default Plan for the Scenario is + absent (scenario-scoped, model A).""" + ... diff --git a/tests/repositories/scenario_export/__init__.py b/tests/repositories/scenario_export/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/repositories/scenario_export/test_scenario_export_postgres_repository.py b/tests/repositories/scenario_export/test_scenario_export_postgres_repository.py new file mode 100644 index 000000000..993e3dd42 --- /dev/null +++ b/tests/repositories/scenario_export/test_scenario_export_postgres_repository.py @@ -0,0 +1,61 @@ +"""Behaviour of the Postgres-backed ScenarioExportRepository (ADR-0065): reading +one Scenario's Properties — those with a default Plan under it — as the export +read-model, from persisted data only (no live EPC calls).""" + +from __future__ import annotations + +from sqlalchemy import Engine +from sqlmodel import Session + +from datatypes.epc.domain.epc import Epc +from infrastructure.postgres.modelling import PlanModel # registers `plan` +from infrastructure.postgres.property_table import PropertyRow # registers `property` +from repositories.scenario_export.scenario_export_postgres_repository import ( + ScenarioExportPostgresRepository, +) + + +def test_returns_a_row_for_a_property_with_a_default_plan_under_the_scenario( + db_engine: Engine, +) -> None: + # arrange — a Property with a default Plan under scenario 5 carrying the SAP + # calculator's post-works figures. + with Session(db_engine) as session: + session.add( + PropertyRow( + id=1, + portfolio_id=100, + landlord_property_id="LP1", + uprn=100023, + address="12 Oak Street", + postcode="AB1 2CD", + ) + ) + session.add( + PlanModel( + portfolio_id=100, + property_id=1, + scenario_id=5, + is_default=True, + post_sap_points=78.0, + post_epc_rating=Epc.C, + cost_of_works=4200.0, + ) + ) + session.commit() + + # act + with Session(db_engine) as session: + rows = ScenarioExportPostgresRepository(session).rows_for( + portfolio_id=100, scenario_id=5, property_ids=[1] + ) + + # assert — one read-model row carrying identity and the Plan's post-works + # figures. + assert len(rows) == 1 + row = rows[0] + assert row.property_id == 1 + assert row.landlord_property_id == "LP1" + assert row.uprn == "100023" + assert row.post_sap_points == 78.0 + assert row.cost_of_works == 4200.0