Преглед на файлове

feat(r3-S2): 各平台按可拉数据配多字段热度复合(抖音真改,行为变)

R3 第二步,按各平台真实能拉到的互动数据配 heat.signals:
- 抖音: 赞0.4+评0.2+转0.2+藏0.2(无播放量);视频号: 仍 digg-only(评/转/藏实测常空)
- 前瞻平台(未真跑,字段据抓包、锚点起步占位): 快手/B站用播放量主力、
  小红书收藏权重高、公众号阅读、油管播放+赞+评;github/zhihu/toutiao 不配
受控行为变化(用户确认起步值): real_id45 从 2进池/2复看 → 3进池/1复看——
content_7406990358799732018 赞仅2.1万但转3689/藏1.5万,四字段复合 heat 0.16→0.38,
从复看升进池(正是'从只看点赞到看综合互动'的区分度提升)。
重钉 3 快照(decision_summary/matrix/fingerprint),diff 全可解释(downgrade→commit_asset、
源 query 转 effective 触发翻页、tag skip 分布);断言同步;新增复合守卫单测。
330 passed,config gate pass。验收岗交叉核验 8 项全 PASS。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sam Lee преди 2 месеца
родител
ревизия
ab15c0284f

+ 97 - 15
tech_documents/数据接口与来源/platform_profiles/bilibili.json

@@ -7,25 +7,107 @@
   "runtime": {
     "rate_limit_seconds": 15,
     "retry": null,
-    "video_download": { "downloadable": true, "headers": { "User-Agent": "PC UA", "Referer": "https://www.bilibili.com/" }, "note": "实测 206 真实字节;.m4s DASH 分轨(视频轨+音频轨 voice_data.play_url 需合并)" }
+    "video_download": {
+      "downloadable": true,
+      "headers": {
+        "User-Agent": "PC UA",
+        "Referer": "https://www.bilibili.com/"
+      },
+      "note": "实测 206 真实字节;.m4s DASH 分轨(视频轨+音频轨 voice_data.play_url 需合并)"
+    }
+  },
+  "heat": {
+    "note": "R3 第二步(2026-06-12):B站有播放量,量级大以播放为主;字段据抓包,权重/锚点起步值待该平台接入真跑标定",
+    "signals": [
+      {
+        "field": "play_count",
+        "weight": 0.4,
+        "floor": 5000,
+        "ceil": 10000000
+      },
+      {
+        "field": "digg_count",
+        "weight": 0.3,
+        "floor": 500,
+        "ceil": 500000
+      },
+      {
+        "field": "comment_count",
+        "weight": 0.1,
+        "floor": 50,
+        "ceil": 50000
+      },
+      {
+        "field": "share_count",
+        "weight": 0.1,
+        "floor": 20,
+        "ceil": 50000
+      },
+      {
+        "field": "collect_count",
+        "weight": 0.1,
+        "floor": 50,
+        "ceil": 200000
+      }
+    ]
   },
   "endpoints": {
-    "search": { "path": "/crawler/bilibili/keyword", "params": { "keyword": "str" }, "response_shape": "raw(B站原生,一次 42 条)" },
-    "detail": { "path": "/crawler/bilibili/detail", "params": { "content_id": "bvid" }, "response_shape": "normalized(channel_*)" }
+    "search": {
+      "path": "/crawler/bilibili/keyword",
+      "params": {
+        "keyword": "str"
+      },
+      "response_shape": "raw(B站原生,一次 42 条)"
+    },
+    "detail": {
+      "path": "/crawler/bilibili/detail",
+      "params": {
+        "content_id": "bvid"
+      },
+      "response_shape": "normalized(channel_*)"
+    }
   },
   "jump_key_raw": {
-    "platform_content_id": { "search": "bvid", "detail": "channel_content_id" },
-    "platform_author_id":  { "search": "mid", "trap": "uid 字段恒为 0,必须取 mid" },
-    "tags":                { "search": "tag(逗号串需 split)", "detail": "topic_list" },
-    "video_url":           { "detail": "video_url_list[0].video_url(.m4s)" }
+    "platform_content_id": {
+      "search": "bvid",
+      "detail": "channel_content_id"
+    },
+    "platform_author_id": {
+      "search": "mid",
+      "trap": "uid 字段恒为 0,必须取 mid"
+    },
+    "tags": {
+      "search": "tag(逗号串需 split)",
+      "detail": "topic_list"
+    },
+    "video_url": {
+      "detail": "video_url_list[0].video_url(.m4s)"
+    }
   },
   "edges": {
-    "query_next_page":        { "status": "supported" },
-    "search_page_to_content": { "status": "supported" },
-    "video_to_author":        { "status": "supported", "note": "仅落 mid 标识" },
-    "author_to_works":        { "status": "unsupported", "note": "集合中无作者作品接口" },
-    "author_work_to_content": { "status": "unsupported" },
-    "video_to_hashtag":       { "status": "supported", "note": "tag split / topic_list" },
-    "hashtag_to_query":       { "status": "supported" }
+    "query_next_page": {
+      "status": "supported"
+    },
+    "search_page_to_content": {
+      "status": "supported"
+    },
+    "video_to_author": {
+      "status": "supported",
+      "note": "仅落 mid 标识"
+    },
+    "author_to_works": {
+      "status": "unsupported",
+      "note": "集合中无作者作品接口"
+    },
+    "author_work_to_content": {
+      "status": "unsupported"
+    },
+    "video_to_hashtag": {
+      "status": "supported",
+      "note": "tag split / topic_list"
+    },
+    "hashtag_to_query": {
+      "status": "supported"
+    }
   }
