Part A: 数据层
- 新增 WechatCardState 模型(order_id ↔ alarm_id 映射 + response_code)
- 新建 models_iot.py(IoT 工单只读 ORM:ops_order + security_ext + clean_ext)
- config.py 新增 IOT_DATABASE_URL 配置
Part B: 企微解耦(alarm_id → order_id)
- wechat_service: response_code 存储迁移到 wechat_card_state,集中 helper
- 卡片发送/更新方法改用 order_id,按钮 key: confirm_{order_id}
- wechat_callback: 按钮解析改 order_id,反查 alarm_id(可空)
- wechat_notify_api: send-card/sync-status 以 orderId 为主键
- yudao_aiot_alarm: 卡片操作改用 order_id,删重复 helper
Part C: Agent 工具全面改为工单驱动
- 新建 order_query.py(查 IoT ops_order,支持安保+保洁工单)
- 新建 order_action.py(操作工单状态 + 提交处理结果)
- 更新 prompts.py 为工单助手
- 更新工具注册(__init__.py)
Part D: 日报改为工单驱动
- daily_report_service 从查 alarm_event 改为查 IoT ops_order + 扩展表
- 支持安保+保洁工单统计
139 lines
5.3 KiB
Python
139 lines
5.3 KiB
Python
"""
|
||
IoT 工单只读 ORM 模型
|
||
|
||
映射 aiot-platform 库的工单表,vsp-service 通过第二个 SQLAlchemy 引擎跨库只读查询。
|
||
"""
|
||
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
|
||
from sqlalchemy import (
|
||
Column, String, Integer, SmallInteger, BigInteger, DateTime, Text,
|
||
create_engine, Index,
|
||
)
|
||
from sqlalchemy.orm import declarative_base, sessionmaker
|
||
|
||
from app.config import settings
|
||
from app.utils.logger import logger
|
||
|
||
IotBase = declarative_base()
|
||
|
||
|
||
class IotOpsOrder(IotBase):
|
||
"""IoT 通用工单主表(只读)"""
|
||
__tablename__ = "ops_order"
|
||
|
||
id = Column(BigInteger, primary_key=True)
|
||
order_code = Column(String(64), comment="工单编号")
|
||
order_type = Column(String(32), comment="工单类型: SECURITY/CLEAN")
|
||
title = Column(String(200), comment="工单标题")
|
||
description = Column(Text, comment="工单描述")
|
||
priority = Column(SmallInteger, comment="优先级: 0低/1中/2高")
|
||
status = Column(String(32), comment="状态: PENDING/ASSIGNED/ARRIVED/PAUSED/COMPLETED/CANCELLED")
|
||
area_id = Column(BigInteger, comment="区域ID")
|
||
location = Column(String(200), comment="位置")
|
||
assignee_id = Column(BigInteger, comment="指派人ID")
|
||
assignee_name = Column(String(100), comment="指派人姓名")
|
||
start_time = Column(DateTime, comment="开始时间")
|
||
end_time = Column(DateTime, comment="结束时间")
|
||
creator = Column(String(64), comment="创建者")
|
||
create_time = Column(DateTime, comment="创建时间")
|
||
update_time = Column(DateTime, comment="更新时间")
|
||
deleted = Column(SmallInteger, default=0, comment="是否删除")
|
||
tenant_id = Column(BigInteger, default=0, comment="租户编号")
|
||
|
||
|
||
class IotOpsOrderSecurityExt(IotBase):
|
||
"""安保工单扩展表(只读)"""
|
||
__tablename__ = "ops_order_security_ext"
|
||
|
||
id = Column(BigInteger, primary_key=True)
|
||
ops_order_id = Column(BigInteger, nullable=False, comment="工单ID")
|
||
alarm_id = Column(String(64), comment="关联告警ID")
|
||
alarm_type = Column(String(32), comment="告警类型")
|
||
camera_id = Column(String(64), comment="摄像头ID")
|
||
camera_name = Column(String(128), comment="摄像头名称")
|
||
roi_id = Column(String(64), comment="ROI区域ID")
|
||
image_url = Column(String(512), comment="截图URL")
|
||
assigned_user_id = Column(BigInteger, comment="处理人ID")
|
||
assigned_user_name = Column(String(100), comment="处理人姓名")
|
||
result = Column(Text, comment="处理结果")
|
||
result_img_urls = Column(Text, comment="结果图片URL")
|
||
false_alarm = Column(SmallInteger, comment="是否误报")
|
||
dispatched_time = Column(DateTime, comment="派单时间")
|
||
confirmed_time = Column(DateTime, comment="确认时间")
|
||
completed_time = Column(DateTime, comment="完成时间")
|
||
creator = Column(String(64))
|
||
create_time = Column(DateTime)
|
||
updater = Column(String(64))
|
||
update_time = Column(DateTime)
|
||
deleted = Column(SmallInteger, default=0)
|
||
tenant_id = Column(BigInteger, default=0)
|
||
|
||
|
||
class IotOpsOrderCleanExt(IotBase):
|
||
"""保洁工单扩展表(只读)"""
|
||
__tablename__ = "ops_order_clean_ext"
|
||
|
||
id = Column(BigInteger, primary_key=True)
|
||
ops_order_id = Column(BigInteger, nullable=False, comment="工单ID")
|
||
is_auto = Column(SmallInteger, default=1, comment="是否自动工单")
|
||
expected_duration = Column(Integer, comment="预计作业时长(分钟)")
|
||
arrived_time = Column(DateTime, comment="实际到岗时间")
|
||
completed_time = Column(DateTime, comment="实际完成时间")
|
||
pause_start_time = Column(DateTime, comment="暂停开始时间")
|
||
pause_end_time = Column(DateTime, comment="暂停结束时间")
|
||
total_pause_seconds = Column(Integer, default=0, comment="累计暂停时长(秒)")
|
||
cleaning_type = Column(String(32), comment="保洁类型: ROUTINE/DEEP/SPOT/EMERGENCY")
|
||
difficulty_level = Column(SmallInteger, comment="难度等级(1-5)")
|
||
creator = Column(String(64))
|
||
create_time = Column(DateTime)
|
||
updater = Column(String(64))
|
||
update_time = Column(DateTime)
|
||
deleted = Column(SmallInteger, default=0)
|
||
tenant_id = Column(BigInteger, default=0)
|
||
dispatched_time = Column(DateTime, comment="实际下发时间")
|
||
first_dispatched_time = Column(DateTime, comment="首次下发时间")
|
||
|
||
|
||
# ==================== IoT 数据库连接管理 ====================
|
||
|
||
_iot_engine = None
|
||
_IotSessionLocal = None
|
||
|
||
|
||
def get_iot_engine():
|
||
"""获取 IoT 数据库引擎(懒初始化)"""
|
||
global _iot_engine
|
||
if _iot_engine is None:
|
||
iot_url = settings.iot_database_url
|
||
if not iot_url:
|
||
raise RuntimeError("IOT_DATABASE_URL 未配置,无法连接 IoT 数据库")
|
||
_iot_engine = create_engine(
|
||
iot_url,
|
||
echo=False,
|
||
pool_recycle=1800,
|
||
pool_pre_ping=True,
|
||
)
|
||
logger.info(f"IoT 数据库引擎已创建")
|
||
return _iot_engine
|
||
|
||
|
||
def get_iot_session():
|
||
"""获取 IoT 数据库只读 session"""
|
||
global _IotSessionLocal
|
||
if _IotSessionLocal is None:
|
||
_IotSessionLocal = sessionmaker(
|
||
bind=get_iot_engine(), autocommit=False, autoflush=False,
|
||
)
|
||
return _IotSessionLocal()
|
||
|
||
|
||
def close_iot_db():
|
||
"""关闭 IoT 数据库连接"""
|
||
global _iot_engine, _IotSessionLocal
|
||
if _iot_engine:
|
||
_iot_engine.dispose()
|
||
_iot_engine = None
|
||
_IotSessionLocal = None
|