81 lines
2.9 KiB
Python
81 lines
2.9 KiB
Python
"""Snapshot persistence APIs for AI PMO cadence service."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from .shared import Any, Optional, PortfolioSnapshot, and_, select
|
|
|
|
|
|
class CadenceSnapshotsMixin:
|
|
"""Generates and retrieves cadence snapshots."""
|
|
|
|
async def generate_snapshot(
|
|
self,
|
|
*,
|
|
cadence_type: str,
|
|
project_id: Optional[int] = None,
|
|
) -> dict[str, Any]:
|
|
normalized_type = str(cadence_type or "").strip().lower()
|
|
if normalized_type not in self.SUPPORTED_CADENCE_TYPES:
|
|
raise ValueError("unsupported cadence_type")
|
|
|
|
if normalized_type == "daily_standup":
|
|
payload = await self._build_daily_standup(project_id=project_id)
|
|
elif normalized_type == "weekly_review":
|
|
payload = await self._build_weekly_review(project_id=project_id)
|
|
elif normalized_type == "risk_review":
|
|
payload = await self._build_risk_review(project_id=project_id)
|
|
else:
|
|
payload = await self._build_milestone_check(project_id=project_id)
|
|
|
|
scope_ref = f"project:{int(project_id)}" if project_id is not None else f"tenant:{self.tenant_id}"
|
|
row = PortfolioSnapshot(
|
|
tenant_id=self.tenant_id,
|
|
scope_type=normalized_type[:20],
|
|
scope_ref=scope_ref[:64],
|
|
summary=payload.get("summary"),
|
|
payload=payload,
|
|
)
|
|
self.db.add(row)
|
|
await self.db.flush()
|
|
return {
|
|
"id": int(row.id),
|
|
"scope_type": row.scope_type,
|
|
"scope_ref": row.scope_ref,
|
|
"summary": row.summary,
|
|
"payload": row.payload,
|
|
"created_at": row.created_at.isoformat() if row.created_at else None,
|
|
}
|
|
|
|
async def get_latest_snapshot(
|
|
self,
|
|
*,
|
|
cadence_type: str,
|
|
project_id: Optional[int] = None,
|
|
) -> Optional[dict[str, Any]]:
|
|
normalized_type = str(cadence_type or "").strip().lower()
|
|
scope_ref = f"project:{int(project_id)}" if project_id is not None else f"tenant:{self.tenant_id}"
|
|
row = (
|
|
await self.db.execute(
|
|
select(PortfolioSnapshot)
|
|
.where(
|
|
and_(
|
|
PortfolioSnapshot.tenant_id == self.tenant_id,
|
|
PortfolioSnapshot.scope_type == normalized_type,
|
|
PortfolioSnapshot.scope_ref == scope_ref,
|
|
)
|
|
)
|
|
.order_by(PortfolioSnapshot.id.desc())
|
|
.limit(1)
|
|
)
|
|
).scalar_one_or_none()
|
|
if row is None:
|
|
return None
|
|
return {
|
|
"id": int(row.id),
|
|
"scope_type": row.scope_type,
|
|
"scope_ref": row.scope_ref,
|
|
"summary": row.summary,
|
|
"payload": row.payload,
|
|
"created_at": row.created_at.isoformat() if row.created_at else None,
|
|
}
|