PMFlow/backend/app/services/ai_pmo_cadence/mixin_builders.py

107 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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