Model/applications/ara_first_run/handler.py
Khalim Conn-Kowlessar 75fbba60fc feat(ara): AraFirstRunTriggerBody + ara_first_run lambda skeleton (#1130)
Stage-2 entry point for the First Run use case. Adds the
`ara_first_run` Lambda package mirroring the `postcode_splitter`
template, its typed trigger contract, and a stub `FirstRunPipeline`.

- `AraFirstRunTriggerBody`: thin command of five fields — `task_id`,
  `sub_task_id` (UUID, lifecycle), `portfolio_id`, `property_ids`,
  `scenario_ids` (int business IDs). No `model_config` override, so
  Pydantic's default `extra="ignore"` lets the FastAPI backend add
  fields without breaking deployed lambdas. UPRNs / Scenario defs are
  deliberately off the event — read from source-of-truth tables.
- Thin `handler.py`: validate-and-delegate only, via a named
  `dispatch_first_run` seam (testable without the Lambda runtime).
  Subtask status (in-progress/complete/failed) + CloudWatch log URL
  come for free from the existing `@subtask_handler()` decorator.
- `FirstRunPipeline` (orchestration/) stub: `run(command)` receives the
  validated command. Declares a structural `FirstRunCommand` Protocol
  (the three business fields) that `AraFirstRunTriggerBody` satisfies,
  so orchestration needs no application-layer import — rhymes with the
  `EpcFetcher`/`SolarFetcher` Protocols on IngestionOrchestrator
  (ADR-0011). Full Ingestion→Baseline→Modelling composition lands in
  #1136.
- Dockerfile / requirements.txt / local_handler/ mirror postcode_splitter.

TDD: 7 new tests (trigger-body validation incl. forward-compat +
id-types, pipeline seam, handler delegation). pyright strict clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 20:38:15 +00:00

34 lines
1.1 KiB
Python

from __future__ import annotations
from typing import Any, Protocol
from applications.ara_first_run.ara_first_run_trigger_body import (
AraFirstRunTriggerBody,
)
from orchestration.first_run_pipeline import FirstRunPipeline
from orchestration.task_orchestrator import TaskOrchestrator
from utilities.aws_lambda.subtask_handler import subtask_handler
class _RunsFirstRun(Protocol):
"""The slice of FirstRunPipeline the handler delegates to."""
def run(self, command: AraFirstRunTriggerBody) -> None: ...
def dispatch_first_run(body: dict[str, Any], *, pipeline: _RunsFirstRun) -> None:
"""Validate the raw event body and hand the command to the pipeline.
The handler's entire job — kept as a named seam so it is exercised without
the Lambda runtime. No business logic lives here: validate, then delegate
(issue #1130).
"""
trigger = AraFirstRunTriggerBody.model_validate(body)
pipeline.run(trigger)
@subtask_handler()
def handler(
body: dict[str, Any], context: Any, task_orchestrator: TaskOrchestrator
) -> None:
dispatch_first_run(body, pipeline=FirstRunPipeline())