feat(classify): super-linear positive weighting by hit count
正向改为按命中次数 n(require_title + reaction + charge + weak)做平方加权,使多信号同现的真 reaction 不被单个负向词打穿(1+1>2、1+1+1>5);标题不含 require_title 直接 0 分。库内 796 条有效视频复测:线以下误伤由 47 降至 14。
This commit is contained in:
1 parent
742c67a1c3
commit
337e379514
4 files changed
+78
-75
No files matched your search
+47
-39
@@ -68,59 +68,67 @@ def test_date_hits():
|
||||
assert date_hits("咻咻满 reaction") == []
|
||||
|
||||
|
||||
def test_score_strong_reaction_requires_title():
|
||||
# 强正向 reaction 只在标题含 require_title 时成立。
|
||||
assert score("咻咻满 reaction", duration=60) == 3
|
||||
assert score("咻咻满 反应", duration=60) == 3
|
||||
def test_hit_count_is_superlinear():
|
||||
# 正向命中次数 n:require_title + reaction + charge + weak,得分 = n^2
|
||||
# 1 -> 1, 2 -> 4, 3 -> 9(满足 1+1>2、1+1+1>5)
|
||||
assert score("咻咻满", duration=60) == 1
|
||||
assert score("咻咻满 reaction", duration=60) == 4
|
||||
assert score("咻咻满 反应", duration=60) == 4
|
||||
assert score("咻咻满 reaction 充电", duration=60) == 9
|
||||
assert score("咻咻满 reaction 加急充电专场", duration=60) == 25
|
||||
assert score("咻咻满 充电", duration=60) == 4
|
||||
assert score("咻咻满 加急专场", duration=60) == 9
|
||||
assert score("咻咻满 双人", duration=60) == 4
|
||||
assert score("咻咻满 reaction 双人", duration=60) == 9
|
||||
|
||||
|
||||
def test_reaction_requires_title():
|
||||
# 标题不含 require_title 直接 0 分,即使命中 reaction
|
||||
assert score("其他 UP 的 reaction", duration=61 * 60) == 0
|
||||
|
||||
|
||||
def test_score_charge_levels():
|
||||
assert score("咻咻满 充电", duration=60) == 1
|
||||
assert score("咻咻满 加急专场", duration=60) == 3
|
||||
assert score("咻咻满 reaction 充电", duration=60) == 4
|
||||
assert score("咻咻满 reaction 加急充电专场", duration=60) == 6
|
||||
def test_duration_boost():
|
||||
assert score("咻咻满 reaction", duration=61 * 60) == 6
|
||||
assert score("咻咻满 reaction", duration=31 * 60) == 5
|
||||
assert score("咻咻满 reaction", duration=30 * 60) == 4
|
||||
# 只有 require_title 一个命中时,时长仍计入(基础分偏低,不足以过线)
|
||||
assert score("咻咻满", duration=0) == 1
|
||||
assert score("咻咻满", duration=61 * 60) == 3
|
||||
assert score("咻咻满 双人", duration=61 * 60) == 6
|
||||
|
||||
|
||||
def test_score_weak_positive():
|
||||
assert score("咻咻满 双人", duration=60) == 1
|
||||
assert score("咻咻满 reaction 双人", duration=60) == 4
|
||||
def test_negative_and_self():
|
||||
assert score("咻咻满 切片", duration=60) == -5
|
||||
assert score("咻咻满 reaction 切片", duration=60) == -2
|
||||
assert score("咻咻满 reaction 直播录像", duration=60) == -2
|
||||
assert score("咻咻满 reaction", up_mid=37754047, duration=60) == 1
|
||||
|
||||
|
||||
def test_duration_only_counts_with_signal():
|
||||
assert score("咻咻满 reaction", duration=61 * 60) == 5
|
||||
assert score("咻咻满 reaction", duration=31 * 60) == 4
|
||||
assert score("咻咻满 reaction", duration=30 * 60) == 3
|
||||
# 无 reaction / charge 信号时,时长不给分(避免纯长录像进池)。
|
||||
assert score("咻咻满", duration=61 * 60) == 0
|
||||
assert score("咻咻满 双人", duration=61 * 60) == 1
|
||||
|
||||
|
||||
def test_score_negative_and_self():
|
||||
assert score("咻咻满 切片", duration=60) == -6
|
||||
assert score("咻咻满 reaction 切片", duration=60) == -3
|
||||
assert score("咻咻满 reaction 直播录像", duration=60) == -3
|
||||
assert score("咻咻满 reaction", up_mid=37754047, duration=60) == 0
|
||||
|
||||
|
||||
def test_score_date_negative():
|
||||
assert score("咻咻满 reaction 2026-10-28 完整录播", duration=60) == -9
|
||||
assert score("咻咻满 reaction 2026-10-28", duration=60) == -3
|
||||
assert score("咻咻满 充电 10月25日", duration=60) == -5
|
||||
def test_date_negative():
|
||||
assert score("咻咻满 reaction 2026-10-28 完整录播", duration=60) == -8
|
||||
assert score("咻咻满 reaction 2026-10-28", duration=60) == -2
|
||||
assert score("咻咻满 充电 10月25日", duration=60) == -2
|
||||
disabled = dict(CLASSIFY, date_negative=False)
|
||||
assert score("咻咻满 reaction 2026-10-28", duration=60, classify=disabled) == 3
|
||||
assert score("咻咻满 reaction 2026-10-28", duration=60, classify=disabled) == 4
|
||||
|
||||
|
||||
def test_multi_hit_survives_single_negative():
|
||||
# 回归:命中 咻咻满+reaction+充电 的 204min reaction 标题,扣掉「切片」后仍应过线
|
||||
title = "【充电2.0 | 咻咻满reaction】翻唱和直播切片完全是在开盲盒呀!不知道今天会开到哪一款"
|
||||
assert score(title, duration=204 * 60) == 5
|
||||
assert score(title, duration=60) == 3
|
||||
|
||||
|
||||
def main():
|
||||
test_title_helpers()
|
||||
test_parse_duration()
|
||||
test_date_hits()
|
||||
test_score_strong_reaction_requires_title()
|
||||
test_score_charge_levels()
|
||||
test_score_weak_positive()
|
||||
test_duration_only_counts_with_signal()
|
||||
test_score_negative_and_self()
|
||||
test_score_date_negative()
|
||||
test_hit_count_is_superlinear()
|
||||
test_reaction_requires_title()
|
||||
test_duration_boost()
|
||||
test_negative_and_self()
|
||||
test_date_negative()
|
||||
test_multi_hit_survives_single_negative()
|
||||
print("OK: 候选标题归一化、时长解析与分类打分测试全部通过")
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ def write_config(tmp, **discover):
|
||||
"date_negative": True,
|
||||
"duration_boost": {"gt_30min": 1, "gt_60min": 2},
|
||||
},
|
||||
"notify": {"min_score": 3, "max_items_per_message": 10},
|
||||
"notify": {"min_score": 4, "max_items_per_message": 10},
|
||||
}
|
||||
path = Path(tmp) / "config.json"
|
||||
path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
|
||||
@@ -130,7 +130,7 @@ def test_filters_and_scoring():
|
||||
assert summary.blacklisted == 1
|
||||
assert summary.candidates == 1 # aid 6
|
||||
assert summary.notified == 0
|
||||
assert repo.get_candidate(6)["score"] == 0
|
||||
assert repo.get_candidate(6)["score"] == 1
|
||||
assert repo.get_candidate(2) is None
|
||||
assert repo.get_candidate(4) is None
|
||||
assert repo.get_candidate(5) is None
|
||||
|
||||
Reference in new issue
Block a user