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:
197
app/models.py
197
app/models.py
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user