Files
security-ai-edge/docs/code_review_report.md
16337 a9a5457583 优化:算法模块全面重构 — 提取基类、常量化阈值、性能优化
- 提取 BaseAlgorithm 基类,四个算法共享 ROI 检查、目标类过滤、告警 ID 管理
- 硬编码比率阈值提取为类常量(RATIO_ON_DUTY_CONFIRM 等)
- 滑动窗口添加 maxlen=1000 防内存溢出
- tracks 合并遍历 _scan_tracks() 减少重复遍历
- 比率/均值缓存,process() 入口计算一次
- 拥堵消散比例可配置(dissipation_ratio 参数)
- 入侵 CONFIRMING_CLEAR 逻辑拆分为独立方法
- 补齐 AlgorithmType 枚举(illegal_parking、vehicle_congestion)
- 修复 _leave_start_time None guard 防 TypeError
- 修复 AlgorithmManager 默认参数与构造函数不一致
- 热更新补充支持 illegal_parking 和 vehicle_congestion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:10:27 +08:00

27 KiB
Raw Blame History

algorithms.py 代码审查报告

审查日期: 2026-04-02 审查文件: algorithms.py (1733行) 审查范围: LeavePostAlgorithm, IntrusionAlgorithm, IllegalParkingAlgorithm, VehicleCongestionAlgorithm, AlgorithmManager


1. 功能基线清单

1.1 LeavePostAlgorithm (离岗检测)

状态定义 (7个):

状态 常量 含义
INIT STATE_INIT 初始化,等待检测到人
CONFIRMING_ON_DUTY STATE_CONFIRMING_ON_DUTY 上岗确认中(需持续检测到人)
ON_DUTY STATE_ON_DUTY 已确认在岗
CONFIRMING_OFF_DUTY STATE_CONFIRMING_OFF_DUTY 离岗确认中(持续未检测到人)
OFF_DUTY_COUNTDOWN STATE_OFF_DUTY_COUNTDOWN 离岗倒计时(确认离岗后等待告警)
ALARMED STATE_ALARMED 已告警(等待回岗)
NON_WORK_TIME STATE_NON_WORK_TIME 非工作时间

状态转换矩阵:

从 \ 到 INIT CONFIRMING_ON_DUTY ON_DUTY CONFIRMING_OFF_DUTY OFF_DUTY_COUNTDOWN ALARMED NON_WORK_TIME
INIT - roi_has_person==True - - - - not in_working_hours
CONFIRMING_ON_DUTY detection_ratio==0 - elapsed>=confirm_on_duty_sec AND ratio>=0.6 - - - not in_working_hours
ON_DUTY - - - detection_ratio<0.2 - - not in_working_hours
CONFIRMING_OFF_DUTY - - detection_ratio>=0.5 - elapsed>=confirm_off_duty_sec AND ratio<0.2 - not in_working_hours
OFF_DUTY_COUNTDOWN - - roi_has_person==True - - elapsed>=leave_countdown_sec AND cooldown ok not in_working_hours
ALARMED - roi_has_person==True - - - - not in_working_hours
NON_WORK_TIME in_working_hours - - - - - -

