fix: 出现人物统计去重 + 导航移至页面顶部
- person 字段是自由文本,一帧可为多人组合且带括号特征描述
('张三 (红色T恤), 汤圆, 人物3'),COUNT(DISTINCT person) 把每种
组合字符串当一个人物,全局误报 20 人。新增 resolve_persons:
去括号描述 -> 按中英逗号/顿号/斜杠拆分 -> family_members
抽象标签映射真名,去重后全局实际 6 人
- 导航从侧边栏 radio 改为顶部 segmented_control:窄屏下
Streamlit 侧边栏自动收起,页面入口藏进汉堡菜单导致
"成员命名/问答界面不见了";放主区域顶部任何屏宽可见
This commit is contained in:
@@ -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(
|
||||
'<div style="font-size:11px;color:#64748b;margin-top:3px;">SENTINEL HOME AI</div>'
|
||||
'</div>', 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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user