reaction 词表用 re|reaction|反应 但须与咻咻满或充电同现,兼容 reactio/reation 等拼写又避免通用词加分;负向改为每词 -2 且命中多条额外叠加(k*(k-1)),日期 -4;纯享(误伤纯享受)移出负向,转场移出正向。库内 796 条有效视频复测:线以下由 14 降至 8。
159 lines
6.1 KiB
Python
159 lines
6.1 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""候选分类纯函数测试: uv run python tests/test_classify.py"""
|
||
|
||
import os
|
||
import sys
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
sys.stdout.reconfigure(encoding="utf-8")
|
||
|
||
from reaction_board.classify import ( # noqa: E402
|
||
clean_title,
|
||
contains_any,
|
||
date_hits,
|
||
matched_words,
|
||
normalize_title,
|
||
parse_duration,
|
||
passes_required_title,
|
||
score_item,
|
||
)
|
||
|
||
CLASSIFY = {
|
||
"require_title": "咻咻满",
|
||
"self_mid": 37754047,
|
||
"reaction": ["re", "reaction", "反应"],
|
||
"charge": ["加急", "特急", "充电", "专场"],
|
||
"weak_positive": ["双人", "路人", "第一次", "首次"],
|
||
"negative": ["歌切", "切片", "搬运", "完整版", "录播", "录像"],
|
||
"date_negative": True,
|
||
"duration_boost": {"gt_30min": 1, "gt_60min": 2},
|
||
}
|
||
|
||
|
||
def score(title, up_mid=100, duration=0, classify=CLASSIFY):
|
||
return score_item(
|
||
title=title, up_mid=up_mid,
|
||
duration_seconds=duration, classify=classify,
|
||
)
|
||
|
||
|
||
def test_title_helpers():
|
||
assert normalize_title("咻咻满 REACTION") == "咻咻满 reaction"
|
||
assert clean_title('<em class="keyword">咻咻满</em> reaction') == "咻咻满 reaction"
|
||
assert passes_required_title("【咻咻满】初听", "咻咻满")
|
||
assert passes_required_title("咻咻满", "咻咻满")
|
||
assert not passes_required_title("其他 UP 的 reaction", "咻咻满")
|
||
assert contains_any("咻咻满 REACTION", ["reaction"])
|
||
assert not contains_any("咻咻满", [])
|
||
assert matched_words("咻咻满 REACTION 反应", ["reaction", "反应"]) == ["reaction", "反应"]
|
||
assert matched_words("咻咻满", []) == []
|
||
|
||
|
||
def test_parse_duration():
|
||
assert parse_duration("105:5") == 105 * 60 + 5
|
||
assert parse_duration("1:02:03") == 3723
|
||
assert parse_duration("15:30") == 930
|
||
assert parse_duration(600) == 600
|
||
assert parse_duration("bad") == 0
|
||
assert parse_duration(None) == 0
|
||
assert parse_duration(-5) == 0
|
||
|
||
|
||
def test_date_hits():
|
||
assert date_hits("咻咻满-2026-10-28-素颜满") == ["2026-10-28"]
|
||
assert date_hits("【咻咻满】10月25日直播片段") == ["10月25日"]
|
||
assert date_hits("咻咻满 ☆ 2026.10.26") == ["2026.10.26"]
|
||
assert date_hits("【咻咻满】2026/10/25直播回放") == ["2026/10/25"]
|
||
assert date_hits("咻咻满:《发如雪》2024年10月5日直播歌切") == ["2024年10月5日"]
|
||
assert date_hits("咻咻满 reaction") == []
|
||
|
||
|
||
def test_hit_count_is_superlinear():
|
||
# 正向命中次数 n:require_title + reaction(0/1) + charge 词数 + weak 词数,得分 = n^2
|
||
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_typo_and_gate():
|
||
# "re" 兼容 reaction/reactio/reation 等拼写
|
||
assert score("咻咻满 reactio", duration=60) == 4
|
||
assert score("咻咻满 Reactuon 充电", duration=60) == 9
|
||
# 标题不含 require_title 直接 0 分
|
||
assert score("其他 UP 的 reaction", duration=61 * 60) == 0
|
||
assert score("普通视频 reactio", duration=61 * 60) == 0
|
||
|
||
|
||
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
|
||
assert score("咻咻满", duration=0) == 1
|
||
assert score("咻咻满", duration=61 * 60) == 3
|
||
assert score("咻咻满 双人", duration=61 * 60) == 6
|
||
|
||
|
||
def test_negative_single_and_multi():
|
||
# 单个负向词 -2
|
||
assert score("咻咻满 切片", duration=60) == -1
|
||
assert score("咻咻满 reaction 切片", duration=60) == 2
|
||
assert score("咻咻满 reaction 直播录像", duration=60) == 2
|
||
# 2 个负向词:-2*2 -2 = -6
|
||
assert score("咻咻满 歌切 切片", duration=60) == -5
|
||
# 3 个负向词:-2*3 -6 = -12
|
||
assert score("咻咻满 reaction 歌切 切片 完整版", duration=60) == -8
|
||
|
||
|
||
def test_self_penalty():
|
||
assert score("咻咻满 reaction", up_mid=37754047, duration=60) == 1
|
||
|
||
|
||
def test_date_negative():
|
||
assert score("咻咻满 reaction 2026-10-28 完整录播", duration=60) == -2
|
||
assert score("咻咻满 reaction 2026-10-28", duration=60) == 0
|
||
assert score("咻咻满 充电 10月25日", duration=60) == 0
|
||
disabled = dict(CLASSIFY, date_negative=False)
|
||
assert score("咻咻满 reaction 2026-10-28", duration=60, classify=disabled) == 4
|
||
|
||
|
||
def test_removed_words():
|
||
# 纯享不再算负向
|
||
assert score("咻咻满 reaction 纯享受", duration=60) == 4
|
||
# 转场不再算正向(作为错别字忽略)
|
||
assert score("咻咻满 转场", duration=60) == 1
|
||
|
||
|
||
def test_multi_hit_survives_single_negative():
|
||
# 回归:命中 咻咻满+reaction+充电 的 204min reaction 标题,扣掉单个「切片」后仍应过线
|
||
title = "【充电2.0 | 咻咻满reaction】翻唱和直播切片完全是在开盲盒呀!不知道今天会开到哪一款"
|
||
assert score(title, duration=204 * 60) == 9
|
||
assert score(title, duration=60) == 7
|
||
|
||
|
||
def main():
|
||
test_title_helpers()
|
||
test_parse_duration()
|
||
test_date_hits()
|
||
test_hit_count_is_superlinear()
|
||
test_reaction_typo_and_gate()
|
||
test_duration_boost()
|
||
test_negative_single_and_multi()
|
||
test_self_penalty()
|
||
test_date_negative()
|
||
test_removed_words()
|
||
test_multi_hit_survives_single_negative()
|
||
|
||
# 直播回放示例:命中 require+专场+路人+第一次 = 4 -> 16;39min > 30 -> +1 = 17
|
||
title = "6【直播回放】咻咻满专场 路人第一次听满满唱歌 真的超级绝超级好听耶~"
|
||
assert score(title, duration=39 * 60) == 17
|
||
print("OK: 候选标题归一化、时长解析与分类打分测试全部通过")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |