Files
company-ai-platform/app/modules/dashboard/service.py
JiuContinent 19e59e83cc ```
feat: 添加数据库迁移脚本并更新Dockerfile配置

- 在Dockerfile中添加alembic配置文件和目录的复制指令
- 更新alembic/env.py注册新的模块模型:events、workflows、writebacks
- 生成完整的初始数据库schema迁移脚本,包含以下表:
  - approval_requests, attendance_records, audit_logs, domain_events
  - expenses, feishu_event_receipts, fund_accounts, legacy_sync_runs
  - official_writeback_runs, performance_metrics, policies, procurements
  - projects, report_push_runs, risk_event_actions, risk_events
  - standards, suppliers, work_reports, work_tasks, workflow_actions
  - workflow_instances等21个数据表结构定义
- 在API路由器中添加新模块的路由:events、workflows、writebacks、observability
```
2026-07-08 14:08:03 +08:00

116 lines
5.0 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 (
LegacySyncRun,
Project,
ReportPushRun,
RiskEvent,
WorkReport,
WorkTask,
)
from app.modules.business.service import serialize_model
from app.modules.events.constants import EventStatus
from app.modules.events.models import DomainEvent
from app.modules.reports.constants import ReportPushStatus
from app.modules.risk.service import RiskService
from app.modules.workflows.constants import WorkflowStatus
from app.modules.workflows.models import WorkflowInstance
from app.modules.writebacks.constants import WritebackStatus
from app.modules.writebacks.models import OfficialWritebackRun
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)
unassigned_open_risks = self._count(
RiskEvent,
RiskEvent.status == StatusValue.OPEN,
RiskEvent.assigned_to.is_(None),
)
failed_push_runs = self._count(ReportPushRun, ReportPushRun.status == ReportPushStatus.FAILED)
pending_events = self._count(DomainEvent, DomainEvent.status == EventStatus.PENDING)
failed_events = self._count(DomainEvent, DomainEvent.status == EventStatus.FAILED)
running_workflows = self._count(
WorkflowInstance,
WorkflowInstance.status == WorkflowStatus.RUNNING,
)
failed_workflows = self._count(
WorkflowInstance,
WorkflowInstance.status == WorkflowStatus.FAILED,
)
disabled_writebacks = self._count(
OfficialWritebackRun,
OfficialWritebackRun.status == WritebackStatus.DISABLED,
)
failed_writebacks = self._count(
OfficialWritebackRun,
OfficialWritebackRun.status == WritebackStatus.FAILED,
)
latest_reports = self.db.execute(
select(WorkReport).order_by(WorkReport.id.desc()).limit(5)
).scalars()
latest_push_runs = self.db.execute(
select(ReportPushRun).order_by(ReportPushRun.id.desc()).limit(10)
).scalars()
latest_sync_runs = self.db.execute(
select(LegacySyncRun).order_by(LegacySyncRun.id.desc()).limit(10)
).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,
"unassigned_open_risks": unassigned_open_risks,
"failed_push_runs": failed_push_runs,
"pending_events": pending_events,
"failed_events": failed_events,
"running_workflows": running_workflows,
"failed_workflows": failed_workflows,
"disabled_writebacks": disabled_writebacks,
"failed_writebacks": failed_writebacks,
"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_push_runs": [serialize_model(item) for item in latest_push_runs],
"latest_sync_runs": [serialize_model(item) for item in latest_sync_runs],
"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)