- vlm_service.py: 新增illegal_parking和vehicle_congestion VLM复核提示词模板 - alarm_event_service.py: 新增违停告警级别逻辑(按停留时长分级)和拥堵告警级别 - wechat_service.py: ALARM_TYPE_NAMES新增车辆违停/拥堵中文映射 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
697 lines
24 KiB
Python
697 lines
24 KiB
Python
"""
|
||
告警事件服务(新三表结构)
|
||
处理 alarm_event / alarm_event_ext / alarm_llm_analysis 的 CRUD
|
||
"""
|
||
import uuid
|
||
from datetime import datetime, timezone
|
||
from typing import Optional, List, Dict, Any, Tuple
|
||
|
||
from sqlalchemy import func
|
||
|
||
from app.models import AlarmEvent, AlarmEventExt, AlarmLlmAnalysis, get_session
|
||
from app.services.oss_storage import get_oss_storage
|
||
from app.utils.logger import logger
|
||
from app.utils.timezone import beijing_now
|
||
|
||
|
||
def generate_alarm_id() -> str:
|
||
"""生成告警ID: ALM + YYYYMMDDHHmmss + 8位uuid"""
|
||
timestamp = beijing_now().strftime("%Y%m%d%H%M%S")
|
||
unique_id = uuid.uuid4().hex[:8].upper()
|
||
return f"ALM{timestamp}{unique_id}"
|
||
|
||
|
||
def _determine_alarm_level(alarm_type: str, confidence: float, duration_ms: Optional[int] = None) -> int:
|
||
"""
|
||
根据告警类型、置信度和持续时长确定告警级别
|
||
返回: 1提醒 2一般 3严重 4紧急
|
||
"""
|
||
if alarm_type == "intrusion":
|
||
return 3 # 严重
|
||
elif alarm_type == "leave_post":
|
||
# 告警触发时 duration_ms 为 None,设置为一般级别
|
||
if duration_ms is None:
|
||
return 2 # 一般级别(刚触发,持续时长未知)
|
||
|
||
# 根据持续时长判断级别
|
||
if duration_ms > 30 * 60 * 1000:
|
||
return 3 # 严重
|
||
elif duration_ms > 10 * 60 * 1000:
|
||
return 2 # 一般
|
||
return 1 # 提醒
|
||
elif alarm_type == "illegal_parking":
|
||
# 违停:根据停留时长判断级别
|
||
if duration_ms is None:
|
||
return 2 # 一般级别(刚触发)
|
||
if duration_ms > 60 * 60 * 1000:
|
||
return 3 # 严重(超过1小时)
|
||
elif duration_ms > 15 * 60 * 1000:
|
||
return 2 # 一般(超过15分钟)
|
||
return 1 # 提醒
|
||
elif alarm_type == "vehicle_congestion":
|
||
return 2 # 一般级别(拥堵本身不分等级,由持续时长在resolve时重算)
|
||
elif confidence and confidence > 0.9:
|
||
return 3 # 严重
|
||
elif confidence and confidence > 0.7:
|
||
return 2 # 一般
|
||
|
||
return 2 # 默认一般
|
||
|
||
|
||
class AlarmEventService:
|
||
"""告警事件服务"""
|
||
|
||
def __init__(self):
|
||
self.oss = get_oss_storage()
|
||
|
||
def create_from_mqtt(self, mqtt_data: Dict[str, Any]) -> Optional[AlarmEvent]:
|
||
"""从 MQTT 消息创建告警事件"""
|
||
db = get_session()
|
||
try:
|
||
alarm_id = generate_alarm_id()
|
||
|
||
# 解析时间
|
||
timestamp_str = mqtt_data.get("timestamp")
|
||
if timestamp_str:
|
||
try:
|
||
event_time = datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
|
||
except ValueError:
|
||
event_time = beijing_now()
|
||
else:
|
||
event_time = beijing_now()
|
||
|
||
# 解析 first_frame_time(告警首次触发时间)
|
||
first_frame_str = mqtt_data.get("first_frame_time")
|
||
first_frame_time = None
|
||
if first_frame_str:
|
||
try:
|
||
first_frame_time = datetime.fromisoformat(first_frame_str.replace("Z", "+00:00"))
|
||
except ValueError:
|
||
pass
|
||
|
||
# 置信度保持 float 0-1
|
||
confidence = mqtt_data.get("confidence")
|
||
if confidence is not None:
|
||
confidence = float(confidence)
|
||
# 如果传入的是 0-100 范围的值,转为 0-1
|
||
if confidence > 1:
|
||
confidence = confidence / 100.0
|
||
|
||
alarm_type = mqtt_data.get("alert_type", "unknown")
|
||
# 告警创建时不传递 duration_ms,传递 None
|
||
alarm_level = _determine_alarm_level(alarm_type, confidence or 0, None)
|
||
|
||
alarm = AlarmEvent(
|
||
alarm_id=alarm_id,
|
||
alarm_type=alarm_type,
|
||
algorithm_code=mqtt_data.get("algorithm", "YOLO"),
|
||
device_id=mqtt_data.get("camera_id", "unknown"),
|
||
scene_id=mqtt_data.get("roi_id"),
|
||
event_time=event_time,
|
||
first_frame_time=first_frame_time,
|
||
duration_ms=None,
|
||
last_frame_time=None,
|
||
alarm_level=alarm_level,
|
||
confidence_score=confidence,
|
||
alarm_status="NEW",
|
||
handle_status="UNHANDLED",
|
||
edge_node_id=mqtt_data.get("device_id"),
|
||
)
|
||
|
||
db.add(alarm)
|
||
|
||
# 写入扩展表
|
||
ext_data = {}
|
||
for key in ("bind_id", "target_class", "bbox", "message", "alert_id"):
|
||
val = mqtt_data.get(key)
|
||
if val is not None:
|
||
ext_key = "edge_alert_id" if key == "alert_id" else key
|
||
ext_data[ext_key] = val
|
||
|
||
if ext_data:
|
||
ext = AlarmEventExt(
|
||
alarm_id=alarm_id,
|
||
ext_type="EDGE",
|
||
ext_data=ext_data,
|
||
)
|
||
db.add(ext)
|
||
|
||
db.commit()
|
||
db.refresh(alarm)
|
||
|
||
logger.info(f"新告警事件创建: {alarm_id}, type={alarm_type}")
|
||
return alarm
|
||
|
||
except Exception as e:
|
||
db.rollback()
|
||
logger.error(f"创建告警事件失败: {e}")
|
||
return None
|
||
finally:
|
||
db.close()
|
||
|
||
def create_from_edge_report(self, data: Dict[str, Any]) -> Optional[AlarmEvent]:
|
||
"""
|
||
从边缘端 HTTP 上报创建告警事件
|
||
|
||
边缘端通过 POST /admin-api/aiot/alarm/edge/report 上报告警。
|
||
使用边缘端生成的 alarm_id,支持幂等(重复 alarm_id 跳过)。
|
||
|
||
Args:
|
||
data: 边缘端上报数据,字段与 alarm_event 表对齐
|
||
|
||
Returns:
|
||
AlarmEvent 或 None
|
||
"""
|
||
db = get_session()
|
||
try:
|
||
alarm_id = data.get("alarm_id")
|
||
if not alarm_id:
|
||
logger.error("边缘上报缺少 alarm_id")
|
||
return None
|
||
|
||
# 幂等校验:alarm_id 已存在则跳过
|
||
existing = db.query(AlarmEvent).filter(AlarmEvent.alarm_id == alarm_id).first()
|
||
if existing:
|
||
logger.info(f"告警已存在,跳过: {alarm_id}")
|
||
return existing
|
||
|
||
# 解析时间
|
||
event_time_str = data.get("event_time")
|
||
if event_time_str:
|
||
try:
|
||
event_time = datetime.fromisoformat(event_time_str.replace("Z", "+00:00"))
|
||
except ValueError:
|
||
event_time = beijing_now()
|
||
else:
|
||
event_time = beijing_now()
|
||
|
||
# 置信度
|
||
confidence = data.get("confidence_score")
|
||
if confidence is not None:
|
||
confidence = float(confidence)
|
||
if confidence > 1:
|
||
confidence = confidence / 100.0
|
||
|
||
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
|
||
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,
|
||
algorithm_code=data.get("algorithm_code"),
|
||
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"),
|
||
edge_node_id=ext_data.get("edge_node_id"),
|
||
)
|
||
|
||
db.add(alarm)
|
||
|
||
# 写入扩展表
|
||
ext_data = data.get("ext_data")
|
||
if ext_data:
|
||
ext = AlarmEventExt(
|
||
alarm_id=alarm_id,
|
||
ext_type="EDGE_HTTP",
|
||
ext_data=ext_data,
|
||
)
|
||
db.add(ext)
|
||
|
||
db.commit()
|
||
db.refresh(alarm)
|
||
|
||
logger.info(f"边缘端告警创建成功: {alarm_id}, type={alarm_type}, device={data.get('device_id')}")
|
||
return alarm
|
||
|
||
except Exception as e:
|
||
db.rollback()
|
||
logger.error(f"创建边缘端告警失败: {e}")
|
||
return None
|
||
finally:
|
||
db.close()
|
||
|
||
def create_from_http(self, data: Dict[str, Any], snapshot_data: Optional[bytes] = None) -> Optional[AlarmEvent]:
|
||
"""从 HTTP 请求创建告警事件"""
|
||
db = get_session()
|
||
try:
|
||
alarm_id = generate_alarm_id()
|
||
|
||
# 解析时间
|
||
trigger_time = data.get("trigger_time") or data.get("timestamp")
|
||
if isinstance(trigger_time, str):
|
||
try:
|
||
event_time = datetime.fromisoformat(trigger_time.replace("Z", "+00:00"))
|
||
except ValueError:
|
||
event_time = beijing_now()
|
||
elif isinstance(trigger_time, datetime):
|
||
event_time = trigger_time
|
||
else:
|
||
event_time = beijing_now()
|
||
|
||
confidence = data.get("confidence")
|
||
if confidence is not None:
|
||
confidence = float(confidence)
|
||
if confidence > 1:
|
||
confidence = confidence / 100.0
|
||
|
||
duration_minutes = data.get("duration_minutes")
|
||
duration_ms = None
|
||
if duration_minutes is not None:
|
||
duration_ms = int(float(duration_minutes) * 60 * 1000)
|
||
|
||
alarm_type = data.get("alert_type", "unknown")
|
||
alarm_level = _determine_alarm_level(alarm_type, confidence or 0, duration_ms)
|
||
|
||
snapshot_url = None
|
||
if snapshot_data:
|
||
snapshot_url = self.oss.upload_image(snapshot_data)
|
||
|
||
alarm = AlarmEvent(
|
||
alarm_id=alarm_id,
|
||
alarm_type=alarm_type,
|
||
algorithm_code=data.get("algorithm"),
|
||
device_id=data.get("camera_id", "unknown"),
|
||
scene_id=data.get("roi_id"),
|
||
event_time=event_time,
|
||
duration_ms=duration_ms,
|
||
alarm_level=alarm_level,
|
||
confidence_score=confidence,
|
||
alarm_status="NEW",
|
||
handle_status="UNHANDLED",
|
||
snapshot_url=snapshot_url,
|
||
edge_node_id=data.get("device_id"),
|
||
)
|
||
|
||
db.add(alarm)
|
||
|
||
# 写入扩展表
|
||
ext_data = {}
|
||
for key in ("bind_id", "target_class", "bbox", "message"):
|
||
val = data.get(key)
|
||
if val is not None:
|
||
ext_data[key] = val
|
||
|
||
if ext_data:
|
||
ext = AlarmEventExt(
|
||
alarm_id=alarm_id,
|
||
ext_type="POST",
|
||
ext_data=ext_data,
|
||
)
|
||
db.add(ext)
|
||
|
||
db.commit()
|
||
db.refresh(alarm)
|
||
|
||
logger.info(f"HTTP告警事件创建: {alarm_id}")
|
||
return alarm
|
||
|
||
except Exception as e:
|
||
db.rollback()
|
||
logger.error(f"HTTP创建告警事件失败: {e}")
|
||
return None
|
||
finally:
|
||
db.close()
|
||
|
||
def get_alarm(self, alarm_id: str) -> Optional[Dict]:
|
||
"""获取告警详情(含扩展信息)"""
|
||
db = get_session()
|
||
try:
|
||
alarm = db.query(AlarmEvent).filter(AlarmEvent.alarm_id == alarm_id).first()
|
||
if not alarm:
|
||
return None
|
||
|
||
result = alarm.to_dict()
|
||
|
||
# 关联扩展
|
||
ext = db.query(AlarmEventExt).filter(AlarmEventExt.alarm_id == alarm_id).first()
|
||
if ext:
|
||
result["ext"] = ext.to_dict()
|
||
|
||
# 关联 LLM 分析
|
||
analyses = db.query(AlarmLlmAnalysis).filter(
|
||
AlarmLlmAnalysis.alarm_id == alarm_id
|
||
).order_by(AlarmLlmAnalysis.created_at.desc()).all()
|
||
if analyses:
|
||
result["llm_analyses"] = [a.to_dict() for a in analyses]
|
||
|
||
return result
|
||
finally:
|
||
db.close()
|
||
|
||
def get_alarms(
|
||
self,
|
||
device_id: Optional[str] = None,
|
||
alarm_type: Optional[str] = None,
|
||
alarm_status: Optional[str] = None,
|
||
alarm_level: Optional[int] = None,
|
||
start_time: Optional[datetime] = None,
|
||
end_time: Optional[datetime] = None,
|
||
edge_node_id: Optional[str] = None,
|
||
page: int = 1,
|
||
page_size: int = 20,
|
||
) -> Tuple[List[AlarmEvent], int]:
|
||
"""分页查询告警列表"""
|
||
db = get_session()
|
||
try:
|
||
query = db.query(AlarmEvent)
|
||
|
||
if device_id:
|
||
query = query.filter(AlarmEvent.device_id == device_id)
|
||
if alarm_type:
|
||
query = query.filter(AlarmEvent.alarm_type == alarm_type)
|
||
if alarm_status:
|
||
query = query.filter(AlarmEvent.alarm_status == alarm_status)
|
||
if alarm_level is not None:
|
||
query = query.filter(AlarmEvent.alarm_level == alarm_level)
|
||
if edge_node_id:
|
||
query = query.filter(AlarmEvent.edge_node_id == edge_node_id)
|
||
if start_time:
|
||
query = query.filter(AlarmEvent.event_time >= start_time)
|
||
if end_time:
|
||
query = query.filter(AlarmEvent.event_time <= end_time)
|
||
|
||
total = query.count()
|
||
alarms = (
|
||
query.order_by(AlarmEvent.event_time.desc())
|
||
.offset((page - 1) * page_size)
|
||
.limit(page_size)
|
||
.all()
|
||
)
|
||
|
||
return alarms, total
|
||
finally:
|
||
db.close()
|
||
|
||
def handle_alarm(
|
||
self,
|
||
alarm_id: str,
|
||
alarm_status: Optional[str] = None,
|
||
handle_status: Optional[str] = None,
|
||
remark: Optional[str] = None,
|
||
handler: Optional[str] = None,
|
||
) -> Optional[AlarmEvent]:
|
||
"""处理告警"""
|
||
db = get_session()
|
||
try:
|
||
alarm = db.query(AlarmEvent).filter(AlarmEvent.alarm_id == alarm_id).first()
|
||
if not alarm:
|
||
return None
|
||
|
||
if alarm_status:
|
||
alarm.alarm_status = alarm_status
|
||
if handle_status:
|
||
alarm.handle_status = handle_status
|
||
if remark is not None:
|
||
alarm.handle_remark = remark
|
||
if handler:
|
||
alarm.handler = handler
|
||
|
||
now = beijing_now()
|
||
alarm.handled_at = now
|
||
alarm.updated_at = now
|
||
|
||
# 如果没有 duration_ms 且有 event_time,计算告警时长
|
||
if alarm.duration_ms is None and alarm.event_time:
|
||
try:
|
||
delta = now - alarm.event_time
|
||
alarm.duration_ms = int(delta.total_seconds() * 1000)
|
||
except Exception:
|
||
pass
|
||
|
||
# 如果没有 last_frame_time,设置为当前处理时间
|
||
if alarm.last_frame_time is None:
|
||
alarm.last_frame_time = now
|
||
|
||
db.commit()
|
||
db.refresh(alarm)
|
||
|
||
logger.info(f"告警已处理: {alarm_id}, status={alarm_status}")
|
||
return alarm
|
||
finally:
|
||
db.close()
|
||
|
||
def delete_alarm(self, alarm_id: str) -> bool:
|
||
"""删除告警(含扩展和分析)"""
|
||
db = get_session()
|
||
try:
|
||
alarm = db.query(AlarmEvent).filter(AlarmEvent.alarm_id == alarm_id).first()
|
||
if not alarm:
|
||
return False
|
||
|
||
# 删除关联数据
|
||
db.query(AlarmEventExt).filter(AlarmEventExt.alarm_id == alarm_id).delete()
|
||
db.query(AlarmLlmAnalysis).filter(AlarmLlmAnalysis.alarm_id == alarm_id).delete()
|
||
db.delete(alarm)
|
||
db.commit()
|
||
|
||
logger.info(f"告警已删除: {alarm_id}")
|
||
return True
|
||
except Exception as e:
|
||
db.rollback()
|
||
logger.error(f"删除告警失败: {e}")
|
||
return False
|
||
finally:
|
||
db.close()
|
||
|
||
def get_statistics(self) -> Dict:
|
||
"""获取告警统计"""
|
||
db = get_session()
|
||
try:
|
||
total = db.query(AlarmEvent).count()
|
||
|
||
# 按 alarm_status 计数
|
||
by_status = {}
|
||
for row in db.query(
|
||
AlarmEvent.alarm_status, func.count(AlarmEvent.alarm_id)
|
||
).group_by(AlarmEvent.alarm_status).all():
|
||
by_status[row[0]] = row[1]
|
||
|
||
# 按 alarm_type 计数
|
||
by_type = {}
|
||
for row in db.query(
|
||
AlarmEvent.alarm_type, func.count(AlarmEvent.alarm_id)
|
||
).group_by(AlarmEvent.alarm_type).all():
|
||
by_type[row[0]] = row[1]
|
||
|
||
# 按 alarm_level 计数
|
||
by_level = {}
|
||
for row in db.query(
|
||
AlarmEvent.alarm_level, func.count(AlarmEvent.alarm_id)
|
||
).group_by(AlarmEvent.alarm_level).all():
|
||
by_level[row[0]] = row[1]
|
||
|
||
return {
|
||
"total": total,
|
||
"byStatus": by_status,
|
||
"byType": by_type,
|
||
"byLevel": by_level,
|
||
}
|
||
finally:
|
||
db.close()
|
||
|
||
def get_device_summary(
|
||
self,
|
||
page: int = 1,
|
||
page_size: int = 10,
|
||
) -> Dict:
|
||
"""按设备(摄像头)分组统计告警汇总"""
|
||
db = get_session()
|
||
try:
|
||
query = db.query(
|
||
AlarmEvent.device_id,
|
||
func.count(AlarmEvent.alarm_id).label("total_count"),
|
||
func.max(AlarmEvent.event_time).label("last_event_time"),
|
||
).group_by(AlarmEvent.device_id)
|
||
|
||
total = query.count()
|
||
|
||
results = (
|
||
query.order_by(func.count(AlarmEvent.alarm_id).desc())
|
||
.offset((page - 1) * page_size)
|
||
.limit(page_size)
|
||
.all()
|
||
)
|
||
|
||
summary_list = []
|
||
for row in results:
|
||
# 该设备待处理数量
|
||
unhandled_count = (
|
||
db.query(AlarmEvent)
|
||
.filter(AlarmEvent.device_id == row.device_id)
|
||
.filter(AlarmEvent.handle_status == "UNHANDLED")
|
||
.count()
|
||
)
|
||
|
||
# 最新一条告警
|
||
latest = (
|
||
db.query(AlarmEvent)
|
||
.filter(AlarmEvent.device_id == row.device_id)
|
||
.order_by(AlarmEvent.event_time.desc())
|
||
.first()
|
||
)
|
||
|
||
summary_list.append({
|
||
"deviceId": row.device_id,
|
||
"deviceName": row.device_id,
|
||
"totalCount": row.total_count,
|
||
"unhandledCount": unhandled_count,
|
||
"lastEventTime": row.last_event_time.isoformat() if row.last_event_time else None,
|
||
"lastAlarmType": latest.alarm_type if latest else None,
|
||
"lastAlarmTypeName": latest.alarm_type if latest else None,
|
||
})
|
||
|
||
return {
|
||
"list": summary_list,
|
||
"total": total,
|
||
}
|
||
finally:
|
||
db.close()
|
||
|
||
def resolve_alarm(self, alarm_id: str, duration_ms: int, last_frame_time: str, resolve_type: str) -> bool:
|
||
"""
|
||
更新告警的持续时长和结束时间
|
||
|
||
先到先得:如果告警已被人工处理到终态(CLOSED/FALSE),仅更新时长,不覆盖状态。
|
||
"""
|
||
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:
|
||
parsed_time = datetime.fromisoformat(last_frame_time.replace("Z", "+00:00"))
|
||
alarm.last_frame_time = parsed_time.replace(microsecond=0)
|
||
except Exception:
|
||
alarm.last_frame_time = beijing_now().replace(microsecond=0)
|
||
|
||
# 先到先得:已被人工处理到终态的不覆盖
|
||
terminal_statuses = ("CLOSED", "FALSE")
|
||
terminal_handle = ("DONE", "IGNORED")
|
||
|
||
if alarm.alarm_status in terminal_statuses or alarm.handle_status in terminal_handle:
|
||
logger.info(f"告警已为终态({alarm.alarm_status}/{alarm.handle_status}),仅更新时长: {alarm_id}")
|
||
elif resolve_type == "person_returned":
|
||
alarm.alarm_status = "CLOSED"
|
||
alarm.handle_status = "DONE"
|
||
alarm.handle_remark = "人员回岗自动关闭"
|
||
alarm.handled_at = beijing_now()
|
||
elif resolve_type == "non_work_time":
|
||
alarm.alarm_status = "CLOSED"
|
||
alarm.handle_status = "DONE"
|
||
alarm.handle_remark = "非工作时间自动关闭"
|
||
alarm.handled_at = beijing_now()
|
||
elif resolve_type == "intrusion_cleared":
|
||
alarm.alarm_status = "CLOSED"
|
||
alarm.handle_status = "DONE"
|
||
alarm.handle_remark = "入侵消失自动关闭(持续无人180秒)"
|
||
alarm.handled_at = beijing_now()
|
||
|
||
alarm.updated_at = beijing_now()
|
||
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 is_alarm_terminal(self, alarm_id: str) -> bool:
|
||
"""判断告警是否已到终态(先到先得判断用)"""
|
||
db = get_session()
|
||
try:
|
||
alarm = db.query(AlarmEvent).filter(AlarmEvent.alarm_id == alarm_id).first()
|
||
if not alarm:
|
||
return False
|
||
return alarm.alarm_status in ("CLOSED", "FALSE") or alarm.handle_status in ("DONE", "IGNORED")
|
||
except Exception:
|
||
return False
|
||
finally:
|
||
db.close()
|
||
|
||
def count_alarms_by_edge_node(self, edge_node_id: str) -> int:
|
||
"""统计指定边缘节点的告警数量"""
|
||
db = get_session()
|
||
try:
|
||
count = db.query(AlarmEvent).filter(
|
||
AlarmEvent.edge_node_id == edge_node_id
|
||
).count()
|
||
return count
|
||
except Exception as e:
|
||
logger.error(f"统计边缘节点告警数失败: {e}")
|
||
return 0
|
||
finally:
|
||
db.close()
|
||
|
||
def save_llm_analysis(
|
||
self,
|
||
alarm_id: str,
|
||
llm_model: str,
|
||
analysis_type: str,
|
||
summary: Optional[str] = None,
|
||
is_false_alarm: Optional[bool] = None,
|
||
risk_score: Optional[int] = None,
|
||
confidence_score: Optional[float] = None,
|
||
suggestion: Optional[str] = None,
|
||
) -> Optional[AlarmLlmAnalysis]:
|
||
"""保存大模型分析结果"""
|
||
db = get_session()
|
||
try:
|
||
analysis = AlarmLlmAnalysis(
|
||
alarm_id=alarm_id,
|
||
llm_model=llm_model,
|
||
analysis_type=analysis_type,
|
||
summary=summary,
|
||
is_false_alarm=is_false_alarm,
|
||
risk_score=risk_score,
|
||
confidence_score=confidence_score,
|
||
suggestion=suggestion,
|
||
)
|
||
db.add(analysis)
|
||
db.commit()
|
||
db.refresh(analysis)
|
||
logger.info(f"LLM分析结果已保存: alarm={alarm_id}, model={llm_model}")
|
||
return analysis
|
||
except Exception as e:
|
||
db.rollback()
|
||
logger.error(f"保存LLM分析失败: {e}")
|
||
return None
|
||
finally:
|
||
db.close()
|
||
|
||
|
||
# 全局单例
|
||
alarm_event_service = AlarmEventService()
|
||
|
||
|
||
def get_alarm_event_service() -> AlarmEventService:
|
||
"""获取告警事件服务单例"""
|
||
return alarm_event_service
|