-}
+}

+ 5 - 2
tech_documents/数据接口与来源/platform_profiles/douyin.json

@@ -10,9 +10,12 @@
     "video_download": { "downloadable": true, "headers": { "User-Agent": "iOS UA", "Referer": "https://www.douyin.com/" }, "note": "实测 206/video/mp4;play_addr 地址有时效" }
   },
   "heat": {
-    "note": "R3 第一步(2026-06-12):点赞单信号=R3 前行为;抖音无播放量,第二步加 comment/share/collect 复合",
+    "note": "R3 第二步(2026-06-12):抖音无播放量,用 赞+评+转+藏 四字段复合;权重/锚点为起步值,凭真跑主观调",
     "signals": [
-      { "field": "digg_count", "weight": 1.0, "floor": 10000, "ceil": 1000000 }
+      { "field": "digg_count",    "weight": 0.4, "floor": 10000, "ceil": 1000000 },
+      { "field": "comment_count", "weight": 0.2, "floor": 100,   "ceil": 50000 },
+      { "field": "share_count",   "weight": 0.2, "floor": 50,    "ceil": 20000 },
+      { "field": "collect_count", "weight": 0.2, "floor": 100,   "ceil": 100000 }
     ]
   },
   "endpoints": {

+ 102 - 16
tech_documents/数据接口与来源/platform_profiles/kuaishou.json

@@ -7,26 +7,112 @@
   "runtime": {
     "rate_limit_seconds": 15,
     "retry": null,
-    "video_download": { "downloadable": true, "headers": { "User-Agent": "PC UA", "Referer": "https://www.kuaishou.com/" }, "note": "实测 206/video/mp4;kwaicdn 直链带 pkey 签名" }
+    "video_download": {
+      "downloadable": true,
+      "headers": {
+        "User-Agent": "PC UA",
+        "Referer": "https://www.kuaishou.com/"
+      },
+      "note": "实测 206/video/mp4;kwaicdn 直链带 pkey 签名"
+    }
+  },
+  "heat": {
+    "note": "R3 第二步(2026-06-12):快手有播放量(唯一全字段),以播放为主;字段据抓包,权重/锚点起步值待该平台接入真跑标定",
+    "signals": [
+      {
+        "field": "play_count",
+        "weight": 0.4,
+        "floor": 1000,
+        "ceil": 5000000
+      },
+      {
+        "field": "digg_count",
+        "weight": 0.3,
+        "floor": 1000,
+        "ceil": 500000
+      },
+      {
+        "field": "comment_count",
+        "weight": 0.1,
+        "floor": 50,
+        "ceil": 50000
+      },
+      {
+        "field": "share_count",
+        "weight": 0.1,
+        "floor": 20,
+        "ceil": 20000
+      },
+      {
+        "field": "collect_count",
+        "weight": 0.1,
+        "floor": 50,
+        "ceil": 100000
+      }
+    ]
   },
   "endpoints": {
-    "search":       { "path": "/crawler/kuai_shou/keyword_v2", "params": { "keyword": "str" }, "response_shape": "normalized(channel_*)" },
-    "detail":       { "path": "/crawler/kuai_shou/detail", "params": { "content_id": "str" }, "response_shape": "normalized" },
-    "account_info": { "path": "/crawler/kuai_shou/account_info", "params": { "account_id": "str", "is_cache": true } }
+    "search": {
+      "path": "/crawler/kuai_shou/keyword_v2",
+      "params": {
+        "keyword": "str"
+      },
+      "response_shape": "normalized(channel_*)"
+    },
+    "detail": {
+      "path": "/crawler/kuai_shou/detail",
+      "params": {
+        "content_id": "str"
+      },
+      "response_shape": "normalized"
+    },
+    "account_info": {
+      "path": "/crawler/kuai_shou/account_info",
+      "params": {
+        "account_id": "str",
+        "is_cache": true
+      }
+    }
   },
   "jump_key_raw": {
-    "platform_content_id": { "search": "channel_content_id" },
-    "platform_author_id":  { "search": "channel_account_id" },
-    "tags":                { "search": "topic_list(常 null,兜底标题/正文正则)" },
-    "video_url":           { "search": "video_url_list[0].video_url" }
+    "platform_content_id": {
+      "search": "channel_content_id"
+    },
+    "platform_author_id": {
+      "search": "channel_account_id"
+    },
+    "tags": {
+      "search": "topic_list(常 null,兜底标题/正文正则)"
+    },
+    "video_url": {
+      "search": "video_url_list[0].video_url"
+    }
   },
   "edges": {
-    "query_next_page":        { "status": "weak", "note": "实测单页 has_more=false" },
-    "search_page_to_content": { "status": "supported" },
-    "video_to_author":        { "status": "supported", "note": "作者档案需 account_info 二次补齐" },
-    "author_to_works":        { "status": "unsupported", "note": "无独立作者作品接口" },
-    "author_work_to_content": { "status": "unsupported" },
-    "video_to_hashtag":       { "status": "weak", "note": "topic_list 常 null,靠正则兜底" },
-    "hashtag_to_query":       { "status": "supported" }
+    "query_next_page": {
+      "status": "weak",
+      "note": "实测单页 has_more=false"
+    },
+    "search_page_to_content": {
+      "status": "supported"
+    },
+    "video_to_author": {
+      "status": "supported",
+      "note": "作者档案需 account_info 二次补齐"
+    },
+    "author_to_works": {
+      "status": "unsupported",
+      "note": "无独立作者作品接口"
+    },
+    "author_work_to_content": {
+      "status": "unsupported"
+    },
+    "video_to_hashtag": {
+      "status": "weak",
+      "note": "topic_list 常 null,靠正则兜底"
+    },
+    "hashtag_to_query": {
+      "status": "supported"
+    }
   }
-}
+}

