from datetime import date from typing import Any from app.core.constants import ActorValue from app.core.config import get_settings from app.modules.reports.constants import ( LifecycleFilterKey, LifecycleResponseKey, LifecycleSection, ReportTitle, ) from app.modules.reports.services.common import _json_safe class ReportLifecycleReportMixin: def project_lifecycle_report( self, project_code: str | None = None, owner: str | None = None, period_start: date | None = None, period_end: date | None = None, include_ai: bool = False, actor: str = ActorValue.API, ) -> dict[str, Any]: """Build a full lifecycle report for project progress, delivery, cost, and risk.""" filters = self._lifecycle_filters(project_code, owner, period_start, period_end) project_conditions = filters[LifecycleSection.PROJECTS] task_conditions = filters[LifecycleSection.TASKS] procurement_conditions = filters[LifecycleSection.PROCUREMENTS] expense_conditions = filters[LifecycleSection.EXPENSES] attendance_conditions = filters[LifecycleSection.ATTENDANCE] risk_conditions = filters[LifecycleSection.RISKS] project_stats = self._lifecycle_project_stats(project_conditions) task_stats = self._lifecycle_task_stats(task_conditions) procurement_stats = self._lifecycle_procurement_stats(procurement_conditions) expense_stats = self._lifecycle_expense_stats(expense_conditions) fund_stats = self._lifecycle_fund_stats() supplier_stats = self._lifecycle_supplier_stats() attendance_stats = self._lifecycle_attendance_stats(attendance_conditions) risk_stats = self._lifecycle_risk_stats( project_conditions, task_conditions, risk_conditions, ) include_global_risk = not (project_code or owner) health = self._lifecycle_health( project_stats, task_stats, risk_stats, supplier_stats, include_global_risk, ) attention = self._lifecycle_attention(project_conditions, task_conditions) recommendations = self._lifecycle_recommendations( project_stats, task_stats, procurement_stats, expense_stats, fund_stats, supplier_stats, risk_stats, include_global_risk, ) finance = ( self.project_finance_needs_report( project_code=project_code, owner=owner, as_of=period_end, include_ai=False, actor=actor, ) if get_settings().finance_needs_enabled else None ) metrics = { LifecycleSection.HEALTH: health, LifecycleSection.PROJECTS: project_stats, LifecycleSection.TASKS: task_stats, LifecycleSection.PROCUREMENTS: procurement_stats, LifecycleSection.EXPENSES: expense_stats, LifecycleSection.FUNDS: fund_stats, LifecycleSection.SUPPLIERS: supplier_stats, LifecycleSection.ATTENDANCE: attendance_stats, LifecycleSection.RISKS: risk_stats, LifecycleSection.FINANCE: finance["summary"] if finance else {"status": "未启用"}, } lines = self._lifecycle_lines( filters[LifecycleFilterKey.LABELS], metrics, recommendations, ) if finance: lines.extend(finance[LifecycleResponseKey.LINES]) report = { LifecycleResponseKey.TITLE: ReportTitle.PROJECT_LIFECYCLE, LifecycleResponseKey.FILTERS: filters[LifecycleFilterKey.LABELS], LifecycleResponseKey.METRICS: _json_safe(metrics), LifecycleResponseKey.ATTENTION: _json_safe(attention), LifecycleResponseKey.RECOMMENDATIONS: recommendations, LifecycleResponseKey.LINES: lines, LifecycleResponseKey.CONTENT: "\n".join(lines), "source_lifecycle": self.source_project_lifecycle_summary(project_code, owner), "finance": finance, "finance_chart_data": finance["finance_chart_data"] if finance else None, } if include_ai: report[LifecycleResponseKey.AI_ANALYSIS] = self._lifecycle_ai_analysis(report, actor) return report