"""Cadence payload builders for AI PMO cadence service.""" from __future__ import annotations from .shared import Any, Optional, Project, select, timedelta, utc_now_naive class CadenceBuildersMixin: """Builds cadence snapshots for daily/weekly/risk/milestone views.""" async def _build_daily_standup(self, *, project_id: Optional[int]) -> dict[str, Any]: now = utc_now_naive() since = now - timedelta(days=1) work_items = await self._list_recent_work_items(since=since, project_id=project_id) blocked_tasks = await self._list_blocked_tasks(project_id=project_id) recent_signals = await self._list_recent_signals(since=since, project_id=project_id) pending_manual_count = await self._count_work_items(state="waiting_manual", project_id=project_id) return { "summary": { "cadence_type": "daily_standup", "recent_work_item_count": len(work_items), "pending_manual_count": pending_manual_count, "blocked_task_count": len(blocked_tasks), "recent_signal_count": len(recent_signals), }, "work_items": work_items[:10], "blocked_tasks": blocked_tasks[:10], "recent_signals": recent_signals[:10], "focus_actions": self._build_focus_actions( pending_manual_count=pending_manual_count, blocked_task_count=len(blocked_tasks), ), } async def _build_weekly_review(self, *, project_id: Optional[int]) -> dict[str, Any]: now = utc_now_naive() since = now - timedelta(days=7) weekly_rows = await self._list_recent_work_items(since=since, project_id=project_id) budgets = await self._list_budget_hotspots(project_id=project_id) capacity = await self._list_capacity_hotspots(project_id=project_id) program_view = await self.build_program_view() if project_id is None else None by_state: dict[str, int] = {} for item in weekly_rows: state = str(item.get("state") or "unknown") by_state[state] = by_state.get(state, 0) + 1 return { "summary": { "cadence_type": "weekly_review", "work_item_total": len(weekly_rows), "by_state": by_state, "budget_hotspot_count": len(budgets), "capacity_hotspot_count": len(capacity), }, "work_items": weekly_rows[:20], "budget_hotspots": budgets[:10], "capacity_hotspots": capacity[:10], "program_view": program_view, } async def _build_risk_review(self, *, project_id: Optional[int]) -> dict[str, Any]: blocked_tasks = await self._list_blocked_tasks(project_id=project_id) signal_sources = await self._list_pending_signal_sources(project_id=project_id) notifications = await self._list_unread_risk_notifications(project_id=project_id) return { "summary": { "cadence_type": "risk_review", "blocked_task_count": len(blocked_tasks), "pending_signal_source_count": len(signal_sources), "unread_risk_notification_count": len(notifications), }, "blocked_tasks": blocked_tasks[:20], "pending_signal_sources": signal_sources[:20], "risk_notifications": notifications[:20], } async def _build_milestone_check(self, *, project_id: Optional[int]) -> dict[str, Any]: overdue_tasks = await self._list_overdue_tasks(project_id=project_id) work_items = await self._count_work_items(state="waiting_manual", project_id=project_id) stmt = select(Project.id, Project.title, Project.status).where(Project.tenant_id == self.tenant_id) if project_id is not None: stmt = stmt.where(Project.id == int(project_id)) projects = (await self.db.execute(stmt)).all() return { "summary": { "cadence_type": "milestone_check", "project_count": len(projects), "overdue_task_count": len(overdue_tasks), "pending_manual_count": work_items, }, "projects": [ {"project_id": int(pid), "title": str(title or ""), "status": str(status or "")} for pid, title, status in projects ], "overdue_tasks": overdue_tasks[:20], } @staticmethod def _build_focus_actions(*, pending_manual_count: int, blocked_task_count: int) -> list[str]: actions: list[str] = [] if pending_manual_count > 0: actions.append("优先清理待人工确认队列,避免自动执行链路阻塞。") if blocked_task_count > 0: actions.append("优先处理 blocked tasks,降低关键依赖压力。") if not actions: actions.append("当前节奏平稳,继续按计划推进。") return actions