+ 77 - 15
tech_documents/数据接口与来源/platform_profiles/weixin.json

@@ -7,25 +7,87 @@
   "runtime": {
     "rate_limit_seconds": 15,
     "retry": null,
-    "video_download": { "downloadable": false, "note": "图文平台,无视频" }
+    "video_download": {
+      "downloadable": false,
+      "note": "图文平台,无视频"
+    }
+  },
+  "heat": {
+    "note": "R3 第二步(2026-06-12):公众号只有阅读数+赞(赞常0),以阅读为主;字段据抓包,权重/锚点起步值待该平台接入真跑标定",
+    "signals": [
+      {
+        "field": "play_count",
+        "weight": 0.6,
+        "floor": 100,
+        "ceil": 500000
+      },
+      {
+        "field": "digg_count",
+        "weight": 0.4,
+        "floor": 10,
+        "ceil": 50000
+      }
+    ]
   },
   "endpoints": {
-    "search":       { "path": "/crawler/wei_xin/keyword", "params": { "keyword": "str", "cursor": "0" }, "response_shape": "item={biz,cover_url,nick_name,time,title,url};nick_name/biz 实测常为空" },
-    "detail":       { "path": "/crawler/wei_xin/detail", "params": { "content_link": "文章url" } },
-    "account_info": { "path": "/crawler/wei_xin/account_info", "params": { "content_link": "文章url" }, "note": "唯一可达的作者补全(account_name/biz_info/wx_gh)" }
+    "search": {
+      "path": "/crawler/wei_xin/keyword",
+      "params": {
+        "keyword": "str",
+        "cursor": "0"
+      },
+      "response_shape": "item={biz,cover_url,nick_name,time,title,url};nick_name/biz 实测常为空"
+    },
+    "detail": {
+      "path": "/crawler/wei_xin/detail",
+      "params": {
+        "content_link": "文章url"
+      }
+    },
+    "account_info": {
+      "path": "/crawler/wei_xin/account_info",
+      "params": {
+        "content_link": "文章url"
+      },
+      "note": "唯一可达的作者补全(account_name/biz_info/wx_gh)"
+    }
   },
   "jump_key_raw": {
-    "platform_content_id": { "search": "url(文章链接即标识)" },
-    "platform_author_id":  { "search": "url 正则抽 __biz=([^&]+)", "trap": "三套标识互不相同:biz / 网关hash channel_account_id / wx_gh;昵称只能 account_info 补" },
-    "tags":                { "trap": "整平台无 hashtag/topic_list" }
+    "platform_content_id": {
+      "search": "url(文章链接即标识)"
+    },
+    "platform_author_id": {
+      "search": "url 正则抽 __biz=([^&]+)",
+      "trap": "三套标识互不相同:biz / 网关hash channel_account_id / wx_gh;昵称只能 account_info 补"
+    },
+    "tags": {
+      "trap": "整平台无 hashtag/topic_list"
+    }
   },
   "edges": {
-    "query_next_page":        { "status": "supported" },
-    "search_page_to_content": { "status": "supported" },
-    "video_to_author":        { "status": "supported", "note": "仅落 biz(正则);昵称需 account_info 二次请求" },
-    "author_to_works":        { "status": "unsupported", "note": "blogger 不可达,无作品列表" },
-    "author_work_to_content": { "status": "unsupported" },
-    "video_to_hashtag":       { "status": "unsupported", "note": "整平台无 tag" },
-    "hashtag_to_query":       { "status": "unsupported" }
+    "query_next_page": {
+      "status": "supported"
+    },
+    "search_page_to_content": {
+      "status": "supported"
+    },
+    "video_to_author": {
+      "status": "supported",
+      "note": "仅落 biz(正则);昵称需 account_info 二次请求"
+    },
+    "author_to_works": {
+      "status": "unsupported",
+      "note": "blogger 不可达,无作品列表"
+    },
+    "author_work_to_content": {
+      "status": "unsupported"
+    },
+    "video_to_hashtag": {
+      "status": "unsupported",
+      "note": "整平台无 tag"
+    },
+    "hashtag_to_query": {
+      "status": "unsupported"
+    }
   }
