feat(aiot): 离岗检测重写 - 单次告警 + 回岗确认 + 持续时长追踪
算法逻辑修改: - OFF_DUTY状态只告警一次,不再每600秒重复告警 - 人员回岗后需经CONFIRMING(10秒)重新确认才恢复ON_DUTY - 确认在岗后清除冷却记录,允许新一轮离岗检测 - 非工作时间进入时清除冷却记录 持续时长追踪(新增resolve机制): - 离岗告警记录alarm_id和leave_start_time - 人员回岗确认后生成resolve事件(duration_ms + last_frame_time) - 进入非工作时间时也生成resolve事件 - ResultReporter新增report_alarm_resolve()写入Redis队列 - AlarmUploadWorker新增_process_resolve() HTTP POST到云端 - main.py区分普通告警和resolve事件,回填alarm_id到算法实例 - 告警ext_data附加first_frame_time(离岗开始时间)
This commit is contained in:
@@ -47,6 +47,9 @@ class LeavePostAlgorithm:
|
||||
self.alarm_sent: bool = False
|
||||
self.last_person_time: Optional[datetime] = None
|
||||
|
||||
self._last_alarm_id: Optional[str] = None # 最近一次告警ID
|
||||
self._leave_start_time: Optional[datetime] = None # 离岗开始时间(LEAVING 状态的开始时间)
|
||||
|
||||
def _is_in_working_hours(self, dt: Optional[datetime] = None) -> bool:
|
||||
if not self.working_hours:
|
||||
return True
|
||||
@@ -123,15 +126,28 @@ class LeavePostAlgorithm:
|
||||
alerts = []
|
||||
|
||||
if not in_work:
|
||||
if self.state == self.STATE_OFF_DUTY and self._last_alarm_id and self._leave_start_time:
|
||||
duration_ms = int((current_time - self._leave_start_time).total_seconds() * 1000)
|
||||
alerts.append({
|
||||
"alert_type": "alarm_resolve",
|
||||
"resolve_alarm_id": self._last_alarm_id,
|
||||
"duration_ms": duration_ms,
|
||||
"last_frame_time": current_time.isoformat(),
|
||||
"resolve_type": "non_work_time",
|
||||
})
|
||||
self._last_alarm_id = None
|
||||
self._leave_start_time = None
|
||||
self.state = self.STATE_NON_WORK_TIME
|
||||
self.detection_history.clear()
|
||||
self.alarm_sent = False
|
||||
return []
|
||||
return alerts
|
||||
|
||||
if self.state == self.STATE_NON_WORK_TIME:
|
||||
self.state = self.STATE_WAITING
|
||||
self.state_start_time = None
|
||||
self.detection_history.clear()
|
||||
self.alarm_sent = False
|
||||
self.alert_cooldowns.clear() # 新工作时段,清除冷却记录
|
||||
|
||||
roi_has_person = False
|
||||
for det in tracks:
|
||||
@@ -162,6 +178,8 @@ class LeavePostAlgorithm:
|
||||
# 持续在岗达到确认时长,正式确认上岗
|
||||
self.state = self.STATE_ON_DUTY
|
||||
self.state_start_time = current_time
|
||||
# 确认在岗后清除冷却记录,允许新一轮离岗检测告警
|
||||
self.alert_cooldowns.clear()
|
||||
|
||||
elif self.state == self.STATE_ON_DUTY:
|
||||
self.detection_history.append((current_time, roi_has_person))
|
||||
@@ -178,6 +196,7 @@ class LeavePostAlgorithm:
|
||||
self.state_start_time = current_time
|
||||
elif elapsed >= self.confirm_leave_sec:
|
||||
# 确认离岗后直接触发告警,不再进入 OFF_DUTY 二次等待
|
||||
leaving_start_time = self.state_start_time # 保存 LEAVING 状态开始时间(人员离开时间)
|
||||
self.state = self.STATE_OFF_DUTY
|
||||
self.state_start_time = current_time
|
||||
|
||||
@@ -196,30 +215,37 @@ class LeavePostAlgorithm:
|
||||
})
|
||||
self.alert_cooldowns[cooldown_key] = now
|
||||
|
||||
# 保存告警追踪信息(alarm_id 由 main.py 通过 set_last_alarm_id() 回填)
|
||||
self._last_alarm_id = None
|
||||
self._leave_start_time = leaving_start_time # LEAVING 状态开始时间 = 人员离开时间
|
||||
|
||||
elif self.state == self.STATE_OFF_DUTY:
|
||||
# OFF_DUTY 状态:等待人员回岗或冷却后可再次告警
|
||||
# OFF_DUTY 状态:只等待人员回岗,不再重复告警
|
||||
# 必须经过 CONFIRMING 重新确认在岗后,才允许新一轮离岗检测
|
||||
if roi_has_person:
|
||||
self.state = self.STATE_ON_DUTY
|
||||
self.state_start_time = current_time
|
||||
else:
|
||||
elapsed = (current_time - self.state_start_time).total_seconds()
|
||||
cooldown_key = f"{camera_id}_{roi_id}"
|
||||
now = datetime.now()
|
||||
if cooldown_key in self.alert_cooldowns and (now - self.alert_cooldowns[cooldown_key]).total_seconds() > self.cooldown_sec:
|
||||
bbox = self._get_latest_bbox(tracks, roi_id)
|
||||
elapsed_minutes = int(elapsed / 60)
|
||||
# 生成 resolve 事件(人员回岗)
|
||||
if self._last_alarm_id and self._leave_start_time:
|
||||
duration_ms = int((current_time - self._leave_start_time).total_seconds() * 1000)
|
||||
alerts.append({
|
||||
"track_id": roi_id,
|
||||
"camera_id": camera_id,
|
||||
"bbox": bbox,
|
||||
"duration_minutes": elapsed_minutes,
|
||||
"alert_type": "leave_post",
|
||||
"message": f"持续离岗 {elapsed_minutes} 分钟",
|
||||
"alert_type": "alarm_resolve",
|
||||
"resolve_alarm_id": self._last_alarm_id,
|
||||
"duration_ms": duration_ms,
|
||||
"last_frame_time": current_time.isoformat(),
|
||||
"resolve_type": "person_returned",
|
||||
})
|
||||
self.alert_cooldowns[cooldown_key] = now
|
||||
self._last_alarm_id = None
|
||||
self._leave_start_time = None
|
||||
self.state = self.STATE_CONFIRMING
|
||||
self.state_start_time = current_time
|
||||
self.detection_history.clear()
|
||||
self.detection_history.append((current_time, True))
|
||||
|
||||
return alerts
|
||||
|
||||
def set_last_alarm_id(self, alarm_id: str):
|
||||
"""由 main.py 在告警生成后回填 alarm_id"""
|
||||
self._last_alarm_id = alarm_id
|
||||
|
||||
def reset(self):
|
||||
self.state = self.STATE_WAITING
|
||||
self.state_start_time = None
|
||||
@@ -227,6 +253,8 @@ class LeavePostAlgorithm:
|
||||
self.alarm_sent = False
|
||||
self.last_person_time = None
|
||||
self.alert_cooldowns.clear()
|
||||
self._last_alarm_id = None
|
||||
self._leave_start_time = None
|
||||
|
||||
def get_state(self, roi_id: str) -> Dict[str, Any]:
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user