返回 last30days-skill
test_reddit_stream_keepers.py
根目录 / tests / test_reddit_stream_keepers.py
1 """Reddit engagement keepers: the month's most-discussed on-topic threads survive
2 per-stream truncation, and the fused pool reserves slots for them."""
3
4 from lib import fusion, pipeline, schema
5
6
7 def _reddit_item(i, *, score=1, ncmt=0, relevance=0.5, title="Kanye West thread", rank=None):
8 item = schema.SourceItem(
9 item_id=f"R{i}",
10 source="reddit",
11 title=f"{title} {i}",
12 body=f"{title} {i}",
13 url=f"https://www.reddit.com/r/Kanye/comments/t{i:03d}/",
14 engagement={"score": score, "num_comments": ncmt},
15 )
16 item.local_relevance = relevance
17 item.local_rank_score = rank if rank is not None else relevance
18 return item
19
20
21 def _stream(n=36):
22 items = [_reddit_item(i, relevance=0.5 - i * 0.01, rank=0.5 - i * 0.01) for i in range(n)]
23 return items
24
25
26 class TestStreamKeepers:
27 def test_high_engagement_thread_ranked_23rd_survives_truncation(self):
28 items = _stream()
29 big = _reddit_item(99, score=16180, ncmt=1450, relevance=0.19, rank=0.36,
30 title="Kanye West Heads to Putin's Russia")
31 items.insert(22, big)
32 kept = pipeline._apply_reddit_stream_keepers("reddit", items, 12, "Kanye West")
33 assert len(kept) == 12
34 assert big in kept
35
36 def test_zero_relevance_thread_is_not_kept(self):
37 items = _stream()
38 big = _reddit_item(99, score=16180, ncmt=1450, relevance=0.0, rank=0.0, title="Something else")
39 items.insert(22, big)
40 kept = pipeline._apply_reddit_stream_keepers("reddit", items, 12, "Kanye West")
41 assert big not in kept
42
43 def test_thread_without_entity_head_token_is_not_kept(self):
44 items = _stream()
45 big = _reddit_item(99, score=16180, ncmt=1450, relevance=0.19, rank=0.36,
46 title="Ye Heads to Putin's Russia")
47 items.insert(22, big)
48 kept = pipeline._apply_reddit_stream_keepers("reddit", items, 12, "Kanye West")
49 assert big not in kept
50
51 def test_short_stream_is_returned_whole(self):
52 items = _stream(2)
53 kept = pipeline._apply_reddit_stream_keepers("reddit", items, 12, "Kanye West")
54 assert kept == items
55
56 def test_non_reddit_stream_is_plain_truncation(self):
57 items = _stream()
58 for it in items:
59 it.source = "x"
60 kept = pipeline._apply_reddit_stream_keepers("x", items, 12, "Kanye West")
61 assert kept == items[:12]
62
63 def test_generic_head_token_requires_higher_relevance(self):
64 items = _stream()
65 big = _reddit_item(99, score=9000, ncmt=900, relevance=0.15, rank=0.2,
66 title="AI second brain setups")
67 items.insert(22, big)
68 kept = pipeline._apply_reddit_stream_keepers("reddit", items, 12, "AI second brain")
69 assert big not in kept
70 big.local_relevance = 0.3
71 kept = pipeline._apply_reddit_stream_keepers("reddit", items, 12, "AI second brain")
72 assert big in kept
73
74
75 def _cand(i, source, rrf, *, relevance=0.5, score=0, ncmt=0, title="Kanye West item"):
76 item = schema.SourceItem(
77 item_id=f"{source}{i}",
78 source=source,
79 title=f"{title} {i}",
80 body=f"{title} {i}",
81 url=f"https://example.com/{source}/{i}",
82 engagement={"score": score, "num_comments": ncmt},
83 )
84 return schema.Candidate(
85 candidate_id=f"{source}-{i}",
86 item_id=item.item_id,
87 source=source,
88 title=item.title,
89 url=item.url,
90 snippet="",
91 subquery_labels=["primary"],
92 native_ranks={f"primary:{source}": i + 1},
93 local_relevance=relevance,
94 freshness=80,
95 engagement=5.0,
96 source_quality=0.7,
97 rrf_score=rrf,
98 sources=[source],
99 source_items=[item],
100 )
101
102
103 class TestPoolReservation:
104 def _fused(self):
105 fused = [_cand(i, "tiktok", 0.05 - i * 0.0005) for i in range(45)]
106 fused += [_cand(i, "reddit", 0.001 - i * 0.00001, score=1, ncmt=1) for i in range(5)]
107 return fused
108
109 def test_top_engagement_reddit_candidate_is_reserved_at_default_depth(self):
110 fused = self._fused()
111 big = _cand(99, "reddit", 0.0001, relevance=0.19, score=16180, ncmt=1450)
112 fused.append(big)
113 pool = fusion._diversify_pool(fused, 40, entity="kanye west")
114 assert len(pool) == 40
115 assert big in pool
116
117 def test_out_of_window_reddit_candidate_is_not_reserved(self):
118 fused = self._fused()
119 big = _cand(99, "reddit", 0.0001, relevance=0.19, score=16180, ncmt=1450)
120 big.metadata["range_from"] = "2026-08-01"
121 big.metadata["range_to"] = "2026-08-31"
122 big.source_items[0].published_at = "2025-01-01"
123 big.source_items[0].date_confidence = "high"
124 fused.append(big)
125 pool = fusion._diversify_pool(fused, 40, entity="kanye west")
126 assert big not in pool
127
128 def test_entity_miss_reddit_candidate_is_not_reserved(self):
129 fused = self._fused()
130 big = _cand(99, "reddit", 0.0001, relevance=0.19, score=16180, ncmt=1450, title="Unrelated viral thread")
131 fused.append(big)
132 pool = fusion._diversify_pool(fused, 40, entity="kanye west")
133 assert big not in pool
134
135 def test_reservation_scales_with_pool_limit(self):
136 assert fusion._reddit_reserve_for(15) == 2
137 assert fusion._reddit_reserve_for(40) == 3
138 assert fusion._reddit_reserve_for(60) == 4
139
140
141 class TestKeeperAndReservationBounds:
142 def test_keepers_never_exceed_the_stream_limit(self):
143 items = [_reddit_item(i, score=1000 - i, ncmt=50, relevance=0.5, rank=0.5 - i * 0.01) for i in range(10)]
144 kept = pipeline._apply_reddit_stream_keepers("reddit", items, 1, "Kanye West")
145 assert len(kept) == 1
146 kept = pipeline._apply_reddit_stream_keepers("reddit", items, 0, "Kanye West")
147 assert kept == []
148
149 def test_reservation_survives_many_qualifying_sources(self):
150 fused = []
151 for source in ("tiktok", "x", "youtube", "hackernews", "grounding", "instagram", "github", "linkedin"):
152 fused += [_cand(i, source, 0.05 - i * 0.0005) for i in range(4)]
153 big = _cand(99, "reddit", 0.0001, relevance=0.19, score=16180, ncmt=1450)
154 fused.append(big)
155 pool = fusion._diversify_pool(fused, 15, entity="kanye west")
156 assert len(pool) == 15
157 assert big in pool
158
158 lines PYTHON