Model/backend/app/modelling/router.py
Khalim Conn-Kowlessar 489e2b5d47 Trigger-run fans out one sub_task and message per scenario batch 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:02:12 +00:00

73 lines
2.3 KiB
Python

"""The Modelling Run Distributor: POST /v1/modelling/trigger-run (ADR-0055).
Accepts a portfolio-scoped modelling request expressed as filters, resolves
them to a concrete property set (ADR-0056), pre-creates one batch sub_task per
SQS message under the app-owned task, and fans the batches out to the
modelling_e2e workers. Never models synchronously; owns nothing after the
fan-out — progress and terminal state roll up from the workers.
"""
import json
from collections.abc import Callable, Iterator
from typing import Any, cast
import boto3
from fastapi import APIRouter, Depends
from sqlmodel import Session
from backend.app.config import get_settings
from backend.app.db.connection import db_engine
from backend.app.dependencies import validate_token
from backend.app.modelling.schemas import TriggerRunRequest
# Sends pre-serialised message bodies to the modelling_e2e queue. A seam so
# tests record bodies instead of calling AWS.
MessageSender = Callable[[list[str]], None]
def get_session() -> Iterator[Session]:
with Session(db_engine) as session:
yield session
def get_message_sender() -> MessageSender:
settings = get_settings()
client: Any = cast(
Any, boto3.client("sqs", settings.AWS_DEFAULT_REGION)
) # pyright: ignore[reportUnknownMemberType]
queue_url = settings.MODELLING_E2E_SQS_URL
def send(bodies: list[str]) -> None:
# send_message_batch caps at 10 entries per call — chunk accordingly.
for start in range(0, len(bodies), 10):
chunk = bodies[start : start + 10]
client.send_message_batch(
QueueUrl=queue_url,
Entries=[
{"Id": str(index), "MessageBody": body}
for index, body in enumerate(chunk)
],
)
return send
router = APIRouter(
prefix="/modelling",
tags=["modelling"],
dependencies=[Depends(validate_token)],
)
@router.post("/trigger-run", status_code=202)
async def trigger_run(
body: TriggerRunRequest,
session: Session = Depends(get_session),
send_messages: MessageSender = Depends(get_message_sender),
) -> dict[str, str]:
raise NotImplementedError
# json is used by the implementation; referenced here so the stub imports are
# stable for the first RED.
_ = json