diff --git a/repositories/scenario_export/scenario_export_postgres_repository.py b/repositories/scenario_export/scenario_export_postgres_repository.py index ff8326627..1b6ca3315 100644 --- a/repositories/scenario_export/scenario_export_postgres_repository.py +++ b/repositories/scenario_export/scenario_export_postgres_repository.py @@ -1,7 +1,8 @@ from __future__ import annotations -from typing import Sequence +from typing import Any, Optional, Sequence +from sqlalchemy import text from sqlmodel import Session from domain.scenario_export.scenario_sheet import PropertyScenarioData @@ -9,6 +10,35 @@ from repositories.scenario_export.scenario_export_repository import ( ScenarioExportRepository, ) +# The default Plan per (scenario, property), scoped to the requested Properties. +# The JOIN to `plan` is the model-A scenario-scoping (ADR-0065): a Property with +# no default Plan under the Scenario yields no row and drops out. +_ROWS_SQL = text( + "SELECT p.id, p.landlord_property_id, p.uprn, p.address, p.postcode," + " pl.post_sap_points, pl.post_epc_rating, pl.cost_of_works," + " pl.contingency_cost, pl.co2_savings, pl.energy_bill_savings," + " pl.energy_consumption_savings, pl.valuation_increase" + " FROM property p" + " JOIN plan pl ON pl.property_id = p.id" + " WHERE p.portfolio_id = :portfolio_id" + " AND pl.scenario_id = :scenario_id" + " AND pl.is_default = TRUE" + " AND p.id = ANY(:property_ids)" + " ORDER BY p.id" +) + + +def _str(value: Any) -> Optional[str]: + """Render a persisted scalar (int UPRN, Epc enum band) as its string form, + preserving absence as ``None``.""" + if value is None: + return None + return str(getattr(value, "value", value)) + + +def _float(value: Any) -> Optional[float]: + return None if value is None else float(value) + class ScenarioExportPostgresRepository(ScenarioExportRepository): """Reads Scenario Export rows from Postgres (ADR-0065). Hand-rolled SQL over @@ -25,4 +55,31 @@ class ScenarioExportPostgresRepository(ScenarioExportRepository): scenario_id: int, property_ids: Sequence[int], ) -> list[PropertyScenarioData]: - raise NotImplementedError + if not property_ids: + return [] + result = self._session.connection().execute( + _ROWS_SQL, + { + "portfolio_id": portfolio_id, + "scenario_id": scenario_id, + "property_ids": list(property_ids), + }, + ) + return [ + PropertyScenarioData( + property_id=row[0], + landlord_property_id=_str(row[1]), + uprn=_str(row[2]), + address=_str(row[3]), + postcode=_str(row[4]), + post_sap_points=_float(row[5]), + post_epc_rating=_str(row[6]), + cost_of_works=_float(row[7]), + contingency_cost=_float(row[8]), + co2_savings=_float(row[9]), + energy_bill_savings=_float(row[10]), + energy_consumption_savings=_float(row[11]), + valuation_increase=_float(row[12]), + ) + for row in result.all() + ]