返回 last30days-skill
test_first_party_author_cap.py
根目录 / tests / test_first_party_author_cap.py
1 """The topic subject gets a higher per-author cap than incidental authors.
2
3 The flat cap of 3 is anti-flooding protection and is right for third parties.
4 But on a person or company topic the subject is the point of the query: the
5 measured 'Peter Steinberger steipete' run recovered 8 subject-authored posts,
6 and a flat cap would discard 5 of them after the rest of Phase A worked to
7 retrieve and keep them.
8
9 The raised cap stays bounded so a prolific subject cannot crowd out all
10 commentary about them.
11 """
12
13 from lib import fusion, schema
14
15
16 def _cand(cid: str, author: str) -> schema.Candidate:
17 url = f"https://x.com/{author}/status/{cid}"
18 cand = schema.Candidate(
19 candidate_id=cid,
20 item_id=f"i{cid}",
21 source="x",
22 title=f"post {cid}",
23 url=url,
24 snippet="s",
25 subquery_labels=["primary"],
26 native_ranks={"primary:x": 1},
27 local_relevance=0.5,
28 freshness=80,
29 engagement=50,
30 source_quality=0.68,
31 rrf_score=0.02,
32 )
33 cand.source_items = [
34 schema.SourceItem(
35 item_id=f"i{cid}", source="x", title=f"post {cid}",
36 body="b", url=url, author=author,
37 )
38 ]
39 return cand
40
41
42 def _subject_posts(n: int, author: str = "steipete"):
43 return [_cand(str(i), author) for i in range(1, n + 1)]
44
45
46 def test_third_party_author_is_still_capped_at_three():
47 kept = fusion._apply_per_author_cap(_subject_posts(8, author="rando"))
48 assert len(kept) == 3, "the anti-flooding cap must be unchanged for third parties"
49
50
51 def test_subject_keeps_more_than_three_posts():
52 kept = fusion._apply_per_author_cap(
53 _subject_posts(8), first_party_handles={"steipete"}
54 )
55 assert len(kept) > 3, (
56 "the subject of the topic was capped at 3 of 8 recovered posts, "
57 "discarding evidence the rest of Phase A worked to keep"
58 )
59
60
61 def test_subject_cap_is_bounded():
62 """A prolific subject must not fill the pool."""
63 kept = fusion._apply_per_author_cap(
64 _subject_posts(40), first_party_handles={"steipete"}
65 )
66 assert len(kept) <= fusion._MAX_ITEMS_PER_FIRST_PARTY_AUTHOR
67 assert len(kept) < 40
68
69
70 def test_mixed_pool_caps_each_author_by_its_own_rule():
71 pool = _subject_posts(8) + [_cand(f"r{i}", "rando") for i in range(1, 8)]
72 kept = fusion._apply_per_author_cap(pool, first_party_handles={"steipete"})
73 subject = [c for c in kept if c.source_items[0].author == "steipete"]
74 third = [c for c in kept if c.source_items[0].author == "rando"]
75 assert len(subject) > 3
76 assert len(third) == 3
77
78
79 def test_no_handles_behaves_exactly_as_before():
80 pool = _subject_posts(8)
81 assert len(fusion._apply_per_author_cap(pool)) == \
82 len(fusion._apply_per_author_cap(pool, first_party_handles=set()))
83
84
85 def test_handles_are_matched_case_insensitively():
86 kept = fusion._apply_per_author_cap(
87 _subject_posts(8), first_party_handles={"SteiPete"}
88 )
89 assert len(kept) > 3
90
91
92 def test_ordering_within_an_author_is_preserved():
93 """Candidates arrive sorted by quality; the cap keeps the best N."""
94 kept = fusion._apply_per_author_cap(
95 _subject_posts(8), first_party_handles={"steipete"}
96 )
97 assert [c.candidate_id for c in kept] == \
98 [str(i) for i in range(1, len(kept) + 1)]
99
100
101 def test_weighted_rrf_accepts_first_party_handles():
102 import inspect
103 sig = inspect.signature(fusion.weighted_rrf)
104 assert "first_party_handles" in sig.parameters, (
105 "the fusion entry point must accept the run's resolved handles or the "
106 "raised cap can never fire in production"
107 )
108 assert sig.parameters["first_party_handles"].default in (None, frozenset()), (
109 "must stay optional so the discovery caller is unaffected"
110 )
111
111 lines PYTHON