fix: Task #1693 修复新消息被旧消息盖过的两个bug

1. MAX_NEW_ITEMS截帽前按freshness_score降序排列
   - 旧消息在OCR中排前面(顶部先读),不排序会把最新消息截掉
   - 截帽前排序确保带新鲜时间戳的消息优先保留

2. 第二关DB查询改为查本条消息自身记录
   - 旧消息通过get_latest_record_timestamp()搭上新消息的新鲜DB记录
   - 导致旧消息time_diff≤300s通关,分数盖过真正的新消息
   - 新增get_message_record_timestamp()查询本条消息自身的入库时间
This commit is contained in:
youpaishidifu
2026-09-11 13:12:30 +08:00
parent 385995bf4f
commit 6f4b6a645e
+54 -8
View File
@@ -986,6 +986,44 @@ class MessageHistory:
finally: finally:
conn.close() conn.close()
def get_message_record_timestamp(self, platform: str, text: str) -> Optional[datetime]:
"""获取特定消息的入库时间(用于第二关判断,防止旧消息搭便车)"""
# ⭐ Task #1693: 查询本条消息自身的 created_at,而非全局最新记录
self._ensure_user_messages_table()
conn = self._get_conn()
try:
# 使用模糊匹配:消息可能经过 OCR 噪声清理,与入库原文有微小差异
# 先尝试精确匹配,失败则用 LIKE 模糊匹配
cursor = conn.execute(
"SELECT created_at FROM user_messages WHERE platform=? AND content=? ORDER BY created_at DESC LIMIT 1",
(platform, text)
)
row = cursor.fetchone()
if row:
try:
return datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError):
return None
# 精确匹配失败:尝试模糊匹配(CJK 骨架子串)
# 提取消息的中文骨架,用 LIKE 查找包含该骨架的最近记录
cjk_skeleton = ''.join(c for c in text if '\u4e00' <= c <= '\u9fff')
if len(cjk_skeleton) >= 4:
cursor = conn.execute(
"SELECT created_at FROM user_messages WHERE platform=? AND content LIKE ? ORDER BY created_at DESC LIMIT 1",
(platform, f'%{cjk_skeleton}%')
)
row = cursor.fetchone()
if row:
try:
return datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError):
return None
return None
finally:
conn.close()
class MessageMonitor: class MessageMonitor:
"""多线程消息监控器""" """多线程消息监控器"""
@@ -10724,7 +10762,11 @@ class MessageMonitor:
self._seen_messages = set(list(seen)[-300:]) self._seen_messages = set(list(seen)[-300:])
# 单次 poll 最多处理 3 条新消息,防止系统消息海量涌入触发回环 # 单次 poll 最多处理 3 条新消息,防止系统消息海量涌入触发回环
# ⭐ Task #1693: 截帽前按 freshness_score 降序排列,确保带新鲜时间戳的消息
# 优先保留。旧消息(freshness<0)在 OCR 中排前面(顶部先读),
# 不排序会把最新消息截掉——恰好与预期相反。
MAX_NEW_ITEMS = 3 MAX_NEW_ITEMS = 3
new_items.sort(key=lambda it: freshness_scores.get(it.strip(), 0), reverse=True)
new_items = new_items[:MAX_NEW_ITEMS] new_items = new_items[:MAX_NEW_ITEMS]
# 过滤纯时间/日期文本 → 不应进入 KB 匹配(OCR 时间戳分割遗留的噪音片段) # 过滤纯时间/日期文本 → 不应进入 KB 匹配(OCR 时间戳分割遗留的噪音片段)
@@ -10975,14 +11017,18 @@ class MessageMonitor:
logger.debug(f"[{platform}] 第一关未通过:无新鲜时间戳(自身),进入第二关: {item[:50]}") logger.debug(f"[{platform}] 第一关未通过:无新鲜时间戳(自身),进入第二关: {item[:50]}")
# 第二关:查库判断 # 第二关:查库判断
# ⭐ Task #1693: 修复旧消息"搭便车"问题
# 旧消息通过 get_latest_record_timestamp() 搭上新消息的新鲜DB记录,
# 导致 time_diff≤300s → 通关 → 分数从15→20/18→23,盖过真正的新消息。
# 修复:改为查询「本条消息自身的 DB 记录」时间戳,而非全局最新记录。
if self._msg_history: if self._msg_history:
latest_ts = self._msg_history.get_latest_record_timestamp(platform) # 查询本条消息自身的入库时间(created_at)
has_record = self._msg_history.has_ocr_message_record(platform, cleaned_item) my_record_ts = self._msg_history.get_message_record_timestamp(platform, cleaned_item)
if latest_ts is not None: if my_record_ts is not None:
# 有数据库记录:计算本地时间与记录时间戳的差值 # 有本条消息的数据库记录:计算本地时间与记录时间戳的差值
time_diff_seconds = (datetime.now() - latest_ts).total_seconds() time_diff_seconds = (datetime.now() - my_record_ts).total_seconds()
logger.debug(f"[{platform}] 第二关:有DB记录,时间差={time_diff_seconds:.0f}秒: {item[:50]}") logger.debug(f"[{platform}] 第二关:有本条DB记录,时间差={time_diff_seconds:.0f}秒: {item[:50]}")
if time_diff_seconds <= 300: if time_diff_seconds <= 300:
# 本地时间 - 记录时间戳 ≤ 300秒:通关 # 本地时间 - 记录时间戳 ≤ 300秒:通关
@@ -10994,9 +11040,9 @@ class MessageMonitor:
score = -100.0 score = -100.0
logger.info(f"[{platform}] 第二关拦截:DB记录时间差{time_diff_seconds:.0f}秒>300秒,丢弃: {item[:50]}") logger.info(f"[{platform}] 第二关拦截:DB记录时间差{time_diff_seconds:.0f}秒>300秒,丢弃: {item[:50]}")
else: else:
# 无数据库记录:返团拦截 # 无本条消息的数据库记录:返团拦截
score = -100.0 score = -100.0
logger.info(f"[{platform}] 第二关拦截:无DB记录,丢弃: {item[:50]}") logger.info(f"[{platform}] 第二关拦截:无本条消息DB记录,丢弃: {item[:50]}")
else: else:
# msg_history不可用:强制丢弃 # msg_history不可用:强制丢弃
score = -100.0 score = -100.0