mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 4b — closes the #1157 tracer. ModellingOrchestrator.run(property_ids, scenario_ids, portfolio_id) now does real work in one Unit of Work, committed once (ADR-0011/0012/0016/0017): read Property (effective EPC) + Scenario via repos → recommend_cavity_wall → select its Option → PackageScorer.score (role-2 package total) + marginal_impacts (role-3 attribution) → build Plan/PlanMeasure → uow.plan.save → commit. - AraFirstRunPipeline / ModellingStage thread portfolio_id from the trigger body (one source of truth); handler builds the real orchestrator (unit_of_work + Sap10Calculator), dropping the Scenario/Materials stubs. - ScenarioRepository.get_many promoted to @abstractmethod now the bare-stub instantiations are gone. - New ara_first_run-style integration test: a property with an uninsulated cavity wall yields a persisted Plan + one cavity_wall_insulation Plan Measure (priced from the Product, figures present, linked by plan_id). Numeric SAP correctness is pinned separately in test_elmhurst_cascade_pins. - Existing pipeline integration test updated: seeds scenario 7 and runs the real Modelling stage (its already-insulated sample wall yields an empty package — no crash). 121 pass across repositories/modelling/orchestration/app; pyright strict clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
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], portfolio_id: 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, command.portfolio_id
|
|
)
|