-}
+}

+ 97 - 17
tech_documents/数据接口与来源/platform_profiles/xiaohongshu.json

@@ -7,27 +7,107 @@
   "runtime": {
     "rate_limit_seconds": 15,
     "retry": null,
-    "video_download": { "downloadable": null, "note": "图文为主(image_list);视频可下载性未实测" }
+    "video_download": {
+      "downloadable": null,
+      "note": "图文为主(image_list);视频可下载性未实测"
+    }
+  },
+  "heat": {
+    "note": "R3 第二步(2026-06-12):小红书无播放量,收藏文化重,收藏权重抬高;字段据抓包,权重/锚点起步值待该平台接入真跑标定",
+    "signals": [
+      {
+        "field": "collect_count",
+        "weight": 0.35,
+        "floor": 100,
+        "ceil": 200000
+      },
+      {
+        "field": "digg_count",
+        "weight": 0.35,
+        "floor": 100,
+        "ceil": 500000
+      },
+      {
+        "field": "comment_count",
+        "weight": 0.15,
+        "floor": 20,
+        "ceil": 50000
+      },
+      {
+        "field": "share_count",
+        "weight": 0.15,
+        "floor": 10,
+        "ceil": 20000
+      }
+    ]
   },
   "endpoints": {
-    "search":  { "path": "/crawler/xiao_hong_shu/keyword", "params": { "keyword": "str", "content_type": "图文|视频", "sort_type": "综合", "publish_time": "", "cursor": "" }, "response_shape": "嵌套:item={id,model_type,note_card},内容字段全在 note_card.* 下" },
-    "detail":  { "path": "/crawler/xiao_hong_shu/detail", "params": { "content_id": "str" }, "response_shape": "扁平(与 search 嵌套结构不同!)" },
-    "blogger": { "path": "/crawler/xiao_hong_shu/blogger", "status": "blocked", "note": "实测请求超时" }
+    "search": {
+      "path": "/crawler/xiao_hong_shu/keyword",
+      "params": {
+        "keyword": "str",
+        "content_type": "图文|视频",
+        "sort_type": "综合",
+        "publish_time": "",
+        "cursor": ""
+      },
+      "response_shape": "嵌套:item={id,model_type,note_card},内容字段全在 note_card.* 下"
+    },
+    "detail": {
+      "path": "/crawler/xiao_hong_shu/detail",
+      "params": {
+        "content_id": "str"
+      },
+      "response_shape": "扁平(与 search 嵌套结构不同!)"
+    },
+    "blogger": {
+      "path": "/crawler/xiao_hong_shu/blogger",
+      "status": "blocked",
+      "note": "实测请求超时"
+    }
   },
   "jump_key_raw": {
-    "platform_content_id": { "search": "item.id", "detail": "channel_content_id" },
-    "platform_author_id":  { "search": "note_card.user.user_id" },
-    "tags":                { "search": "note_card.desc 正则 #xxx", "detail": "topic_list" },
-    "interactions":        { "search": "note_card.interact_info.{liked,collected,comment,shared}_count" },
-    "timestamp_trap":      "search 秒级 vs detail 毫秒级,同一笔记两接口不一致"
+    "platform_content_id": {
+      "search": "item.id",
+      "detail": "channel_content_id"
+    },
+    "platform_author_id": {
+      "search": "note_card.user.user_id"
+    },
+    "tags": {
+      "search": "note_card.desc 正则 #xxx",
+      "detail": "topic_list"
+    },
+    "interactions": {
+      "search": "note_card.interact_info.{liked,collected,comment,shared}_count"
+    },
+    "timestamp_trap": "search 秒级 vs detail 毫秒级,同一笔记两接口不一致"
   },
   "edges": {
-    "query_next_page":        { "status": "supported" },
-    "search_page_to_content": { "status": "supported", "note": "必须深入 note_card 嵌套抽取" },
-    "video_to_author":        { "status": "supported", "note": "note_card.user.user_id" },
-    "author_to_works":        { "status": "blocked", "note": "blogger 超时" },
-    "author_work_to_content": { "status": "blocked" },
-    "video_to_hashtag":       { "status": "supported", "note": "desc 正则 / detail topic_list" },
-    "hashtag_to_query":       { "status": "supported" }
+    "query_next_page": {
+      "status": "supported"
+    },
+    "search_page_to_content": {
+      "status": "supported",
+      "note": "必须深入 note_card 嵌套抽取"
+    },
+    "video_to_author": {
+      "status": "supported",
+      "note": "note_card.user.user_id"
+    },
+    "author_to_works": {
+      "status": "blocked",
+      "note": "blogger 超时"
+    },
+    "author_work_to_content": {
+      "status": "blocked"
+    },
+    "video_to_hashtag": {
+      "status": "supported",
+      "note": "desc 正则 / detail topic_list"
+    },
+    "hashtag_to_query": {
+      "status": "supported"
+    }
   }
