import uuid from datetime import date, datetime from typing import Literal from pydantic import BaseModel, ConfigDict, field_validator from api.schemas.workplan_dependency import WorkplanDepStub from api.workplan_status import normalize_workplan_status WorkplanStatus = Literal[ "proposed", "ready", "active", "blocked", "backlog", "finished", "archived", ] ExecutionState = Literal["manual", "queued", "scheduled", "launching", "paused", "completed", "cancelled"] LaunchMode = Literal["manual", "queued", "scheduled", "immediate"] ConcurrencyMode = Literal["sequential", "parallel"] class WorkplanStatusMixin(BaseModel): @field_validator("status", mode="before", check_fields=False) @classmethod def _normalise_status(cls, value): return normalize_workplan_status(value) class WorkplanCreate(WorkplanStatusMixin): repo_id: uuid.UUID topic_id: uuid.UUID | None = None slug: str title: str description: str | None = None status: WorkplanStatus = "active" owner: str | None = None due_date: date | None = None planning_priority: str | None = None planning_order: int | None = None execution_state: ExecutionState = "manual" launch_mode: LaunchMode = "manual" concurrency_mode: ConcurrencyMode = "sequential" queue_rank: int | None = None execution_group: str | None = None scheduled_for: datetime | None = None repo_goal_id: uuid.UUID | None = None class WorkplanUpdate(WorkplanStatusMixin): title: str | None = None description: str | None = None status: WorkplanStatus | None = None owner: str | None = None due_date: date | None = None planning_priority: str | None = None planning_order: int | None = None execution_state: ExecutionState | None = None launch_mode: LaunchMode | None = None concurrency_mode: ConcurrencyMode | None = None queue_rank: int | None = None execution_group: str | None = None scheduled_for: datetime | None = None topic_id: uuid.UUID | None = None repo_id: uuid.UUID | None = None repo_goal_id: uuid.UUID | None = None class WorkplanRead(WorkplanStatusMixin): model_config = ConfigDict(from_attributes=True) id: uuid.UUID repo_id: uuid.UUID topic_id: uuid.UUID | None = None repo_goal_id: uuid.UUID | None = None slug: str title: str description: str | None = None status: WorkplanStatus owner: str | None = None due_date: date | None = None planning_priority: str | None = None planning_order: int | None = None execution_state: ExecutionState = "manual" launch_mode: LaunchMode = "manual" concurrency_mode: ConcurrencyMode = "sequential" queue_rank: int | None = None execution_group: str | None = None scheduled_for: datetime | None = None created_at: datetime updated_at: datetime class WorkplanWithTaskCounts(WorkplanRead): tasks_total: int = 0 tasks_wait: int = 0 tasks_todo: int = 0 tasks_progress: int = 0 tasks_done: int = 0 tasks_cancel: int = 0 class WorkplanWithDeps(WorkplanWithTaskCounts): """WorkplanWithTaskCounts enriched with dependency graph edges.""" depends_on: list[WorkplanDepStub] = [] blocks: list[WorkplanDepStub] = [] blocked_reasons: list[dict] = []