返回 last30days-skill
test_reddit_relevance_ranking.py
根目录 / tests / test_reddit_relevance_ranking.py
1 """Tests for relevance-floor + relevance-first ranking in the Reddit paths.
2
3 Reddit's highest-upvote content (relationship drama, AITA, viral news) often
4 has near-zero topic overlap. Before this change both the keyed (ScrapeCreators)
5 and keyless (RSS) paths ranked the final list engagement-first, so a viral
6 off-topic post outranked on-topic posts. These tests pin the new behavior:
7 on-topic posts rank first and pure zero-overlap posts are dropped when anything
8 relevant remains.
9 """
10
11 from unittest import mock
12
13 from lib import reddit, reddit_keyless
14
15
16 # --------------------------------------------------------------------------- #
17 # Shared ranking key
18 # --------------------------------------------------------------------------- #
19
20 class TestRelevanceRankKey:
21 def test_on_topic_low_upvote_beats_off_topic_viral(self):
22 on_topic = {"relevance": 0.3, "engagement": {"score": 10, "num_comments": 5}}
23 off_topic = {"relevance": 0.0, "engagement": {"score": 99999, "num_comments": 4000}}
24 assert reddit._relevance_rank_key(on_topic) > reddit._relevance_rank_key(off_topic)
25
26 def test_engagement_bonus_is_bounded(self):
27 # Even astronomical engagement adds at most 0.25, so it can never lift a
28 # relevance-0 post above a post that cleared the floor.
29 huge = {"relevance": 0.0, "engagement": {"score": 10**9, "num_comments": 10**9}}
30 assert reddit._relevance_rank_key(huge) <= 0.25 + 1e-9
31 floored = {"relevance": 0.3, "engagement": {"score": 0, "num_comments": 0}}
32 assert reddit._relevance_rank_key(floored) > reddit._relevance_rank_key(huge)
33
34 def test_keyless_key_matches_keyed_semantics(self):
35 on_topic = {"relevance": 0.3, "engagement": {"score": 10, "num_comments": 5}}
36 off_topic = {"relevance": 0.0, "engagement": {"score": 99999, "num_comments": 4000}}
37 assert reddit_keyless._relevance_rank_key(on_topic) > reddit_keyless._relevance_rank_key(off_topic)
38
39
40 # --------------------------------------------------------------------------- #
41 # Keyed path (reddit.search_reddit)
42 # --------------------------------------------------------------------------- #
43
44 def _raw(rid, title, ups, sub):
45 return {
46 "id": f"t3_{rid}",
47 "title": title,
48 "selftext": "",
49 "permalink": f"/r/{sub}/comments/{rid}/post/",
50 "subreddit": sub,
51 "created_utc": 1716000000, # 2024-05-18, kept by a wide date range
52 "ups": ups,
53 "num_comments": max(1, ups // 10),
54 }
55
56
57 class TestKeyedRanking:
58 def test_on_topic_outranks_viral_and_zero_overlap_dropped(self):
59 topic = "electric vehicle home charging"
60 on_topic = _raw("aaa", "Electric vehicle home charging setup guide", 5, "electricvehicles")
61 viral = _raw("bbb", "AITA for not sharing my lottery winnings", 99999, "AmItheAsshole")
62
63 with mock.patch.object(reddit, "_global_search", return_value=[on_topic, viral]), \
64 mock.patch.object(reddit, "_subreddit_search", return_value=[]):
65 result = reddit.search_reddit(topic, "2000-01-01", "2100-01-01", depth="default", token="x")
66
67 items = result["items"]
68 urls = [it["url"] for it in items]
69 # Zero-overlap viral post is stripped because an on-topic post exists.
70 assert any("electricvehicles" in u for u in urls)
71 assert not any("AmItheAsshole" in u for u in urls)
72 # On-topic post leads.
73 assert "electricvehicles" in items[0]["url"]
74
75
76 # --------------------------------------------------------------------------- #
77 # Keyless path (reddit_keyless.search_and_enrich)
78 # --------------------------------------------------------------------------- #
79
80 def _kpost(rid, rel, score, date="2026-05-20"):
81 return {
82 "id": "", "title": f"Post {rid}", "url": f"https://www.reddit.com/r/t/comments/{rid}/p/",
83 "score": score, "num_comments": score, "subreddit": "t", "created_utc": None,
84 "author": "u", "selftext": "", "date": date,
85 "engagement": {"score": score, "num_comments": score, "upvote_ratio": None},
86 "relevance": rel, "why_relevant": "Reddit RSS", "metadata": {},
87 }
88
89
90 class TestKeylessRanking:
91 def test_relevance_first_and_zero_overlap_dropped(self):
92 on_strong = _kpost("aaa", 0.5, 10)
93 on_weak = _kpost("bbb", 0.2, 5000)
94 off_viral = _kpost("ccc", 0.0, 99999)
95
96 with mock.patch.object(reddit_keyless, "_discover",
97 return_value=[off_viral, on_weak, on_strong]), \
98 mock.patch.object(reddit_keyless, "_enrich", side_effect=lambda posts, depth: posts):
99 out = reddit_keyless.search_and_enrich(
100 "some topic", "2026-05-07", "2026-06-06", depth="default")
101
102 urls = [p["url"] for p in out]
103 # Zero-overlap viral post dropped; on-topic posts kept, strongest first.
104 assert "ccc" not in "".join(urls)
105 assert out[0]["url"].endswith("/aaa/p/")
106 assert len(out) == 2
107
107 lines PYTHON