mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
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>
44 lines
1.4 KiB
Python
44 lines
1.4 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
from uuid import UUID
|
|
|
|
from applications.ara_first_run.ara_first_run_trigger_body import (
|
|
AraFirstRunTriggerBody,
|
|
)
|
|
from applications.ara_first_run.handler import dispatch_first_run
|
|
from orchestration.first_run_pipeline import FirstRunCommand
|
|
|
|
|
|
class _SpyPipeline:
|
|
"""Records the command it is asked to run, instead of composing stages."""
|
|
|
|
def __init__(self) -> None:
|
|
self.received: Optional[FirstRunCommand] = None
|
|
|
|
def run(self, command: FirstRunCommand) -> None:
|
|
self.received = command
|
|
|
|
|
|
def test_validates_the_event_body_and_delegates_the_command_to_the_pipeline() -> None:
|
|
# Arrange — a raw SQS body, as the decorator hands it to the handler.
|
|
body = {
|
|
"task_id": "e295d89b-a7c5-4a9a-8b4e-b405fab1f298",
|
|
"sub_task_id": "f4a9944f-41f0-4a33-8669-5016ec574068",
|
|
"portfolio_id": 42,
|
|
"property_ids": [101, 102],
|
|
"scenario_ids": [7],
|
|
}
|
|
pipeline = _SpyPipeline()
|
|
|
|
# Act
|
|
dispatch_first_run(body, pipeline=pipeline)
|
|
|
|
# Assert — the raw body was validated into the typed trigger and handed
|
|
# straight on, untouched.
|
|
received = pipeline.received
|
|
assert isinstance(received, AraFirstRunTriggerBody)
|
|
assert received.task_id == UUID("e295d89b-a7c5-4a9a-8b4e-b405fab1f298")
|
|
assert received.portfolio_id == 42
|
|
assert received.property_ids == [101, 102]
|
|
assert received.scenario_ids == [7]
|