"""FC-3i: task_run — per-Celery-task lifecycle audit row. One row inserted by the task_prerun signal at task start, updated by task_postrun / task_failure / task_retry. The shape supports the SystemActivity dashboard's three panes: per-queue queue+worker summary, recent failures (24h, grouped by error_type), and full paginated activity history. Retention: ok rows pruned after 24h, error/timeout after 7d (see backend.app.tasks.maintenance.prune_task_runs). Recovery: rows stuck in 'running' for >5 min flipped to 'error' by backend.app.tasks.maintenance.recover_stalled_task_runs (Beat 5 min). """ from datetime import datetime from sqlalchemy import DateTime, Integer, String, Text from sqlalchemy.orm import Mapped, mapped_column from .base import Base class TaskRun(Base): __tablename__ = "task_run" id: Mapped[int] = mapped_column(Integer, primary_key=True) celery_task_id: Mapped[str] = mapped_column( String(64), nullable=False, index=True, ) queue: Mapped[str] = mapped_column(String(32), nullable=False, index=True) task_name: Mapped[str] = mapped_column(String(128), nullable=False, index=True) target_id: Mapped[int | None] = mapped_column(Integer, nullable=True) started_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True, ) finished_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True, ) duration_ms: Mapped[int | None] = mapped_column(Integer, nullable=True) status: Mapped[str] = mapped_column( String(16), nullable=False, default="running", index=True, ) error_type: Mapped[str | None] = mapped_column(String(128), nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) retry_count: Mapped[int | None] = mapped_column(Integer, nullable=True) worker_hostname: Mapped[str | None] = mapped_column(String(128), nullable=True) args_summary: Mapped[str | None] = mapped_column(String(255), nullable=True)