Model/orchestration/modelling_orchestrator.py
Khalim Conn-Kowlessar b77fe26892 feat(first-run): FirstRunPipeline E2E — Ingestion → Baseline → Modelling (#1136)
Completes the First Run spine. Replaces the #1130 stub FirstRunPipeline
with the real three-stage composition and wires it into the handler.

- `FirstRunPipeline.run(command)` sequences Ingestion → Baseline →
  Modelling, threading **only** `property_ids` between stages (and
  `scenario_ids` into Modelling, off the command — never a prior stage's
  output). Stages are injected behind thin `IngestionStage` /
  `BaselineStage` / `ModellingStage` Protocols (the EpcFetcher/SolarFetcher
  idiom), so the handler owns wiring and tests substitute fakes (ADR-0011).
- `ModellingOrchestrator` stub + `ScenarioRepository` / `MaterialsRepository`
  seam ports — `run(property_ids, scenario_ids)` reads through repos, does
  no scoring yet. Method shapes deferred to the Modelling per-service grills
  (Scenario / Scenario Phase / Snapshot / Optimised Package / Plans are rich
  — not pre-empted here).
- Handler delegates to the real pipeline via `build_first_run_pipeline`
  (Postgres-backed repos off the session). The Ingestion source clients
  (EPC API / Google Solar / geospatial S3) are isolated behind one
  `_source_clients_from_env` seam that raises until the deploy/Terraform
  config settles — out of scope for this slice. Subtask complete/failed +
  CloudWatch URL still come from `@subtask_handler`.

Integration test (the criterion's centrepiece): wires REAL Ingestion +
REAL Baseline + stub Modelling through a shared fake EPC repo, with a
repo-backed PropertyRepo composing the Property from that slice. Proves
Baseline reads the very EPC Ingestion persisted — the through-repos
hand-off, no in-memory coupling. Plus a composition test pinning stage
order + only-property_ids threading.

TDD, one test → one impl. pyright strict clean; AAA layout. 116 pass in
the tests/ tree, no regressions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 22:32:58 +00:00

29 lines
1.1 KiB
Python

from __future__ import annotations
from repositories.materials.materials_repository import MaterialsRepository
from repositories.scenario.scenario_repository import ScenarioRepository
class ModellingOrchestrator:
"""Stage 3 — scores each baselined Property against its Scenarios, producing
Recommendations -> an Optimised Package per Scenario Phase -> Plans
(CONTEXT.md: Modelling).
Stub at this stage (#1136): ``run`` reads its inputs through repos (it takes
only ``property_ids`` + ``scenario_ids``, never an in-memory hand-off from
Baseline) but does no scoring yet. Full Modelling lands via later TDD slices
+ per-service grills. The Scenario / Materials repos are injected now so the
composition and wiring are real even while the body is empty.
"""
def __init__(
self,
*,
scenario_repo: ScenarioRepository,
materials_repo: MaterialsRepository,
) -> None:
self._scenario_repo = scenario_repo
self._materials_repo = materials_repo
def run(self, property_ids: list[int], scenario_ids: list[int]) -> None:
return None