| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- """M9 修复:作者画像按作者缓存(画像是作者级,同 run 内同作者只拉一次)。"""
- from content_agent.business_modules.progressive_screening import _ProgressiveContext
- class CountingLimiter:
- def __init__(self):
- self.calls = 0
- def wait(self):
- self.calls += 1
- return 0.0
- class CountingPortraitClient:
- def __init__(self):
- self.calls = []
- def fetch_account_fans_portrait(self, account_id):
- self.calls.append(account_id)
- return {
- "fans": {
- "age": {
- "data": {
- "41-50": {"percentage": "31.73%", "preference": "210"},
- "50-": {"percentage": "29.76%", "preference": "130"},
- }
- }
- }
- }
- def _ctx(client, *, limiter=None, portrait_limiter=None):
- return _ProgressiveContext(
- run_id="r", policy_run_id="p", source_context={}, policy_bundle={},
- platform_client=client, runtime=None, gemini_video_client=None,
- limiter=limiter, portrait_limiter=portrait_limiter,
- archive_dispatcher=None, platform="douyin", portrait_client=client,
- )
- def _bundle(author, q=80, p=70):
- return {
- "pattern_match_result": {"query_relevance_score": q},
- "content_engagement_metrics": {"platform_performance": {"platform_performance_score": p}},
- "content": {"author": {"platform_author_id": author}},
- }
- def test_portrait_cached_by_author():
- client = CountingPortraitClient()
- ctx = _ctx(client)
- bundles = [_bundle("a1"), _bundle("a1"), _bundle("a2")]
- ctx._inject_fifty_plus(bundles)
- assert client.calls == ["a1", "a2"] # 同作者只拉一次
- assert all(b["content_audience_50plus"]["status"] == "ok" for b in bundles)
- def test_portrait_uses_separate_limiter_bucket():
- client = CountingPortraitClient()
- search_limiter = CountingLimiter()
- portrait_limiter = CountingLimiter()
- ctx = _ctx(client, limiter=search_limiter, portrait_limiter=portrait_limiter)
- ctx._inject_fifty_plus([_bundle("a1"), _bundle("a2")])
- assert search_limiter.calls == 0
- assert portrait_limiter.calls == 2
- def test_portrait_fetched_when_query_passes_regardless_of_platform():
- # 修复:门只看 query 相关性,平台分再低也拉画像、算适老性(原来 platform<65 会跳过)。
- client = CountingPortraitClient()
- ctx = _ctx(client)
- bundles = [_bundle("a1", q=70, p=20)] # 平台仅 20
- ctx._inject_fifty_plus(bundles)
- assert client.calls == ["a1"]
- assert bundles[0]["content_audience_50plus"]["status"] == "ok"
- def test_portrait_skipped_when_query_below_gate():
- client = CountingPortraitClient()
- ctx = _ctx(client)
- bundles = [_bundle("a1", q=60, p=90)] # 相关性 60 < 65 → 不拉
- ctx._inject_fifty_plus(bundles)
- assert client.calls == []
- assert bundles[0]["content_audience_50plus"]["status"] == "not_attempted"
- class FlakyPortraitClient:
- def __init__(self, fail_times):
- self.fail_times = fail_times
- self.calls = 0
- def fetch_account_fans_portrait(self, account_id):
- self.calls += 1
- if self.calls <= self.fail_times:
- raise RuntimeError("热点宝 429")
- return {"fans": {"age": {"data": {"50-": {"percentage": "40%", "preference": "150"}}}}}
- def test_portrait_retry_recovers_transient_failure():
- client = FlakyPortraitClient(fail_times=1) # 第一次失败、第二次成功
- ctx = _ctx(client)
- bundles = [_bundle("a1")]
- ctx._inject_fifty_plus(bundles)
- assert client.calls == 2 # 重试了一次
- assert bundles[0]["content_audience_50plus"]["status"] == "ok"
- def test_portrait_unavailable_records_failure_reason():
- client = FlakyPortraitClient(fail_times=9) # 一直失败
- ctx = _ctx(client)
- bundles = [_bundle("a1")]
- ctx._inject_fifty_plus(bundles)
- block = bundles[0]["content_audience_50plus"]
- assert block["status"] == "unavailable"
- assert "热点宝 429" in block["failure_reason"] # 原因被记下,不再吞掉
|