Files
security-ai-edge/docs/code_review_report.md

647 lines
27 KiB
Markdown
Raw Normal View 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"]
- 告警字段包含 `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) CONGESTED
(avg>=threshold)
```