From 3fb86ee44973a6486c60d4d083b6b0d61fa0b42c Mon Sep 17 00:00:00 2001 From: ericwyuan Date: Fri, 21 Aug 2026 00:22:24 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=87=BA=E7=8E=B0=E4=BA=BA=E7=89=A9?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E5=8E=BB=E9=87=8D=20+=20=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E7=A7=BB=E8=87=B3=E9=A1=B5=E9=9D=A2=E9=A1=B6=E9=83=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - person 字段是自由文本,一帧可为多人组合且带括号特征描述 ('张三 (红色T恤), 汤圆, 人物3'),COUNT(DISTINCT person) 把每种 组合字符串当一个人物,全局误报 20 人。新增 resolve_persons: 去括号描述 -> 按中英逗号/顿号/斜杠拆分 -> family_members 抽象标签映射真名,去重后全局实际 6 人 - 导航从侧边栏 radio 改为顶部 segmented_control:窄屏下 Streamlit 侧边栏自动收起,页面入口藏进汉堡菜单导致 "成员命名/问答界面不见了";放主区域顶部任何屏宽可见 --- fam-ui/src/app.py | 56 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/fam-ui/src/app.py b/fam-ui/src/app.py index 2e72e44..76cef5c 100644 --- a/fam-ui/src/app.py +++ b/fam-ui/src/app.py @@ -6,6 +6,7 @@ FAM-UI - 家庭多模态智能监控系统前端 v2 - AI 对话 / 对话历史 / 成员命名 / 统计图表 """ import os +import re import sys import json import base64 @@ -59,6 +60,22 @@ def parse_ts(ts): return None +def resolve_persons(person_str, alias_map): + """解析一帧 person 字段为真实人物集合 + + person 是自由文本,可能为多人组合且带括号特征描述,如 + '张三 (红色T恤, 白色裤子), 汤圆 (蓝色上衣), 人物3'。 + 统计真实人数需: 去括号描述 -> 按分隔符拆分 -> 抽象标签映射真名。 + """ + s = re.sub(r'[((][^(())]*[))]', '', person_str or '') + out = set() + for n in re.split(r'[,,、/]+', s): + n = n.strip() + if n and n != '无人': + out.add(alias_map.get(n, n)) + return out + + def load_frame_b64(event_id, frame_index): """读取落盘关键帧为 base64,不存在返回 None""" path = os.path.join(FRAME_DIR, f'event_{event_id}', f'frame_{frame_index}.jpg') @@ -383,13 +400,13 @@ st.sidebar.markdown( '
SENTINEL HOME AI
' '', unsafe_allow_html=True) -page = st.sidebar.radio("功能页面", [ - "🕒 事件时间轴", - "💬 AI 对话", - "📝 对话历史", - "👤 成员命名", - "📈 统计图表" -], label_visibility="collapsed") +# 顶部横向导航(窄屏下侧边栏自动收起,放主区域保证任何屏宽都可见) +page = st.segmented_control( + "功能页面", + ["🕒 事件时间轴", "💬 AI 对话", "📝 对话历史", "👤 成员命名", "📈 统计图表"], + default="🕒 事件时间轴", + label_visibility="collapsed" +) or "🕒 事件时间轴" # 侧边栏底部:任务队列状态 try: @@ -433,26 +450,41 @@ if page == "🕒 事件时间轴": conn = get_db_conn() try: cursor = conn.cursor() + cursor.execute( + "SELECT abstract_label, real_name FROM family_members " + "WHERE real_name IS NOT NULL AND is_active = TRUE") + alias_map = {r['abstract_label']: r['real_name'] for r in cursor.fetchall()} + if date_str: cursor.execute(""" SELECT COUNT(DISTINCT me.event_id) AS ev, COUNT(ed.detail_id) AS fr, - COALESCE(SUM(ed.is_attention_event), 0) AS att, - COUNT(DISTINCT CASE WHEN ed.person <> '无人' THEN ed.person END) AS ps + COALESCE(SUM(ed.is_attention_event), 0) AS att FROM monitor_events me LEFT JOIN event_details ed ON ed.event_id = me.event_id WHERE DATE(me.event_start_time) = %s """, (date_str,)) + s = cursor.fetchone() or {} + cursor.execute( + """SELECT ed.person FROM event_details ed + JOIN monitor_events me ON me.event_id = ed.event_id + WHERE DATE(me.event_start_time) = %s""", (date_str,)) else: cursor.execute(""" SELECT COUNT(DISTINCT me.event_id) AS ev, COUNT(ed.detail_id) AS fr, - COALESCE(SUM(ed.is_attention_event), 0) AS att, - COUNT(DISTINCT CASE WHEN ed.person <> '无人' THEN ed.person END) AS ps + COALESCE(SUM(ed.is_attention_event), 0) AS att FROM monitor_events me LEFT JOIN event_details ed ON ed.event_id = me.event_id """) - s = cursor.fetchone() or {} + s = cursor.fetchone() or {} + cursor.execute( + "SELECT ed.person FROM event_details ed " + "JOIN monitor_events me ON me.event_id = ed.event_id") + persons = set() + for r in cursor.fetchall(): + persons |= resolve_persons(r['person'], alias_map) + s['ps'] = len(persons) finally: conn.close()