feat(aiot): 告警结束接口 + 持续时长更新 + first_frame_time存储

新增告警结束接口:
- 新增EdgeAlarmResolve请求模型
- 新增POST /edge/resolve端点(无需认证,Edge设备调用)
- 新增resolve_alarm服务方法:更新duration_ms、last_frame_time
- 人员回岗/非工作时间自动设置alarm_status=CLOSED、handle_status=DONE

告警创建修复:
- create_from_edge_report现在从ext_data读取first_frame_time写入数据库
- create_from_edge_report现在从ext_data读取duration_ms写入数据库
- 统一edge_node_id从ext_data提取
This commit is contained in:
2026-02-11 17:56:02 +08:00
parent cf41db2983
commit f3af9cac22
3 changed files with 85 additions and 4 deletions

View File

@@ -171,12 +171,21 @@ class AlarmEventService:
alarm_type = data.get("alarm_type", "unknown")
alarm_level = data.get("alarm_level")
ext_data = data.get("ext_data") or {}
if alarm_level is None:
# 从 ext_data 取 duration_ms
ext_data = data.get("ext_data") or {}
duration_ms = ext_data.get("duration_ms")
alarm_level = _determine_alarm_level(alarm_type, confidence or 0, duration_ms)
# 解析 first_frame_time离岗开始时间由 Edge 在 ext_data 中传递)
first_frame_time = None
first_frame_time_str = ext_data.get("first_frame_time")
if first_frame_time_str:
try:
first_frame_time = datetime.fromisoformat(first_frame_time_str.replace("Z", "+00:00"))
except ValueError:
first_frame_time = None
alarm = AlarmEvent(
alarm_id=alarm_id,
alarm_type=alarm_type,
@@ -184,12 +193,14 @@ class AlarmEventService:
device_id=data.get("device_id", "unknown"),
scene_id=data.get("scene_id"),
event_time=event_time,
first_frame_time=first_frame_time,
duration_ms=ext_data.get("duration_ms"),
alarm_level=alarm_level,
confidence_score=confidence,
alarm_status="NEW",
handle_status="UNHANDLED",
snapshot_url=data.get("snapshot_url"), # COS object_key
edge_node_id=data.get("ext_data", {}).get("edge_node_id") if data.get("ext_data") else None,
snapshot_url=data.get("snapshot_url"),
edge_node_id=ext_data.get("edge_node_id"),
)
db.add(alarm)
@@ -520,6 +531,46 @@ class AlarmEventService:
finally:
db.close()
def resolve_alarm(self, alarm_id: str, duration_ms: int, last_frame_time: str, resolve_type: str) -> bool:
"""更新告警的持续时长和结束时间"""
db = get_session()
try:
alarm = db.query(AlarmEvent).filter(AlarmEvent.alarm_id == alarm_id).first()
if not alarm:
return False
alarm.duration_ms = duration_ms
# 解析 last_frame_time
try:
alarm.last_frame_time = datetime.fromisoformat(last_frame_time.replace("Z", "+00:00"))
except Exception:
alarm.last_frame_time = datetime.now(timezone.utc)
# 如果是人员回岗,标记为自动关闭
if resolve_type == "person_returned":
alarm.alarm_status = "CLOSED"
alarm.handle_status = "DONE"
alarm.handle_remark = "人员回岗自动关闭"
alarm.handled_at = datetime.now(timezone.utc)
elif resolve_type == "non_work_time":
alarm.alarm_status = "CLOSED"
alarm.handle_status = "DONE"
alarm.handle_remark = "非工作时间自动关闭"
alarm.handled_at = datetime.now(timezone.utc)
alarm.updated_at = datetime.now(timezone.utc)
db.commit()
logger.info(f"告警已更新结束信息: {alarm_id}, duration={duration_ms}ms, type={resolve_type}")
return True
except Exception as e:
db.rollback()
logger.error(f"更新告警结束信息失败: {e}")
return False
finally:
db.close()
def save_llm_analysis(
self,
alarm_id: str,