"""预警 API""" from fastapi import APIRouter, Depends, Query, HTTPException from sqlalchemy.orm import Session from app.database import get_db from app.auth_middleware import require_auth, require_role from app.models import KPIAlert, OperationLog, ActionPlan import logging logger = logging.getLogger("cma.alerts") router = APIRouter(prefix="/api/cma/alerts", tags=["预警"], dependencies=[Depends(require_role("ceo", "finance", "business", "it"))], ) @router.get("") def list_alerts(status: str = None, page: int = Query(1, ge=1), db: Session = Depends(get_db)): query = db.query(KPIAlert) if status: query = query.filter(KPIAlert.status == status) total = query.count() alerts = query.order_by(KPIAlert.created_at.desc()).offset((page-1)*20).limit(20).all() return {"total": total, "data": [{c.name: getattr(a, c.name) for c in KPIAlert.__table__.columns} for a in alerts]} @router.post("/{alert_id}/resolve") def resolve_alert(alert_id: int, data: dict, db: Session = Depends(get_db)): alert = db.query(KPIAlert).filter(KPIAlert.id == alert_id).first() if not alert: raise HTTPException(404, "预警不存在") alert.status = "resolved" alert.resolution = data.get("resolution", "") alert.assignee = data.get("assignee", alert.assignee) from datetime import datetime; alert.resolved_at = datetime.now() db.commit() db.refresh(alert) return { "message": "已处理", "assignee": alert.assignee, "alert": {c.name: getattr(alert, c.name) for c in KPIAlert.__table__.columns}, "suggest_create_action_plan": alert.alert_level == "red", } @router.post("/{alert_id}/process") def process_alert(alert_id: int, data: dict, db: Session = Depends(get_db)): """标记预警为处理中""" alert = db.query(KPIAlert).filter(KPIAlert.id == alert_id).first() if not alert: raise HTTPException(404, "预警不存在") if alert.status == "resolved": raise HTTPException(400, "已处理的预警不能重复处理") assignee = data.get("assignee") if not assignee: raise HTTPException(400, "缺少处理人") alert.status = "processing" alert.assignee = assignee db.commit() db.refresh(alert) return {"message": "已标记为处理中", "alert": {c.name: getattr(alert, c.name) for c in KPIAlert.__table__.columns}} @router.post("/{alert_id}/escalate") def escalate_alert(alert_id: int, data: dict, db: Session = Depends(get_db)): """升级预警级别""" alert = db.query(KPIAlert).filter(KPIAlert.id == alert_id).first() if not alert: raise HTTPException(404, "预警不存在") assignee = data.get("assignee") if not assignee: raise HTTPException(400, "缺少处理人") if alert.status == "resolved": raise HTTPException(400, "已处理的预警不能升级") if alert.alert_level != "red": alert.alert_level = "red" alert.assignee = assignee db.commit() db.refresh(alert) return {"message": "已升级", "alert": {c.name: getattr(alert, c.name) for c in KPIAlert.__table__.columns}} @router.get("/check-timeout") def check_alert_timeout(db: Session = Depends(get_db)): """超时预警检测 — 超过24小时未处理的pending预警自动升级为红色""" from datetime import datetime, timedelta cutoff = datetime.now() - timedelta(hours=24) timeout_alerts = db.query(KPIAlert).filter( KPIAlert.status == "pending", KPIAlert.created_at < cutoff, KPIAlert.alert_level != "red", ).all() upgraded_count = 0 for alert in timeout_alerts: alert.alert_level = "red" alert.status = "processing" upgraded_count += 1 if upgraded_count: db.commit() return {"total_timeout": len(timeout_alerts), "upgraded_count": upgraded_count} @router.post("/{alert_id}/create-action-plan") def create_action_plan_from_alert(alert_id: int, data: dict, db: Session = Depends(get_db)): """从预警创建改善行动计划""" alert = db.query(KPIAlert).filter(KPIAlert.id == alert_id).first() if not alert: raise HTTPException(404, "预警不存在") if alert.action_plan_linked_id: raise HTTPException(400, "已关联改善计划") plan = ActionPlan( alert_id=alert.id, kpi_id=alert.kpi_id, title=f"改善: {alert.alert_message}", assignee=data.get("assignee", ""), priority="high" if alert.alert_level == "red" else "medium", created_by=data.get("created_by", ""), ) db.add(plan) db.commit() db.refresh(plan) alert.action_plan_linked_id = plan.id db.commit() return {"message": "改善行动计划已创建", "plan_id": plan.id, "priority": plan.priority} # ────────────────────────────────────────────── # 功能4: 风险矩阵热力图 (CMA P2 - ERM框架、风险识别四象限) RISK_MATRIX_DATA = { "hanke": { "entity_name": "陕西酣客(白酒经销)", "quadrants": [ { "impact": "high", "probability": "high", "label": "高影响×高概率", "risks": [ { "id": "risk_001", "name": "流动性风险", "impact_label": "高", "probability_label": "高", "detail": "现金2.2万 vs 短债350万 → 断流风险", "impact_value": 90, "probability_value": 85, "type": "red", "measures": ["催收大额应收", "协商短期借款续贷"], "responsible": "任富海", "deadline": "7月底", }, { "id": "risk_002", "name": "合规风险", "impact_label": "高", "probability_label": "高", "detail": "欠税426万 · 折旧违规", "impact_value": 95, "probability_value": 80, "type": "red", "measures": ["补缴欠税计划", "重新梳理折旧政策"], "responsible": "任富海", "deadline": "8月底", }, ], }, { "impact": "high", "probability": "medium", "label": "高影响×中概率", "risks": [ { "id": "risk_003", "name": "政策风险", "impact_label": "高", "probability_label": "中", "detail": "白酒消费税调整可能导致成本上升15-20%", "impact_value": 85, "probability_value": 50, "type": "orange", "measures": ["关注政策动向", "预留税务缓冲资金"], "responsible": "财务部", "deadline": "持续关注", }, ], }, { "impact": "medium", "probability": "high", "label": "中影响×高概率", "risks": [ { "id": "risk_004", "name": "运营风险", "impact_label": "中", "probability_label": "高", "detail": "Model C 成本模型未落地,成本核算偏差", "impact_value": 60, "probability_value": 80, "type": "yellow", "measures": ["推动Model C落地", "建立成本标准化流程"], "responsible": "财务部", "deadline": "8月中", }, ], }, { "impact": "medium", "probability": "medium", "label": "中影响×中概率", "risks": [ { "id": "risk_005", "name": "战略风险", "impact_label": "中", "probability_label": "中", "detail": "酒类零交易,新业务方向不确定", "impact_value": 55, "probability_value": 55, "type": "yellow", "measures": ["制定新业务评估框架", "定期战略复盘"], "responsible": "管理层", "deadline": "9月底", }, ], }, { "impact": "low", "probability": "low", "label": "低影响×低概率", "risks": [ { "id": "risk_006", "name": "市场风险", "impact_label": "低", "probability_label": "低", "detail": "行业需求波动,但酣客已基本退出市场", "impact_value": 25, "probability_value": 20, "type": "green", "measures": ["定期监控行业数据"], "responsible": "业务部", "deadline": "每季度", }, { "id": "risk_007", "name": "人员风险", "impact_label": "低", "probability_label": "低", "detail": "核心团队稳定,短期内无流失风险", "impact_value": 20, "probability_value": 15, "type": "green", "measures": ["保持团队激励", "关键岗位备份"], "responsible": "人事部", "deadline": "持续", }, ], }, ], }, "bohai": { "entity_name": "陕西博海科技(IT服务)", "quadrants": [ { "impact": "high", "probability": "medium", "label": "高影响×中概率", "risks": [ { "id": "risk_b_001", "name": "现金流风险", "impact_label": "高", "probability_label": "中", "detail": "应收账款账期延长,现金流紧张", "impact_value": 85, "probability_value": 55, "type": "orange", "measures": ["加快应收催收", "建立信用管理制度"], "responsible": "任富海", "deadline": "7月底", }, ], }, { "impact": "medium", "probability": "high", "label": "中影响×高概率", "risks": [ { "id": "risk_b_002", "name": "项目交付风险", "impact_label": "中", "probability_label": "高", "detail": "多个项目并行,交付压力大", "impact_value": 65, "probability_value": 75, "type": "yellow", "measures": ["优化项目排期", "增加外包资源"], "responsible": "项目部", "deadline": "持续", }, ], }, { "impact": "low", "probability": "medium", "label": "低影响×中概率", "risks": [ { "id": "risk_b_003", "name": "技术迭代风险", "impact_label": "低", "probability_label": "中", "detail": "新技术跟踪不及时,可能落后", "impact_value": 30, "probability_value": 45, "type": "green", "measures": ["定期技术培训", "技术栈评估"], "responsible": "技术部", "deadline": "每季度", }, ], }, ], }, } @router.get("/risk-matrix") def get_risk_matrix(entity: str = Query("hanke", description="hanke/bohai")): """风险矩阵热力图数据 - CMA P2 ERM四象限""" data = RISK_MATRIX_DATA.get(entity) if not data: data = RISK_MATRIX_DATA["hanke"] data["entity_name"] = f"未知实体({entity}),默认返回酣客数据" return data