import uuid from datetime import datetime from typing import Any 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): proposed: int = 0 ready: int = 0 active: int = 0 blocked: int = 0 backlog: int = 0 finished: int = 0 archived: int = 0 total: int = 0 class TaskTotals(BaseModel): wait: int = 0 todo: int = 0 progress: int = 0 done: int = 0 cancel: 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] waiting_tasks: list[TaskRead] 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 class DashboardWorkplanRow(BaseModel): id: uuid.UUID title: str status: str domain: str = "unknown" repo_label: str = "unassigned" workplan_filename: str | None = None workplan_relative_path: str | None = None workplan_archived: bool = False health_labels: list[str] = [] href: str done: int = 0 progress: int = 0 wait: int = 0 todo: int = 0 total: int = 0 created_at: datetime updated_at: datetime class DashboardSourceMeta(BaseModel): ok: bool = True stale: bool = False cache_age_seconds: float | None = None refresh_in_progress: bool = False error: str | None = None class DashboardOverview(BaseModel): generated_at: datetime totals: Totals topics: list[TopicWithWorkstreams] blocking_decisions: list[DecisionRead] waiting_tasks: list[TaskRead] blocked_tasks: list[TaskRead] = [] recent_progress: list[ProgressEventRead] next_steps: list[NextStep] = [] contribution_counts: dict[str, int] = {} licence_risk_count: int = 0 open_capability_requests: int = 0 sbom_snapshot_count: int = 0 sbom_package_total: int = 0 registration_milestones: list[ProgressEventRead] = [] workplan_rows: list[DashboardWorkplanRow] = [] sources: dict[str, DashboardSourceMeta] = {} diagnostics: dict[str, Any] = {}