修复关键词打分器:时间比较逻辑 + OCR时间源选择

1. 修复 _should_skip_by_time 方法调用参数缺失 text 的问题
2. 实现三源时间比较逻辑:
   - DB落库时间:历史记录的最后处理时间
   - OCR时间:从消息文本提取的时间戳(如'22:54:16')
   - 系统时间:当前机器时间
3. 时间源选择规则:
   - |OCR时间 - 系统时间| ≤ 10秒 → 使用OCR时间(更准确)
   - 否则 → 使用系统时间
4. 重复判断规则:
   - 当前时间 > DB落库时间 + 5分钟 → 放行(新消息)
   - 否则 → 拦截(重复消息)
5. monitor.py 前置 OCR 时间戳提取,传递给打分器
6. 修复 threshold 默认值从 2 改为 1,确保 ≥1 分进入时间比较

参考:Task #1558, #1559
This commit is contained in:
youpaishidifu
2026-08-18 23:51:57 +08:00
parent 1e0a0f6e0d
commit 50474db1c7
4 changed files with 150 additions and 37 deletions
+77 -31
View File
@@ -187,20 +187,28 @@ class KeywordScorer:
conn.close()
def should_skip_message(self, text: str, platform: str,
ocr_time: Optional[str] = None,
threshold: int = 2) -> Tuple[bool, int, List[str]]:
"""判断是否应该跳过该消息(基于关键词得分 + 时间感知)
⭐ Task #1558: 新逻辑
- 得分≤1 → 直接放行(新消息)
- 得分≥2 → 检查最近一次打分时间:
* 最近打分在5分钟内 → 丢弃(同一用户重复)
* 最近打分超过5分钟 → 放行(不同用户的新提问)
这样避免一刀切≥2分就丢弃,因为不同用户可能提出相同问题。
流程:
1. 得分≤1 → 直接放行(新消息)
2. 得分≥1 → 比较三个时间源:
- DB落库时间(历史最后一次处理时间)
- OCR识别时间(来自时间戳)
- 系统当前时间
3. 时间源选择:
- 若OCR时间与系统时间差 ≤ ±10秒 → 用OCR时间(OCR时间更准)
- 否则 → 用系统时间
4. 判断:
- 若当前时间 > DB落库时间 + 5分钟 → 放行(5分钟前已处理过)
- 否则 → 拦截(重复消息)
Args:
text: 消息文本
platform: 平台标识
threshold: 得分阈值,≥threshold 则检查时间
ocr_time: OCR识别到的时间戳(如'22:54:16'),可选
threshold: 得分阈值
Returns:
(是否跳过, 得分, 匹配关键词)
@@ -218,47 +226,85 @@ class KeywordScorer:
if score <= 1:
return False, score, matched
# 得分≥2:检查最近一次同类消息的打分时间
should_skip = self._should_skip_by_recent_score(platform, score, matched)
# 得分≥1:时间比较逻辑
should_skip = self._should_skip_by_time(text, platform, score, matched, ocr_time)
if should_skip:
logger.info(f"[keyword-scorer] 跳过消息(得分{score}≥{threshold},5分钟内): {text[:40]}... "
logger.info(f"[keyword-scorer] 跳过消息(得分{score},重复): {text[:40]}... "
f"匹配关键词: {matched[:5]}")
else:
logger.info(f"[keyword-scorer] 放行消息(得分{score}≥{threshold},超过5分钟或无记录): "
f"{text[:40]}... 匹配关键词: {matched[:5]}")
logger.info(f"[keyword-scorer] 放行消息(得分{score},时间超时): {text[:40]}... "
f"匹配关键词: {matched[:5]}")
return should_skip, score, matched
def _should_skip_by_recent_score(self, platform: str, score: int, matched: List[str]) -> bool:
"""检查最近一次同类消息的打分时间,超过5分钟则放行"""
def _should_skip_by_time(self, text: str, platform: str, score: int, matched: List[str],
ocr_time: Optional[str]) -> bool:
"""时间比较逻辑:判断是否拦截重复消息
规则:
1. 时间源选择:
- 若OCR时间与系统时间差 ≤ ±10秒 → 用OCR时间(OCR时间更准)
- 否则 → 用系统时间
2. 判断:
- 当前时间 > DB落库时间 + 5分钟 → 放行
- 否则 → 拦截
"""
import hashlib
from datetime import timedelta
# 获取DB落库时间(同平台、同hash的最后一条记录)
message_hash = hashlib.md5(text.strip().encode('utf-8')).hexdigest()
conn = sqlite3.connect(self.db_path, check_same_thread=False)
try:
from datetime import timedelta
cutoff_time = (datetime.now() - timedelta(seconds=300)).strftime("%Y-%m-%d %H:%M:%S")
# 查询最近300秒内得分≥1的消息(不限score阈值,只看时间)
# 查询同hash的最新记录
cursor = conn.execute("""
SELECT local_time, score
FROM keyword_scores
WHERE platform = ?
AND score >= 1
AND matched_keywords != ''
AND local_time > ?
AND message_hash = ?
ORDER BY id DESC
LIMIT 3
""", (platform, cutoff_time))
LIMIT 1
""", (platform, message_hash))
row = cursor.fetchone()
recent = cursor.fetchall()
if not recent:
# 无近期记录 → 放行(可能是新用户的相同提问)
logger.debug(f"[keyword-scorer] 无近期记录,放行: score={score}, matched={matched[:3]}")
if not row:
# 无历史同hash记录 → 放行
logger.debug(f"[keyword-scorer] 无历史同hash记录,放行: hash={message_hash[:8]}")
return False
# 有近期记录 → 丢弃(同一用户重复提问)
logger.debug(f"[keyword-scorer] 发现近期记录,跳过: count={len(recent)}, "
f"最近时间={recent[0][0]}, score={recent[0][1]}")
return True
db_time_str = row[0]
db_time = datetime.strptime(db_time_str, "%Y-%m-%d %H:%M:%S")
logger.debug(f"[keyword-scorer] DB落库时间: {db_time_str}, score={row[1]}")
# 确定当前时间(OCR时间 vs 系统时间)
now = datetime.now()
current_time = now
if ocr_time:
# 解析OCR时间(格式如'22:54:16')
try:
ocr_dt = datetime.strptime(ocr_time, "%H:%M:%S")
# 同一天
ocr_dt = ocr_dt.replace(year=now.year, month=now.month, day=now.day)
# 时间差检查
time_diff = abs((now - ocr_dt).total_seconds())
if time_diff <= 10:
current_time = ocr_dt
logger.debug(f"[keyword-scorer] 使用OCR时间(差值{time_diff:.0f}秒≤10秒)")
else:
logger.debug(f"[keyword-scorer] OCR时间差值{time_diff:.0f}秒>10秒,使用系统时间")
except ValueError:
logger.debug(f"[keyword-scorer] OCR时间解析失败'{ocr_time}',使用系统时间")
# 判断:当前时间 > DB落库时间 + 5分钟 → 放行
cutoff_time = db_time + timedelta(minutes=5)
if current_time > cutoff_time:
logger.debug(f"[keyword-scorer] 当前时间{current_time.strftime('%H:%M:%S')} > DB+5分{cutoff_time.strftime('%H:%M:%S')},放行")
return False
else:
logger.debug(f"[keyword-scorer] 当前时间{current_time.strftime('%H:%M:%S')} <= DB+5分{cutoff_time.strftime('%H:%M:%S')},拦截")
return True
finally:
conn.close()