diff --git a/app/models.py b/app/models.py index 7cf8442..191eea7 100644 --- a/app/models.py +++ b/app/models.py @@ -1,8 +1,15 @@ +""" +数据模型定义 +""" import enum +import os from datetime import datetime, timezone from typing import Optional -from sqlalchemy import Column, String, Integer, DateTime, Text, Enum, JSON, create_engine -from sqlalchemy.orm import declarative_base, sessionmaker +from sqlalchemy import ( + Column, String, Integer, BigInteger, DateTime, Text, Enum, JSON, + ForeignKey, create_engine, Index +) +from sqlalchemy.orm import declarative_base, sessionmaker, relationship from sqlalchemy.pool import StaticPool from app.config import settings @@ -10,47 +17,102 @@ from app.config import settings Base = declarative_base() +# ==================== 枚举定义 ==================== + class AlertStatus(str, enum.Enum): - PENDING = "pending" - CONFIRMED = "confirmed" - IGNORED = "ignored" - RESOLVED = "resolved" + """告警状态""" + PENDING = "pending" # 待处理 + CONFIRMED = "confirmed" # 已确认 + IGNORED = "ignored" # 已忽略 + RESOLVED = "resolved" # 已解决 + DISPATCHED = "dispatched" # 已派单 class AlertLevel(str, enum.Enum): + """告警级别""" LOW = "low" MEDIUM = "medium" HIGH = "high" CRITICAL = "critical" +class WorkOrderStatus(str, enum.Enum): + """工单状态""" + CREATED = "created" # 已创建 + ASSIGNED = "assigned" # 已派发 + PROCESSING = "processing" # 处理中 + COMPLETED = "completed" # 已完成 + CLOSED = "closed" # 已关闭 + + +class WorkOrderPriority(str, enum.Enum): + """工单优先级""" + LOW = "low" + MEDIUM = "medium" + HIGH = "high" + URGENT = "urgent" + + +class DeviceStatus(str, enum.Enum): + """设备状态""" + ONLINE = "online" + OFFLINE = "offline" + ERROR = "error" + + +# ==================== 数据模型 ==================== + class Alert(Base): + """告警表""" __tablename__ = "alerts" id = Column(Integer, primary_key=True, autoincrement=True) alert_no = Column(String(32), unique=True, nullable=False, index=True) + # 来源信息 camera_id = Column(String(64), nullable=False, index=True) roi_id = Column(String(64), nullable=True) + bind_id = Column(String(64), nullable=True) + device_id = Column(String(64), nullable=True, index=True) + + # 告警内容 alert_type = Column(String(64), nullable=False) algorithm = Column(String(128), nullable=True) confidence = Column(Integer, nullable=True) duration_minutes = Column(Integer, nullable=True) trigger_time = Column(DateTime, nullable=False) message = Column(Text, nullable=True) + bbox = Column(JSON, nullable=True) + # 截图 snapshot_url = Column(String(512), nullable=True) snapshot_path = Column(String(512), nullable=True) - status = Column(Enum(AlertStatus), default=AlertStatus.PENDING) + # 处理状态 + status = Column(Enum(AlertStatus), default=AlertStatus.PENDING, index=True) + level = Column(Enum(AlertLevel), default=AlertLevel.MEDIUM) handle_remark = Column(Text, nullable=True) handled_by = Column(String(64), nullable=True) handled_at = Column(DateTime, nullable=True) + # AI分析 ai_analysis = Column(JSON, nullable=True) + # 关联工单 + work_order_id = Column(Integer, ForeignKey("work_orders.id"), nullable=True) + + # 时间戳 created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) - updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), + onupdate=lambda: datetime.now(timezone.utc)) + + # 关系 + work_order = relationship("WorkOrder", back_populates="alerts", foreign_keys=[work_order_id]) + + __table_args__ = ( + Index('idx_alert_trigger_time', 'trigger_time'), + Index('idx_alert_camera_status', 'camera_id', 'status'), + ) def to_dict(self) -> dict: return { @@ -58,23 +120,142 @@ class Alert(Base): "alert_no": self.alert_no, "camera_id": self.camera_id, "roi_id": self.roi_id, + "bind_id": self.bind_id, + "device_id": self.device_id, "alert_type": self.alert_type, "algorithm": self.algorithm, "confidence": self.confidence, "duration_minutes": self.duration_minutes, "trigger_time": self.trigger_time.isoformat() if self.trigger_time else None, "message": self.message, + "bbox": self.bbox, "snapshot_url": self.snapshot_url, "status": self.status.value if self.status else None, + "level": self.level.value if self.level else None, "handle_remark": self.handle_remark, "handled_by": self.handled_by, "handled_at": self.handled_at.isoformat() if self.handled_at else None, "ai_analysis": self.ai_analysis, + "work_order_id": self.work_order_id, "created_at": self.created_at.isoformat() if self.created_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None, } +class WorkOrder(Base): + """工单表""" + __tablename__ = "work_orders" + + id = Column(Integer, primary_key=True, autoincrement=True) + order_no = Column(String(32), unique=True, nullable=False, index=True) + + # 关联告警 + alert_id = Column(Integer, nullable=True) + alert_no = Column(String(32), nullable=True) + + # 工单内容 + title = Column(String(255), nullable=False) + description = Column(Text, nullable=True) + priority = Column(Enum(WorkOrderPriority), default=WorkOrderPriority.MEDIUM) + + # 派发信息 + assignee_id = Column(String(64), nullable=True, index=True) + assignee_name = Column(String(64), nullable=True) + department = Column(String(64), nullable=True) + + # 状态 + status = Column(Enum(WorkOrderStatus), default=WorkOrderStatus.CREATED, index=True) + + # 处理结果 + result = Column(Text, nullable=True) + attachments = Column(JSON, nullable=True) + + # 时间 + deadline = Column(DateTime, nullable=True) + assigned_at = Column(DateTime, nullable=True) + started_at = Column(DateTime, nullable=True) + completed_at = Column(DateTime, nullable=True) + created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), + onupdate=lambda: datetime.now(timezone.utc)) + + # 关系 + alerts = relationship("Alert", back_populates="work_order", foreign_keys=[Alert.work_order_id]) + + def to_dict(self) -> dict: + return { + "id": self.id, + "order_no": self.order_no, + "alert_id": self.alert_id, + "alert_no": self.alert_no, + "title": self.title, + "description": self.description, + "priority": self.priority.value if self.priority else None, + "assignee_id": self.assignee_id, + "assignee_name": self.assignee_name, + "department": self.department, + "status": self.status.value if self.status else None, + "result": self.result, + "attachments": self.attachments, + "deadline": self.deadline.isoformat() if self.deadline else None, + "assigned_at": self.assigned_at.isoformat() if self.assigned_at else None, + "started_at": self.started_at.isoformat() if self.started_at else None, + "completed_at": self.completed_at.isoformat() if self.completed_at else None, + "created_at": self.created_at.isoformat() if self.created_at else None, + "updated_at": self.updated_at.isoformat() if self.updated_at else None, + } + + +class EdgeDevice(Base): + """边缘设备表""" + __tablename__ = "edge_devices" + + id = Column(Integer, primary_key=True, autoincrement=True) + device_id = Column(String(64), unique=True, nullable=False, index=True) + device_name = Column(String(128), nullable=True) + + # 状态 + status = Column(Enum(DeviceStatus), default=DeviceStatus.OFFLINE, index=True) + last_heartbeat = Column(DateTime, nullable=True) + + # 运行信息 + uptime_seconds = Column(BigInteger, nullable=True) + frames_processed = Column(BigInteger, nullable=True) + alerts_generated = Column(BigInteger, nullable=True) + + # 配置 + ip_address = Column(String(45), nullable=True) + stream_count = Column(Integer, nullable=True) + config_version = Column(String(32), nullable=True) + + # 扩展 + extra_info = Column(JSON, nullable=True) + + created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) + updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), + onupdate=lambda: datetime.now(timezone.utc)) + + def to_dict(self) -> dict: + return { + "id": self.id, + "device_id": self.device_id, + "device_name": self.device_name, + "status": self.status.value if self.status else None, + "last_heartbeat": self.last_heartbeat.isoformat() if self.last_heartbeat else None, + "uptime_seconds": self.uptime_seconds, + "frames_processed": self.frames_processed, + "alerts_generated": self.alerts_generated, + "ip_address": self.ip_address, + "stream_count": self.stream_count, + "config_version": self.config_version, + "extra_info": self.extra_info, + "created_at": self.created_at.isoformat() if self.created_at else None, + "updated_at": self.updated_at.isoformat() if self.updated_at else None, + } + + +# ==================== 数据库管理 ==================== + _engine = None _SessionLocal = None