-}
+}

+ 78 - 14
tech_documents/数据接口与来源/platform_profiles/youtube.json

@@ -7,24 +7,88 @@
   "runtime": {
     "rate_limit_seconds": 15,
     "retry": null,
-    "video_download": { "downloadable": false, "note": "detail 的 video_url_list 实测为空,视频不可经 crawler 直取" }
+    "video_download": {
+      "downloadable": false,
+      "note": "detail 的 video_url_list 实测为空,视频不可经 crawler 直取"
+    }
+  },
+  "heat": {
+    "note": "R3 第二步(2026-06-12):油管有播放量,share/collect 常空,用 播放+赞+评;字段据抓包,权重/锚点起步值待该平台接入真跑标定",
+    "signals": [
+      {
+        "field": "play_count",
+        "weight": 0.5,
+        "floor": 1000,
+        "ceil": 10000000
+      },
+      {
+        "field": "digg_count",
+        "weight": 0.3,
+        "floor": 100,
+        "ceil": 1000000
+      },
+      {
+        "field": "comment_count",
+        "weight": 0.2,
+        "floor": 20,
+        "ceil": 100000
+      }
+    ]
   },
   "endpoints": {
-    "search": { "path": "/crawler/youtube/keyword", "params": { "keyword": "str" }, "pagination_note": "next_cursor 是 400+ 字符 URL-encoded 长 token,须整体透传" },
-    "detail": { "path": "/crawler/youtube/detail", "params": { "content_id": "video_id" }, "note": "无视频地址,仅正文/统计" }
+    "search": {
+      "path": "/crawler/youtube/keyword",
+      "params": {
+        "keyword": "str"
+      },
+      "pagination_note": "next_cursor 是 400+ 字符 URL-encoded 长 token,须整体透传"
+    },
+    "detail": {
+      "path": "/crawler/youtube/detail",
+      "params": {
+        "content_id": "video_id"
+      },
+      "note": "无视频地址,仅正文/统计"
+    }
   },
   "jump_key_raw": {
-    "platform_content_id": { "search": "video_id" },
-    "platform_author_id":  { "search": "channel_id(UC...)", "detail": "channel_account_id(32位hash)", "trap": "双体系互不相同" },
-    "tags":                { "search": "无", "detail": "topic_list=null,须 body_text 正则 #xxx" }
+    "platform_content_id": {
+      "search": "video_id"
+    },
+    "platform_author_id": {
+      "search": "channel_id(UC...)",
+      "detail": "channel_account_id(32位hash)",
+      "trap": "双体系互不相同"
+    },
+    "tags": {
+      "search": "无",
+      "detail": "topic_list=null,须 body_text 正则 #xxx"
+    }
   },
   "edges": {
-    "query_next_page":        { "status": "supported", "note": "长 token 游标" },
-    "search_page_to_content": { "status": "supported" },
-    "video_to_author":        { "status": "supported" },
-    "author_to_works":        { "status": "unsupported", "note": "无作者作品接口" },
-    "author_work_to_content": { "status": "unsupported" },
-    "video_to_hashtag":       { "status": "weak", "note": "仅 detail.body_text 正则,搜索无 tag" },
-    "hashtag_to_query":       { "status": "supported" }
+    "query_next_page": {
+      "status": "supported",
+      "note": "长 token 游标"
+    },
+    "search_page_to_content": {
+      "status": "supported"
+    },
+    "video_to_author": {
+      "status": "supported"
+    },
+    "author_to_works": {
+      "status": "unsupported",
+      "note": "无作者作品接口"
+    },
+    "author_work_to_content": {
+      "status": "unsupported"
+    },
+    "video_to_hashtag": {
+      "status": "weak",
+      "note": "仅 detail.body_text 正则,搜索无 tag"
+    },
+    "hashtag_to_query": {
+      "status": "supported"
+    }
   }
