""" 告警服务 处理告警 CRUD(旧版告警系统,已迁移至 alarm_event_service) """ import uuid from datetime import datetime, timezone from typing import Optional, List, Dict, Any from app.models import Alert, AlertStatus, AlertLevel, get_session from app.schemas import AlertCreate, AlertHandleRequest from app.services.oss_storage import get_oss_storage from app.utils.logger import logger class AlertService: """告警服务""" def __init__(self): self.oss = get_oss_storage() def generate_alert_no(self) -> str: """生成告警编号""" timestamp = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S") unique_id = uuid.uuid4().hex[:8].upper() return f"ALT{timestamp}{unique_id}" def create_alert( self, alert_data: AlertCreate, snapshot_data: Optional[bytes] = None, ) -> Alert: """创建告警(HTTP 方式)""" db = get_session() try: alert = Alert( alert_no=self.generate_alert_no(), camera_id=alert_data.camera_id, roi_id=alert_data.roi_id, bind_id=getattr(alert_data, 'bind_id', None), device_id=getattr(alert_data, 'device_id', None), alert_type=alert_data.alert_type, algorithm=alert_data.algorithm, confidence=alert_data.confidence, duration_minutes=alert_data.duration_minutes, trigger_time=alert_data.trigger_time, message=alert_data.message, bbox=getattr(alert_data, 'bbox', None), status=AlertStatus.PENDING, level=AlertLevel.MEDIUM, ) if snapshot_data: snapshot_url = self.oss.upload_image(snapshot_data) alert.snapshot_url = snapshot_url db.add(alert) db.commit() db.refresh(alert) logger.info(f"告警创建成功: {alert.alert_no}") return alert finally: db.close() def _determine_level(self, data: Dict[str, Any]) -> AlertLevel: """根据告警数据确定告警级别""" alert_type = data.get("alert_type", "") confidence = data.get("confidence", 0) # 根据告警类型和置信度确定级别 if alert_type == "intrusion": return AlertLevel.HIGH elif alert_type == "leave_post": duration = data.get("duration_minutes", 0) if duration and duration > 30: return AlertLevel.HIGH elif duration and duration > 10: return AlertLevel.MEDIUM return AlertLevel.LOW elif confidence and confidence > 0.9: return AlertLevel.HIGH elif confidence and confidence > 0.7: return AlertLevel.MEDIUM return AlertLevel.MEDIUM def get_alert(self, alert_id: int) -> Optional[Alert]: """获取告警详情""" db = get_session() try: return db.query(Alert).filter(Alert.id == alert_id).first() finally: db.close() def get_alert_by_no(self, alert_no: str) -> Optional[Alert]: """根据告警编号获取""" db = get_session() try: return db.query(Alert).filter(Alert.alert_no == alert_no).first() finally: db.close() def get_alerts( self, camera_id: Optional[str] = None, device_id: Optional[str] = None, alert_type: Optional[str] = None, status: Optional[str] = None, level: Optional[str] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, page: int = 1, page_size: int = 20, ) -> tuple[List[Alert], int]: """获取告警列表""" db = get_session() try: query = db.query(Alert) if camera_id: query = query.filter(Alert.camera_id == camera_id) if device_id: query = query.filter(Alert.device_id == device_id) if alert_type: query = query.filter(Alert.alert_type == alert_type) if status: query = query.filter(Alert.status == status) if level: query = query.filter(Alert.level == level) if start_time: query = query.filter(Alert.trigger_time >= start_time) if end_time: query = query.filter(Alert.trigger_time <= end_time) total = query.count() alerts = ( query.order_by(Alert.created_at.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() ) return alerts, total finally: db.close() def handle_alert( self, alert_id: int, handle_data: AlertHandleRequest, handled_by: Optional[str] = None, ) -> Optional[Alert]: """处理告警""" db = get_session() try: alert = db.query(Alert).filter(Alert.id == alert_id).first() if not alert: return None alert.status = AlertStatus(handle_data.status) alert.handle_remark = handle_data.remark alert.handled_by = handled_by alert.handled_at = datetime.now(timezone.utc) alert.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(alert) logger.info(f"告警处理完成: {alert.alert_no}, 状态: {handle_data.status}") return alert finally: db.close() def dispatch_alert(self, alert_id: int, work_order_id: int) -> Optional[Alert]: """派发告警(关联工单)""" db = get_session() try: alert = db.query(Alert).filter(Alert.id == alert_id).first() if not alert: return None alert.status = AlertStatus.DISPATCHED alert.work_order_id = work_order_id alert.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(alert) logger.info(f"告警已派单: {alert.alert_no} -> 工单 {work_order_id}") return alert finally: db.close() def delete_alert(self, alert_id: int) -> bool: """删除告警""" db = get_session() try: alert = db.query(Alert).filter(Alert.id == alert_id).first() if not alert: return False db.delete(alert) db.commit() logger.info(f"告警已删除: {alert.alert_no}") return True except Exception as e: db.rollback() logger.error(f"删除告警失败: {e}") return False finally: db.close() def get_statistics(self) -> dict: """获取告警统计""" db = get_session() try: total = db.query(Alert).count() pending = db.query(Alert).filter(Alert.status == AlertStatus.PENDING).count() confirmed = db.query(Alert).filter(Alert.status == AlertStatus.CONFIRMED).count() ignored = db.query(Alert).filter(Alert.status == AlertStatus.IGNORED).count() resolved = db.query(Alert).filter(Alert.status == AlertStatus.RESOLVED).count() dispatched = db.query(Alert).filter(Alert.status == AlertStatus.DISPATCHED).count() by_type = {} for alert in db.query(Alert.alert_type).distinct(): alert_type = alert[0] by_type[alert_type] = db.query(Alert).filter(Alert.alert_type == alert_type).count() by_level = {} for level in AlertLevel: by_level[level.value] = db.query(Alert).filter(Alert.level == level).count() return { "total": total, "pending": pending, "confirmed": confirmed, "ignored": ignored, "resolved": resolved, "dispatched": dispatched, "by_type": by_type, "by_level": by_level, } finally: db.close() def update_ai_analysis(self, alert_id: int, analysis: dict) -> Optional[Alert]: """更新 AI 分析结果""" db = get_session() try: alert = db.query(Alert).filter(Alert.id == alert_id).first() if alert: alert.ai_analysis = analysis db.commit() db.refresh(alert) return alert finally: db.close() def get_camera_alert_summary( self, page: int = 1, page_size: int = 10, ) -> dict: """以摄像头为维度获取告警汇总""" from sqlalchemy import func db = get_session() try: # 按摄像头分组统计总数和最近时间 query = db.query( Alert.camera_id, func.count(Alert.id).label("total_count"), func.max(Alert.created_at).label("last_alert_time"), ).group_by(Alert.camera_id) # 总数 total = query.count() # 分页 results = ( query.order_by(func.count(Alert.id).desc()) .offset((page - 1) * page_size) .limit(page_size) .all() ) summary_list = [] for row in results: # 获取该摄像头待处理告警数量 pending_count = ( db.query(Alert) .filter(Alert.camera_id == row.camera_id) .filter(Alert.status == AlertStatus.PENDING) .count() ) # 获取该摄像头最新的一条告警 latest_alert = ( db.query(Alert) .filter(Alert.camera_id == row.camera_id) .order_by(Alert.created_at.desc()) .first() ) summary_list.append({ "cameraId": row.camera_id, "cameraName": row.camera_id, # TODO: 从设备服务获取摄像头名称 "totalCount": row.total_count, "pendingCount": pending_count, "lastAlertTime": row.last_alert_time.isoformat() if row.last_alert_time else None, "lastAlertType": latest_alert.alert_type if latest_alert else None, "lastAlertTypeName": latest_alert.alert_type if latest_alert else None, # 前端会映射 }) return { "list": summary_list, "total": total, } finally: db.close() # 全局单例 alert_service = AlertService() def get_alert_service() -> AlertService: """获取告警服务单例""" return alert_service