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