# 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"] - 告警字段包含 `confidence` 和 `duration_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_count` 和 `confidence` **构造函数参数:** - `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()` 方法具有相同签名: ```python def process( self, roi_id: str, camera_id: str, tracks: List[Dict], current_time: Optional[datetime] = None, ) -> List[Dict] ``` **tracks 输入格式 (每个元素):** ```python { "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 告警: ```python { "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 告警: ```python { "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 告警: ```python { "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 告警: ```python { "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 事件 (所有算法统一格式): ```python { "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() 签名 ```python 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() 签名 ```python 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_redis` 和 `reload_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` 枚举: ```python 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.py` 的 `ROIInfo.confirm_leave_sec` 默认值为 **10**,而 `AlgorithmManager.default_params["leave_post"]["confirm_leave_sec"]` 为 **30**,`LeavePostAlgorithm.confirm_off_duty_sec` 默认为 **30**。 --- ## 7. main.py 集成要点 ### 7.1 main.py 对 algorithms.py 的依赖 1. **直接访问算法内部属性** (第905-911行): ```python 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行): ```python 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行): ```python "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) ```