diff --git a/monitor.py b/monitor.py index 1b22950..83b5659 100644 --- a/monitor.py +++ b/monitor.py @@ -848,6 +848,20 @@ class MessageHistory: finally: conn.close() + def has_any_records_for_platform(self, platform: str) -> bool: + """检查该平台在DB中是否有任何消息记录(用于补分逻辑判断)""" + self._ensure_ocr_staging_table() + conn = self._get_conn() + try: + cursor = conn.execute( + "SELECT COUNT(*) FROM ocr_staging WHERE platform=?", + (platform,) + ) + count = cursor.fetchone()[0] + return count > 0 + finally: + conn.close() + class MessageMonitor: """多线程消息监控器""" @@ -2934,17 +2948,17 @@ class MessageMonitor: pass _t = threading.Thread(target=_read_worker, daemon=True) _t.start() - # ⭐ 急停检查:轮询 _running,急停后立即放弃等待(丢弃未出结果的 OCR) + # ⭐ 急停检查:轮询 _running 和 _stop_event,急停后立即放弃等待(丢弃未出结果的 OCR) _deadline = time.time() + timeout line = None - while self._running and time.time() < _deadline: + while self._running and not self._stop_event.is_set() and time.time() < _deadline: try: line = _q.get(timeout=0.05) break except _QueueEmpty: continue if line is None: - if not self._running: + if not self._running or self._stop_event.is_set(): logger.debug("[ocr] EasyOCR 等待期间急停,丢弃未出结果的 OCR 请求") else: self._easyocr_consecutive_failures += 1 @@ -7379,14 +7393,17 @@ class MessageMonitor: # 从文本中提取时间戳 timestamps = re.findall(r'\d{1,2}:\d{1,2}(?::\d{1,2})?', text) if not timestamps: - return 0.0 + # ⭐ Task #1661: 无时间戳返回特殊值-999.0,区分"无时间戳"与"有时间戳但较旧" + # 调用方据此判断:无时间戳→不补分、视为陈旧消息 + return -999.0 # 取最后一个时间戳(通常是最新消息的时间戳) ts_str = timestamps[-1] age_seconds = self._timestamp_age_seconds(ts_str) if age_seconds is None: - return 0.0 + # 时间戳解析失败也视为无有效时间戳 + return -999.0 # 根据时间差给分(越接近得分越高) if age_seconds <= 300: # 5分钟内 @@ -9836,12 +9853,28 @@ class MessageMonitor: new_items = [] # ⭐ 时间戳新鲜度评分:在关键词打分和查重之前计算,作为加分项 freshness_scores = {} # 存储每条消息的时间戳新鲜度分数 + # ⭐ Task #1661: 维护上一条带合法时间戳消息的新鲜度,供无时间戳消息继承 + # QQ/微信中用户连发多条消息时,仅首条带时间戳,后续紧邻消息无时间戳 + # 无时间戳消息继承前驱时间戳新鲜度 → 同批次消息共享时间上下文,避免误处理旧消息 + _last_ts_freshness = None # 上一条带合法时间戳消息的新鲜度分数 + _last_ts_text = None # 上一条带合法时间戳消息的文本(仅日志用) for t in texts: ts = t.strip() if not ts: continue # 计算时间戳新鲜度分数(作为后续综合得分的加分项) freshness_score = self._timestamp_freshness_score(ts) + # ⭐ Task #1661: 无时间戳(-999.0) → 继承同批次紧邻前驱消息的时间戳新鲜度 + if freshness_score == -999.0: + if _last_ts_freshness is not None: + logger.debug(f"[{platform}] 无时间戳消息继承前驱时间戳新鲜度 " + f"({_last_ts_text[:20]!r}→{ts[:20]!r}): {_last_ts_freshness:.1f}") + freshness_score = _last_ts_freshness + # else: 整批都无时间戳,保持-999.0(保守,不补分、扣10分过滤) + else: + # 有合法时间戳:更新前驱状态,供后续无时间戳消息继承 + _last_ts_freshness = freshness_score + _last_ts_text = ts # 存储原始文本和新鲜度分数 freshness_scores[ts] = freshness_score # === URL-only 兜底:无中文且含 URL → 碎片 === @@ -10531,6 +10564,11 @@ class MessageMonitor: old_score = score score = max(score + 5.0, 5.0) logger.info(f"[{platform}] 时间戳新鲜度≥5直接放行(+5): {old_score:.1f}→{score:.1f}: {item[:50]}") + elif freshness_score == -999.0: + # ⭐ Task #1661: 无有效时间戳(OCR丢失或解析失败) + # 不补分、直接扣分为负,避免旧消息因"DB无记录"误获补分 + score -= 10.0 + logger.info(f"[{platform}] 无有效时间戳(OCR丢失?)扣10分: {score:.1f}: {item[:50]}") elif freshness_score < 0: # 负分(超过1小时):扣10分 score += freshness_score # 加上负分 = 扣分 @@ -10540,11 +10578,14 @@ class MessageMonitor: if self._msg_history and self._msg_history.has_ocr_message_record(platform, cleaned_item): # DB有记录:不加分 logger.info(f"[{platform}] 时间戳新鲜度<5且DB有记录,不加分: {item[:50]}") - else: - # DB无记录:补分到5(确保最终分数至少为5,不因负基础分而被丢弃) + elif self._msg_history and self._msg_history.has_any_records_for_platform(platform): + # DB存在但该消息无记录:补分到5(确保最终分数至少为5,不因负基础分而被丢弃) old_score = score score = max(score + 5.0, 5.0) logger.info(f"[{platform}] 时间戳新鲜度<5且DB无记录,补分到5: {old_score:.1f}→{score:.1f}: {item[:50]}") + else: + # ⭐ Task #1662: DB不存在或无记录时跳过,不加分(避免旧消息因补分逻辑被误处理) + logger.info(f"[{platform}] 时间戳新鲜度<5且DB无记录/不存在,跳过补分: {item[:50]}") # ⭐ 检查分数是否>0:分数<=0的消息不应被处理 if score <= 0: @@ -10601,8 +10642,7 @@ class MessageMonitor: ) _is_same_round_dup = True break - # 高相似度匹配 - from difflib import SequenceMatcher + # 高相似度匹配(SequenceMatcher 已在文件头全局导入) sim = SequenceMatcher(None, sk_cur, sk_prev).ratio() if sim >= 0.7: logger.info(