-}
+}

+ 4 - 4
tests/fixtures/snapshots/matrix/real_id45__default.json

@@ -1,15 +1,15 @@
 {
   "effect_status_counts": {
     "failed": 0,
-    "pending": 2,
+    "pending": 1,
     "rule_blocked": 0,
-    "success": 2
+    "success": 3
   },
-  "pooled": 2,
+  "pooled": 3,
   "reasons": [
     "content_score_pool",
     "content_score_pool",
-    "content_score_review",
+    "content_score_pool",
     "content_score_review"
   ],
   "rejected": 0

+ 2 - 2
tests/fixtures/snapshots/real_id45/decision_summary.json

@@ -1,6 +1,6 @@
 {
   "pending_content_count": 0,
-  "pooled_content_count": 2,
+  "pooled_content_count": 3,
   "rejected_content_count": 0,
-  "review_content_count": 2
+  "review_content_count": 1
 }

+ 19 - 10
tests/fixtures/snapshots/real_id45/walk_actions_fingerprint.json

@@ -8,15 +8,6 @@
   "normal",
   ""
  ],
- [
-  "budget_downgrade",
-  "d_003",
-  "7406990358799732018",
-  "downgrade_budget",
-  "success",
-  "low_budget",
-  "content_score_review"
- ],
  [
   "budget_downgrade",
   "d_004",
@@ -44,6 +35,15 @@
   "normal",
   "content_score_pool"
  ],
