Files
company-ai-platform/app/modules/dashboard/service.py
JiuContinent 4d09d8e2e3 ```
feat: 添加仪表板路由和响应数据脱敏功能

- 添加了仪表板模块路由并集成到主路由器中
- 实现了敏感数据响应脱敏配置和功能
- 增加了 Feishu 审批卡片操作处理功能
- 支持通过任务队列异步推送日常简报和项目周报
- 添加了风险事件生成的任务队列支持
- 在 smoke 测试中增加了相关功能验证

refactor: 格式化模型注册模块导入列表

- 将单行导入列表改为多行格式以提高可读性
```
2026-07-07 18:33:07 +08:00

62 lines
2.6 KiB
Python

from typing import Any
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.modules.approvals.constants import ApprovalStatus
from app.modules.approvals.models import ApprovalRequest
from app.modules.audit.models import AuditLog
from app.modules.business.constants import DONE_STATUSES, PROJECT_CLOSED_STATUSES, StatusValue
from app.modules.business.models import Project, RiskEvent, WorkReport, WorkTask
from app.modules.business.service import serialize_model
from app.modules.risk.service import RiskService
class DashboardService:
"""Build lightweight operational dashboard data for V2."""
def __init__(self, db: Session):
self.db = db
self.risks = RiskService(db)
def summary(self) -> dict[str, Any]:
active_projects = self._count(Project, Project.status.notin_(PROJECT_CLOSED_STATUSES))
open_tasks = self._count(WorkTask, WorkTask.status.notin_(DONE_STATUSES))
pending_approvals = self._count(
ApprovalRequest,
ApprovalRequest.status == ApprovalStatus.PENDING,
)
open_risk_events = self._count(RiskEvent, RiskEvent.status == StatusValue.OPEN)
latest_reports = self.db.execute(
select(WorkReport).order_by(WorkReport.id.desc()).limit(5)
).scalars()
latest_audit_logs = self.db.execute(
select(AuditLog).order_by(AuditLog.id.desc()).limit(10)
).scalars()
risk_summary = self.risks.summary()
return {
"metrics": {
"active_projects": active_projects,
"open_tasks": open_tasks,
"pending_approvals": pending_approvals,
"open_risk_events": open_risk_events,
"risk_level": risk_summary["risk_level"],
"risk_score": float(risk_summary["risk_score"]),
},
"risk_counts": {
"overdue_tasks": len(risk_summary["overdue_tasks"]),
"delayed_projects": len(risk_summary["delayed_projects"]),
"over_budget_projects": len(risk_summary["over_budget_projects"]),
"fund_risks": len(risk_summary["fund_risks"]),
"supplier_risks": len(risk_summary["supplier_risks"]),
},
"latest_reports": [serialize_model(item) for item in latest_reports],
"latest_audit_logs": [serialize_model(item) for item in latest_audit_logs],
}
def _count(self, model: type, *conditions: Any) -> int:
stmt = select(func.count()).select_from(model)
for condition in conditions:
stmt = stmt.where(condition)
return int(self.db.execute(stmt).scalar() or 0)