"""The distributor's view of the app-owned task's sub_tasks (ADR-0055). Intention-revealing methods over parameterised SQL. SQL rather than the SQLModel mirrors because the FastAPI app registers the legacy ``backend.app.db.models`` mirrors of ``sub_task``, and importing the ``infrastructure.postgres`` mirror of the same table into one process double-registers it and crashes the app at import (contained here until the DDD cut-over). """ import json from datetime import datetime, timezone from typing import Any from uuid import UUID from sqlalchemy import text from sqlmodel import Session class ModellingRunTasks: def __init__(self, session: Session) -> None: self._session = session def already_distributed(self, task_id: UUID) -> bool: """Whether the task already has sub_tasks — i.e. a distributor has already fanned it out (the 409 guard against double-submits).""" row = self._session.connection().execute( text("SELECT 1 FROM sub_task WHERE task_id = :task_id LIMIT 1"), {"task_id": task_id}, ).first() return row is not None def create_batch_subtasks( self, task_id: UUID, messages: list[dict[str, Any]] ) -> None: """Pre-create one ``waiting`` sub_task per batch message under the app-owned task. Each sub_task's inputs are its exact message payload (minus the self-referential subtask_id) — the fixed progress denominator and the batch's re-run recipe.""" now = datetime.now(timezone.utc) self._session.connection().execute( text( "INSERT INTO sub_task (id, task_id, status, inputs, updated_at)" " VALUES (:id, :task_id, 'waiting', :inputs, :updated_at)" ), [ { "id": message["subtask_id"], "task_id": task_id, "inputs": json.dumps( {k: v for k, v in message.items() if k != "subtask_id"} ), "updated_at": now, } for message in messages ], ) self._session.commit()