Compare commits

4 Commits

Author SHA1 Message Date
736b0a05d5 修复: 日报首响过滤上限 360→60 分钟,完结过滤上限 24h→6h 2026-04-15 11:36:40 +08:00
33e272cbc7 修复: VLM 非机动车违停 prompt 参数缺失 + 返回格式兼容
- 移除 non_motor_vehicle_parking prompt 中未传递的 {timestamp} 占位符
- 统一该算法的 prompt 输出格式为 confirmed/description(与其他算法一致)
- 解析时兼容 is_real/reason 字段,防止旧版 prompt 或模型返回不一致
2026-04-13 10:21:33 +08:00
f8b4b65ced 新增: 算法全局参数菜单项 2026-04-09 17:55:59 +08:00
caa7adb27c 新增: 非机动车违停告警类型和VLM复核提示词 2026-04-09 10:00:56 +08:00
4 changed files with 45 additions and 11 deletions

View File

@@ -34,6 +34,7 @@ class AlarmType(str, Enum):
INTRUSION = "intrusion" INTRUSION = "intrusion"
ILLEGAL_PARKING = "illegal_parking" ILLEGAL_PARKING = "illegal_parking"
VEHICLE_CONGESTION = "vehicle_congestion" VEHICLE_CONGESTION = "vehicle_congestion"
NON_MOTOR_VEHICLE_PARKING = "non_motor_vehicle_parking"
ALARM_TYPE_NAMES: Dict[str, str] = { ALARM_TYPE_NAMES: Dict[str, str] = {
@@ -41,6 +42,7 @@ ALARM_TYPE_NAMES: Dict[str, str] = {
AlarmType.INTRUSION: "周界入侵", AlarmType.INTRUSION: "周界入侵",
AlarmType.ILLEGAL_PARKING: "车辆违停", AlarmType.ILLEGAL_PARKING: "车辆违停",
AlarmType.VEHICLE_CONGESTION: "车辆拥堵", AlarmType.VEHICLE_CONGESTION: "车辆拥堵",
AlarmType.NON_MOTOR_VEHICLE_PARKING: "非机动车违停",
} }
# VLM 场景下的简短名称(用于截图分析提示词,尽量精炼) # VLM 场景下的简短名称(用于截图分析提示词,尽量精炼)
@@ -49,6 +51,7 @@ ALARM_TYPE_SHORT_NAMES: Dict[str, str] = {
AlarmType.INTRUSION: "入侵", AlarmType.INTRUSION: "入侵",
AlarmType.ILLEGAL_PARKING: "违停", AlarmType.ILLEGAL_PARKING: "违停",
AlarmType.VEHICLE_CONGESTION: "拥堵", AlarmType.VEHICLE_CONGESTION: "拥堵",
AlarmType.NON_MOTOR_VEHICLE_PARKING: "非机动车违停",
} }
@@ -147,8 +150,9 @@ ALARM_LEVEL_NAMES: Dict[int, str] = {
ALARM_TYPE_DEFAULT_LEVEL: Dict[str, int] = { ALARM_TYPE_DEFAULT_LEVEL: Dict[str, int] = {
AlarmType.INTRUSION: 1, # 重要 AlarmType.INTRUSION: 1, # 重要
AlarmType.LEAVE_POST: 2, # 普通 AlarmType.LEAVE_POST: 2, # 普通
AlarmType.ILLEGAL_PARKING: 2, # 普通 AlarmType.ILLEGAL_PARKING: 1, # 重要(与 edge 端一致)
AlarmType.VEHICLE_CONGESTION: 2, # 普通 AlarmType.VEHICLE_CONGESTION: 2, # 普通
AlarmType.NON_MOTOR_VEHICLE_PARKING: 2, # 普通
} }

View File

@@ -328,6 +328,18 @@ def _build_aiot_menus():
"visible": True, "visible": True,
"keepAlive": True, "keepAlive": True,
}, },
{
"id": 2030,
"parentId": 2000,
"name": "算法全局参数",
"path": "device/algorithm",
"component": "aiot/device/algorithm/index",
"componentName": "AiotDeviceAlgorithm",
"icon": "ep:setting",
"sort": 5,
"visible": True,
"keepAlive": True,
},
{ {
"id": 2040, "id": 2040,
"parentId": 2000, "parentId": 2000,

View File

@@ -193,11 +193,11 @@ async def _build_daily_report_data() -> Optional[Dict]:
false_alarm_count += 1 false_alarm_count += 1
if sec_ext.dispatched_time and sec_ext.confirmed_time: if sec_ext.dispatched_time and sec_ext.confirmed_time:
delta = (sec_ext.confirmed_time - sec_ext.dispatched_time).total_seconds() / 60.0 delta = (sec_ext.confirmed_time - sec_ext.dispatched_time).total_seconds() / 60.0
if 0 <= delta <= 360: if 0 <= delta <= 60:
response_times.append(delta) response_times.append(delta)
if sec_ext.dispatched_time and sec_ext.completed_time: if sec_ext.dispatched_time and sec_ext.completed_time:
delta = (sec_ext.completed_time - sec_ext.dispatched_time).total_seconds() / 60.0 delta = (sec_ext.completed_time - sec_ext.dispatched_time).total_seconds() / 60.0
if 0 <= delta <= 24 * 60: if 0 <= delta <= 6 * 60:
close_times.append(delta) close_times.append(delta)
clean_ext = clean_ext_map.get(order.id) clean_ext = clean_ext_map.get(order.id)
@@ -207,11 +207,11 @@ async def _build_daily_report_data() -> Optional[Dict]:
dispatch_time = clean_ext.first_dispatched_time or clean_ext.dispatched_time dispatch_time = clean_ext.first_dispatched_time or clean_ext.dispatched_time
if dispatch_time and clean_ext.arrived_time: if dispatch_time and clean_ext.arrived_time:
delta = (clean_ext.arrived_time - dispatch_time).total_seconds() / 60.0 delta = (clean_ext.arrived_time - dispatch_time).total_seconds() / 60.0
if 0 <= delta <= 360: if 0 <= delta <= 60:
response_times.append(delta) response_times.append(delta)
if dispatch_time and clean_ext.completed_time: if dispatch_time and clean_ext.completed_time:
delta = (clean_ext.completed_time - dispatch_time).total_seconds() / 60.0 delta = (clean_ext.completed_time - dispatch_time).total_seconds() / 60.0
if 0 <= delta <= 24 * 60: if 0 <= delta <= 6 * 60:
close_times.append(delta) close_times.append(delta)
camera_counter = Counter() camera_counter = Counter()

View File

@@ -19,6 +19,7 @@ VLM_TYPE_NAMES = {
"intrusion": "周界入侵", "intrusion": "周界入侵",
"illegal_parking": "车辆违停", "illegal_parking": "车辆违停",
"vehicle_congestion": "车辆拥堵", "vehicle_congestion": "车辆拥堵",
"non_motor_vehicle_parking": "非机动车违停",
} }
# 算法类型 → VLM Prompt 模板 # 算法类型 → VLM Prompt 模板
@@ -58,6 +59,20 @@ description要求≤15字直接说结论注明大致车辆数。
告警成立示例:"约5辆车拥堵在路口" 告警成立示例:"约5辆车拥堵在路口"
误报示例:"车辆正常通行无拥堵" 误报示例:"车辆正常通行无拥堵"
仅输出JSON{{"confirmed":true,"description":"..."}}""", 仅输出JSON{{"confirmed":true,"description":"..."}}""",
"non_motor_vehicle_parking": """你是安防监控AI复核员。算法类型非机动车违停检测监控区域{roi_name}
任务:判断图中是否有非机动车(自行车、电动车、摩托车等)违规停放在禁停区域。
分析要点:
1. 是否存在非机动车(自行车、电动车、共享单车等)
2. 非机动车是否处于静止停放状态(而非骑行经过)
3. 是否在禁停区域/消防通道内
4. 停放是否造成通道阻塞
- confirmed=true有非机动车违停告警成立
- confirmed=false无非机动车违停误报
description要求≤15字直接说结论。
告警成立示例:"电动车违停在消防通道"
误报示例:"该区域无非机动车违停"
仅输出JSON{{"confirmed":true,"description":"..."}}""",
} }
# 通用降级 prompt未知算法类型时使用 # 通用降级 prompt未知算法类型时使用
@@ -172,13 +187,16 @@ class VLMService:
content = content.strip() content = content.strip()
result = json.loads(content) result = json.loads(content)
# 兼容不同 prompt 返回格式is_real/reason vs confirmed/description
confirmed = result.get("confirmed") if "confirmed" in result else result.get("is_real", True)
description = result.get("description") or result.get("reason", "")
logger.info( logger.info(
f"VLM 复核完成: confirmed={result.get('confirmed')}, " f"VLM 复核完成: confirmed={confirmed}, "
f"desc={result.get('description', '')[:30]}" f"desc={description[:30]}"
) )
return { return {
"confirmed": result.get("confirmed", True), "confirmed": confirmed,
"description": result.get("description", ""), "description": description,
"skipped": False, "skipped": False,
} }