from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass from domain.modelling.plan import Plan @dataclass(frozen=True) class PlanSaveRequest: """Bundles the five fields the plan repository needs to persist one Plan. Mirrors ``EpcSaveRequest`` in shape — used by ``PlanRepository.save_batch()`` to accumulate write intent before the batch is flushed in one Unit of Work.""" plan: Plan property_id: int scenario_id: int portfolio_id: int is_default: bool class PlanRepository(ABC): """Persists a Plan (and its Plan Measures) for a Property + Scenario. A re-run INSERTs a fresh Plan and keeps the prior one as history rather than deleting it. When the new Plan is the default, prior default Plans for the same (Property, Scenario) are demoted to `is_default=False`, so the current Plan is the one with `is_default=True` (ADR-0012 / ADR-0017). `portfolio_id` and `is_default` are supplied by the orchestrator (the former from the trigger, the latter from the Scenario). """ @abstractmethod def save( self, plan: Plan, *, property_id: int, scenario_id: int, portfolio_id: int, is_default: bool, ) -> int: """Persist ``plan`` and return its Plan id. Keeps prior Plans for ``(property_id, scenario_id)`` as history; when ``is_default`` is True, demotes those prior Plans to ``is_default=False``.""" ... @abstractmethod def save_batch(self, requests: list[PlanSaveRequest]) -> list[int]: """Persist a batch of Plans in three statements regardless of batch size. Returns one plan id per request in input order. Fires a single demote UPDATE only when at least one request has ``is_default=True``. Keeps prior Plans as history (ADR-0017).""" ...