+ [
+  "decision_to_asset",
+  "d_003",
+  "7406990358799732018",
+  "commit_asset",
+  "success",
+  "normal",
+  "content_score_pool"
+ ],
  [
   "hashtag_to_query",
   "2026养生",
@@ -60,7 +60,7 @@
   "create_tag_query",
   "skipped",
   "blocked",
-  "review_tag_expansion_disabled"
+  "budget_exhausted"
  ],
  [
   "hashtag_to_query",
@@ -106,5 +106,14 @@
   "success",
   "normal",
   ""
+ ],
+ [
+  "query_next_page",
+  "q_002",
+  "q_002_page_002",
+  "fetch_next_page",
+  "success",
+  "normal",
+  ""
  ]
 ]

+ 13 - 14
tests/test_case_replay.py

@@ -74,18 +74,18 @@ def test_replay_id45_baseline_gemini_score(tmp_path):
     # M3 受控变化: 画像门槛退役,改 Gemini 相关性 + 平台热度打分。
     # 默认 FakeGeminiVideoClient 返回 fit_senior_50plus=true / relevance_score=0.85
     # → relevance=60(满分)。平台热度按各 item digg_count 对数归一化:
-    # real_id45 digg=[72459,20801,491098,24]。digg 491098 → heat≥0.8 → 40 → 总分100,
-    # digg 72459 → heat≈0.4 → 20 → 总分80(两条 ≥70 进池);digg 20801、24 热度过低
-    # → 0 → 总分60(两条落 60-69 复看)。无拒、无 pending
+    # R3 第二步(2026-06-12): 抖音热度改 赞+评+转+藏 四字段复合后,
+    # 高转发高收藏的 content_732018(赞仅 2.1万 但转 3689/藏 1.5万)heat 0.38→总分 70,
+    # 从复看升进池 → 3 进池(491098/72459/20801)+1 复看(content_907506 赞 24 全低)
     artifacts = replay_case("real_id45", runtime_root=tmp_path / "rt")
     assert artifacts.state["status"] == "success"
