107 lines
4.9 KiB
Python
107 lines
4.9 KiB
Python
"""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
|