feat: 增强数据模型

- Alert: 新增 bind_id/device_id/bbox/level/work_order_id
- 新增 WorkOrder 工单模型
- 新增 EdgeDevice 边缘设备模型
- 新增 AlertLevel/WorkOrderStatus/DeviceStatus 枚举
- 修复 SQLite 主键自增问题

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-05 13:56:48 +08:00
parent 78d147b7da
commit 9ea47938dc

View File

@@ -1,8 +1,15 @@
"""
数据模型定义
"""
import enum import enum
import os
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Optional from typing import Optional
from sqlalchemy import Column, String, Integer, DateTime, Text, Enum, JSON, create_engine from sqlalchemy import (
from sqlalchemy.orm import declarative_base, sessionmaker 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 sqlalchemy.pool import StaticPool
from app.config import settings from app.config import settings
@@ -10,47 +17,102 @@ from app.config import settings
Base = declarative_base() Base = declarative_base()
# ==================== 枚举定义 ====================
class AlertStatus(str, enum.Enum): class AlertStatus(str, enum.Enum):
PENDING = "pending" """告警状态"""
CONFIRMED = "confirmed" PENDING = "pending" # 待处理
IGNORED = "ignored" CONFIRMED = "confirmed" # 已确认
RESOLVED = "resolved" IGNORED = "ignored" # 已忽略
RESOLVED = "resolved" # 已解决
DISPATCHED = "dispatched" # 已派单
class AlertLevel(str, enum.Enum): class AlertLevel(str, enum.Enum):
"""告警级别"""
LOW = "low" LOW = "low"
MEDIUM = "medium" MEDIUM = "medium"
HIGH = "high" HIGH = "high"
CRITICAL = "critical" 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): class Alert(Base):
"""告警表"""
__tablename__ = "alerts" __tablename__ = "alerts"
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
alert_no = Column(String(32), unique=True, nullable=False, index=True) alert_no = Column(String(32), unique=True, nullable=False, index=True)
# 来源信息
camera_id = Column(String(64), nullable=False, index=True) camera_id = Column(String(64), nullable=False, index=True)
roi_id = Column(String(64), nullable=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) alert_type = Column(String(64), nullable=False)
algorithm = Column(String(128), nullable=True) algorithm = Column(String(128), nullable=True)
confidence = Column(Integer, nullable=True) confidence = Column(Integer, nullable=True)
duration_minutes = Column(Integer, nullable=True) duration_minutes = Column(Integer, nullable=True)
trigger_time = Column(DateTime, nullable=False) trigger_time = Column(DateTime, nullable=False)
message = Column(Text, nullable=True) message = Column(Text, nullable=True)
bbox = Column(JSON, nullable=True)
# 截图
snapshot_url = Column(String(512), nullable=True) snapshot_url = Column(String(512), nullable=True)
snapshot_path = 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) handle_remark = Column(Text, nullable=True)
handled_by = Column(String(64), nullable=True) handled_by = Column(String(64), nullable=True)
handled_at = Column(DateTime, nullable=True) handled_at = Column(DateTime, nullable=True)
# AI分析
ai_analysis = Column(JSON, nullable=True) 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)) 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: def to_dict(self) -> dict:
return { return {
@@ -58,23 +120,142 @@ class Alert(Base):
"alert_no": self.alert_no, "alert_no": self.alert_no,
"camera_id": self.camera_id, "camera_id": self.camera_id,
"roi_id": self.roi_id, "roi_id": self.roi_id,
"bind_id": self.bind_id,
"device_id": self.device_id,
"alert_type": self.alert_type, "alert_type": self.alert_type,
"algorithm": self.algorithm, "algorithm": self.algorithm,
"confidence": self.confidence, "confidence": self.confidence,
"duration_minutes": self.duration_minutes, "duration_minutes": self.duration_minutes,
"trigger_time": self.trigger_time.isoformat() if self.trigger_time else None, "trigger_time": self.trigger_time.isoformat() if self.trigger_time else None,
"message": self.message, "message": self.message,
"bbox": self.bbox,
"snapshot_url": self.snapshot_url, "snapshot_url": self.snapshot_url,
"status": self.status.value if self.status else None, "status": self.status.value if self.status else None,
"level": self.level.value if self.level else None,
"handle_remark": self.handle_remark, "handle_remark": self.handle_remark,
"handled_by": self.handled_by, "handled_by": self.handled_by,
"handled_at": self.handled_at.isoformat() if self.handled_at else None, "handled_at": self.handled_at.isoformat() if self.handled_at else None,
"ai_analysis": self.ai_analysis, "ai_analysis": self.ai_analysis,
"work_order_id": self.work_order_id,
"created_at": self.created_at.isoformat() if self.created_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, "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 _engine = None
_SessionLocal = None _SessionLocal = None