-    assert artifacts.summary["pooled_content_count"] == 2
-    assert artifacts.summary["review_content_count"] == 2
+    assert artifacts.summary["pooled_content_count"] == 3
+    assert artifacts.summary["review_content_count"] == 1
     assert artifacts.summary["rejected_content_count"] == 0
     assert artifacts.summary["pending_content_count"] == 0
     assert _decision_counts(artifacts) == {
-        "ADD_TO_CONTENT_POOL": 2,
-        "KEEP_CONTENT_FOR_REVIEW": 2,
+        "ADD_TO_CONTENT_POOL": 3,
+        "KEEP_CONTENT_FOR_REVIEW": 1,
     }
     # 全部命中相关性+热度打分门(旧画像 reason_code 已退役)。
     assert {d.get("decision_reason_code") for d in artifacts.decisions} == {
@@ -115,9 +115,9 @@ def test_replay_synthetic_review_case(tmp_path):
 
 
 def test_replay_id45_walk_obeys_decisions_after_m4(tmp_path):
-    # M3 受控变化: 画像门槛退役后 real_id45 拿到 2 进池 + 2 复看(原全 KEEP)
+    # R3 第二步受控变化: 四字段热度复合后 real_id45 = 3 进池 + 1 复看
     # 进池内容驱动正常预算扩散——query 翻页、tag 扩词、作者抓作品均 success/normal;
-    # 仅 2 条复看内容触发 budget_downgrade(low_budget)。动作仍全部带归属包与执行事实。
+    # 仅 1 条复看内容触发 budget_downgrade(low_budget)。动作仍全部带归属包与执行事实。
     artifacts = replay_case("real_id45", runtime_root=tmp_path / "rt")
     walk_actions = artifacts.files["walk_actions.jsonl"]
 
@@ -130,12 +130,11 @@ def test_replay_id45_walk_obeys_decisions_after_m4(tmp_path):
     skipped_tags = [row for row in tag_actions if row["walk_status"] == "skipped"]
     assert executed_tags
     assert all(row["budget_tier"] == "normal" for row in executed_tags)
-    # R8 受控变化(2026-06-12): 预算/资格拦下的标签候选不再静默;
-    # R7 放宽 tag 预算 1→3 后,3 个名额被进池内容的标签占满,
-    # 仍有 1 条进池内容排不上队(budget_exhausted)、2 条复看内容无资格(deny)。
+    # R8/R7 + R3 第二步: tag 预算 3 个名额被进池内容占满,executed 3;
+    # 现在 3 进池内容,2 条排不上队(budget_exhausted)、1 条复看内容无资格(deny)。
     assert sorted(row["reason_code"] for row in skipped_tags) == [
         "budget_exhausted",
-        "review_tag_expansion_disabled",
+        "budget_exhausted",
         "review_tag_expansion_disabled",
     ]
     assert len(executed_tags) == 3
@@ -146,7 +145,7 @@ def test_replay_id45_walk_obeys_decisions_after_m4(tmp_path):
     assert all(row["budget_tier"] == "normal" for row in author_actions)
 
     downgrades = [row for row in walk_actions if row["edge_id"] == "budget_downgrade"]
-    assert len(downgrades) == 2
+    assert len(downgrades) == 1
     assert all(row["budget_tier"] == "low_budget" for row in downgrades)
     assert all(row["reason_code"] == "content_score_review" for row in downgrades)
     # M4 砍包受控变化:Budget 包及 binding 已删,KEEP 的戳回退内容包(=executed_rule_pack_id)。

+ 2 - 1
tests/test_config_case_matrix.py

@@ -81,7 +81,8 @@ def test_senior_block_changes_outcome(tmp_path):
     # Default: no item is blocked by the senior-fit gate; items flow into pool / review.
     assert "content_not_fit_senior" not in base["reasons"]
     assert base["effect_status_counts"]["rule_blocked"] == 0
-    assert base["pooled"] == 2
+    # R3 第二步: 四字段热度复合后 real_id45 默认 3 进池(原 2)。
+    assert base["pooled"] == 3
     # Blocked variant: every item trips the (config-inverted) hard gate -> rule_blocked reject.
     assert blocked["reasons"] == ["content_not_fit_senior"] * 4
     assert blocked["effect_status_counts"]["rule_blocked"] == 4

+ 5 - 4
tests/test_platform_heat.py

@@ -34,10 +34,11 @@ def test_heat_missing_or_zero_digg_is_zero():
     assert heat_score(_stat("not-a-number"), "douyin") == 0.0
 
 
-def test_heat_unknown_platform_uses_default_anchor():
-    # 无 heat 段平台回退默认锚点 (100, 1e5):below floor -> 0, at/above ceil -> 1
-    assert heat_score(_stat(50), "bilibili") == 0.0
-    assert heat_score(_stat(100000), "bilibili") == 1.0
+def test_heat_no_heat_block_platform_uses_default_anchor():
+    # 无 heat 段的平台(github profile 无 heat 段)回退默认锚点 (100, 1e5):
+    # below floor -> 0, at/above ceil -> 1。
+    assert heat_score(_stat(50), "github") == 0.0
+    assert heat_score(_stat(100000), "github") == 1.0
 
 
 def test_heat_shipinhao_low_digg_not_unfairly_zero():

+ 4 - 4
tests/test_replay_gemini_seam.py

@@ -19,16 +19,16 @@ from tests.replay_harness import replay_case
 
 def test_replay_default_pool_stub_scores_into_pool(tmp_path):
     # 默认 FakeGeminiVideoClient = pool stub(relevance 0.85 → relevance 60)。
-    # real_id45 digg=[72459,20801,491098,24]:高热两条进池、低热两条 60-69 复看。
+    # R3 第二步: 抖音四字段热度复合后,高转发高收藏的一条从复看升进池 → 3 进池/1 复看。
     artifacts = replay_case("real_id45", runtime_root=tmp_path / "rt")
     assert artifacts.state["status"] == "success"
-    assert artifacts.summary["pooled_content_count"] == 2
-    assert artifacts.summary["review_content_count"] == 2
+    assert artifacts.summary["pooled_content_count"] == 3
+    assert artifacts.summary["review_content_count"] == 1
     assert artifacts.summary["rejected_content_count"] == 0
     assert [d["decision_reason_code"] for d in artifacts.decisions] == [
         "content_score_pool",
         "content_score_pool",
-        "content_score_review",
+        "content_score_pool",
         "content_score_review",
     ]