import uuid from datetime import datetime from pydantic import BaseModel from api.schemas.decision import DecisionRead from api.schemas.domain import DomainSummary from api.schemas.progress_event import ProgressEventRead from api.schemas.task import TaskRead from api.schemas.topic import TopicWithWorkstreams from api.schemas.workstream import WorkstreamWithDeps class TopicTotals(BaseModel): active: int = 0 paused: int = 0 archived: int = 0 total: int = 0 class WorkstreamTotals(BaseModel): active: int = 0 blocked: int = 0 completed: int = 0 archived: int = 0 total: int = 0 class TaskTotals(BaseModel): todo: int = 0 in_progress: int = 0 blocked: int = 0 done: int = 0 cancelled: int = 0 total: int = 0 class DecisionTotals(BaseModel): open: int = 0 resolved: int = 0 escalated: int = 0 superseded: int = 0 total: int = 0 class Totals(BaseModel): topics: TopicTotals workstreams: WorkstreamTotals tasks: TaskTotals decisions: DecisionTotals class NextStep(BaseModel): """A derived suggestion pointing to where work should happen next. Suggestions are never persisted — they are computed on demand from current hub state: recently resolved decisions, newly unblocked tasks, cleared dependencies. """ type: str # unblocked_task | resolved_decision | dependency_cleared domain: str | None = None workstream_id: uuid.UUID | None = None workstream_title: str | None = None workstream_slug: str | None = None task_id: uuid.UUID | None = None task_title: str | None = None message: str # plain-language explanation class StateSummary(BaseModel): generated_at: datetime totals: Totals topics: list[TopicWithWorkstreams] blocking_decisions: list[DecisionRead] blocked_tasks: list[TaskRead] recent_progress: list[ProgressEventRead] open_workstreams: list[WorkstreamWithDeps] next_steps: list[NextStep] = [] domains: list[DomainSummary] = [] contribution_counts: dict[str, int] = {} licence_risk_count: int = 0 open_capability_requests: int = 0