from __future__ import annotations from typing import Protocol class AraFirstRunCommand(Protocol): """The slice of the trigger the pipeline threads downstream. Only the business fields — UPRNs and Scenario definitions are read from their source-of-truth tables, not carried here. ``task_id``/``sub_task_id`` are deliberately absent: the SubTask lifecycle is the decorator's concern, not the pipeline's. ``AraFirstRunTriggerBody`` satisfies this structurally, so ``orchestration`` need not import the application-layer event type. """ @property def portfolio_id(self) -> int: ... @property def property_ids(self) -> list[int]: ... @property def scenario_ids(self) -> list[int]: ... class IngestionStage(Protocol): """Stage 1 — acquires and persists each Property's external source data.""" def run(self, property_ids: list[int]) -> None: ... class PropertyBaselineStage(Protocol): """Stage 2 — establishes each Property's Baseline Performance.""" def run(self, property_ids: list[int]) -> None: ... class ModellingStage(Protocol): """Stage 3 — scores each Property against its Scenarios into Plans.""" def run(self, property_ids: list[int], scenario_ids: list[int]) -> None: ... class AraFirstRunPipeline: """Composes the First Run stages end-to-end: Ingestion -> Baseline -> Modelling. Threads **only** ``property_ids`` between stages (and ``scenario_ids`` into Modelling, off the command — not a prior stage). The stages communicate through repos, never via in-memory hand-off, which is what makes each stage independently runnable for the single-property review flow (ADR-0011, ADR-0003). Stage orchestrators are injected so the handler owns wiring and tests substitute fakes. """ def __init__( self, *, ingestion: IngestionStage, baseline: PropertyBaselineStage, modelling: ModellingStage, ) -> None: self._ingestion = ingestion self._baseline = baseline self._modelling = modelling def run(self, command: AraFirstRunCommand) -> None: self._ingestion.run(command.property_ids) self._baseline.run(command.property_ids) self._modelling.run(command.property_ids, command.scenario_ids)