关键行为:

  • 使用滑动窗口10秒平滑检测结果计算 detection_ratio
  • ALARMED -> CONFIRMING_ON_DUTY 复用上岗确认状态(不是独立的回岗确认状态)
  • 进入 ON_DUTY 状态时,若存在 _last_alarm_id,自动发送 alarm_resolve 事件
  • 进入 NON_WORK_TIME 时,若有未结束告警,发送 resolve_type="non_work_time" 的 resolve 事件
  • 冷却期检查使用 cooldown_key = f"{camera_id}_{roi_id}"
  • _last_alarm_id 由外部 main.py 通过 set_last_alarm_id() 回填
  • _leave_start_time 在进入 OFF_DUTY_COUNTDOWN 时记录(值等于 state_start_time

构造函数参数:

  • confirm_on_duty_sec: int = 10 (上岗确认窗口)
  • confirm_off_duty_sec: int = 30 (离岗确认窗口)
  • confirm_return_sec: int = 10 (回岗确认窗口 -- 注意: 代码中实际未使用此参数)
  • leave_countdown_sec: int = 300 (离岗倒计时)
  • cooldown_sec: int = 600 (告警冷却期)
  • working_hours: Optional[List[Dict]] = None
  • target_class: Optional[str] = "person"
  • alarm_level: Optional[int] = None (默认2)
  • confirm_leave_sec: Optional[int] = None (向后兼容旧参数名)

1.2 IntrusionAlgorithm (周界入侵)

状态定义 (4个):

状态 常量 含义
IDLE STATE_IDLE 空闲,无入侵
CONFIRMING_INTRUSION STATE_CONFIRMING_INTRUSION 入侵确认中
ALARMED STATE_ALARMED 已告警(等待入侵消失)
CONFIRMING_CLEAR STATE_CONFIRMING_CLEAR 入侵消失确认中

状态转换矩阵:

从 \ 到 IDLE CONFIRMING_INTRUSION ALARMED CONFIRMING_CLEAR
IDLE - roi_has_person==True - -
CONFIRMING_INTRUSION not roi_has_person OR cooldown内 OR state_start_time==None - elapsed>=confirm_intrusion_seconds AND cooldown ok -
ALARMED - - - not roi_has_person
CONFIRMING_CLEAR elapsed>=confirm_clear_seconds AND no person - person_elapsed>=confirm_intrusion_seconds (持续有人) OR state_start_time==None -

关键行为:

  • 不使用滑动窗口,直接使用当前帧的 roi_has_person 判断
  • CONFIRMING_CLEAR 有子状态追踪: _person_detected_in_clear_time 用于判断短暂有人 vs 持续有人
  • 冷却期内入侵确认直接回到 IDLE不触发告警
  • 包含防御性编程: state_start_time==None 时重置到 IDLE
  • _check_target_class 允许 target_class 为 None匹配所有类别

构造函数参数:

  • cooldown_seconds: int = 300
  • confirm_seconds: int = 5 (向后兼容)
  • confirm_intrusion_seconds: Optional[int] = None (默认使用 confirm_seconds)
  • confirm_clear_seconds: Optional[int] = None (默认180)
  • target_class: Optional[str] = None
  • alarm_level: Optional[int] = None (默认1)

1.3 IllegalParkingAlgorithm (车辆违停)

状态定义 (5个):

状态 常量 含义
IDLE STATE_IDLE 空闲
CONFIRMING_VEHICLE STATE_CONFIRMING_VEHICLE 车辆确认中
PARKED_COUNTDOWN STATE_PARKED_COUNTDOWN 违停倒计时
ALARMED STATE_ALARMED 已告警
CONFIRMING_CLEAR STATE_CONFIRMING_CLEAR 消失确认中

状态转换矩阵:

从 \ 到 IDLE CONFIRMING_VEHICLE PARKED_COUNTDOWN ALARMED CONFIRMING_CLEAR
IDLE - roi_has_vehicle - - -
CONFIRMING_VEHICLE ratio<0.3 OR state_start_time==None - elapsed>=confirm_vehicle_sec AND ratio>=0.6 - -
PARKED_COUNTDOWN ratio<0.2 (车离开) OR cooldown内 OR state_start_time==None - - elapsed>=parking_countdown_sec AND cooldown ok -
ALARMED - - - - ratio<0.15
CONFIRMING_CLEAR elapsed>=confirm_clear_sec AND ratio<0.2 OR state_start_time==None - - ratio>=0.5 -

关键行为:

  • 使用滑动窗口WINDOW_SIZE_SEC=10秒
  • 支持多类车辆: target_classes 默认 ["car", "truck", "bus", "motorcycle"]
  • 告警字段包含 confidenceduration_minutes
  • CONFIRMING_CLEAR -> IDLE 时清除 alert_cooldowns新车违停可正常告警
  • ALARMED 进入 CONFIRMING_CLEAR 的阈值(0.15)比其他算法更严格

构造函数参数:

  • confirm_vehicle_sec: int = 15
  • parking_countdown_sec: int = 300
  • confirm_clear_sec: int = 120
  • cooldown_sec: int = 1800
  • target_classes: Optional[List[str]] = None (默认 ["car", "truck", "bus", "motorcycle"])
  • alarm_level: Optional[int] = None (默认1)

1.4 VehicleCongestionAlgorithm (车辆拥堵)

状态定义 (4个):

状态 常量 含义
NORMAL STATE_NORMAL 正常
CONFIRMING_CONGESTION STATE_CONFIRMING_CONGESTION 拥堵确认中
CONGESTED STATE_CONGESTED 拥堵中
CONFIRMING_CLEAR STATE_CONFIRMING_CLEAR 消散确认中

状态转换矩阵:

从 \ 到 NORMAL CONFIRMING_CONGESTION CONGESTED CONFIRMING_CLEAR
NORMAL - avg_count >= count_threshold - -
CONFIRMING_CONGESTION avg_count < count_threshold OR cooldown内 OR state_start_time==None - elapsed >= confirm_congestion_sec AND cooldown ok -
CONGESTED - - - avg_count < count_threshold * 0.5
CONFIRMING_CLEAR elapsed >= confirm_clear_sec OR state_start_time==None - avg_count >= count_threshold -

关键行为:

  • 使用滑动窗口WINDOW_SIZE_SEC=10秒存储车辆计数取平均值判断
  • 消散需车辆数降到阈值的 50% 以下才开始确认(避免抖动)
  • CONFIRMING_CLEAR -> NORMAL 时清除 alert_cooldowns
  • 告警字段包含 vehicle_countconfidence

构造函数参数:

  • count_threshold: int = 5
  • confirm_congestion_sec: int = 60
  • confirm_clear_sec: int = 180
  • cooldown_sec: int = 1800
  • target_classes: Optional[List[str]] = None (默认 ["car", "truck", "bus", "motorcycle"])
  • alarm_level: Optional[int] = None (默认2)

1.5 AlgorithmManager

数据结构:

self.algorithms: Dict[str, Dict[str, Dict[str, Algorithm]]]
  结构: { roi_id: { "{roi_id}_{bind_id}": { algo_type: algo_instance } } }

公开方法:

  • start_config_subscription() - 启动 Redis 配置订阅
  • stop_config_subscription() - 停止配置订阅
  • load_bind_from_redis(bind_id) - 从 Redis 加载单个绑定配置
  • reload_bind_algorithm(bind_id) - 重载单个绑定
  • reload_algorithm(roi_id) - 重载单个 ROI 的所有算法
  • update_algorithm_params(roi_id, bind_id, bind_config) - 仅更新参数,保留状态
  • reload_all_algorithms(preserve_state=True) - 重载全部算法
  • register_algorithm(roi_id, bind_id, algorithm_type, params) - 注册算法(带缓存)
  • process(roi_id, bind_id, camera_id, algorithm_type, tracks, current_time) - 处理检测结果
  • update_roi_params(roi_id, bind_id, algorithm_type, params) - 更新参数
  • reset_algorithm(roi_id, bind_id=None) - 重置算法状态
  • reset_all() - 重置所有算法
  • remove_roi(roi_id) - 移除 ROI
  • remove_bind(roi_id, bind_id) - 移除绑定
  • get_status(roi_id) - 获取状态

2. 接口契约清单

2.1 process() 方法统一签名

所有四个算法的 process() 方法具有相同签名:

def process(
    self,
    roi_id: str,
    camera_id: str,
    tracks: List[Dict],
    current_time: Optional[datetime] = None,
) -> List[Dict]

tracks 输入格式 (每个元素):

{
    "track_id": str,           # 跟踪ID
    "class": str,              # 检测类别 ("person", "car", "truck", ...)
    "confidence": float,       # 置信度
    "bbox": List[float],       # 边界框 [x1, y1, x2, y2]
    "matched_rois": [          # 匹配的ROI列表
        {"roi_id": str}
    ],
}

2.2 告警输出格式

LeavePostAlgorithm 告警:

{
    "track_id": str,           # 等于 roi_id
    "camera_id": str,
    "bbox": List[float],       # 可能为空 []
    "alert_type": "leave_post",
    "alarm_level": int,        # 默认 2
    "message": "人员离岗告警",
    "first_frame_time": str,   # 格式: '%Y-%m-%d %H:%M:%S'
}

注意: leave_post 告警使用 track_id 而非 roi_id 字段名(与其他算法不同)。

IntrusionAlgorithm 告警:

{
    "roi_id": str,
    "camera_id": str,
    "bbox": List[float],
    "alert_type": "intrusion",
    "alarm_level": int,        # 默认 1
    "message": "检测到周界入侵",
    "first_frame_time": str,   # 格式: '%Y-%m-%d %H:%M:%S'
}

IllegalParkingAlgorithm 告警:

{
    "roi_id": str,
    "camera_id": str,
    "bbox": List[float],
    "alert_type": "illegal_parking",
    "alarm_level": int,        # 默认 1
    "confidence": float,
    "message": str,            # 动态生成,包含停留分钟数
    "first_frame_time": str,   # 格式: '%Y-%m-%d %H:%M:%S',可能为 None
    "duration_minutes": float,
}

VehicleCongestionAlgorithm 告警:

{
    "roi_id": str,
    "camera_id": str,
    "bbox": List[float],
    "alert_type": "vehicle_congestion",
    "alarm_level": int,        # 默认 2
    "confidence": float,
    "message": str,            # 动态生成,包含平均车辆数和持续秒数
    "first_frame_time": str,   # 格式: '%Y-%m-%d %H:%M:%S',可能为 None
    "vehicle_count": int,
}

alarm_resolve 事件 (所有算法统一格式):

{
    "alert_type": "alarm_resolve",
    "resolve_alarm_id": str,
    "duration_ms": int,
    "last_frame_time": str,    # 格式: '%Y-%m-%d %H:%M:%S'
    "resolve_type": str,       # "person_returned" | "non_work_time" | "intrusion_cleared" | "vehicle_left" | "congestion_cleared"
}

2.3 AlgorithmManager.process() 签名

def process(
    self,
    roi_id: str,
    bind_id: str,
    camera_id: str,
    algorithm_type: str,
    tracks: List[Dict],
    current_time: Optional[datetime] = None,
) -> List[Dict]

2.4 AlgorithmManager.register_algorithm() 签名

def register_algorithm(
    self,
    roi_id: str,
    bind_id: str,
    algorithm_type: str,                 # "leave_post" | "intrusion" | "illegal_parking" | "vehicle_congestion"
    params: Optional[Dict[str, Any]] = None,
)

3. 已发现的潜在问题

3.1 Critical (必须修复)

[C1] LeavePostAlgorithm: confirm_return_sec 参数声明但从未使用

  • 位置: 第53行声明但状态机中 ALARMED -> CONFIRMING_ON_DUTY -> ON_DUTY 的转换直接复用 confirm_on_duty_sec
  • 影响: 使用者设置 confirm_return_sec 以为可以独立控制回岗确认时长,但实际无效
  • 建议: 文档中声明此参数复用 confirm_on_duty_sec,或实现独立的回岗确认逻辑

[C2] LeavePostAlgorithm: resolve 事件的 duration_ms 计算依赖 _leave_start_time,但该值可能为 None

  • 位置: 第325行 duration_ms = int((current_time - self._leave_start_time).total_seconds() * 1000)
  • 场景: 如果算法在 OFF_DUTY_COUNTDOWN 之前(即 _leave_start_time 赋值之前)因某种异常跳到 ON_DUTY 且 _last_alarm_id 非空,会抛出 TypeError
  • 风险: 低概率但会导致该帧整个 process 调用抛异常

[C3] LeavePostAlgorithm: _leave_start_time 赋值时机问题

  • 位置: 第277行 self._leave_start_time = self.state_start_time
  • state_start_time 此时是 CONFIRMING_OFF_DUTY 的开始时间(非 OFF_DUTY_COUNTDOWN 的开始时间,因为 state_start_time 在下一行第276行才被更新为 current_time
  • 实际效果: _leave_start_time 记录的是离岗确认开始时间,不是倒计时开始时间
  • 审查结论: 这是有意设计,离开时间应该从人离开被确认开始计算,但代码注释"记录离开时间"可能造成误解

3.2 Important (应该修复)

[I1] LeavePostAlgorithm 告警字典使用 track_id 而非 roi_id

  • 位置: 第299行 "track_id": roi_id
  • 其他三个算法统一使用 "roi_id": roi_id
  • 影响: main.py 中的 _handle_detections 不直接使用此字段(它有自己的 roi_id所以不影响功能但接口不一致

[I2] AlgorithmManager 缺乏线程安全

  • process() 方法未加锁第1637-1651行register_algorithm() 也未加锁
  • _update_lock 仅在 load_bind_from_redisreload_all_algorithms 中使用
  • 风险: 如果 config_update_worker 线程调用 reload_all_algorithms 同时主线程调用 process,可能读到不一致的 self.algorithms 字典
  • 缓解: Python GIL 在字典读操作上提供了一定程度的原子性保护,实际崩溃概率很低

[I3] AlgorithmManager.default_params 中 illegal_parking 的 confirm_clear_sec 默认值(30) 与 IllegalParkingAlgorithm 构造函数默认值(120) 不一致

  • 位置: 第1233行 vs 第751行
  • 影响: 通过 AlgorithmManager 创建的 illegal_parking 算法 confirm_clear_sec 为 30直接创建为 120

[I4] AlgorithmManager.default_params 中 vehicle_congestion 的 count_threshold 默认值(3) 与 VehicleCongestionAlgorithm 构造函数默认值(5) 不一致

  • 位置: 第1237行 vs 第1004行
  • 影响: 通过 AlgorithmManager 创建的算法阈值为 3直接创建为 5

[I5] update_algorithm_params 仅支持 leave_post 和 intrusion

  • 位置: 第1461-1496行
  • 缺少 illegal_parking 和 vehicle_congestion 的参数更新逻辑第1494行注释 "其他算法类型可以在此添加"
  • 影响: reload_all_algorithms(preserve_state=True) 对 illegal_parking/vehicle_congestion 会回退到 load_bind_from_redis,会重置算法状态

[I6] load_bind_from_redis 仅支持 leave_post 和 intrusion

  • 位置: 第1322-1403行
  • 缺少 illegal_parking 和 vehicle_congestion 的 Redis 加载逻辑
  • 影响: 从 Redis 热更新配置时,这两种算法无法被加载

[I7] IntrusionAlgorithm: _get_latest_bbox 不检查 target_class

  • 位置: 第456-460行
  • 与 LeavePostAlgorithm 不同第139-142行会检查 target_class
  • 影响: 可能返回非目标类别的 bbox

[I8] _is_in_working_hours 不支持跨午夜时间段

  • 位置: 第112行 if start_minutes <= current_minutes < end_minutes
  • 如果 working_hours 配置为 {"start": "22:00", "end": "06:00"},则无法正确判断
  • 影响: 夜班场景可能不工作

3.3 Suggestions (建议改进)

[S1] 滑动窗口的 window_size_sec 硬编码为 10 秒

  • LeavePostAlgorithm 第80行: self.window_size_sec = 10
  • IllegalParkingAlgorithm 第744行: WINDOW_SIZE_SEC = 10
  • VehicleCongestionAlgorithm 第1000行: WINDOW_SIZE_SEC = 10
  • 建议: 提取为可配置参数

[S2] LeavePostAlgorithm._update_detection_window 与 IllegalParkingAlgorithm._update_window 实现逻辑相同但代码重复

  • 可提取为基类方法或工具函数

[S3] LeavePostAlgorithm.get_state() 使用 datetime.now() 而非参数传入的 current_time

  • 位置: 第367-372行
  • 在测试场景中会导致状态信息不准确(不影响核心逻辑,仅影响监控展示)

[S4] AlgorithmManager.get_status() 中 leave_post 分支访问 alarm_sent 属性

  • 位置: 第1724行
  • LeavePostAlgorithm 实际上没有 alarm_sent 属性getattr 返回 False
  • 这是旧版本残留代码

[S5] AlgorithmManager.remove_roi() 中 bind_id 解析逻辑脆弱

  • 位置: 第1703行 key.split("_")[-1]
  • 如果 bind_id 本身包含下划线,解析会出错
  • key 格式为 "{roi_id}_{bind_id}",应该用 key[len(roi_id)+1:] 提取 bind_id

[S6] _is_in_working_hours 中的 bare except: (第99行, 第124行)

  • 应该至少 except Exception 或更具体的异常类型

4. 测试覆盖分析

4.1 test_leave_post_full_workflow.py 覆盖的场景

场景 状态路径 覆盖
上岗确认成功 INIT -> CONFIRMING_ON_DUTY -> ON_DUTY YES
离岗确认 ON_DUTY -> CONFIRMING_OFF_DUTY -> OFF_DUTY_COUNTDOWN YES
倒计时触发告警 OFF_DUTY_COUNTDOWN -> ALARMED YES
回岗 resolve ALARMED -> CONFIRMING_ON_DUTY -> ON_DUTY (+ resolve) YES
告警字段验证 无 duration_minutes, 有 first_frame_time YES
resolve 字段验证 duration_ms, resolve_alarm_id, resolve_type YES
set_last_alarm_id 回填 - YES

4.2 test_vehicle_algorithms.py 覆盖的场景

IllegalParkingAlgorithm:

场景 覆盖
完整生命周期 IDLE->CONFIRMING->COUNTDOWN->ALARMED->CLEAR->IDLE YES
车辆短暂路过不触发 YES
多类车辆检测 (truck, bus) YES
person 不触发违停 YES
冷却期内不重复告警 YES
resolve 事件发送 YES

VehicleCongestionAlgorithm:

场景 覆盖
完整生命周期 NORMAL->CONFIRMING->CONGESTED->CLEAR->NORMAL YES
少于阈值不触发 YES
短暂拥堵不触发 YES
resolve 事件发送 YES

AlgorithmManager:

场景 覆盖
注册 illegal_parking YES
注册 vehicle_congestion YES
process 调用 YES
get_status 调用 YES
重复注册走缓存 YES
reset_algorithm YES

4.3 测试覆盖缺口

LeavePostAlgorithm 未覆盖:

  • CONFIRMING_ON_DUTY -> INIT (人消失)
  • CONFIRMING_OFF_DUTY -> ON_DUTY (人回来ratio>=0.5)
  • OFF_DUTY_COUNTDOWN -> ON_DUTY (倒计时期间回来)
  • 非工作时间自动 resolve
  • NON_WORK_TIME -> INIT (工作时间恢复)
  • 冷却期内不重复告警
  • 空 tracks 输入
  • working_hours 配置解析(字符串格式)

IntrusionAlgorithm 完全未测试:

  • 完整生命周期 IDLE->CONFIRMING->ALARMED->CLEAR->IDLE
  • 入侵确认中人消失
  • CONFIRMING_CLEAR 中短暂有人 vs 持续有人
  • 冷却期
  • resolve 事件
  • target_class=None 匹配所有类别
  • state_start_time==None 的防御性代码分支

IllegalParkingAlgorithm 未覆盖:

  • state_start_time==None 的防御性代码分支 (CONFIRMING_VEHICLE, PARKED_COUNTDOWN, CONFIRMING_CLEAR)
  • CONFIRMING_CLEAR -> ALARMED (车辆又出现, ratio>=0.5)
  • ALARMED 状态下 ratio 在 0.15-0.5 之间(维持 ALARMED

VehicleCongestionAlgorithm 未覆盖:

  • state_start_time==None 的防御性代码分支
  • CONFIRMING_CLEAR -> CONGESTED (又拥堵了)
  • 消散阈值 0.5*count_threshold 的边界值
  • 冷却期测试

AlgorithmManager 未覆盖:

  • start_config_subscription / stop_config_subscription
  • load_bind_from_redis
  • reload_all_algorithms (含孤立实例清理)
  • update_algorithm_params
  • remove_roi / remove_bind
  • 并发调用安全性
  • register_algorithm 的 leave_post 和 intrusion 类型

5. 优化安全边界

5.1 不可修改区域 (功能合约)

以下代码是外部依赖的契约,修改会破坏 main.py 或其他模块:

  1. 所有算法的 process() 方法签名 -- main.py 的 _handle_detections 直接调用
  2. 告警字典的字段名和类型 -- main.py 的 _handle_detections 依赖 alert_type, alarm_level, confidence, bbox, message, first_frame_time, duration_minutes, vehicle_count
  3. alarm_resolve 事件格式 -- main.py 的 resolve 逻辑依赖 resolve_alarm_id, duration_ms, last_frame_time, resolve_type
  4. set_last_alarm_id(alarm_id) 方法 -- main.py 回填 alarm_id
  5. reset() 方法 -- AlgorithmManager 调用
  6. get_state() 方法 -- AlgorithmManager.get_status() 调用
  7. AlgorithmManager.process() 签名和返回值 -- main.py 直接调用
  8. AlgorithmManager.register_algorithm() 签名 -- main.py 直接调用
  9. AlgorithmManager.algorithms 的三层字典结构 -- main.py 直接访问内部实例来获取 _leave_start_time 等属性 (第905-911行)

5.2 可安全优化区域

以下代码修改不会影响外部行为:

  1. 滑动窗口实现 -- _update_detection_window, _update_window, _update_count_window 的内部实现可以优化,只要 _get_detection_ratio(), _get_window_ratio(), _get_avg_count() 的语义不变
  2. _check_detection_in_roi / _check_target_class / _check_target_classes -- 内部实现可优化,接口不变即可
  3. _get_latest_bbox / _get_max_confidence -- 辅助方法,内部实现可优化
  4. _is_in_working_hours / _parse_time_to_minutes -- 内部实现可优化(建议修复跨午夜问题)
  5. AlgorithmManager 的 Redis 相关方法 -- load_bind_from_redis, reload_*, _config_update_worker 可以修改,不影响算法核心逻辑
  6. 日志输出 -- 所有 logger.* 调用可以调整
  7. default_params 字典 -- 可以修正默认值不一致的问题

5.3 高风险修改区域 (需要完整回归测试)

  1. 状态转换条件 (ratio 阈值) -- 任何 detection_ratio, window_ratio 的阈值变更都可能影响告警灵敏度

    • LeavePostAlgorithm: 0.6 (上岗), 0.2 (离岗开始), 0.5 (离岗恢复), 0.2 (离岗确认)
    • IllegalParkingAlgorithm: 0.3 (放弃确认), 0.6 (确认有车), 0.2 (车离开), 0.15 (开始消失确认), 0.5 (车又来), 0.2 (消失确认)
    • VehicleCongestionAlgorithm: count_threshold (开始确认), 0.5*count_threshold (开始消散)
  2. 时间比较逻辑 -- elapsed >= xxx_sec 的方向(大于等于 vs 大于)

  3. 冷却期检查 -- cooldown_key 的构造方式和比较逻辑

  4. resolve 事件触发逻辑 -- LeavePostAlgorithm 的 "进入 ON_DUTY 且 _last_alarm_id 存在" 的检查

5.4 重复代码可提取区域

以下方法在多个算法中重复实现,可以提取为基类或 mixin

方法 出现在 可提取
_check_detection_in_roi 全部4个 YES
_check_target_class LeavePost, Intrusion YES
_check_target_classes IllegalParking, VehicleCongestion YES
_get_latest_bbox 全部4个 YES (注意 Intrusion 不检查 target_class)
_get_max_confidence IllegalParking, VehicleCongestion YES
set_last_alarm_id 全部4个 YES
滑动窗口逻辑 LeavePost, IllegalParking, VehicleCongestion YES

6. config_models.py 与 algorithms.py 的一致性

6.1 AlgorithmType 枚举缺失

config_models.py 中的 AlgorithmType 枚举:

LEAVE_POST = "leave_post"
INTRUSION = "intrusion"
CROWD_DETECTION = "crowd_detection"   # 已在 algorithms.py 中注释掉
FACE_RECOGNITION = "face_recognition" # algorithms.py 中不存在

缺失:

  • ILLEGAL_PARKING = "illegal_parking" -- algorithms.py 已实现但枚举未添加
  • VEHICLE_CONGESTION = "vehicle_congestion" -- algorithms.py 已实现但枚举未添加

6.2 ROIInfo 默认值

config_models.pyROIInfo.confirm_leave_sec 默认值为 10,而 AlgorithmManager.default_params["leave_post"]["confirm_leave_sec"]30LeavePostAlgorithm.confirm_off_duty_sec 默认为 30


7. main.py 集成要点

7.1 main.py 对 algorithms.py 的依赖

  1. 直接访问算法内部属性 (第905-911行):

    for attr in ('_leave_start_time', '_parking_start_time', '_congestion_start_time', '_intrusion_start_time'):
        val = getattr(algo, attr, None)
    

    这是紧耦合,如果内部属性名变更会导致 first_frame_time 丢失。

  2. alarm_id 回填 (第943-945行):

    algo.set_last_alarm_id(alarm_info.alarm_id)
    
  3. 两层去重机制:

    • ROI级别: _active_alarms[f"{roi_id}_{alert_type}"]
    • 摄像头级别: _camera_alert_cooldown[f"{camera_id}_{alert_type}"] (30秒冷却)
  4. duration_ms 在 ext_data 中的计算 (第925行):

    "duration_ms": int(alert.get("duration_minutes", 0) * 60 * 1000) if alert.get("duration_minutes") else None,
    

    仅 IllegalParkingAlgorithm 的告警包含 duration_minutes,其他算法的 ext_data.duration_ms 为 None。


附录: 状态机可视化

LeavePostAlgorithm

         +--> NON_WORK_TIME --+
         |  (any state)       |  (in_working_hours)
         |                    v
  INIT --+--> CONFIRMING_ON_DUTY ---> ON_DUTY ---> CONFIRMING_OFF_DUTY
    ^          |        ^                              |
    +----------+        |                              v
   (ratio==0)           |                    OFF_DUTY_COUNTDOWN
                        |                       |          |
                        |     (roi_has_person)  |          v
                        |       +---------------+       ALARMED
                        |       |                          |
                        +-------+  (roi_has_person)        |
                              ON_DUTY <-- (if _last_alarm_id, send resolve)

IntrusionAlgorithm

  IDLE ---> CONFIRMING_INTRUSION ---> ALARMED ---> CONFIRMING_CLEAR ---> IDLE
    ^            |                                     |        |
    +------------+                                     +--------+
   (person gone)                                  (person back >= confirm_intrusion_sec)
                                                  -> ALARMED

IllegalParkingAlgorithm

  IDLE --> CONFIRMING_VEHICLE --> PARKED_COUNTDOWN --> ALARMED --> CONFIRMING_CLEAR --> IDLE
    ^          |                       |                              |
    +----------+                       |                              v
  (ratio<0.3)              (ratio<0.2) |                           ALARMED
    ^                          |       |                          (ratio>=0.5)
    +--------------------------+       v
                                    ALARMED

VehicleCongestionAlgorithm

  NORMAL --> CONFIRMING_CONGESTION --> CONGESTED --> CONFIRMING_CLEAR --> NORMAL
    ^              |                                      |
    +--------------+                                      v
  (avg<threshold)                                      CONGESTED
                                                    (avg>=threshold)