Просмотр исходного кода

docs: add product and architecture specifications

Sam Lee 1 неделя назад
Родитель
Сommit
4e0ee3aa32
43 измененных файлов с 10807 добавлено и 0 удалено
  1. 64 0
      product_documents/README.md
  2. 351 0
      product_documents/prd/V1落地版本.md
  3. 1137 0
      product_documents/prd/V1落地版本细化版.md
  4. 386 0
      product_documents/prd/产品方案总表.md
  5. 452 0
      product_documents/抖音游走策略/douyin_available_walk_strategy.v1.json
  6. 186 0
      product_documents/抖音游走策略/douyin_evidence_bundle.v1.json
  7. 294 0
      product_documents/抖音游走策略/runtime_v1_records_schema.md
  8. 366 0
      product_documents/抖音游走策略/抖音接口真实case样例.json
  9. 832 0
      product_documents/抖音游走策略/抖音真实游走策略图.html
  10. 512 0
      product_documents/旧版和上下游理解/DemandAgent与PatternTree游走整合理解.md
  11. 774 0
      product_documents/旧版和上下游理解/oldContentAgentAnalysis.md
  12. 110 0
      product_documents/旧版和上下游理解/新旧对比文档.md
  13. 188 0
      product_documents/旧版和上下游理解/旧版ContentFindAgent外部数据源接口审计.md
  14. 509 0
      product_documents/规则包/douyin_rule_packs.v1.json
  15. 362 0
      product_documents/规则包/抖音规则包V1.md
  16. 629 0
      tech_documents/Pattern和分类树反查手册.md
  17. 194 0
      tech_documents/content_agent_architecture/00_架构总览.md
  18. 140 0
      tech_documents/content_agent_architecture/01_统一叫法与术语表.md
  19. 161 0
      tech_documents/content_agent_architecture/02_业务模块划分.md
  20. 122 0
      tech_documents/content_agent_architecture/03_模块关系与数据责任.md
  21. 77 0
      tech_documents/content_agent_architecture/04_核心运行链路.md
  22. 136 0
      tech_documents/content_agent_architecture/05_规则包与游走策略设计.md
  23. 75 0
      tech_documents/content_agent_architecture/06_判断证据包与候选模型.md
  24. 107 0
      tech_documents/content_agent_architecture/07_运行记录与来源路径.md
  25. 66 0
      tech_documents/content_agent_architecture/08_结果沉淀与来源反查.md
  26. 103 0
      tech_documents/content_agent_architecture/09_模块接口与外部接入说明.md
  27. 19 0
      tech_documents/content_agent_architecture/ADR/0001_为什么规则包独立于游走策略.md
  28. 19 0
      tech_documents/content_agent_architecture/ADR/0002_为什么保留全量候选池.md
  29. 19 0
      tech_documents/content_agent_architecture/ADR/0003_为什么要单独记录source_edge.md
  30. 19 0
      tech_documents/content_agent_architecture/ADR/0004_source_edges写入边界.md
  31. 20 0
      tech_documents/content_agent_architecture/ADR/0005_先模块化单体后服务化队列化.md
  32. 19 0
      tech_documents/content_agent_architecture/ADR/0006_规则包只负责产出RuleDecision.md
  33. 20 0
      tech_documents/content_agent_architecture/ADR/0007_V1引入薄FastAPI和LangGraph.md
  34. 91 0
      tech_documents/content_agent_architecture/future/01_策略学习与实验闭环.md
  35. 85 0
      tech_documents/content_agent_architecture/future/02_高吞吐执行架构预研.md
  36. 185 0
      tech_documents/content_agent_architecture/review/archive/2026-06-05_架构交叉验证报告.md
  37. 332 0
      tech_documents/content_agent_code_blueprint/01_V1代码落地蓝图.md
  38. 60 0
      tech_documents/content_agent_code_blueprint/02_待决策事项.md
  39. 292 0
      tech_documents/content_agent_code_blueprint/03_Pattern回扣流程.md
  40. 684 0
      tech_documents/data_interface.md
  41. 326 0
      tech_documents/demandagent给下游的迭代需求.md
  42. 284 0
      tech_documents/前置的坑.md
  43. 0 0
      za.md

+ 64 - 0
product_documents/README.md

@@ -0,0 +1,64 @@
+# 产品方案文档索引
+
+用途:让产品、技术和后续实现人员快速知道该看哪份文档,避免被旧路径和内部黑话带偏。
+
+## 1. 先读哪几份
+
+| 顺序 | 文档 | 主要回答什么 |
+|---:|---|---|
+| 1 | [prd/产品方案总表.md](prd/产品方案总表.md) | Content Agent 要解决什么问题,主流程怎么走 |
+| 2 | [prd/V1落地版本.md](prd/V1落地版本.md) | V1 先做哪些能力,哪些暂不做 |
+| 3 | [prd/V1落地版本细化版.md](prd/V1落地版本细化版.md) | V1 的字段、状态、运行记录和验收口径 |
+| 4 | [抖音游走策略/runtime_v1_records_schema.md](抖音游走策略/runtime_v1_records_schema.md) | V1 本地运行记录文件怎么保存 |
+| 5 | [规则包/抖音规则包V1.md](规则包/抖音规则包V1.md) | 抖音规则如何判断入池、候选、待观察和淘汰 |
+| 6 | [抖音游走策略/douyin_available_walk_strategy.v1.json](抖音游走策略/douyin_available_walk_strategy.v1.json) | 抖音可以从哪些节点继续扩展 |
+| 7 | [规则包/douyin_rule_packs.v1.json](规则包/douyin_rule_packs.v1.json) | 规则包的机器可读配置 |
+
+## 2. 当前主流程
+
+```text
+上游需求和来源证据
+-> 生成搜索词
+-> 抖音搜索或作者作品抓取
+-> 形成候选内容和判断证据
+-> 规则包输出判断结果
+-> 游走策略决定下一步动作
+-> 保存运行记录、来源路径和最终结果
+-> 复盘哪些搜索词、作者和规则值得保留
+```
+
+## 3. 统一叫法
+
+| 推荐叫法 | 说明 |
+|---|---|
+| 业务模块 | 一块相对独立的业务责任,不等于微服务 |
+| 搜索词(query) | 真正拿去平台搜索的词 |
+| 候选内容 | 被找回来、等待判断或已经判断过的视频/作品 |
+| 判断证据包(EvidenceBundle) | 规则判断前汇总的视频、作者、画像、互动、风险和来源信息 |
+| 规则判断结果(RuleDecision) | 规则包输出的入池、候选、待观察、淘汰、扩展或停止结果 |
+| 下一步动作(WalkAction) | 游走策略给出的继续翻页、看作者、看作者作品、扩散 tag 或停止 |
+| 运行追踪记录(trace) | 一次运行每一步留下的输入、输出、状态和错误 |
+| 来源路径 | 结果从哪个需求、seed、搜索词、视频、作者一路走过来 |
+| 待观察(pending) | 暂不进入正式结果,但保留证据和记录,后续复盘 |
+
+## 4. 字段阅读规则
+
+真实 JSON 字段、枚举值、文件名和 API 字段不随便改名。第一次看到核心字段时,文档会用括号补一句中文解释,例如:
+
+| 字段或枚举 | 中文解释 |
+|---|---|
+| `aweme_id` | 抖音视频 ID |
+| `sec_uid` | 抖音作者 ID |
+| `trace_id` | 本次运行 ID |
+| `source_evidence` | 来源证据 |
+| `effect_status` | 搜索词效果状态 |
+| `POOL` | 入池 |
+| `CANDIDATE` | 候选 |
+| `PENDING` | 待观察 |
+| `REJECT` | 淘汰 |
+| `source_edges.jsonl` | 来源记录文件 |
+| `rule_decisions.jsonl` | 规则判断结果文件 |
+
+## 5. 归档资料
+
+[旧版和上下游理解/](旧版和上下游理解/) 是历史理解和旧系统对照资料。本轮只迁移目录,不重写正文。当前产品和技术实现优先看上面的活跃文档。

+ 351 - 0
product_documents/prd/V1落地版本.md

@@ -0,0 +1,351 @@
+# V1 落地版本:Pattern -> 抖音
+
+更新时间:2026-06-04
+
+## 阅读约定
+
+本文第一次出现核心字段或枚举时,会用括号补一句中文解释。常见字段包括:`aweme_id`(抖音视频 ID)、`sec_uid`(抖音作者 ID)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`effect_status`(搜索词效果状态)、`POOL`(入池)、`CANDIDATE`(候选)、`PENDING`(待观察)、`REJECT`(淘汰)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 0. 结论和主干
+
+V1 只做一件事:**从票圈视频 Pattern 出发,只走抖音渠道,跑通可追溯的内容发现闭环。**
+
+主链路:
+
+```text
+Pattern 策略种子
+-> Query
+-> 抖音关键词搜索
+-> 视频 / 作者
+-> 判断信号汇总
+-> 规则包决策
+-> 作者作品一跳扩展 / 内容沉淀 / 作者沉淀 / 淘汰记录
+-> 策略学习复盘
+```
+
+V1 的产品主干:
+
+| 模块 | 结论 |
+|---|---|
+| 数据源 | 只用 Pattern;优先读取 `demand_content.ext_data.evidence_pack` |
+| Query | 只由 Pattern 的 `seed_terms / itemset_items / category_bindings / element_bindings` 生成 |
+| Platform | 只接抖音关键词搜索和作者作品 |
+| 判断 | 内容画像、账号画像、互动表现、相关性、风险先汇总,再由规则包决策 |
+| 游走 | 默认跑 `query -> 视频 -> 作者 -> 作者作品`,并启用强相关 tag 扩散,最多 10 次跳跃 |
+| 沉淀 | 最终内容、作者、淘汰和来源记录都必须保留运行追踪记录(trace) |
+| 策略学习 | 只做复盘:哪些 seed / query / 作者扩展有效,下一轮怎么小改 |
+
+完整节点图保留在:[抖音真实游走策略图.html](../抖音游走策略/抖音真实游走策略图.html)。
+
+可工程化的边规则配置保留在:[douyin_available_walk_strategy.v1.json](../抖音游走策略/douyin_available_walk_strategy.v1.json)。
+
+规则包配置保留在:[douyin_rule_packs.v1.json](../规则包/douyin_rule_packs.v1.json),人读版见:[抖音规则包V1.md](../规则包/抖音规则包V1.md)。
+
+## 0.1 V1 范围
+
+V1 只跑一条最小闭环:
+
+```text
+数据源
+-> Query
+-> Platform
+-> 判断
+-> 游走
+-> 资产清洗沉淀
+-> 策略学习
+```
+
+业务路径固定为:
+
+```text
+票圈视频 Pattern
+-> Pattern 策略种子
+-> 抖音 Query
+-> 抖音关键词搜索
+-> 视频判断
+-> 作者一跳扩展
+-> 作者作品判断
+-> 内容 / 作者 / 运行追踪记录(trace)沉淀
+-> 策略复盘
+```
+
+V1 目标:用一个票圈视频 Pattern 跑通从数据源到抖音内容入池的最小闭环。
+
+暂不做:
+
+- 小红书。
+- Case 独立数据源。
+- Pattern 派生 Case。
+- 历史作者、热点、养号入口。
+- 相关搜索、共创、相似作者。
+- 无限游走。
+- AIGC 写侧计划。
+- OSS 上传。
+- show 可视化后端联调。
+
+## 0.2 真实入口
+
+| 阶段 | 使用入口 | 状态 |
+|---|---|---|
+| 数据源 | `demand_content.ext_data.evidence_pack` / `open_aigc_pattern.topic_pattern_element` | 已验证 / V1 使用 |
+| Query | OpenRouter | 鉴权通过,按预算开关调用 |
+| Platform | 抖音关键词搜索 `/crawler/dou_yin/keyword` | 已验证 |
+| 判断 | 热点宝内容画像、账号画像 | 已验证 |
+| 游走 | 抖音作者作品 `/crawler/dou_yin/blogger` | 已验证 |
+| 内容资产 | `demand_find_content_result` | 已验证,只放最终结果 |
+| 作者资产 | `demand_find_author` | 已验证 |
+
+V1 先用本地 JSON 记录运行过程和 trace,不接 show 可视化后端。
+
+## 0.3 本地 JSON 承载
+
+目录:
+
+```text
+runtime/v1/{trace_id}/
+```
+
+文件:
+
+| 文件 | 用途 | 主要负责写入的模块 |
+|---|---|---|
+| `source_context.json` | `demand_content` 和 `evidence_pack` | 数据源与种子模块 |
+| `pattern_seed_pack.json` | 本次选中的 Pattern 和策略种子 | 数据源与种子模块 |
+| `queries.jsonl` | 生成出来的 query | 搜索意图模块 |
+| `candidate_pool.jsonl` | 全量视频和作者作品候选 | 候选与证据模块 |
+| `media_assets.jsonl` | 视频媒体元数据,本地 V1 默认只记录 metadata | 候选与证据模块 |
+| `rule_decisions.jsonl` | 每个候选的规则判断 | 判断规则模块 |
+| `source_edges.jsonl` | Pattern、query、视频、作者、作品之间的来源记录 | 运行记录模块 |
+| `search_clues.jsonl` | 有效、失败、待重试 query | 运行记录模块 |
+| `trace_events.jsonl` | 每一步输入、输出、状态 | 运行记录模块 |
+| `final_output.json` | 最终入库候选汇总 | 结果沉淀与来源反查模块 |
+
+本地 JSON 只做 V1 运行产物。后续如果补真实候选池、判断日志、来源关系和搜索线索表,再从这些文件抽象正式表结构。
+
+规则包只直接写 `rule_decisions.jsonl`;`source_edges.jsonl`、`search_clues.jsonl` 和 `trace_events.jsonl` 由运行记录模块追加,`final_output.json` 由结果沉淀与来源反查模块汇总。
+
+## 1. 数据源
+
+输入:一个票圈视频 Pattern。
+
+V1 优先从 `demand_content.ext_data.evidence_pack` 启动。Query 必须能追溯到票圈视频来源的 Pattern 词、分类路径或 `evidence_pack.seed_terms`,不能混入其他业务 Case 的词。
+
+如果没有 `evidence_pack`,可从 `topic_pattern_element` 做临时读取,但只能作为 V1 过渡,不作为长期主入口。
+
+从 `topic_pattern_element` 临时读取时,进入 V1 后统一使用这些字段名:
+
+- `pattern_execution_id`
+- `post_id`
+- `name`
+- `element_type`
+- `category_path`
+- `point_type`
+- `point_text`
+
+V1 不重新游走 Pattern 树,只取当前 Pattern 的元素词和分类路径。
+
+产出 `pattern_seed_pack.json`:
+
+```json
+{
+  "trace_id": "v1_xxx",
+  "pattern_execution_id": "401",
+  "itemsets": [
+    {
+      "itemset_id": "itemset_001",
+      "items": ["祝福词句", "福袋", "节日祝福", "分享", "动态捕捉"]
+    }
+  ],
+  "seed_terms": ["早上好", "祝福词句", "福袋", "节日祝福"],
+  "category_bindings": ["表象 / 符号 / 表达符号 / 祝福语", "信息传递 / 信息扩散"],
+  "element_bindings": [],
+  "matched_post_ids": ["post_001"]
+}
+```
+
+## 2. Query
+
+输入:`seed_terms` 和 `itemsets`。
+
+动作:
+
+1. 每组 seed 生成 1-2 个抖音 query。
+2. query 不要脱离 Pattern 词。
+3. query 要适合抖音搜索,不要写成长句。
+4. 每个 query 都记录来源,具体来源词从 `pattern_seed_pack.json` 反查,不把来源词数组作为必填字段。
+
+输出 `queries.jsonl`:
+
+```json
+{"query_id":"q_001","query":"早上好祝福视频","generation_type":"item_combo","origin_source":"pattern_itemset","immediate_source":"pattern_query","effect_status":"pending"}
+{"query_id":"q_002","query":"福袋烟花祝福","generation_type":"item_combo","origin_source":"pattern_itemset","immediate_source":"pattern_query","effect_status":"pending"}
+```
+
+## 3. Platform
+
+对每个 query 调抖音关键词搜索:
+
+```text
+POST /crawler/dou_yin/keyword
+```
+
+参数:
+
+- `keyword`
+- `content_type=视频`
+- `sort_type`
+- `publish_time`
+- `cursor`
+
+保留字段:
+
+- `aweme_id`
+- `desc`
+- `author.nickname`
+- `author.sec_uid`
+- `statistics`
+- `has_more`
+- `next_cursor`
+
+所有结果先写 `candidate_pool.jsonl`,不只保存最终入选。
+
+## 4. 判断
+
+判断分两类:视频判断和作者判断。视频是否可用、作者是否值得扩展,不能互相替代。
+
+每条视频先过硬门槛:
+
+- 没有 `aweme_id`:淘汰。
+- 不能回扣 Pattern / seed:淘汰。
+- 没有作者 `sec_uid`:不扩作者。
+- 明显不相关:淘汰。
+- 重复内容:淘汰。
+- 风险内容:淘汰。
+
+再做软评分:
+
+Pattern 回扣不参与软评分,它是所有视频的必选硬门槛。
+
+| 分项 | 看什么 |
+|---|---|
+| 分类绑定 | 能不能绑定到分类或元素 |
+| 平台调性 | 是否像抖音上能传播的内容 |
+| 互动 | 点赞、评论、分享 |
+| 50+ 画像 | 内容画像是否适配 |
+| 可改编性 | 是否适合后续内容生产 |
+
+输出动作:
+
+- `入池`
+- `待观察(PENDING)`
+- `淘汰`
+- `扩作者`
+- `不扩作者`
+
+判断结果写 `rule_decisions.jsonl`。
+
+## 5. 游走
+
+V1 默认做作者一跳,并允许强相关 tag 扩散:
+
+```text
+query -> 视频 -> 作者 -> 作者作品
+视频 -> Hashtag -> Query
+```
+
+只有满足作者扩展条件时,才拉作者作品。
+
+```text
+POST /crawler/dou_yin/blogger
+```
+
+参数:
+
+- `account_id = author.sec_uid`
+- `sort_type`
+- `cursor`
+
+返回的作者作品重新进入视频判断。
+
+tag 扩散规则:
+
+- 一次 tag 扩散跳跃 = `Video -> Hashtag -> Query`。
+- 单次运行 全局最多 10 次 tag 扩散跳跃。
+- 只扩强相关 tag,泛 tag、风险 tag、无法回扣 Pattern 的 tag 不扩。
+
+不继续扩相似作者或相关内容。
+
+## 6. 资产清洗沉淀
+
+入库前做四件事:
+
+1. 内容去重:按 `aweme_id`。
+2. 作者归一:按 `author.sec_uid`。
+3. 分类或元素绑定:必须绑定上,绑定不上淘汰。
+4. 风险清洗:风险内容不入库。
+
+最终内容写:
+
+```text
+demand_find_content_result
+```
+
+优质作者写:
+
+```text
+demand_find_author
+```
+
+全量候选、淘汰原因、来源关系继续留在本地 JSON。V1 不要求和可视化后端联调。
+
+如果 `demand_find_content_result` 暂时没有 `source_evidence` 字段,必须写旁路来源记录文件,不能丢。
+
+## 7. 策略学习
+
+复盘只看几个问题:
+
+| 问题 | 从哪里看 |
+|---|---|
+| 哪些 Pattern 词有效 | `queries.jsonl`, `candidate_pool.jsonl` |
+| 哪些 query 有召回 | `search_clues.jsonl` |
+| 哪些视频被淘汰 | `rule_decisions.jsonl` |
+| 哪些作者值得扩 | `source_edges.jsonl`, `rule_decisions.jsonl` |
+| 哪些内容最终入库 | `final_output.json`, `demand_find_content_result` |
+
+V1 策略学习不训练模型,只输出下一轮小改动:
+
+- 保留有效 query。
+- 删除空召回 query。
+- 调整 Pattern 词组合。
+- 调整视频判断阈值。
+- 调整作者扩展预算。
+
+## 8. V1 验收标准
+
+一次 V1 跑通必须满足:
+
+- 能选择一个 Pattern。
+- 能产出 3-5 个 query。
+- 能用抖音关键词搜索拿到视频候选。
+- 能保存全量候选到本地 JSON。
+- 能对每条候选给出入池、候选、待观察(PENDING)或淘汰。
+- 能对可扩展作者拉一次作者作品。
+- 能把作者作品重新判断。
+- 能写出最终 `final_output.json`。
+- 能把最终入选内容写入 `demand_find_content_result`,或在写库关闭时只写本地结果。
+- 能从 trace 里看清:这个内容来自哪个 Pattern、哪个 query、哪个作者、为什么入池。
+
+## 9. V1 不解决的问题
+
+- 不证明 Pattern V2 PG 到 MySQL `topic_pattern_*` 的强来源关系。
+- 不接小红书。
+- 不补 TikHub。
+- 不补 OSS。
+- 不补真实候选池表。
+- 不补真实判断日志表。
+- 不补真实来源关系表。
+- 不接 show 可视化后端。
+- 不做多轮游走。
+
+这些不是忽略,而是留到 V1 跑通后再补。

+ 1137 - 0
product_documents/prd/V1落地版本细化版.md

@@ -0,0 +1,1137 @@
+# V1 落地版本细化版:Pattern -> 抖音
+
+更新时间:2026-06-04
+
+## 阅读约定
+
+本文第一次出现核心字段或枚举时,会用括号补一句中文解释。常见字段包括:`aweme_id`(抖音视频 ID)、`sec_uid`(抖音作者 ID)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`effect_status`(搜索词效果状态)、`POOL`(入池)、`CANDIDATE`(候选)、`PENDING`(待观察)、`REJECT`(淘汰)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 0. 结论和主干
+
+V1 的落地目标不是把所有游走可能性一次做完,而是先跑通一个稳定版本:
+
+```text
+Pattern evidence_pack
+-> Pattern 策略种子
+-> Query
+-> 抖音关键词搜索
+-> 视频候选
+-> 判断信号汇总
+-> 规则包决策
+-> 作者作品一跳扩展
+-> 内容 / 作者 / 淘汰 / 运行追踪记录(trace)沉淀
+-> 策略学习复盘
+```
+
+V1 默认启用的游走边:
+
+| 边 | 用途 |
+|---|---|
+| `PatternSeed -> Query` | 把 Pattern 种子变成抖音搜索词 |
+| `Query -> SearchPage` | 关键词搜索召回视频 |
+| `SearchPage -> Video` | 拆出视频候选 |
+| `SearchPage / Video -> Author` | 拿作者线索 |
+| `Author -> AuthorWorksPage` | 拉作者作品 |
+| `AuthorWorksPage -> Video` | 作者作品重新进入视频判断 |
+| `Video -> Hashtag -> Query` | 强相关 tag 生成新 query |
+| `Video / Author -> 画像与互动信号` | 进入判断信号汇总 |
+| `判断信号汇总 -> 规则包决策` | 输出入池、扩作者、停止、待观察(PENDING)或淘汰 |
+
+V1 对 tag 扩散的限制:
+
+- 一次 tag 扩散跳跃 = `Video -> Hashtag -> Query`。
+- 单次运行 最多 10 次 tag 扩散跳跃。
+- 只扩强相关 tag,泛 tag、风险 tag、无法回扣 Pattern 的 tag 不扩。
+
+V1 暂不默认启用:
+
+- 相似作者、共创作者、相关搜索。
+- 不受预算控制的深层游走。
+
+完整图和边规则不塞进本文正文,分别放在:
+
+- [抖音真实游走策略图.html](../抖音游走策略/抖音真实游走策略图.html)
+- [douyin_available_walk_strategy.v1.json](../抖音游走策略/douyin_available_walk_strategy.v1.json)
+- [douyin_rule_packs.v1.json](../规则包/douyin_rule_packs.v1.json)
+- [抖音规则包V1.md](../规则包/抖音规则包V1.md)
+
+## 0.1 V1 总目标
+
+V1 固定按七阶段跑:
+
+```text
+数据源
+-> Query
+-> Platform
+-> 判断
+-> 游走
+-> 资产清洗沉淀
+-> 策略学习
+```
+
+业务路径:
+
+```text
+DemandAgent evidence_pack
+-> Pattern 策略种子
+-> 抖音 Query
+-> 抖音视频召回
+-> 视频判断
+-> 作者一跳扩展
+-> 作者作品判断
+-> 最终内容 / 作者沉淀
+-> 策略复盘
+```
+
+V1 成功不是“搜到视频”,而是证明这条链路可追溯、可判断、可复盘。
+
+## 0.2 不做范围
+
+- 小红书。
+- Case 独立数据源。
+- 历史作者、热点、养号。
+- 相关搜索、共创、相似作者。
+- 多轮无限游走。
+- AIGC 写侧计划。
+- show 真实后端联调。
+
+## 0.3 本地运行产物
+
+目录:
+
+```text
+runtime/v1/{trace_id}/
+```
+
+这些文件是 V1 本地真实运行记录,不是 show 后端数据。show 后续可以读取或由后端转换这些文件,但 V1 先用它们承载真实测试数据和复盘证据。
+
+字段级 schema 见:
+
+```text
+product_documents/抖音游走策略/runtime_v1_records_schema.md
+```
+
+文件:
+
+| 文件 | 记录什么 | 谁写 | 复盘用途 |
+|---|---|---|---|
+| `source_context.json` | `demand_content`、`evidence_pack`、启动参数 | 数据源阶段 | 证明本次运行 从哪个需求单和 Pattern 启动 |
+| `pattern_seed_pack.json` | Pattern item、分类、元素、itemset | 数据源阶段 | 反查 query 和候选是否来自当前 Pattern |
+| `queries.jsonl` | 最终执行 query、生成类型、去重状态 | Query 阶段 | 评估哪些 query 有召回、哪些无效 |
+| `candidate_pool.jsonl` | 抖音搜索和作者作品返回的全量候选元数据 | 候选与证据模块 | 复盘候选来源、分页、去重和召回质量 |
+| `media_assets.jsonl` | 视频媒体状态:仅元数据、本地下载、OSS、不可用 | 候选与证据模块 | 判断是否有视频文件可用于解构;V1 不默认 OSS |
+| `rule_decisions.jsonl` | 视频、作者、tag、路径的规则包判断结果 | 判断规则模块 | 复盘入池、淘汰、扩作者、停止的原因 |
+| `source_edges.jsonl` | Pattern、query、视频、作者、tag 之间的来源记录 | 运行记录模块 | 反查每个结果从哪里来、走过哪条路径 |
+| `search_clues.jsonl` | 有效 query、失败 query、tag 扩散 query 的效果观测 | 运行记录模块 | 复盘下一轮 query 和 tag 扩散是否值得保留 |
+| `trace_events.jsonl` | 每一步输入、输出、状态、错误 | 运行记录模块 | 串起完整执行日志 |
+| `final_output.json` | 最终入池内容、作者资产、淘汰摘要、统计结果 | 资产清洗沉淀阶段 | 作为 V1 跑通验收结果 |
+
+`candidate_pool.jsonl` 默认只代表候选元数据,不代表已经拿到视频文件。
+
+`media_assets.jsonl` 用来单独记录视频媒体状态:
+
+| 字段 | 含义 |
+|---|---|
+| `aweme_id` | 视频 ID |
+| `media_status` | `metadata_only / downloaded_local / oss_uploaded / unavailable` |
+| `video_object_raw` | 接口返回的 `video` 对象摘要 |
+| `play_url` | 如果能解析到播放地址就填 |
+| `download_url` | 如果能解析到下载地址就填 |
+| `local_media_path` | 本地下载路径 |
+| `oss_url` | OSS 地址;V1 默认空 |
+| `used_for_decode` | 是否已用于解构 |
+| `error` | 下载、解析或上传失败原因 |
+
+V1 默认 `media_status = metadata_only`。如果后续确认接口能下载视频,再写本地路径;OSS 只有在真实配置补齐后才写 `oss_uploaded`。
+
+## 1. 数据源
+
+主入口:`demand_content.ext_data.evidence_pack`
+
+V1 的需求来源限定为票圈视频挖出的 Pattern。Query 必须能追溯到票圈视频来源的 `seed_terms / itemset_items / category_bindings`,不能使用跨业务 Case 的词。
+
+V1 必填字段:
+
+| 字段 | 用途 |
+|---|---|
+| `source_kind` | 必须是 `pattern_itemset` 或明确的 Pattern 来源 |
+| `pattern_source_system` | 标记 MySQL Pattern Tree 或 PG Pattern V2 |
+| `pattern_execution_id` | 回查 Pattern execution |
+| `mining_config_id` | 回查挖掘配置 |
+| `itemset_ids` | 当前 Pattern |
+| `itemset_items[]` | Pattern 里的分类 / 元素 |
+| `category_bindings` | 分类树绑定 |
+| `element_bindings` | 元素绑定 |
+| `matched_post_ids` | 上游支撑素材 |
+| `seed_terms` | Query 输入素材 |
+| `trace_id` | 全链路追踪 |
+| `validation_status` | 必须是已通过校验 |
+
+缺任一核心字段:不进入 V1 主流程,写入 `invalid_source`。
+
+产出:
+
+- `source_context.json`
+- `pattern_seed_pack.json`
+
+## 2. Query
+
+Query 只从需求单里的 Pattern item 出发,不混入其他业务 Case 的词。
+
+### 2.1 Seed 词
+
+seed 词不是新概念,就是 Pattern item 的搜索化表达。
+
+```text
+Pattern item
+-> seed 词
+-> query 候选
+-> 去重裁剪
+-> 抖音搜索
+```
+
+示例:
+
+| Pattern item | seed 词 |
+|---|---|
+| 早安问候 | 早安、早上好、问候 |
+| 祝福词句 | 祝福语、好运祝福、健康祝福 |
+| 红色喜庆背景 | 红色喜庆、喜庆背景 |
+
+V1 不需要逐条 query 记录来自哪些 Pattern 词。需求单已经绑定了当前 Pattern。
+
+但每条 query 需要保留轻量 trace:
+
+```text
+pattern_execution_id
+itemset_ids
+generation_type
+```
+
+`generation_type` 取值:
+
+| 类型 | 含义 |
+|---|---|
+| `item_single` | 单个 item 生成 |
+| `item_combo` | item 组合生成 |
+| `light_extension` | 轻微搜索化延伸 |
+| `tag_query` | tag 扩散生成 |
+
+### 2.2 Query 生成规则
+
+先生成 query 候选,再去重裁剪为最终执行 query。
+
+| 规则 | 说明 |
+|---|---|
+| 单 item query | 每个 Pattern item 生成一组 query 候选 |
+| item 组合 query | item 两两组合生成 query 候选 |
+| 少量三 item 组合 | 只在表达自然时保留,避免 query 过长 |
+| 轻微延伸 | 只做口语化、搜索化,不改变原意 |
+| 最终执行 query | 去重后按预算保留 3-5 个 |
+
+组合按“组合”处理,不按“排列”穷举:
+
+```text
+早安 + 祝福
+```
+
+只保留自然表达:
+
+```text
+早安祝福
+早上好祝福
+```
+
+不需要同时保留:
+
+```text
+祝福早安
+```
+
+### 2.3 Query 去重
+
+V1 先做轻量去重,不做复杂语义聚类。
+
+| 去重方式 | 说明 |
+|---|---|
+| 字面去重 | 完全相同的 query 只保留一个 |
+| 归一化去重 | 去掉空格、标点、重复后缀后相同则合并 |
+| 近似意图去重 | 同一组 item 组合最多保留 1-2 个自然表达 |
+
+优先保留:
+
+- 更短、更自然的 query。
+- 更贴近 Pattern item 的 query。
+- 更适合抖音搜索的口语化 query。
+
+### 2.4 禁止扩写
+
+V1 可以轻微延伸,但不能把 Pattern item 扩成另一个需求。
+
+| 类型 | 不扩写原因 | 例子 |
+|---|---|---|
+| 泛流量词 | 容易跑到平台热点 | 热门、爆款、上热门 |
+| 跨赛道词 | 会离开当前 Pattern | 情感语录、人生感悟、养生偏方 |
+| 风险词 | 不适合 50+ 或有合规风险 | 神效、包治、转发发财 |
+| 纯宽泛词 | 单独搜索不稳定 | 视频、内容、分享 |
+| 改变对象的词 | 把原 item 换成另一个对象 | 早安问候扩成佛学开示 |
+
+Query 示例:
+
+```text
+早安祝福
+早上好问候
+好运健康祝福
+红色喜庆祝福
+```
+
+产出:
+
+- `queries.jsonl`
+- `search_clues.jsonl`
+
+## 3. Platform
+
+V1 只跑抖音关键词搜索。
+
+```text
+POST /crawler/dou_yin/keyword
+```
+
+### 3.1 召回预算
+
+| 项 | V1 默认 |
+|---|---:|
+| 每个 query 页数 | 最多 3 页 |
+| 每个 query 视频上限 | 40 条 |
+| 单个 Pattern 视频候选上限 | 150 条 |
+
+### 3.2 保留字段
+
+- `aweme_id`
+- `desc`
+- `author.nickname`
+- `author.sec_uid`
+- `statistics`
+- `has_more`
+- `next_cursor`
+
+召回结果全部写 `candidate_pool.jsonl`,不只写最终入选。
+
+### 3.3 EvidenceBundle 组装规则
+
+规则包不直接吃接口原始返回,而是吃标准化的 `EvidenceBundle`。
+
+JSON 规格见:[douyin_evidence_bundle.v1.json](../抖音游走策略/douyin_evidence_bundle.v1.json)。
+
+```text
+抖音搜索 / 作者作品 / 视频解构 / 画像 / 互动 / 风险 / trace
+-> EvidenceBundle
+-> 规则包决策
+```
+
+EvidenceBundle 分 9 个区块:
+
+| 区块 | 用途 | 来源 |
+|---|---|---|
+| `source_evidence` | 记录 Pattern、query 和来源路径 | 需求单、Query、游走 trace |
+| `entity` | 当前判断对象,视频或作者 | 抖音搜索、作者作品 |
+| `relevance_signal` | Pattern 回扣和分类树 match | 视频解构工程、Pattern、分类树 |
+| `interaction_signal` | 互动表现 | `statistics.*` |
+| `portrait_signal` | 内容 50+ 画像 | 热点宝内容画像 |
+| `account_signal` | 作者 50+ 画像和作品供给 | 热点宝账号画像、作者作品 |
+| `risk_signal` | 风险、可用状态、重复状态 | 风险判断、去重、接口状态 |
+| `walk_context` | 当前游走层级和预算 | 游走过程 |
+| `trace` | 复盘记录 | V1 runtime trace |
+
+视频判断必填:
+
+```text
+source_evidence
+entity
+relevance_signal
+interaction_signal
+portrait_signal
+risk_signal
+walk_context
+trace
+```
+
+内容画像是硬要求:`portrait_signal` 拿不到,视频直接淘汰,不进入软评分。
+
+作者扩展必填:
+
+```text
+source_evidence
+entity
+relevance_signal
+account_signal
+risk_signal
+walk_context
+trace
+```
+
+tag 判断必填:
+
+```text
+source_evidence
+entity
+relevance_signal
+risk_signal
+walk_context
+trace
+```
+
+EvidenceBundle 只组装证据,不直接输出入池、扩作者、停止或淘汰。最终动作由规则包决定。
+
+## 4. 判断
+
+判断分两套:视频判断和作者判断。
+
+视频判断回答:这条视频能不能成为候选内容。
+
+作者判断回答:这个作者值不值得扩作品。
+
+视频判断和作者判断并行。视频淘汰,不代表作者一定淘汰。
+
+### 4.1 视频硬门槛
+
+| 门槛 | 动作 |
+|---|---|
+| 无 `aweme_id` | 淘汰 |
+| 无 `source_evidence` | 淘汰 |
+| 不能回扣 Pattern / seed | 淘汰 |
+| 重复内容 | 淘汰 |
+| 明显跑偏 | 淘汰 |
+| 风险内容 | 淘汰 |
+| 不可爬 / 状态异常 | 淘汰 |
+| 无作者 `sec_uid` | 视频可判断,但不扩作者 |
+
+### 4.1.1 Pattern 回扣判定
+
+视频回扣走结构化判定,不只看标题词或模型猜测:
+
+```text
+抖音视频
+-> 解构工程接口
+-> 视频元素 / 分类
+-> 对比需求单 Pattern
+-> 分类树父节点 / 兄弟节点有限游走
+-> 判断是否 match
+```
+
+判定结果:
+
+| 结果 | 含义 | 动作 |
+|---|---|---|
+| 直接命中 | 视频解构出的元素 / 分类直接命中 Pattern 元素、分类或 itemset | 通过,进入评分 |
+| 游走命中 | 未直接命中,但能通过分类树父节点或兄弟节点游走 match 到 Pattern 所属类目 | 通过,进入评分 |
+| 不命中 | 不能通过解构结果和分类树解释回 Pattern | 淘汰 |
+
+直接命中和游走命中都算回扣通过。差别只记录在 trace 里,不影响进入评分。
+
+Pattern 回扣通过后必须记录:
+
+```text
+video_id
+decode_result
+matched_category_id
+matched_element_id
+matched_itemset_id
+category_tree_walk_path
+match_result
+source_evidence
+trace
+```
+
+tag 回扣单独判断,不走视频解构工程:
+
+```text
+video hashtag
+-> 直接和需求单 Pattern 做 LLM 语义比较
+-> 接近才允许生成新 query
+```
+
+tag 必须能回扣 Pattern。不能回扣 Pattern 的 tag 不生成新 query。
+
+### 4.2 视频软评分
+
+100 分制:
+
+| 维度 | 分值 |
+|---|---:|
+| 抖音调性 | 25 |
+| 互动数据 | 15 |
+| 50+ 内容画像 | 30 |
+| 可改编性 | 10 |
+| 新鲜度 / 可用状态 | 20 |
+
+Pattern 分类 / 元素回扣不参与软评分。它是所有视频候选的必选硬门槛。
+
+### 4.2.1 50+ 判定口径
+
+V1 的 50+ 判定主要依赖两个热点宝画像接口:
+
+| 接口 | 判断对象 | 用途 |
+|---|---|---|
+| 热点宝内容点赞画像 | 视频 | 判断这条视频的实际点赞人群是否偏 50+ |
+| 热点宝账号粉丝画像 | 作者 | 判断这个作者是否值得继续扩作品 |
+
+Pattern 分类回扣已经证明“需求方向”符合当前 50+ 策略,但不再作为软评分项。视频和作者的 50+ 适配,直接看热点宝画像:
+
+```text
+视频是否适合 50+:看内容点赞画像
+作者是否适合扩展:看账号粉丝画像
+```
+
+V1 只在三个地方使用 50+ 口径,并且都给高权重:
+
+| 判断点 | 50+ 证据 | 用途 |
+|---|---|---|
+| 视频要不要入池 / 沉淀 | 热点宝内容点赞画像 | 判断这条视频是否适合 50+ |
+| 作者要不要扩作品 | 热点宝账号粉丝画像 | 判断这个作者是否值得继续游走 |
+| 作者要不要沉淀成作者资产 | 账号粉丝画像 + 作者作品命中情况 | 判断作者是否是长期可复用内容源 |
+
+内容画像是视频判断硬要求:
+
+| 情况 | 动作 |
+|---|---|
+| 内容点赞画像拿不到 | 视频淘汰 |
+| 内容画像明显偏 50+ | 进入软评分 |
+| 内容画像明显不偏 50+ | 不入池 |
+| 内容画像强,但风险高 | 淘汰 |
+| 互动高,但 50+ 内容画像弱 | 不入池 |
+
+账号画像只决定作者是否值得扩,不替代内容画像:
+
+| 情况 | 动作 |
+|---|---|
+| 内容画像强,账号画像弱 | 视频可判断,作者不强扩 |
+| 内容画像弱,账号画像强 | 视频不入池,作者可待观察(PENDING) |
+| 内容画像缺失,账号画像强 | 视频仍淘汰,作者单独判断是否小预算扩 |
+| 内容画像强,账号画像强 | 视频更容易入池,作者可扩 |
+
+视频动作:
+
+| 分数 | 动作 |
+|---:|---|
+| 70+ | 入池 |
+| 60-69 | 候选 |
+| 50-59 | 待观察(PENDING) |
+| <50 | 淘汰 |
+
+### 4.3 作者硬门槛
+
+| 门槛 | 动作 |
+|---|---|
+| 无 `sec_uid` | 不扩 |
+| 作品不可爬 | 不扩 |
+| 账号风险明显 | 不扩 |
+| 主页完全跑偏 | 不扩 |
+| 重复 / 搬运风险高 | 降权或不扩 |
+
+### 4.4 作者软评分
+
+100 分制:
+
+| 维度 | 分值 |
+|---|---:|
+| 账号 50+ 画像 | 35 |
+| 垂类稳定性 | 20 |
+| 相似作品数量 | 20 |
+| 爆款稳定性 | 10 |
+| 更新频率 | 10 |
+| 可爬稳定性 | 5 |
+
+作者动作:
+
+| 分数 | 动作 |
+|---:|---|
+| 80+ | 扩作者 |
+| 60-79 | 小预算扩 |
+| 45-59 | 待观察(PENDING) |
+| <45 | 不扩 |
+
+产出:
+
+- `rule_decisions.jsonl`
+- `运行记录模块` 根据规则决策和游走动作追加 `source_edges.jsonl`
+
+## 5. 游走
+
+### 5.0 V1 保留的游走策略
+
+| 游走策略 | V1 是否保留 | 规则 |
+|---|---|---|
+| `PatternSeed -> Query` | 保留 | 只用 Pattern evidence 生成 query |
+| `Query -> SearchPage` | 保留 | 抖音关键词搜索,连续 3 页即停 |
+| `SearchPage -> Video` | 保留 | 所有视频必须回扣 Pattern |
+| `Video -> Author` | 保留 | 有 `sec_uid` 才扩作者 |
+| `Author -> AuthorWorksPage` | 保留 | 作者作品连续 3 页即停 |
+| `AuthorWorksPage -> Video` | 保留 | 作者作品重新走视频判断 |
+| `Video -> Hashtag -> Query` | 保留 | tag 必须回扣 Pattern,最多 10 次跳跃 |
+| `SearchCursor / AuthorWorksCursor` | 保留 | 只做分页状态,连续三页即停 |
+
+V1 不保留:
+
+- 相似作者。
+- 共创作者。
+- 相关搜索。
+- 不受预算控制的深层游走。
+
+V1 默认做作者一跳,并允许强相关 tag 扩散:
+
+```text
+query -> 视频 -> 作者 -> 作者作品
+视频 -> Hashtag -> Query
+```
+
+### 5.1 作者一跳预算
+
+| 项 | V1 默认 |
+|---|---:|
+| 可扩展作者上限 | 10 个 |
+| 每个作者作品上限 | 20 条 |
+| 每个 query 翻页上限 | 3 页 |
+| 每个作者作品翻页上限 | 3 页 |
+| tag 扩散跳跃上限 | 10 次 |
+
+### 5.2 作者作品接口
+
+```text
+POST /crawler/dou_yin/blogger
+```
+
+参数:
+
+- `account_id = author.sec_uid`
+- `sort_type`
+- `cursor`
+
+作者作品重新走视频判断。
+
+tag 扩散重新生成的 query 回到抖音关键词搜索,并重新走视频判断。
+
+不继续扩相关搜索、相似作者、共创作者。
+
+### 5.3 V1 保留的规则包
+
+| 规则包 | V1 是否保留 | 核心规则 |
+|---|---|---|
+| 视频候选判断规则包 | 保留 | Pattern 回扣是硬门槛,不进软评分 |
+| 作者扩展判断规则包 | 保留 | 作者可扩,但作者作品仍必须回扣 Pattern |
+| tag 扩散规则包 | 保留 | tag 必须回扣 Pattern,最多 10 次跳跃 |
+| 路径停止规则包 | 保留 | 连续低质、连续空召回、连续三页即停 |
+| 预算待观察规则包 | 保留 | 重复率高、pending 态、接近 tag 上限时降预算 |
+
+## 6. 资产清洗沉淀
+
+### 6.1 清洗
+
+| 操作 | 规则 |
+|---|---|
+| 内容去重 | 按 `aweme_id` |
+| 作者归一 | 按 `author.sec_uid` |
+| 分类或元素绑定 | 必须绑定上,绑定不上淘汰 |
+| 风险清洗 | 风险内容不入库 |
+
+### 6.2 资产状态口径
+
+视频状态:
+
+| 状态 | 判定 | 处理 |
+|---|---|---|
+| 入池 | 硬门槛通过,视频评分 `>=70` | 可进入最终内容资产 |
+| 候选 | 硬门槛通过,视频评分 `60-69` | 不直接入正式资产,允许后续人工复看 |
+| 待观察(PENDING) | 硬门槛通过,视频评分 `50-59` | 不入库,只保留运行追踪记录(trace) 和判断记录 |
+| 淘汰 | 视频评分 `<50`,或命中任一硬门槛 | 不继续游走,保留淘汰原因 |
+
+作者沉淀规则放进作者规则包。V1 先按本次运行 内作者作品采样判断,不做历史累计。
+
+作者沉淀必须同时满足:
+
+| 条件 | V1 默认 |
+|---|---:|
+| 账号 50+ 画像 | 通过 |
+| 作者作品可持续获取 | 通过 |
+| 作者作品样本数 | `>=9` |
+| 能回扣 Pattern 的作品数 | `>=3` |
+| 能回扣 Pattern 的作品占比 | `>=1/3` |
+| 作者风险 | 低风险 |
+
+作者动作:
+
+| 状态 | 处理 |
+|---|---|
+| 沉淀作者资产 | 满足作者沉淀规则,写作者资产 |
+| 待观察作者(PENDING_AUTHOR) | 账号画像可用但作品命中不足,先留 trace |
+| 不沉淀作者 | 账号 50+ 弱、作品不可爬、风险高或明显跑偏 |
+
+tag 和 query 暂时不算正式资产,只算运行期搜索线索观测,写入 `search_clues.jsonl`。
+
+### 6.3 入库口径
+
+| 数据 | 处理 |
+|---|---|
+| 全量候选 | 本地 JSON |
+| 待观察内容(PENDING) | 本地 JSON,只留运行追踪记录(trace) |
+| 淘汰原因 | 本地 JSON |
+| 来源关系 | 本地 JSON |
+| 搜索线索 | 本地 JSON |
+| 最终内容 | 可写 `demand_find_content_result` |
+| 作者资产 | 可写 `demand_find_author` |
+
+如果 `demand_find_content_result` 暂时没有 `source_evidence` 字段,必须写旁路来源记录文件,不能丢。
+
+产出:
+
+- `final_output.json`
+- `demand_find_content_result`,可选写库
+- `demand_find_author`,可选写库
+
+## 7. 策略学习
+
+V1 策略学习只做可解释复盘,不训练模型。
+
+### 7.1 跑通评价
+
+| 指标 | 合格标准 |
+|---|---|
+| source evidence 完整率 | 100% |
+| query 生成数 | 3-5 个 |
+| 抖音召回执行 | 至少 1 个 query 有返回 |
+| 候选池写入 | 有 `candidate_pool.jsonl` |
+| 媒体状态写入 | 有 `media_assets.jsonl` |
+| 判断日志写入 | 有 `rule_decisions.jsonl` |
+| 来源记录写入 | 有 `source_edges.jsonl` |
+| 最终输出 | 有 `final_output.json` |
+
+### 7.2 策略效果评价
+
+评价只看本次 V1 run,不做复杂算法。
+
+| 指标 | 定义 | 低线 / 解释 |
+|---|---|---|
+| query 有效率 | 产生入池、候选或待观察视频的 query 数 / 实际执行 query 数 | 只要全空召回或全淘汰,就不算有效 |
+| 视频 Pattern 回扣率 | 直接命中或游走命中的视频数 / 已解构视频数 | 低于 `30%`,复盘 query 或 Pattern item 组合 |
+| 入池率 | 入池视频数 / 完成 EvidenceBundle + 规则判断的视频数 | 正常区间 `5%-30%`;太低说明召回或规则偏紧,太高说明规则偏松 |
+| 待观察率 | 待观察内容(PENDING) / 完成规则判断的视频数 | 只看边缘线索比例,不算正式效果 |
+| 淘汰率 | 淘汰视频 / 完成规则判断的视频数 | 必须能拆出主淘汰原因 |
+| 作者扩展有效率 | 扩展后产生入池或候选视频的作者数 / 实际扩展作者数 | 低于 `20%`,复盘作者扩展规则 |
+| tag 扩散有效率 | tag 生成 query 后产生入池或候选视频的 tag 数 / 实际扩散 tag 数 | 低于 `10%`,下轮收紧或暂停 tag 扩散 |
+| 50+ 命中情况 | 内容画像和账号画像的 `strong / medium / weak / missing` 分布 | 不设固定 40% 阈值;`weak + missing` 过高时复盘 query、tag、作者扩展或画像接口稳定性 |
+| trace 反查完整率 | 可从最终结果反查到 `pattern_execution_id / itemset_ids / source_edges.jsonl` 的结果数 / 最终结果数 | 必须 `100%` |
+
+query / tag / 作者扩展统一使用效果状态:
+
+| `effect_status` | 含义 | 下一轮处理 |
+|---|---|---|
+| `success` | 产生入池或候选结果 | 保留或加权 |
+| `weak_effective` | 只产生待观察(PENDING),或有候选但质量偏弱 | 保留运行追踪记录(trace),不主动加权 |
+| `pending` | 还没有足够结果判断效果 | 等待后续执行 |
+| `failed` | 全部淘汰或空召回 | 降权或删除 |
+| `blocked` | 因接口、画像、媒体或 trace 缺失无法判断 | 不评价策略效果,先修数据链路 |
+
+tag 扩散后的后续游走要同时记录两类来源:
+
+| 字段 | 含义 | 例子 |
+|---|---|---|
+| `origin_source` | 全链路最早由哪里打开 | `pattern_itemset` |
+| `immediate_source` | 当前节点上一跳来自哪里 | `query_from_tag` / `author_work` |
+| `source_edge_ids` | 完整路径对应的来源记录 ID | 对应 `Pattern -> Query -> VideoA -> TagA -> QueryB -> VideoB -> AuthorB -> AuthorWorks -> VideoC` 这一组 `source_edges.jsonl` 记录 |
+
+这样可以同时评估:
+
+- tag 扩散整体有没有价值。
+- 作者作品这条直接边有没有价值。
+
+### 7.3 业务质量评价
+
+50+ 不再用单一 `percentage >= 40%` 判断。V1 先看画像分档:
+
+| 分档 | 口径 | 动作 |
+|---|---|---|
+| `strong` | 50+ 占比高,或 preference / TGI 明显高 | 给高分 |
+| `medium` | 占比一般,但 preference / TGI 有偏好 | 给中分 |
+| `weak` | 占比和 preference / TGI 都弱 | 不入池 |
+| `missing` | 内容画像拿不到 | 淘汰 |
+
+业务质量还要看:
+
+| 指标 | 看什么 |
+|---|---|
+| 抖音调性 | 是否像抖音上能传播的表达 |
+| 互动质量 | 点赞、评论、分享是否有基础吸引力 |
+| 可改编性 | 是否适合后续生产 |
+| 风险混入 | 入池内容是否有明显风险 |
+
+### 7.4 失败复盘
+
+淘汰原因必须标准化。每条淘汰记录必须有 1 个主原因,可以附多个次原因。
+
+主原因:
+
+| reason_code | 含义 |
+|---|---|
+| `pattern_no_match` | 不能回扣 Pattern |
+| `missing_content_portrait` | 内容画像拿不到 |
+| `age_50_plus_weak` | 50+ 画像弱 |
+| `risk_high` | 风险高 |
+| `duplicate` | 重复内容 |
+| `low_score` | 软评分低 |
+| `media_unavailable` | 媒体不可用 |
+| `author_unavailable` | 作者不可扩 |
+| `budget_stop` | 命中预算停止 |
+| `source_trace_broken` | 来源或 trace 断链 |
+
+主原因优先级:
+
+```text
+硬门槛
+-> 内容画像缺失
+-> 50+ 弱
+-> 风险
+-> 分数低
+```
+
+trace 是硬验收:
+
+| 要求 | 处理 |
+|---|---|
+| `trace` 完整率必须 100% | 缺 trace 不能入池 |
+| `source_edges.jsonl` 必须写 | 缺来源记录不能入池 |
+| `final_output.json` 必须带 `source_evidence` 摘要 | 缺摘要不能验收 |
+
+下一轮只输出小改动:
+
+- 保留有效 query。
+- 删除空召回 query。
+- 调整 Pattern 词组合。
+- 调整视频判断阈值。
+- 调整作者扩展预算。
+- 收紧或暂停低效 tag 扩散。
+
+## 8. V1 验收
+
+一次 V1 跑通必须满足:
+
+- 有合法 `evidence_pack`。
+- 生成 3-5 个 query。
+- 至少 1 个 query 拿到抖音候选。
+- 每条候选都有判断结果。
+- 至少尝试 1 次作者扩展。
+- 作者作品重新进入视频判断。
+- 最终有 `final_output.json`。
+- 每条最终内容都有 `source_evidence`。
+- 可以从最终内容反查到 Pattern、itemset、分类或元素绑定。
+
+## 9. 端到端样例
+
+样例来自真实 DemandAgent 输出:
+
+样例来自 DemandAgentNew 的 `cfa_100_mix_20260604/2038_贪污腐败样例`,不依赖本机绝对路径。
+
+为降低事实表述风险,文档展示名使用:
+
+```text
+基层公职人员贪腐警示案例
+```
+
+不把上游素材描述包装成外部事实报道。
+
+### 9.1 数据源输入
+
+选用 `id=1` 的 demand item。
+
+真实字段摘要:
+
+| 字段 | 值 |
+|---|---|
+| `merge_leve2` | 贪污腐败 |
+| `name` | 警花凌娅贪腐案 |
+| 文档展示名,非 JSON 字段 | 基层公职人员贪腐警示案例 |
+| `source_kind` | `pattern_itemset` |
+| `pattern_source_system` | `mysql_topic_pattern` |
+| `pattern_execution_id` | `2038` |
+| `mining_config_id` | `2029` |
+| `source_post_id` | `69509310` |
+| `itemset_ids` | `391369`, `391371` |
+| `support` | `0.1` |
+| `absolute_support` | `5` |
+| `trace_id` | `19b5b21d-a110-4dcd-9478-906a9752faa0` |
+| `source_certainty` | `db_validated` |
+| `validation_status` | `passed` |
+
+`source_certainty=db_validated` 只代表 DB 来源和链路已验证,不代表对外部现实案件事实做过核验。
+
+Pattern itemset:
+
+| itemset | 组合 |
+|---|---|
+| `391369` | 叙事结构 + 综合性腐败 + 表达手法 |
+| `391371` | 叙事结构 + 基层公职人员 + 表达手法 |
+
+seed terms:
+
+```text
+叙事结构
+综合性腐败
+表达手法
+基层公职人员
+警花凌娅
+贪腐
+```
+
+支撑素材:
+
+```text
+69509310
+69509322
+69509597
+69510856
+69514781
+69535519
+69509602
+```
+
+### 9.2 Query 生成
+
+只用 Pattern item 和 seed terms 生成抖音 query,不混入其他 Case。
+
+候选 query 示例:
+
+| query | 生成类型 |
+|---|---|
+| 基层公职人员贪腐案例 | `item_combo` |
+| 警察贪腐警示 | `light_extension` |
+| 基层权力腐败案例 | `item_combo` |
+| 贪腐案警示教育 | `light_extension` |
+
+这些只是 query 生成示例,不代表已经真实搜到抖音结果。
+
+写入:
+
+```text
+queries.jsonl
+source_edges.jsonl
+trace_events.jsonl
+```
+
+### 9.3 Platform 召回
+
+对每个 query 调抖音关键词搜索:
+
+```text
+POST /crawler/dou_yin/keyword
+```
+
+如果接口返回视频,写入:
+
+```text
+candidate_pool.jsonl
+media_assets.jsonl
+trace_events.jsonl
+```
+
+`candidate_pool.jsonl` 记录候选元数据:
+
+```text
+aweme_id
+desc / item_title
+author.nickname
+author.sec_uid
+statistics.*
+has_more
+next_cursor
+```
+
+`media_assets.jsonl` 默认先写:
+
+```text
+media_status = metadata_only
+```
+
+不假设已经拿到视频文件,也不假设已经上传 OSS。
+
+### 9.4 EvidenceBundle
+
+每条视频进入判断前,组装 `EvidenceBundle`。
+
+必须包含:
+
+```text
+source_evidence
+entity
+relevance_signal
+interaction_signal
+portrait_signal
+risk_signal
+walk_context
+trace
+```
+
+其中:
+
+| 区块 | 样例口径 |
+|---|---|
+| `source_evidence` | 指向 `pattern_execution_id=2038`, `itemset_ids=[391369,391371]` |
+| `entity` | 抖音搜索返回的当前判断对象,视频时包含 `aweme_id / desc / author.sec_uid` |
+| `relevance_signal` | 当前判断对象的 Pattern 回扣、分类树 match 或 tag 回扣信号 |
+| `portrait_signal` | 热点宝内容点赞画像 |
+| `interaction_signal` | 抖音 `statistics.*` |
+| `risk_signal` | 风险、重复、可用状态 |
+| `trace` | 本次运行 的 trace |
+
+内容画像拿不到:
+
+```text
+REJECT
+reason_code = missing_content_portrait
+```
+
+### 9.5 视频判断
+
+先判硬门槛:
+
+```text
+source_evidence 完整
+aweme_id 存在
+视频能回扣 Pattern
+内容画像存在
+风险不过高
+```
+
+Pattern 回扣通过方式:
+
+| 结果 | 动作 |
+|---|---|
+| 直接命中 `综合性腐败 / 基层公职人员 / 表达手法` | 进入评分 |
+| 通过分类树父节点或兄弟节点游走命中 | 进入评分 |
+| 不命中 | 淘汰 |
+
+视频动作:
+
+| 分数 | 动作 |
+|---:|---|
+| `70+` | 入池 |
+| `60-69` | 候选 |
+| `50-59` | 待观察(PENDING) |
+| `<50` | 淘汰 |
+
+写入:
+
+```text
+rule_decisions.jsonl
+source_edges.jsonl
+trace_events.jsonl
+```
+
+其中规则包只直接写 `rule_decisions.jsonl`;`source_edges.jsonl` 和 `trace_events.jsonl` 由运行记录模块追加。
+
+### 9.6 作者游走和作者沉淀
+
+如果视频里有 `author.sec_uid`,进入作者判断。
+
+作者扩展:
+
+```text
+author.sec_uid
+-> POST /crawler/dou_yin/blogger
+-> 作者作品重新进入视频判断
+```
+
+作者沉淀必须同时满足:
+
+| 条件 | V1 默认 |
+|---|---:|
+| 账号 50+ 画像 | 通过 |
+| 作者作品可持续获取 | 通过 |
+| 作者作品样本数 | `>=9` |
+| 能回扣 Pattern 的作品数 | `>=3` |
+| 能回扣 Pattern 的作品占比 | `>=1/3` |
+| 作者风险 | 低风险 |
+
+不满足沉淀规则时:
+
+```text
+待观察作者(PENDING_AUTHOR)
+只留运行追踪记录(trace)
+不写正式作者资产
+```
+
+### 9.7 tag 扩散
+
+如果视频 desc 或结构化字段里提取到 hashtag:
+
+```text
+Video -> Hashtag -> Query
+```
+
+tag 必须先和需求单 Pattern 做 LLM 语义比较。
+
+能回扣 Pattern:
+
+```text
+生成 query
+generation_type = tag_query
+```
+
+不能回扣 Pattern:
+
+```text
+NO_GENERATE_QUERY
+```
+
+tag 和 query 只算搜索线索,不算正式资产。
+
+来源归因:
+
+| 字段 | 例子 |
+|---|---|
+| `origin_source` | `pattern_itemset` |
+| `immediate_source` | `query_from_tag` / `author_work` |
+| `source_edge_ids` | 对应 `Pattern -> Query -> VideoA -> TagA -> QueryB -> VideoB` 的来源记录 ID |
+
+### 9.8 final_output
+
+一次运行 结束后写:
+
+```text
+final_output.json
+```
+
+必须包含:
+
+| 内容 | 说明 |
+|---|---|
+| 入池视频 | 通过硬门槛且评分 `>=70` |
+| 候选视频 | 评分 `60-69`,允许人工复看 |
+| 待观察内容(PENDING) | 评分 `50-59`,不入库,只留运行追踪记录(trace) |
+| 淘汰摘要 | 每条淘汰必须有 1 个主原因 |
+| 作者资产 | 满足作者沉淀规则才写 |
+| 搜索线索 | query / tag 效果状态 |
+| summary | query 有效率、入池率、视频 Pattern 回扣率、50+ 命中情况、trace 反查完整率 |
+
+该样例的验收重点:
+
+```text
+不是证明“搜到了某条抖音视频”,
+而是证明从真实 demand_content.evidence_pack 出发,
+用于验证运行后能否生成 query、召回候选、组装 EvidenceBundle、
+执行规则包、记录来源记录和最终复盘。
+```
+
+## 10. 需要确认的参数
+
+| 问题 | 当前建议 |
+|---|---|
+| V1 是否只接受 `evidence_pack` 启动 | 是 |
+| 缺 evidence 是否 fallback | 不 fallback,写 `invalid_source` |
+| query 数量 | 3-5 个 |
+| 每 query 页数 | 最多 3 页,连续三页即停 |
+| 作者扩展上限 | 10 个作者 |
+| 每作者作品上限 | 20 条 |
+| tag 扩散跳跃上限 | 10 次 |
+| 视频入池阈值 | 70+ |
+| 作者扩展阈值 | 80+ |

+ 386 - 0
product_documents/prd/产品方案总表.md

@@ -0,0 +1,386 @@
+# Content Find Agent 产品方案总表
+
+更新时间:2026-06-04
+
+## 阅读约定
+
+本文第一次出现核心字段或枚举时,会用括号补一句中文解释。常见字段包括:`aweme_id`(抖音视频 ID)、`sec_uid`(抖音作者 ID)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`effect_status`(搜索词效果状态)、`POOL`(入池)、`CANDIDATE`(候选)、`PENDING`(待观察)、`REJECT`(淘汰)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 0. 一句话定义
+
+Content Find Agent 是一个从内容证据出发,生成平台可执行搜索动作,筛选优质内容和作者,并把过程沉淀成可复盘资产的内容发现系统。
+
+它不是单纯的“搜视频工具”,而是一条可学习的内容供给链路:
+
+```text
+数据源 -> Query -> Platform -> 判断 -> 游走 -> 资产清洗沉淀 -> 策略学习
+```
+
+本文只写产品总框架和完整 MVP 需要补齐的能力,不展开 V1 的具体执行细节。V1 会单独整理为:`Pattern 数据源 -> 抖音渠道` 的最小闭环方案。
+
+## 1. 状态口径
+
+所有能力都必须标清楚状态,避免把产品设想写成已跑通事实。
+
+| 状态 | 含义 |
+|---|---|
+| 已验证 | 已有 DB、API、代码或只读验证证据 |
+| 待接入 | 产品需要,已有方向,但还没进入主链路 |
+| 缺口 | 没有真实表、接口、key 或后端承载 |
+| 产品意图 | 产品上想要,但当前没有旧版 DB/API backing |
+| V1 使用 | 第一版真实跑通会用到 |
+
+## 2. 总体流程
+
+| 阶段 | 回答的问题 | 主要产出 |
+|---|---|---|
+| 数据源 | 从哪里开始找内容 | Pattern、Case、历史搜索、作者、热点、养号等入口 |
+| Query | 搜什么,为什么搜 | 可执行 query、采集意图、query 来源 |
+| Platform | 在哪个平台怎么执行 | 平台动作、返回字段、可用信号 |
+| 判断 | 找到的东西值不值得留 | 入池、观察、淘汰、停止、继续 |
+| 游走 | 找到一个点以后还能去哪 | 视频到作者、作者到作品等扩展路径 |
+| 资产清洗沉淀 | 结果如何变成资产 | 内容资产、作者资产、来源关系、搜索线索 |
+| 策略学习 | 下一轮怎么改 | Query、规则、预算、数据源优先级的调整建议 |
+
+### 2.1 有需求和无需求两类流程
+
+不是所有数据源都要先构建 Query Prompt。
+
+| 类型 | 数据源 | 主流程 |
+|---|---|---|
+| 有需求数据源 | Pattern、Case、历史优质搜索记录 | 数据源 -> Query Prompt -> Platform -> 判断 / 游走 -> 入库 |
+| 无需求数据源 | 历史沉淀账号、热点、养号 | 数据源 -> 判断 / 游走 -> 分类树绑定 -> 入库 |
+
+无需求数据源没有现成的需求拆解,不能硬塞进“根据需求生成 query”的 Prompt。它们可以用模型做分类绑定、平台调性判断和结果解释,但不走需求 Query Prompt。
+
+### 2.2 分类树绑定要求
+
+任何数据源的游走和判断结果,入库前都必须能追溯到分类树。
+
+| 概念 | 含义 |
+|---|---|
+| 分类 | 分类树父节点 |
+| 元素 | 分类树子节点 |
+| Pattern | 多个元素的稳定组合 |
+| 聚类结果 | 从内容语义或表现聚合出的分类 / 元素线索 |
+| 频繁项集结果 | 从元素共现里挖出的 Pattern 组合 |
+
+入库前必须满足:
+
+```text
+来源可追溯
+-> 能绑定分类或元素
+-> 能说明平台调性
+-> 能记录判断结果
+```
+
+### 2.3 ContentFindAgent 领取需求的证据要求
+
+ContentFindAgent 后续领取 `demand_content` 时,不能只取 `id/name/suggestion/score/merge_leve2/dt`。如果 DemandAgent 或上游证据层已经把来源证据写进 `ext_data.evidence_pack`,CFA 必须解析并一路带下去。
+
+必须带入数据源、Query、判断、游走和入库流程的字段:
+
+```text
+source_kind
+pattern_source_system
+case_id_type
+source_post_id
+pattern_execution_id
+mining_config_id
+itemset_ids
+itemset_items[]
+category_bindings
+element_bindings
+matched_post_ids
+seed_terms
+trace_id
+source_certainty
+validation_status
+```
+
+这组字段的目的不是增加展示信息,而是保证最终可以从某一个 `case_id` 或 `post_id` 反查到 Pattern、itemset、分类节点或元素节点。
+
+## 3. 数据源总表
+
+数据源回答:我们从哪里开始找内容。
+
+| 数据源 | 产品定位 | 当前状态 | MVP 处理 |
+|---|---|---|---|
+| Pattern | 稳定特征组合,适合精搜索 | 已验证 / 部分待接入 | V1 使用 |
+| Case | 历史优质素材和成功表达 | 已验证 | 完整 MVP 使用 |
+| 历史优质搜索记录 | 历史有效 query 和搜索路径 | 部分缺口 | 完整 MVP 使用 |
+| 历史沉淀账号 | 可复用作者资产 | 已验证 | 无需求源,先判断作者再游走 |
+| 热点 | 时效修饰和轻量探索入口 | 已验证 | 无需求源,先预筛再游走到内容 |
+| 养号 | 推荐流采集入口 | 产品意图 | 无接口信息,暂不定顺序 |
+
+### 3.1 Pattern
+
+Pattern 是特征组合,回答“这类内容是什么”。它可以产出策略种子,再生成平台 Query。
+
+V1 先用 Pattern 跑通主链路。`topic_pattern_element` 已验证;`topic_pattern_itemset -> 多 Case` 是重要增强,但仍按待接入口径处理。
+
+### 3.2 Case
+
+Case 是具体历史素材,回答“它怎么表达、为什么有效”。完整 MVP 里,Case 用来补充筛选依据、表达方式和复盘归因。
+
+### 3.3 历史优质搜索记录
+
+历史优质搜索记录不是普通热词,而是过去有效的搜索路径。它必须保留来源归因:来自哪个 Pattern、Case 或任务 trace。
+
+### 3.4 历史沉淀账号
+
+历史沉淀账号是作者资产。接口返回的是作者对象,主要字段包括作者名、作者链接、作者 ID、平台、内容标签、50+ 占比、TGI、是否优质、trace。
+
+推荐顺序:
+
+```text
+历史作者
+-> 先判断作者是否值得扩展
+-> 绑定分类或元素
+-> 再游走到作者作品
+-> 判断作品是否入池
+```
+
+### 3.5 热点
+
+热点主要做时效探索,不作为深度主链路。今日热榜接口返回的是热点来源块和榜单标题,不是最终内容。
+
+当前返回形态:
+
+- 来源块:`source`, `jump_url`, `type`, `rankList`
+- 热点项:`title`, `link`, `heat`, `rank`
+
+推荐顺序:
+
+```text
+热点标题
+-> 预筛和分类树绑定
+-> 作为平台游走入口找内容
+-> 再判断内容是否入池
+```
+
+热点本身不等于需求,也不直接入库为内容资产。
+
+### 3.6 养号
+
+养号是从平台推荐流拿真实 case 的产品方向。当前没有接口信息,先不判断流程顺序,也不写成已接入能力。
+
+## 4. Query 层
+
+Query 回答:把数据源变成什么搜索或采集动作。
+
+Query 层分两步:
+
+```text
+Source Query Builder:把证据变成搜索意图
+抖音搜索改写模块:把搜索意图变成平台动作
+```
+
+| 数据源 | Query 构建方式 |
+|---|---|
+| Pattern | 用 Pattern 词语组合生成策略种子,再生成 query |
+| Case | 用下层特征和解构点生成 query |
+| 历史优质搜索记录 | 复用、回放或小幅改写历史有效 query |
+| 历史沉淀账号 | 不走需求 Query Prompt;先判断作者,再通过游走拉作品 |
+| 热点 | 不走需求 Query Prompt;热点标题只作为游走入口 |
+| 养号 | 当前无接口,不进入 Query 层 |
+
+进入 Query 层的 query 必须保留来源:来自哪个数据源、哪个 seed、哪个版本的 Prompt。
+
+## 5. Platform 层
+
+Platform 回答:在哪个平台执行,以及能拿到什么。
+
+| Platform | 当前定位 | 状态 |
+|---|---|---|
+| 抖音 | V1 主渠道,支持关键词搜索、作者作品、画像信号 | 已验证 |
+| 小红书 | 笔记、作者、话题方向 | 待验证 |
+| 快手 / B站 / 视频号 / 票圈 | 后续平台接入 | 待验证 |
+
+完整 MVP 需要为每个平台定义三件事:
+
+| 项 | 说明 |
+|---|---|
+| 平台动作 | 关键词搜索、作者作品、话题、推荐流等 |
+| 可用字段 | 内容 ID、标题、作者、互动、画像、发布时间等 |
+| 风险边界 | 不可爬、重复、敏感、低质、平台限制 |
+
+## 6. 判断层
+
+判断回答:找到的内容、作者或线索值不值得留。
+
+判断不按页面堆表,而是按规则包组织。每个规则包至少包含:
+
+| 组成 | 含义 |
+|---|---|
+| 硬门槛 | 一票否决,例如无 ID、不可爬、重复、明显跑偏 |
+| 软评分 | 相关性、质量、画像、作者稳定性、互动可信 |
+| 输出动作 | 入池、观察、淘汰、继续、停止、降预算 |
+| 证据字段 | 判断用到了哪些字段 |
+
+完整 MVP 需要的基础规则包:
+
+| 规则包 | 用途 |
+|---|---|
+| 内容候选准入 | 判断视频或笔记能不能入池 |
+| Pattern 回扣 | 判断候选是否能解释回原始 Pattern seed |
+| 作者扩展评估 | 判断作者是否值得继续拉作品 |
+| 作者作品二次判断 | 判断作者作品是否能成为候选内容 |
+| 漂移停止 | 判断游走是否跑偏 |
+| 预算停止 | 控制深度、重复率和成本 |
+
+判断还有一个硬要求:任何入库候选都必须能绑定到分类树的分类或元素。绑定不上,就只能进入观察或淘汰,不能当作已沉淀资产。
+
+## 7. 游走层
+
+游走回答:从一个点出发,还能往哪里走。
+
+完整 MVP 支持多种起点,但必须受规则包和预算控制。
+
+| 起点 | 可扩展方向 | 状态 |
+|---|---|---|
+| 视频 | 作者、作者作品、标签、相关搜索 | 抖音视频到作者已验证 |
+| 笔记 | 作者、话题、同话题笔记 | 小红书待验证 |
+| 作者 | 作者作品、相似作者、共创作者 | 抖音作者作品已验证,其余待验证 |
+| 热点标题 | 平台内容搜索、热点下内容、热点作者 | 今日热榜已验证,后续内容游走待设计 |
+
+游走原则:
+
+- 每一次扩展都要有规则包。
+- 每一次继续或停止都要有原因。
+- 不做无限游走,先做可控层级和预算。
+
+## 8. 资产清洗沉淀层
+
+资产清洗沉淀回答:找到的候选如何变成可复盘、可复用的资产。
+
+完整 MVP 至少需要四类资产:
+
+| 资产 | 产品含义 | 当前状态 |
+|---|---|---|
+| 内容资产 | 入池视频、候选视频、淘汰视频 | 最终结果表已验证,全量候选池缺口 |
+| 作者资产 | 入池作者、观察作者、淘汰作者 | 作者表已验证,分层规则待补 |
+| 来源关系 | 数据源、seed、query、视频、作者之间的路径 | 缺口 |
+| 搜索线索 | 有效 query、失败 query、标签、话题、相关搜索 | 缺口 |
+
+这里先定义产品含义,不展开 DB schema。
+
+资产入库前必须完成:
+
+- 去重
+- 归一
+- 质量和风险清洗
+- 分类或元素绑定
+
+完整来源路径、判断结果、平台调性说明和 trace,进入第 9 步策略学习使用。
+
+最终写入 `demand_find_content_result` 时,不能只留下 `aweme_id + demand_content_id + process_trace`。必须保存结构化 `source_evidence`,或写入旁路文件 / 来源记录文件,至少保留来源路径、Pattern / Case 证据、分类或元素绑定和验证状态。
+
+## 9. 策略学习层
+
+策略学习回答:跑完一轮后,下一轮怎么改。
+
+第一版不做复杂模型,先做可解释复盘。
+
+```text
+收集运行追踪记录(trace)
+-> 看结果
+-> 找原因
+-> 改 query / 规则 / 预算
+-> 小范围再试
+```
+
+策略学习不能只靠普通运行日志。日志可以辅助排查,但产品复盘需要结构化 trace,把前面每一步的输入、输出、规则和决策串起来。
+
+需要学习的 trace 数据:
+
+| 数据 | 看什么 |
+|---|---|
+| 来源路径 | 从哪个数据源、seed、query 或作者进入 |
+| 数据源 | 哪些 Pattern、Case、作者或热点更有效 |
+| Query | 哪些 query 有结果,哪些空召回或跑偏 |
+| Platform | 哪个平台动作更稳定 |
+| 判断 | 命中了哪个规则包,哪些规则误杀、放水或效果稳定 |
+| 游走 | 从哪里走到哪里,哪些扩展路径值得继续 |
+| 资产 | 哪些内容、作者、线索被沉淀 |
+| 平台调性 | 候选为什么符合或不符合平台表达习惯 |
+| 表现 | 曝光、播放、回流、互动等后续效果 |
+
+前几步代码需要留下最小结构化记录:
+
+```text
+trace_id
+stage
+input
+output
+rule_pack
+decision
+source_ref
+source_evidence
+asset_id
+created_at
+```
+
+输出不是“自动训练模型”,而是下一轮可执行的小改动:
+
+- 调整 Query Prompt
+- 调整规则包
+- 调整预算
+- 调整数据源优先级
+- 标记稳定作者或高价值 Pattern
+
+## 10. 完整 MVP 需要补齐的产品能力
+
+| 能力 | 为什么需要 |
+|---|---|
+| 数据源状态表 | 清楚区分已验证、待接入、缺口和产品意图 |
+| Query 生成和版本记录 | 知道每个 query 为什么出现、由哪个 Prompt 生成 |
+| Platform 动作表 | 统一管理每个平台能做什么、能拿什么字段 |
+| 判断规则包 | 让入池、淘汰、继续、停止都可解释 |
+| 游走策略运行记录 | 记录从哪里走到哪里,以及为什么走 |
+| 全量候选池 | 不只保存最终入选,也保存被淘汰和观察的候选 |
+| 来源关系资产 | 让内容、作者、query、seed 可以追溯 |
+| 搜索线索资产 | 保存有效和失败的 query,供下一轮学习 |
+| 策略学习 trace | 复盘输入、query、规则、游走和表现 |
+| source evidence 承载 | 从结果内容反查到 Pattern、Case、分类树节点 |
+
+## 11. 当前真实能力边界
+
+已验证能力:
+
+- Pattern 库、业务库、open_aigc MySQL、open_aigc PG、ODPS。
+- 抖音关键词搜索、抖音作者作品、内容画像、账号画像、今日热榜。
+- 历史沉淀账号表可读,返回作者身份、标签、画像和 trace。
+- 今日热榜接口可读,返回来源块、热点标题、热度、排名和链接。
+- OpenRouter key 鉴权通过。
+- AIGC plan detail 只读查询通过。
+
+仍然缺口:
+
+- 全量候选池。
+- 判断 / 淘汰日志表。
+- 来源关系资产表。
+- `source_evidence` 字段或旁路文件 / 来源记录文件。
+- 搜索线索资产表。
+- show 前端真实后端 API。
+- TikHub key。
+- OSS 过程链接配置。
+- 小红书、共创、相似作者、推荐流等接口。
+
+## 12. V1 和总表的关系
+
+总表描述完整 MVP。V1 只选一条最小路径先跑通:
+
+```text
+Pattern 数据源
+-> Pattern 策略种子
+-> Query
+-> 抖音关键词搜索
+-> 视频判断
+-> 作者一跳扩展
+-> 内容 / 作者 / 运行追踪记录(trace)沉淀
+-> 策略复盘
+```
+
+V1 具体落地见:[V1落地版本.md](./V1落地版本.md)。

+ 452 - 0
product_documents/抖音游走策略/douyin_available_walk_strategy.v1.json

@@ -0,0 +1,452 @@
+{
+  "strategy_id": "douyin_available_walk_strategy_v1",
+  "strategy_name": "douyin available walk strategy",
+  "strategy_name_zh": "抖音可用游走策略 V1",
+  "description": "核心对象说明:EvidenceBundle 是判断证据包,RuleDecision 是规则判断结果,WalkAction 是下一步动作,aweme_id 是抖音视频 ID,sec_uid 是抖音作者 ID,source_evidence 是来源证据,trace 是运行追踪记录。",
+  "strategy_aliases": ["douyin available work strategy", "douyin available walk strategy"],
+  "updated_at": "2026-06-04",
+  "scope": {
+    "source": "PatternSeed",
+    "platform": "douyin",
+    "default_version": "V1",
+    "default_goal": "从票圈视频 Pattern 出发,只走抖音渠道,跑通可追溯的内容发现闭环"
+  },
+  "document_links": {
+    "prd": "../prd/V1落地版本.md",
+    "detailed_prd": "../prd/V1落地版本细化版.md",
+    "html_map": "抖音真实游走策略图.html"
+  },
+  "principles": [
+    "next_cursor 是分页状态节点,不是视频、作者或 tag 这类业务节点。",
+    "ContentPortrait、AccountPortrait、互动表现是判断信号,不能单独决定入池或淘汰。",
+    "判断信号必须先汇总为 EvidenceBundle(判断证据包),再由规则包输出决策动作。",
+    "V1 启用 Video -> Hashtag -> Query tag 扩散,但单次 run 最多允许 10 次 tag 扩散跳跃。",
+    "所有沉淀结果必须保留 source_evidence(来源证据)和 trace(运行追踪记录)。"
+  ],
+  "nodes": [
+    {
+      "id": "PatternSeed",
+      "label": "Pattern 策略种子",
+      "type": "traversal",
+      "key_fields": ["seed_terms", "itemset_items", "category_bindings", "element_bindings"],
+      "can_expand": true
+    },
+    {
+      "id": "Query",
+      "label": "抖音 Query",
+      "type": "traversal",
+      "key_fields": ["keyword"],
+      "can_expand": true
+    },
+    {
+      "id": "SearchPage",
+      "label": "搜索结果页",
+      "type": "traversal",
+      "key_fields": ["aweme_id", "author.sec_uid", "has_more", "next_cursor"],
+      "can_expand": true
+    },
+    {
+      "id": "SearchCursor",
+      "label": "搜索分页状态",
+      "type": "cursor",
+      "key_fields": ["keyword", "next_cursor"],
+      "can_expand": true
+    },
+    {
+      "id": "Video",
+      "label": "抖音视频节点",
+      "type": "traversal",
+      "key_fields": ["aweme_id", "desc", "author.sec_uid", "statistics.*", "cha_list", "text_extra"],
+      "can_expand": true
+    },
+    {
+      "id": "Author",
+      "label": "抖音作者节点",
+      "type": "traversal",
+      "key_fields": ["author.sec_uid", "author.nickname"],
+      "can_expand": true
+    },
+    {
+      "id": "AuthorWorksPage",
+      "label": "作者作品页",
+      "type": "traversal",
+      "key_fields": ["aweme_id", "author.*", "statistics.*", "has_more", "next_cursor"],
+      "can_expand": true
+    },
+    {
+      "id": "AuthorWorksCursor",
+      "label": "作者作品分页状态",
+      "type": "cursor",
+      "key_fields": ["account_id", "next_cursor"],
+      "can_expand": true
+    },
+    {
+      "id": "Hashtag",
+      "label": "视频 hashtag",
+      "type": "traversal",
+      "key_fields": ["tag_text"],
+      "can_expand": true
+    },
+    {
+      "id": "ContentPortrait",
+      "label": "内容画像",
+      "type": "signal",
+      "key_fields": ["content_id", "age_distribution", "tgi"],
+      "can_expand": false
+    },
+    {
+      "id": "AccountPortrait",
+      "label": "账号画像",
+      "type": "signal",
+      "key_fields": ["account_id", "fans_age_distribution", "tgi"],
+      "can_expand": false
+    },
+    {
+      "id": "InteractionPerformance",
+      "label": "互动表现",
+      "type": "signal",
+      "key_fields": ["statistics.*"],
+      "can_expand": false
+    },
+    {
+      "id": "EvidenceBundle",
+      "label": "判断信号汇总",
+      "type": "decision_input",
+      "key_fields": ["portrait_signal", "account_signal", "interaction_signal", "relevance_signal", "risk_signal", "trace"],
+      "can_expand": false
+    },
+    {
+      "id": "RuleDecision",
+      "label": "规则包决策",
+      "type": "decision",
+      "key_fields": ["matched_hard_gates", "scorecard", "final_action", "reason_code"],
+      "can_expand": false
+    },
+    {
+      "id": "WalkAction",
+      "label": "游走动作",
+      "type": "walk_action",
+      "key_fields": ["next_action", "target_refs", "source_edge_basis", "budget_effect", "reason_code"],
+      "can_expand": true
+    },
+    {
+      "id": "RunRecord",
+      "label": "运行记录",
+      "type": "run_record",
+      "key_fields": ["trace_events", "source_edges", "search_clues", "idempotency_key"],
+      "can_expand": false
+    },
+    {
+      "id": "AssetCommit",
+      "label": "资产提交",
+      "type": "asset_commit",
+      "key_fields": ["rule_decision_id", "source_edge_ids", "asset_status"],
+      "can_expand": false
+    },
+    {
+      "id": "OutputAsset",
+      "label": "沉淀结果",
+      "type": "output",
+      "key_fields": ["content_asset", "author_asset", "search_clue", "reject_record", "source_evidence"],
+      "can_expand": false
+    }
+  ],
+  "edge_rules": [
+    {
+      "id": "e01",
+      "from": "PatternSeed",
+      "to": "Query",
+      "input_fields": ["seed_terms"],
+      "interface_or_action": "local_query_build",
+      "creates_new_node": true,
+      "can_loop": false,
+      "v1_default_enabled": true,
+      "control": "query_count_limit"
+    },
+    {
+      "id": "e02",
+      "from": "Query",
+      "to": "SearchPage",
+      "input_fields": ["keyword", "cursor=0"],
+      "interface_or_action": "/crawler/dou_yin/keyword",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "max_pages_per_query"
+    },
+    {
+      "id": "e03",
+      "from": "SearchPage",
+      "to": "SearchCursor",
+      "input_fields": ["next_cursor"],
+      "interface_or_action": "read_pagination_state",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "cursor_dedup"
+    },
+    {
+      "id": "e04",
+      "from": "SearchCursor",
+      "to": "SearchPage",
+      "input_fields": ["keyword", "next_cursor"],
+      "interface_or_action": "/crawler/dou_yin/keyword",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "stop_when_continuous_low_quality"
+    },
+    {
+      "id": "e05",
+      "from": "SearchPage",
+      "to": "Video",
+      "input_fields": ["aweme_id"],
+      "interface_or_action": "split_search_result",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "video_dedup"
+    },
+    {
+      "id": "e06",
+      "from": "SearchPage",
+      "to": "Author",
+      "input_fields": ["author.sec_uid"],
+      "interface_or_action": "split_search_result",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "author_dedup"
+    },
+    {
+      "id": "e07",
+      "from": "Video",
+      "to": "Author",
+      "input_fields": ["author.sec_uid"],
+      "interface_or_action": "read_video_author_field",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "author_budget"
+    },
+    {
+      "id": "e08",
+      "from": "Author",
+      "to": "AuthorWorksPage",
+      "input_fields": ["account_id=author.sec_uid", "cursor=0"],
+      "interface_or_action": "/crawler/dou_yin/blogger",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "max_authors_and_works_per_author"
+    },
+    {
+      "id": "e09",
+      "from": "AuthorWorksPage",
+      "to": "AuthorWorksCursor",
+      "input_fields": ["next_cursor"],
+      "interface_or_action": "read_pagination_state",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "author_cursor_dedup"
+    },
+    {
+      "id": "e10",
+      "from": "AuthorWorksCursor",
+      "to": "AuthorWorksPage",
+      "input_fields": ["account_id", "next_cursor"],
+      "interface_or_action": "/crawler/dou_yin/blogger",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "max_author_work_pages"
+    },
+    {
+      "id": "e11",
+      "from": "AuthorWorksPage",
+      "to": "Video",
+      "input_fields": ["aweme_id"],
+      "interface_or_action": "split_author_work_result",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "video_and_author_dedup"
+    },
+    {
+      "id": "e12",
+      "from": "Video",
+      "to": "Hashtag",
+      "input_fields": ["desc", "cha_list", "text_extra"],
+      "interface_or_action": "local_tag_extract",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "max_tags_per_video_and_relevance_gate"
+    },
+    {
+      "id": "e13",
+      "from": "Hashtag",
+      "to": "Query",
+      "input_fields": ["tag_text"],
+      "interface_or_action": "local_query_from_tag",
+      "creates_new_node": true,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "drift_stop_and_query_dedup"
+    },
+    {
+      "id": "e14",
+      "from": "Video",
+      "to": "ContentPortrait",
+      "input_fields": ["content_id=aweme_id"],
+      "interface_or_action": "hotspot_content_portrait",
+      "creates_new_node": false,
+      "can_loop": false,
+      "v1_default_enabled": true,
+      "control": "signal_only"
+    },
+    {
+      "id": "e15",
+      "from": "Author",
+      "to": "AccountPortrait",
+      "input_fields": ["account_id=author.sec_uid"],
+      "interface_or_action": "hotspot_account_portrait",
+      "creates_new_node": false,
+      "can_loop": false,
+      "v1_default_enabled": true,
+      "control": "signal_only"
+    },
+    {
+      "id": "e16",
+      "from": "Video",
+      "to": "InteractionPerformance",
+      "input_fields": ["statistics.*"],
+      "interface_or_action": "read_metrics",
+      "creates_new_node": false,
+      "can_loop": false,
+      "v1_default_enabled": true,
+      "control": "signal_only"
+    },
+    {
+      "id": "e17",
+      "from": "ContentPortrait / AccountPortrait / InteractionPerformance / relevance / risk",
+      "to": "EvidenceBundle",
+      "input_fields": ["portrait_signal", "account_signal", "interaction_signal", "relevance_signal", "risk_signal", "trace"],
+      "interface_or_action": "merge_decision_evidence",
+      "creates_new_node": false,
+      "can_loop": false,
+      "v1_default_enabled": true,
+      "control": "no_single_signal_decision"
+    },
+    {
+      "id": "e18",
+      "from": "EvidenceBundle",
+      "to": "RuleDecision",
+      "input_fields": ["EvidenceBundle"],
+      "interface_or_action": "hard_gates_and_scorecard",
+      "creates_new_node": false,
+      "can_loop": false,
+      "v1_default_enabled": true,
+      "control": "decision_thresholds"
+    },
+    {
+      "id": "e19",
+      "from": "RuleDecision",
+      "to": "WalkAction",
+      "input_fields": ["RuleDecision", "PathState", "WalkStrategyVersion"],
+      "interface_or_action": "plan_walk_action",
+      "creates_new_node": false,
+      "can_loop": false,
+      "v1_default_enabled": true,
+      "control": "strategy_version_required"
+    },
+    {
+      "id": "e20",
+      "from": "WalkAction",
+      "to": "RunRecord / next_hop",
+      "input_fields": ["WalkAction", "source_edge_basis", "target_refs"],
+      "interface_or_action": "record_trace_and_schedule_next_step",
+      "creates_new_node": false,
+      "can_loop": true,
+      "v1_default_enabled": true,
+      "control": "trace_required"
+    },
+    {
+      "id": "e21",
+      "from": "RuleDecision / RunRecord",
+      "to": "AssetCommit",
+      "input_fields": ["RuleDecision", "source_edge_ids", "asset_status"],
+      "interface_or_action": "commit_asset_snapshot",
+      "creates_new_node": false,
+      "can_loop": false,
+      "v1_default_enabled": true,
+      "control": "asset_status_required"
+    },
+    {
+      "id": "e22",
+      "from": "AssetCommit",
+      "to": "OutputAsset",
+      "input_fields": ["content_asset", "author_asset", "search_clue_asset", "source_edge_ids"],
+      "interface_or_action": "write_final_output",
+      "creates_new_node": false,
+      "can_loop": false,
+      "v1_default_enabled": true,
+      "control": "trace_required"
+    }
+  ],
+  "decision_outputs": [
+    "视频是否入池",
+    "作者是否值得扩展",
+    "tag 是否生成新 query",
+    "当前路径是否停止",
+    "是否降预算待观察"
+  ],
+  "v1_default_path": [
+    "PatternSeed -> Query",
+    "Query -> SearchPage",
+    "SearchPage -> Video",
+    "Video -> Author",
+    "Author -> AuthorWorksPage",
+    "AuthorWorksPage -> Video",
+    "Video -> Hashtag -> Query",
+    "Video/Author -> EvidenceBundle -> RuleDecision",
+    "RuleDecision + PathState -> WalkAction",
+    "WalkAction -> RunRecord + next_hop",
+    "RuleDecision + SourceEdge source_refs -> AssetCommit -> OutputAsset"
+  ],
+  "v1_default_budget": {
+    "max_queries_per_pattern": 5,
+    "max_pages_per_query": 3,
+    "max_video_candidates_per_pattern": 150,
+    "max_expand_authors": 10,
+    "max_author_work_pages": 3,
+    "max_author_works_per_author": 20,
+    "max_tag_expansion_hops": 10,
+    "max_depth": 3
+  },
+  "not_default_in_v1": [
+    "相关搜索",
+    "相似作者",
+    "共创作者",
+    "不受预算控制的深层循环"
+  ],
+  "runtime_trace_files": [
+    "source_context.json",
+    "pattern_seed_pack.json",
+    "queries.jsonl",
+    "candidate_pool.jsonl",
+    "media_assets.jsonl",
+    "rule_decisions.jsonl",
+    "source_edges.jsonl",
+    "search_clues.jsonl",
+    "trace_events.jsonl",
+    "final_output.json"
+  ],
+  "runtime_trace_file_notes": {
+    "rule_decisions.jsonl": "规则判断结果文件",
+    "source_edges.jsonl": "来源记录文件",
+    "trace_events.jsonl": "运行事件文件",
+    "final_output.json": "最终结果文件"
+  }
+}

+ 186 - 0
product_documents/抖音游走策略/douyin_evidence_bundle.v1.json

@@ -0,0 +1,186 @@
+{
+  "schema_version": "evidence_bundle.v1",
+  "bundle_id": "douyin_evidence_bundle_v1",
+  "bundle_name": "抖音 EvidenceBundle V1",
+  "updated_at": "2026-06-04",
+  "strategy_id": "douyin_available_walk_strategy_v1",
+  "platform": "douyin",
+  "source": "PatternSeed",
+  "purpose": "把抖音搜索、作者作品、视频解构、画像、互动、风险和 trace(运行追踪记录)汇总成 EvidenceBundle(判断证据包),供规则包输出 RuleDecision(规则判断结果)。",
+  "field_notes": {
+    "source_evidence": "来源证据,记录候选从哪个 Pattern、query、作者或 tag 来",
+    "sec_uid": "抖音作者 ID",
+    "trace_id": "本次运行 ID",
+    "effect_status": "搜索词效果状态",
+    "POOL": "入池",
+    "CANDIDATE": "候选",
+    "PENDING": "待观察",
+    "REJECT": "淘汰"
+  },
+  "blocks_order": [
+    "source_evidence",
+    "entity",
+    "relevance_signal",
+    "interaction_signal",
+    "portrait_signal",
+    "account_signal",
+    "risk_signal",
+    "walk_context",
+    "trace"
+  ],
+  "fail_fast_rules": [
+    {
+      "rule_id": "missing_source_evidence",
+      "field": "source_evidence",
+      "action": "REJECT",
+      "note": "没有来源证据,视频淘汰。"
+    },
+    {
+      "rule_id": "missing_aweme_id",
+      "field": "entity.aweme_id",
+      "action": "REJECT",
+      "note": "没有 aweme_id(抖音视频 ID),视频淘汰。"
+    },
+    {
+      "rule_id": "missing_content_portrait",
+      "field": "portrait_signal",
+      "action": "REJECT",
+      "note": "内容画像拿不到,视频淘汰。"
+    },
+    {
+      "rule_id": "pattern_no_match",
+      "field": "relevance_signal.match_result",
+      "action": "REJECT",
+      "note": "不能回扣 Pattern,视频或 tag 淘汰。"
+    }
+  ],
+  "block_examples": {
+    "source_evidence": {
+      "pattern_execution_id": "pattern_001",
+      "itemset_ids": ["itemset_001"],
+      "source_kind": "pattern_itemset",
+      "query": "早安祝福",
+      "generation_type": "item_combo",
+      "origin_source": "pattern_itemset",
+      "immediate_source": "query_direct",
+      "source_edge_ids": ["edge_001", "edge_002", "edge_003"]
+    },
+    "entity": {
+      "entity_type": "video",
+      "aweme_id": "7390000000000000000",
+      "desc": "早上好,送你一份好运健康祝福",
+      "author": {
+        "nickname": "幸福每天见",
+        "sec_uid": "MS4wLjABAAAA..."
+      }
+    },
+    "relevance_signal": {
+      "target_type": "video",
+      "pattern_recall": "matched",
+      "match_result": "direct_match",
+      "matched_category_id": "cat_001",
+      "matched_element_id": "element_001",
+      "matched_itemset_id": "itemset_001",
+      "category_tree_walk_path": [],
+      "decode_result": {
+        "categories": ["早安问候", "祝福词句"],
+        "elements": ["早上好", "好运健康"]
+      }
+    },
+    "interaction_signal": {
+      "digg_count": 1200,
+      "comment_count": 80,
+      "share_count": 300,
+      "collect_count": 40,
+      "raw_statistics": {}
+    },
+    "portrait_signal": {
+      "portrait_source": "content_portrait",
+      "age_50_plus_ratio": 0.42,
+      "age_50_plus_tgi": 118,
+      "age_50_plus_level": "medium",
+      "age_distribution": [],
+      "preference": []
+    },
+    "account_signal": {
+      "author_sec_uid": "MS4wLjABAAAA...",
+      "account_50_plus_ratio": 0.48,
+      "account_50_plus_tgi": 130,
+      "age_50_plus_level": "medium",
+      "works_sample_count": 20,
+      "similar_work_count": 6,
+      "author_works_available": true
+    },
+    "risk_signal": {
+      "risk_level": "low",
+      "age_50_plus_safety": "safe",
+      "availability": "available",
+      "duplicate": false
+    },
+    "walk_context": {
+      "current_node": "Video",
+      "from_node": "SearchPage",
+      "edge_id": "e05",
+      "depth": 1,
+      "query_page": 1,
+      "tag_expansion_hop_count": 2,
+      "author_work_page": 0
+    },
+    "trace": {
+      "trace_id": "v1_trace_001",
+      "demand_content_id": "demand_001",
+      "events": [
+        "query_generated",
+        "douyin_search_returned",
+        "video_decoded",
+        "content_portrait_returned",
+        "rule_decision_started"
+      ]
+    }
+  },
+  "video_decision_required_blocks": [
+    "source_evidence",
+    "entity",
+    "relevance_signal",
+    "interaction_signal",
+    "portrait_signal",
+    "risk_signal",
+    "walk_context",
+    "trace"
+  ],
+  "author_expand_required_blocks": [
+    "source_evidence",
+    "entity",
+    "relevance_signal",
+    "account_signal",
+    "risk_signal",
+    "walk_context",
+    "trace"
+  ],
+  "tag_decision_required_blocks": [
+    "source_evidence",
+    "entity",
+    "relevance_signal",
+    "risk_signal",
+    "walk_context",
+    "trace"
+  ],
+  "entity_shape_by_type": {
+    "video": "entity.entity_type=video,包含 aweme_id(抖音视频 ID)、desc、author.sec_uid(抖音作者 ID)等字段",
+    "author": "entity.entity_type=author,包含 author.sec_uid(抖音作者 ID)、author.nickname 等字段",
+    "tag": "entity.entity_type=tag,包含 tag_text"
+  },
+  "relevance_signal_shape_by_type": {
+    "video": "包含 pattern_recall、match_result、category_or_element_binding、decode_result 等字段",
+    "author": "包含 author_homepage_level、vertical_stability、similar_work_count、pattern_recall_work_count、pattern_recall_work_ratio 等字段",
+    "tag": "包含 tag_recall_level、tag_specificity、platform_fit 等字段"
+  },
+  "notes": [
+    "portrait_signal 是视频判断必填块;内容画像拿不到直接淘汰。",
+    "account_signal 用于作者扩展判断;作者强不代表作品自动入池。",
+    "interaction_signal 只能参与软评分,不能单独决定入池。",
+    "age_50_plus_level 只允许 strong / medium / weak / missing;视频和作者沉淀优先使用 strong / medium。",
+    "不同判断类型分别使用 video_decision_required_blocks、author_expand_required_blocks 或 tag_decision_required_blocks;entity_type 使用小写 video / author / tag,决定 entity 和 relevance_signal 的具体字段形态。",
+    "EvidenceBundle 只组装证据,不直接输出最终动作;最终动作由规则包决定。"
+  ]
+}

+ 294 - 0
product_documents/抖音游走策略/runtime_v1_records_schema.md

@@ -0,0 +1,294 @@
+# V1 本地运行记录 Schema
+
+## 阅读约定
+
+本文第一次出现核心字段或枚举时,会用括号补一句中文解释。常见字段包括:`aweme_id`(抖音视频 ID)、`sec_uid`(抖音作者 ID)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`effect_status`(搜索词效果状态)、`POOL`(入池)、`CANDIDATE`(候选)、`PENDING`(待观察)、`REJECT`(淘汰)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+本目录定义 V1 本地真实运行产物,不是 show 后端联调数据。默认目录:
+
+```text
+runtime/v1/{trace_id}/
+```
+
+V1 不假设视频下载或 OSS 已接入。抖音接口默认只沉淀视频 ID、作者、统计、画像、规则判断和 trace(运行追踪记录);视频文件状态写入 `media_assets.jsonl`,默认 `metadata_only`。
+
+## 1. 文件总览
+
+| 文件 | 用途 | 主要负责写入的模块 |
+|---|---|---|
+| `source_context.json` | 本次需求和 `evidence_pack` 原始信息 | 数据源读取 |
+| `pattern_seed_pack.json` | 本次 Pattern、itemset、分类/元素和 seed terms | 数据源读取 |
+| `queries.jsonl` | 由 Pattern item 或 tag 生成的 query | Query 模块 |
+| `candidate_pool.jsonl` | 抖音搜索和作者作品返回的原始候选 | 候选与证据模块,平台接入提供结果 |
+| `media_assets.jsonl` | 视频媒体可用性与链接状态 | 候选与证据模块,平台接入提供结果 |
+| `rule_decisions.jsonl` | 规则判断结果文件 | 判断模块 |
+| `source_edges.jsonl` | 来源记录文件,记录节点之间从哪里来到哪里去 | 运行记录模块 |
+| `search_clues.jsonl` | query / tag 的线索效果 | 运行记录模块 |
+| `trace_events.jsonl` | 每一步运行事件 | 运行记录模块接收全流程事件请求后统一追加 |
+| `final_output.json` | 最终内容、作者、线索、淘汰和复盘汇总 | 资产沉淀 |
+
+## 2. 通用字段
+
+| 字段 | 必填 | 说明 |
+|---|---|---|
+| `trace_id` | 是 | 本次 V1 运行 ID |
+| `demand_content_id` | 是 | 上游需求 ID |
+| `pattern_execution_id` | 是 | Pattern 来源执行 ID |
+| `source_evidence` | 是 | 来源证据,包含 Pattern、itemset、分类/元素和来源路径 |
+| `origin_source` | 是 | 最初来源,如 `pattern_itemset` |
+| `immediate_source` | 是 | 当前候选的直接来源,如 `query_direct`、`query_from_tag`、`author_work` |
+| `created_at` | 是 | ISO 时间 |
+
+常用枚举:
+
+| 字段 | 枚举 |
+|---|---|
+| `effect_status` | `success` / `weak_effective` / `pending` / `blocked` / `failed` |
+| `age_50_plus_level` | `strong` / `medium` / `weak` / `missing` |
+| `video_pool_decision` | `POOL`(入池) / `CANDIDATE`(候选) / `PENDING`(待观察) / `REJECT`(淘汰) |
+| `author_expand_decision` | `EXPAND_AUTHOR` / `EXPAND_AUTHOR_SMALL_BUDGET` / `PENDING_AUTHOR` / `NO_EXPAND` |
+| `author_asset_decision` | `STORE_AUTHOR` / `PENDING_AUTHOR` / `REJECT_AUTHOR` |
+| `media_status` | `metadata_only` / `downloaded_local` / `oss_uploaded` / `unavailable` |
+| `asset_status` | `pooled` / `candidate_asset` / `stored_author` / `clue_only` |
+
+`effect_status` 含义:`success` 表示产生入池或候选结果;`weak_effective` 表示只产生待观察结果或质量偏弱;`pending` 表示暂时还不能评价;`blocked` 表示被接口、画像、媒体或来源记录问题阻断;`failed` 表示空召回或全部淘汰。
+
+## 3. `source_context.json`
+
+用途:保存本次从 DemandAgent / `demand_content` 领取到的信息。
+
+必填:`trace_id`、`demand_content_id`、`merge_leve2`、`name`、`ext_data.evidence_pack`。  
+可空:`suggestion`、`score`、`dt`。
+
+最小样例:
+
+```json
+{
+  "trace_id": "v1_trace_001",
+  "demand_content_id": "1",
+  "merge_leve2": "贪污腐败",
+  "name": "警花凌娅贪腐案",
+  "suggestion": null,
+  "score": null,
+  "dt": "2026-06-04",
+  "ext_data": {
+    "evidence_pack": {
+      "source_kind": "pattern_itemset",
+      "pattern_execution_id": "2038",
+      "source_post_id": "69509310",
+      "itemset_ids": ["391369", "391371"],
+      "seed_terms": ["叙事结构", "综合性腐败", "基层公职人员"]
+    }
+  }
+}
+```
+
+## 4. `pattern_seed_pack.json`
+
+用途:把本次使用的 Pattern 证据整理成 Query 和判断都能复用的 seed 包。
+
+必填:`pattern_execution_id`、`itemsets`、`seed_terms`、`category_bindings` 或 `element_bindings`、`matched_post_ids`。  
+可空:`support`、`absolute_support`。
+
+最小样例:
+
+```json
+{
+  "trace_id": "v1_trace_001",
+  "pattern_execution_id": "2038",
+  "itemsets": [
+    {
+      "itemset_id": "391369",
+      "items": ["叙事结构", "综合性腐败", "表达手法"],
+      "support": 0.1,
+      "absolute_support": 5
+    }
+  ],
+  "seed_terms": ["叙事结构", "综合性腐败", "基层公职人员", "贪腐"],
+  "category_bindings": [{"category_id": "cat_001", "category_path": "社会议题 / 腐败警示"}],
+  "element_bindings": [{"element_id": "element_001", "name": "基层公职人员"}],
+  "matched_post_ids": ["69509310", "69509322"]
+}
+```
+
+## 5. `queries.jsonl`
+
+用途:记录每条生成 query。query 不需要重复记住来自哪些 Pattern 词,但要能通过 `trace_id` 反查到 `pattern_seed_pack.json`。
+
+必填:`query_id`、`query`、`generation_type`、`origin_source`、`immediate_source`、`effect_status`。  
+枚举:`generation_type = item_single / item_combo / light_extension / tag_query`。
+
+样例:
+
+```jsonl
+{"trace_id":"v1_trace_001","query_id":"q_001","query":"基层公职人员贪腐案例","generation_type":"item_combo","origin_source":"pattern_itemset","immediate_source":"pattern_query","effect_status":"pending","created_at":"2026-06-05T10:00:00+08:00"}
+{"trace_id":"v1_trace_001","query_id":"q_tag_001","query":"早上好","generation_type":"tag_query","origin_source":"pattern_itemset","immediate_source":"query_from_tag","parent_aweme_id":"7390000000000000000","effect_status":"pending","created_at":"2026-06-05T10:02:00+08:00"}
+```
+
+## 6. `candidate_pool.jsonl`
+
+用途:保存抖音关键词搜索和作者作品接口返回的候选视频 / 作者作品元数据。
+
+必填:`candidate_id`、`query_id` 或 `author_sec_uid`、`aweme_id`、`desc`、`author_sec_uid`、`statistics`、`immediate_source`。  
+可空:`cha_list`、`text_extra`、`create_time`、`next_cursor`。
+
+样例:
+
+```jsonl
+{"trace_id":"v1_trace_001","candidate_id":"c_001","query_id":"q_001","aweme_id":"7390000000000000000","desc":"基层干部贪腐警示案例","author_sec_uid":"MS4wLjABAAAA001","author_nickname":"警示故事","statistics":{"digg_count":1200,"comment_count":80,"share_count":300},"cha_list":["#警示教育"],"next_cursor":"20","origin_source":"pattern_itemset","immediate_source":"query_direct","created_at":"2026-06-05T10:03:00+08:00"}
+{"trace_id":"v1_trace_001","candidate_id":"c_002","parent_author_sec_uid":"MS4wLjABAAAA001","aweme_id":"7390000000000000001","desc":"同作者另一条警示案例","author_sec_uid":"MS4wLjABAAAA001","statistics":{"digg_count":600,"comment_count":20,"share_count":70},"origin_source":"pattern_itemset","immediate_source":"author_work","created_at":"2026-06-05T10:05:00+08:00"}
+```
+
+## 7. `media_assets.jsonl`
+
+用途:记录视频媒体文件状态。V1 默认不下载视频、不上传 OSS,只保留 metadata。
+
+必填:`aweme_id`、`media_status`、`metadata_source`。  
+可空:`play_url`、`local_path`、`oss_url`、`failure_reason`。
+
+样例:
+
+```jsonl
+{"trace_id":"v1_trace_001","aweme_id":"7390000000000000000","media_status":"metadata_only","metadata_source":"douyin_keyword_search","play_url":null,"local_path":null,"oss_url":null,"created_at":"2026-06-05T10:03:10+08:00"}
+{"trace_id":"v1_trace_001","aweme_id":"7390000000000000099","media_status":"unavailable","metadata_source":"douyin_keyword_search","failure_reason":"no_play_url_returned","created_at":"2026-06-05T10:04:10+08:00"}
+```
+
+## 8. `rule_decisions.jsonl`
+
+用途:保存规则包判断。每条视频、作者、tag、路径停止判断都写一行。
+
+必填:`trace_id`、`decision_id`、`pack_id`、`entity_type`、`entity_id`、`matched_hard_gates`、`scorecard`、`final_action`、`reason_code`、`source_evidence`、`input_snapshot_ref`、`evidence_refs`、`replay_fields`。  
+可空:`score`、`age_50_plus_level`、`effect_status`。
+
+成功、待观察、淘汰样例:
+
+```jsonl
+{"trace_id":"v1_trace_001","decision_id":"d_001","pack_id":"douyin_video_candidate_rule_pack_v1","entity_type":"Video","entity_id":"7390000000000000000","matched_hard_gates":[],"score":72,"scorecard":{"douyin_tone":20,"interaction_performance":10,"age_50_plus_portrait":25,"adaptability":7,"freshness_available":10},"age_50_plus_level":"medium","final_action":"POOL","reason_code":"video_score_pool","effect_status":"success","source_evidence":{"pattern_execution_id":"2038","origin_source":"pattern_itemset","immediate_source":"query_direct"},"input_snapshot_ref":"evidence_bundle:v1_trace_001:c_001:douyin_video_candidate_rule_pack_v1:1.0.0","evidence_refs":["source_evidence","relevance_signal.pattern_recall","portrait_signal.age_50_plus_level","risk_signal.risk_level"],"replay_fields":{"rule_pack_version":"1.0.0","policy_bundle_version":"douyin_policy_bundle_v1","matched_threshold":"score>=70"}}
+{"trace_id":"v1_trace_001","decision_id":"d_002","pack_id":"douyin_video_candidate_rule_pack_v1","entity_type":"Video","entity_id":"7390000000000000001","matched_hard_gates":[],"score":55,"age_50_plus_level":"medium","final_action":"PENDING","reason_code":"video_score_pending","effect_status":"pending","source_evidence":{"pattern_execution_id":"2038","origin_source":"pattern_itemset","immediate_source":"author_work"},"input_snapshot_ref":"evidence_bundle:v1_trace_001:c_002:douyin_video_candidate_rule_pack_v1:1.0.0","evidence_refs":["source_evidence","relevance_signal.pattern_recall","portrait_signal.age_50_plus_level"],"replay_fields":{"rule_pack_version":"1.0.0","policy_bundle_version":"douyin_policy_bundle_v1","matched_threshold":"50<=score<=59"}}
+{"trace_id":"v1_trace_001","decision_id":"d_003","pack_id":"douyin_video_candidate_rule_pack_v1","entity_type":"Video","entity_id":"7390000000000000099","matched_hard_gates":["missing_content_portrait"],"score":null,"age_50_plus_level":"missing","final_action":"REJECT","reason_code":"missing_content_portrait","effect_status":"blocked","source_evidence":{"pattern_execution_id":"2038","origin_source":"pattern_itemset","immediate_source":"query_direct"},"input_snapshot_ref":"evidence_bundle:v1_trace_001:c_099:douyin_video_candidate_rule_pack_v1:1.0.0","evidence_refs":["source_evidence","portrait_signal"],"replay_fields":{"rule_pack_version":"1.0.0","policy_bundle_version":"douyin_policy_bundle_v1","matched_gate":"missing_content_portrait"}}
+```
+
+## 9. `source_edges.jsonl`
+
+用途:保存每一次运行事实边,保证任何结果都能反查来源路径。
+
+写入边界:由运行记录模块追加。游走模块只提供下一步动作和来源/目标引用,资产沉淀只读取并归并来源关系。
+
+必填:`edge_id`、`from_node_type`、`from_node_id`、`to_node_type`、`to_node_id`、`edge_type`、`rule_pack_id`、`decision_id`。  
+枚举:`edge_type = pattern_to_query / query_to_video / video_to_author / author_to_work / video_to_tag / tag_to_query / decision_to_asset`。
+
+样例:
+
+```jsonl
+{"trace_id":"v1_trace_001","edge_id":"edge_001","from_node_type":"PatternSeed","from_node_id":"2038","to_node_type":"Query","to_node_id":"q_001","edge_type":"pattern_to_query","rule_pack_id":null,"decision_id":null,"origin_source":"pattern_itemset","immediate_source":"pattern_query","created_at":"2026-06-05T10:00:00+08:00"}
+{"trace_id":"v1_trace_001","edge_id":"edge_002","from_node_type":"Query","from_node_id":"q_001","to_node_type":"Video","to_node_id":"7390000000000000000","edge_type":"query_to_video","rule_pack_id":"douyin_video_candidate_rule_pack_v1","decision_id":"d_001","origin_source":"pattern_itemset","immediate_source":"query_direct","created_at":"2026-06-05T10:03:00+08:00"}
+```
+
+## 10. `search_clues.jsonl`
+
+用途:记录 query、tag query 的阶段效果。tag query 暂不算正式资产,只是运行期线索观测。
+
+写入边界:由运行记录模块追加。Query 模块只生成 `queries.jsonl`,资产沉淀只把确认可复用的线索写入最终输出或后续线索资产。
+
+必填:`clue_id`、`query_id`、`query`、`origin_source`、`immediate_source`、`effect_status`、`result_count`。  
+可空:`pool_count`、`candidate_count`、`pending_count`、`reject_count`、`next_action`。
+
+样例:
+
+```jsonl
+{"trace_id":"v1_trace_001","clue_id":"clue_001","query_id":"q_001","query":"基层公职人员贪腐案例","origin_source":"pattern_itemset","immediate_source":"pattern_query","result_count":10,"pool_count":2,"candidate_count":3,"pending_count":1,"reject_count":4,"effect_status":"success","next_action":"keep_query"}
+{"trace_id":"v1_trace_001","clue_id":"clue_002","query_id":"q_tag_001","query":"早上好","origin_source":"pattern_itemset","immediate_source":"query_from_tag","result_count":5,"pool_count":0,"candidate_count":1,"pending_count":1,"reject_count":3,"effect_status":"weak_effective","next_action":"low_budget_pending"}
+```
+
+## 11. `trace_events.jsonl`
+
+用途:流水日志,记录每个关键动作是否成功。
+
+必填:`event_id`、`event_type`、`status`、`input_ref`、`output_ref`。  
+枚举:`status = success / pending / blocked / failed`。
+
+样例:
+
+```jsonl
+{"trace_id":"v1_trace_001","event_id":"evt_001","event_type":"query_generated","status":"success","input_ref":"pattern_seed_pack.json","output_ref":"queries.jsonl:q_001","created_at":"2026-06-05T10:00:00+08:00"}
+{"trace_id":"v1_trace_001","event_id":"evt_002","event_type":"content_portrait_missing","status":"blocked","input_ref":"candidate_pool.jsonl:c_099","output_ref":"rule_decisions.jsonl:d_003","created_at":"2026-06-05T10:04:00+08:00"}
+```
+
+## 12. `final_output.json`
+
+用途:一次 V1 运行 的最终复盘和资产输出。
+
+必填:`trace_id`、`content_assets`、`author_assets`、`decision_records`、`search_clues`、`reject_records`、`summary`。  
+要求:每个 `content_asset` 必须能通过 `source_edge_ids` 反查到 `source_context.json` 和 `pattern_seed_pack.json`。
+
+最小样例:
+
+```json
+{
+  "trace_id": "v1_trace_001",
+  "content_assets": [
+    {
+      "aweme_id": "7390000000000000000",
+      "asset_status": "pooled",
+      "decision_id": "d_001",
+      "source_edge_ids": ["edge_001", "edge_002"],
+      "media_status": "metadata_only"
+    }
+  ],
+  "author_assets": [
+  ],
+  "decision_records": [
+    {
+      "decision_id": "d_001",
+      "entity_id": "7390000000000000000",
+      "final_action": "POOL",
+      "reason_code": "video_score_pool",
+      "source_evidence": {
+        "pattern_execution_id": "2038",
+        "source_post_id": "69509310",
+        "candidate_aweme_id": "7390000000000000000",
+        "candidate_relation": "mock_pattern_matched"
+      }
+    },
+    {
+      "decision_id": "d_003",
+      "entity_id": "7390000000000000099",
+      "final_action": "REJECT",
+      "reason_code": "missing_content_portrait",
+      "source_evidence": {
+        "pattern_execution_id": "2038",
+        "source_post_id": "69509310",
+        "candidate_aweme_id": "7390000000000000099",
+        "candidate_relation": "mock_pattern_matched"
+      }
+    }
+  ],
+  "search_clues": [
+    {"query_id": "q_tag_001", "asset_status": "clue_only", "effect_status": "weak_effective"}
+  ],
+  "reject_records": [
+    {"entity_id": "7390000000000000099", "main_reason_code": "missing_content_portrait", "decision_id": "d_003"}
+  ],
+  "summary": {
+    "query_count": 2,
+    "pool_count": 1,
+    "candidate_count": 1,
+    "pending_count": 1,
+    "reject_count": 1,
+    "trace_complete": true
+  }
+}
+```
+
+## 13. 校验口径
+
+- JSON 文件必须能 `JSON.parse`。
+- JSONL 文件必须逐行能 `JSON.parse`。
+- `rule_decisions.jsonl.final_action` 必须来自规则包枚举。
+- `candidate_pool.jsonl.aweme_id` 和 `rule_decisions.jsonl.entity_id` 必须能对上。
+- `final_output.json.content_assets[].source_edge_ids` 必须能在 `source_edges.jsonl` 找到。
+- `source_edges.jsonl` 必须能一路反查到 `source_context.json.ext_data.evidence_pack`。
+- 每条 `RuleDecision` 都必须有 `query_to_video` 和 `pattern_to_query` 来源路径;即使最终是 `REJECT`,也不能断掉来源反查。
+- `source_evidence` 必须完整继承上游 `evidence_pack`,不能把新召回的 `aweme_id` 写进 `source_post_id` 或 `matched_post_ids`。
+- `final_output.json.decision_records` 必须覆盖全部规则判断,确保入池、候选、待观察和淘汰都能复盘。
+- `final_output.json.summary` 和 `search_clues.jsonl` 的计数必须与 `rule_decisions.jsonl.final_action` 对齐。

+ 366 - 0
product_documents/抖音游走策略/抖音接口真实case样例.json

@@ -0,0 +1,366 @@
+{
+  "document_name": "抖音接口真实 case 样例",
+  "generated_at": "2026-06-04T19:35:28+08:00",
+  "field_notes": "核心字段说明:aweme_id 是抖音视频 ID,sec_uid 是抖音作者 ID,trace_id 是本次运行 ID,source_evidence 是来源证据。",
+  "case_order": [
+    "抖音关键词搜索",
+    "抖音作者作品",
+    "热点宝内容点赞画像",
+    "热点宝账号粉丝画像"
+  ],
+  "sample_chain": {
+    "keyword": "早上好祝福视频",
+    "selected_aweme_id": "7615247738577423622",
+    "selected_author_sec_uid": "MS4wLjABAAAAdYcDPf6Y6tD-yq3porlmfvc0Le-xv0cWiutVi8PjJtU",
+    "selected_author_nickname": "让你心情变浅的兔子",
+    "note": "关键词搜索、作者作品和账号画像使用同一条视频作者链路。内容点赞画像使用另一个真实召回视频,因为同链路视频没有稳定返回内容画像。"
+  },
+  "cases": [
+    {
+      "id": "case_01_douyin_keyword_search",
+      "interface_name": "抖音关键词搜索",
+      "endpoint_path": "/crawler/dou_yin/keyword",
+      "business_use": "V1 从 Pattern 生成查询词后,召回抖音候选视频。",
+      "request_payload": {
+        "keyword": "早上好祝福视频",
+        "content_type": "视频",
+        "sort_type": "综合排序",
+        "publish_time": "不限",
+        "cursor": "0",
+        "account_id": "771431222"
+      },
+      "status": {
+        "http_status": 200,
+        "business_code": 0,
+        "pass": true
+      },
+      "result_count": 9,
+      "page_info": {
+        "has_more": true,
+        "next_cursor": "10"
+      },
+      "sample_item": {
+        "aweme_id": "7615247738577423622",
+        "title_or_desc": "早睡早起早上好♡ #二次元 #动漫 #治愈 #动画 #早上好",
+        "author": {
+          "nickname": "让你心情变浅的兔子",
+          "sec_uid": "MS4wLjABAAAAdYcDPf6Y6tD-yq3porlmfvc0Le-xv0cWiutVi8PjJtU"
+        },
+        "statistics": {
+          "digg_count": 1931,
+          "comment_count": 45,
+          "share_count": 1968,
+          "collect_count": 462,
+          "play_count": 0
+        },
+        "available_fields": [
+          "author",
+          "aweme_id",
+          "create_time",
+          "desc",
+          "statistics",
+          "video"
+        ]
+      },
+      "fields_confirmed": [
+        "aweme_id",
+        "desc/item_title",
+        "author.nickname",
+        "author.sec_uid",
+        "statistics.*",
+        "has_more",
+        "next_cursor"
+      ],
+      "v1_meaning": "这是视频候选池入口;aweme_id 用于内容去重和后续画像,author.sec_uid 用于作者游走。"
+    },
+    {
+      "id": "case_02_douyin_author_works",
+      "interface_name": "抖音作者作品",
+      "endpoint_path": "/crawler/dou_yin/blogger",
+      "business_use": "从候选视频作者继续游走,拉作者其他作品做二跳候选。",
+      "request_payload": {
+        "account_id": "MS4wLjABAAAAdYcDPf6Y6tD-yq3porlmfvc0Le-xv0cWiutVi8PjJtU",
+        "sort_type": "最新",
+        "cursor": ""
+      },
+      "status": {
+        "http_status": 200,
+        "business_code": 0,
+        "pass": true
+      },
+      "result_count": 23,
+      "page_info": {
+        "has_more": true,
+        "next_cursor": "1773230100000"
+      },
+      "sample_item": {
+        "aweme_id": "7572866877098118436",
+        "title_or_desc": "当你周围没人的时候... #二次元 #动漫 #搞笑 #治愈",
+        "author": {
+          "nickname": "让你心情变浅的兔子",
+          "sec_uid": "MS4wLjABAAAAdYcDPf6Y6tD-yq3porlmfvc0Le-xv0cWiutVi8PjJtU"
+        },
+        "statistics": {
+          "digg_count": 125538,
+          "comment_count": 10139,
+          "share_count": 202663,
+          "collect_count": 5912,
+          "play_count": 0
+        },
+        "available_fields": [
+          "author",
+          "aweme_id",
+          "create_time",
+          "desc",
+          "item_title",
+          "statistics",
+          "video"
+        ]
+      },
+      "fields_confirmed": [
+        "aweme_id",
+        "desc/item_title",
+        "author.nickname",
+        "author.sec_uid",
+        "statistics.*",
+        "has_more",
+        "next_cursor"
+      ],
+      "v1_meaning": "这是作者二跳入口;同一作者作品可进入候选视频判断,但不能自动入池。"
+    },
+    {
+      "id": "case_03_hotspot_content_portrait",
+      "interface_name": "热点宝内容点赞画像",
+      "endpoint_path": "/crawler/dou_yin/re_dian_bao/video_like_portrait",
+      "business_use": "判断候选视频是否贴近目标人群,例如 50+ 占比和 TGI;只做判断信号,不做召回源。",
+      "request_payload": {
+        "content_id": "7635992906608060495",
+        "need_age": true,
+        "need_gender": true,
+        "need_province": true,
+        "need_city": false,
+        "need_city_level": false,
+        "need_phone_brand": false,
+        "need_phone_price": false
+      },
+      "status": {
+        "http_status": 200,
+        "business_code": 0,
+        "pass": true,
+        "observed_state": "接口可达;该真实视频第 3 次重试返回可用点赞画像"
+      },
+      "source_video": {
+        "source_keyword": "祝福视频",
+        "aweme_id": "7635992906608060495",
+        "title_or_desc": "祝所有考生高考加油! #高考加油 #高考倒计时 #高考 #青春 #高考倒计时祝考生金榜题名 #2026高考 #抖音精选高考应援联盟",
+        "author": {
+          "nickname": "一个富贵",
+          "sec_uid": "MS4wLjABAAAAKouSmCULyRPvwO2ECzsUljHEmlAxvRIJSy3Q30VEuu0"
+        }
+      },
+      "portrait": {
+        "dimension_names": [
+          "省份",
+          "性别",
+          "年龄"
+        ],
+        "dimensions": {
+          "省份": [
+            {
+              "name": "河南",
+              "percentage": "10.28%",
+              "preference": "153.75"
+            },
+            {
+              "name": "山东",
+              "percentage": "9.50%",
+              "preference": "145.86"
+            },
+            {
+              "name": "广东",
+              "percentage": "7.18%",
+              "preference": "64.19"
+            },
+            {
+              "name": "河北",
+              "percentage": "6.84%",
+              "preference": "152.77"
+            },
+            {
+              "name": "江苏",
+              "percentage": "4.91%",
+              "preference": "67.94"
+            }
+          ],
+          "性别": [
+            {
+              "name": "female",
+              "percentage": "56.49%",
+              "preference": "122.44"
+            },
+            {
+              "name": "male",
+              "percentage": "43.51%",
+              "preference": "82.55"
+            }
+          ],
+          "年龄": [
+            {
+              "name": "18-23",
+              "percentage": "56.28%",
+              "preference": "291.27"
+            },
+            {
+              "name": "24-30",
+              "percentage": "23.38%",
+              "preference": "98.12"
+            },
+            {
+              "name": "31-40",
+              "percentage": "12.50%",
+              "preference": "26.18"
+            },
+            {
+              "name": "50-",
+              "percentage": "4.83%",
+              "preference": "13.80"
+            },
+            {
+              "name": "41-50",
+              "percentage": "3.02%",
+              "preference": "12.90"
+            }
+          ]
+        },
+        "has_portrait": true
+      },
+      "stability_observation": {
+        "same_chain_aweme_id_without_portrait": "7615247738577423622",
+        "same_author_work_without_portrait": "7572866877098118436",
+        "success_retry_count": 3,
+        "rule_boundary": "内容画像可能不存在或接口短时波动,但 V1 视频判断仍把 portrait_signal 作为硬要求;拿不到内容画像时淘汰或标记 blocked。"
+      },
+      "fields_confirmed": [
+        "年龄.percentage/preference",
+        "性别.percentage/preference",
+        "省份.percentage/preference"
+      ],
+      "v1_meaning": "内容画像是 V1 视频判断的硬要求;无内容画像时不能用账号画像兜底入池,应淘汰或标记 blocked,账号画像只用于作者扩展判断。"
+    },
+    {
+      "id": "case_04_hotspot_account_portrait",
+      "interface_name": "热点宝账号粉丝画像",
+      "endpoint_path": "/crawler/dou_yin/re_dian_bao/account_fans_portrait",
+      "business_use": "判断作者是否值得扩展,尤其用于作者供给评估和作者作品二跳预算。",
+      "request_payload": {
+        "account_id": "MS4wLjABAAAAdYcDPf6Y6tD-yq3porlmfvc0Le-xv0cWiutVi8PjJtU",
+        "need_age": true,
+        "need_gender": true,
+        "need_province": true,
+        "need_city": false,
+        "need_city_level": false,
+        "need_phone_brand": false,
+        "need_phone_price": false
+      },
+      "status": {
+        "http_status": 200,
+        "business_code": 0,
+        "pass": true
+      },
+      "portrait": {
+        "dimension_names": [
+          "年龄",
+          "性别",
+          "省份"
+        ],
+        "dimensions": {
+          "年龄": [
+            {
+              "name": "31-40",
+              "percentage": "40.11%",
+              "preference": "116.75"
+            },
+            {
+              "name": "24-30",
+              "percentage": "24.70%",
+              "preference": "144.09"
+            },
+            {
+              "name": "18-23",
+              "percentage": "15.96%",
+              "preference": "114.70"
+            },
+            {
+              "name": "50-",
+              "percentage": "12.57%",
+              "preference": "49.73"
+            },
+            {
+              "name": "41-50",
+              "percentage": "6.66%",
+              "preference": "39.63"
+            }
+          ],
+          "性别": [
+            {
+              "name": "female",
+              "percentage": "63.49%",
+              "preference": "137.47"
+            },
+            {
+              "name": "male",
+              "percentage": "36.51%",
+              "preference": "69.17"
+            }
+          ],
+          "省份": [
+            {
+              "name": "广东",
+              "percentage": "10.27%",
+              "preference": "91.13"
+            },
+            {
+              "name": "山东",
+              "percentage": "8.24%",
+              "preference": "125.11"
+            },
+            {
+              "name": "江苏",
+              "percentage": "7.13%",
+              "preference": "97.81"
+            },
+            {
+              "name": "辽宁",
+              "percentage": "5.67%",
+              "preference": "248.28"
+            },
+            {
+              "name": "河北",
+              "percentage": "5.41%",
+              "preference": "119.00"
+            }
+          ]
+        },
+        "has_portrait": true
+      },
+      "fields_confirmed": [
+        "年龄.percentage/preference",
+        "性别.percentage/preference",
+        "省份.percentage/preference"
+      ],
+      "v1_meaning": "账号画像可用于作者扩展判断。这里 50+ 占比偏低,因此该作者不适合作为老年赛道强扩展样本,但接口字段本身可用。"
+    }
+  ],
+  "v1_product_findings": [
+    "关键词搜索可以直接提供 aweme_id 和 author.sec_uid,是视频候选与作者游走的共同起点。",
+    "作者作品接口使用 author.sec_uid 作为 account_id,可以把视频起点扩展为作者作品二跳。",
+    "账号画像提供 percentage / preference,可作为作者判断规则包里的软评分证据。",
+    "内容画像接口返回不稳定;V1 视频判断仍把 portrait_signal 作为硬要求,拿不到时淘汰或标记 blocked。",
+    "画像接口不产生新候选,只辅助判断继续、观察、入池或淘汰。"
+  ],
+  "storage_boundary": [
+    "PRD 样例只保留结构化字段,不保存完整原始响应。",
+    "真实入库仍必须写 source_evidence,绑定 Pattern / 分类 / 元素 / trace。",
+    "不要把热点宝画像误写成召回接口,它是判断信号。"
+  ]
+}

+ 832 - 0
product_documents/抖音游走策略/抖音真实游走策略图.html

@@ -0,0 +1,832 @@
+<!doctype html>
+<html lang="zh-CN">
+<head>
+  <meta charset="utf-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1" />
+  <title>抖音游走节点与边规则图</title>
+  <style>
+    :root {
+      --bg: #f5f7fb;
+      --panel: #ffffff;
+      --ink: #142033;
+      --muted: #5c6a80;
+      --line: #cad6e8;
+      --green: #0f8a5f;
+      --green-bg: #e8f7ef;
+      --blue: #2563eb;
+      --blue-bg: #eaf1ff;
+      --orange: #c66a12;
+      --orange-bg: #fff4df;
+      --purple: #7c3aed;
+      --purple-bg: #f1eaff;
+      --gray: #748196;
+      --gray-bg: #eef2f6;
+      --red: #cf3f3f;
+      --red-bg: #fff0f0;
+    }
+
+    * { box-sizing: border-box; }
+
+    body {
+      margin: 0;
+      background: var(--bg);
+      color: var(--ink);
+      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
+        "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;
+    }
+
+    header {
+      position: sticky;
+      top: 0;
+      z-index: 10;
+      padding: 26px 34px 18px;
+      background: rgba(255, 255, 255, 0.97);
+      border-bottom: 1px solid var(--line);
+      backdrop-filter: blur(10px);
+    }
+
+    h1 {
+      margin: 0 0 10px;
+      font-size: 34px;
+      line-height: 1.2;
+      letter-spacing: 0;
+    }
+
+    .lead {
+      margin: 0;
+      max-width: 1460px;
+      color: var(--muted);
+      font-size: 20px;
+      line-height: 1.55;
+      font-weight: 650;
+    }
+
+    main {
+      max-width: 1760px;
+      margin: 0 auto;
+      padding: 24px;
+    }
+
+    .legend {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 10px;
+      margin-bottom: 18px;
+    }
+
+    .chip {
+      display: inline-flex;
+      align-items: center;
+      gap: 8px;
+      padding: 8px 12px;
+      border: 1px solid var(--line);
+      border-radius: 999px;
+      background: #fff;
+      font-size: 18px;
+      font-weight: 750;
+      color: #25364d;
+      white-space: nowrap;
+    }
+
+    .dot {
+      width: 13px;
+      height: 13px;
+      border-radius: 50%;
+      display: inline-block;
+    }
+
+    .dot.green { background: var(--green); }
+    .dot.blue { background: var(--blue); }
+    .dot.orange { background: var(--orange); }
+    .dot.purple { background: var(--purple); }
+    .dot.gray { background: var(--gray); }
+    .dot.red { background: var(--red); }
+
+    .board {
+      display: grid;
+      gap: 18px;
+    }
+
+    section {
+      padding: 22px 24px;
+      border: 1px solid var(--line);
+      border-radius: 12px;
+      background: var(--panel);
+      box-shadow: 0 12px 30px rgba(20, 32, 51, 0.07);
+    }
+
+    h2 {
+      margin: 0 0 8px;
+      font-size: 28px;
+      line-height: 1.25;
+      letter-spacing: 0;
+    }
+
+    .note {
+      margin: 0 0 18px;
+      color: var(--muted);
+      font-size: 19px;
+      line-height: 1.5;
+      font-weight: 650;
+    }
+
+    .nodes-grid {
+      display: grid;
+      grid-template-columns: repeat(7, minmax(160px, 1fr));
+      gap: 12px;
+      align-items: stretch;
+    }
+
+    .card {
+      min-height: 124px;
+      padding: 15px 16px;
+      border: 2.5px solid var(--line);
+      border-radius: 12px;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      gap: 7px;
+    }
+
+    .card h3 {
+      margin: 0;
+      font-size: 23px;
+      line-height: 1.2;
+      letter-spacing: 0;
+    }
+
+    .card p {
+      margin: 0;
+      color: var(--muted);
+      font-size: 16.5px;
+      line-height: 1.35;
+      font-weight: 650;
+    }
+
+    .field {
+      display: inline-block;
+      width: fit-content;
+      max-width: 100%;
+      padding: 4px 8px;
+      border: 1px solid rgba(102, 119, 143, 0.35);
+      border-radius: 7px;
+      background: rgba(255, 255, 255, 0.75);
+      color: #243247;
+      font-size: 15.5px;
+      line-height: 1.15;
+      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    .green { background: var(--green-bg); border-color: #55b98b; }
+    .blue { background: var(--blue-bg); border-color: #88a8e8; }
+    .orange { background: var(--orange-bg); border-color: #e5a04b; }
+    .purple { background: var(--purple-bg); border-color: #aa8df2; }
+    .gray { background: var(--gray-bg); border-color: #aeb9c8; }
+    .red { background: var(--red-bg); border-color: #ea9191; }
+
+    .diagram {
+      width: 100%;
+      overflow-x: auto;
+      border: 1px solid var(--line);
+      border-radius: 12px;
+      background: #fbfdff;
+    }
+
+    svg {
+      display: block;
+      min-width: 1600px;
+      width: 100%;
+      height: 760px;
+    }
+
+    .svg-title {
+      font-size: 25px;
+      font-weight: 900;
+      fill: var(--ink);
+    }
+
+    .svg-note {
+      font-size: 17px;
+      font-weight: 700;
+      fill: var(--muted);
+    }
+
+    .node-title {
+      font-size: 21px;
+      font-weight: 900;
+      fill: var(--ink);
+    }
+
+    .node-sub {
+      font-size: 15.5px;
+      font-weight: 700;
+      fill: var(--muted);
+    }
+
+    .node-field {
+      font-size: 14px;
+      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+      fill: #25364d;
+      font-weight: 700;
+    }
+
+    .edge {
+      fill: none;
+      stroke: #40516a;
+      stroke-width: 3;
+      marker-end: url(#arrow);
+    }
+
+    .edge.green {
+      stroke: var(--green);
+      stroke-width: 4;
+      marker-end: url(#arrow-green);
+    }
+
+    .edge.orange {
+      stroke: var(--orange);
+      stroke-width: 3.5;
+      stroke-dasharray: 10 7;
+      marker-end: url(#arrow-orange);
+    }
+
+    .edge.gray {
+      stroke: var(--gray);
+      stroke-width: 3;
+      stroke-dasharray: 8 7;
+      marker-end: url(#arrow-gray);
+    }
+
+    .edge-label {
+      font-size: 16px;
+      font-weight: 900;
+      fill: #243247;
+      paint-order: stroke;
+      stroke: #fff;
+      stroke-width: 5px;
+      stroke-linejoin: round;
+    }
+
+    .table-wrap {
+      overflow-x: auto;
+      border: 1px solid var(--line);
+      border-radius: 10px;
+    }
+
+    table {
+      width: 100%;
+      min-width: 1280px;
+      border-collapse: collapse;
+      font-size: 17px;
+    }
+
+    th, td {
+      border: 1px solid var(--line);
+      padding: 12px 13px;
+      text-align: left;
+      vertical-align: top;
+      line-height: 1.42;
+    }
+
+    th {
+      background: #edf3fb;
+      color: #20314b;
+      font-size: 18px;
+      white-space: nowrap;
+    }
+
+    td {
+      background: #fff;
+      color: #33445e;
+      font-weight: 650;
+    }
+
+    .ok { color: var(--green); font-weight: 900; }
+    .no { color: var(--gray); font-weight: 900; }
+    .risk { color: var(--red); font-weight: 900; }
+
+    .cycle-grid {
+      display: grid;
+      grid-template-columns: repeat(3, 1fr);
+      gap: 14px;
+    }
+
+    .cycle-card {
+      padding: 18px;
+      border: 2px solid #e5a04b;
+      border-radius: 12px;
+      background: #fffaf0;
+    }
+
+    .cycle-card h3 {
+      margin: 0 0 10px;
+      font-size: 24px;
+      line-height: 1.22;
+      color: #7a3d00;
+    }
+
+    .cycle-card pre {
+      margin: 0;
+      white-space: pre-wrap;
+      color: #243247;
+      font-size: 18px;
+      line-height: 1.45;
+      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+      font-weight: 700;
+    }
+
+    .summary {
+      padding: 20px 24px;
+      border: 1px solid var(--line);
+      border-radius: 12px;
+      background: #fff;
+      color: var(--muted);
+      font-size: 20px;
+      line-height: 1.65;
+      box-shadow: 0 12px 30px rgba(20, 32, 51, 0.06);
+    }
+
+    .summary strong { color: var(--ink); }
+
+    @media (max-width: 1180px) {
+      main { max-width: 100%; }
+      .nodes-grid,
+      .cycle-grid {
+        grid-template-columns: 1fr;
+      }
+    }
+  </style>
+</head>
+<body>
+  <header>
+    <h1>抖音游走节点与边规则图</h1>
+    <p class="lead">
+      用节点类型图表达全部可能性,用边规则表表达字段和接口。`next_cursor` 不当普通业务节点,而是 `SearchCursor / AuthorWorksCursor` 这类分页状态节点。
+    </p>
+  </header>
+
+  <main>
+    <div class="legend">
+      <span class="chip"><span class="dot green"></span>游走节点:能产生下一跳</span>
+      <span class="chip"><span class="dot orange"></span>分页状态节点:控制循环</span>
+      <span class="chip"><span class="dot gray"></span>判断信号:先汇总再判断</span>
+      <span class="chip"><span class="dot purple"></span>规则包:输出决策动作</span>
+      <span class="chip"><span class="dot red"></span>最终沉淀</span>
+    </div>
+
+    <div class="board">
+      <section>
+        <h2>1. 节点全集:先把“节点”和“字段”分清楚</h2>
+        <p class="note">图上只画节点类型。字段写在节点或边上;`next_cursor` 用分页状态节点承载,不和视频、作者、tag 混在一起。</p>
+        <div class="nodes-grid">
+          <div class="card green"><h3>PatternSeed</h3><span class="field">起点</span><p>策略种子 / 分类元素组合。</p></div>
+          <div class="card green"><h3>Query</h3><span class="field">keyword</span><p>调用抖音搜索。</p></div>
+          <div class="card green"><h3>SearchPage</h3><span class="field">一页搜索结果</span><p>产出视频、作者、分页。</p></div>
+          <div class="card orange"><h3>SearchCursor</h3><span class="field">next_cursor</span><p>同 query 翻下一页。</p></div>
+          <div class="card green"><h3>Video</h3><span class="field">aweme_id</span><p>可跳作者、提 tag、做画像。</p></div>
+          <div class="card green"><h3>Author</h3><span class="field">author.sec_uid</span><p>可拉作者作品。</p></div>
+          <div class="card green"><h3>AuthorWorksPage</h3><span class="field">作品页</span><p>产出作品视频。</p></div>
+          <div class="card orange"><h3>AuthorWorksCursor</h3><span class="field">next_cursor</span><p>同作者翻下一页作品。</p></div>
+          <div class="card green"><h3>Hashtag</h3><span class="field">#xxx</span><p>可生成新 query。</p></div>
+          <div class="card gray"><h3>ContentPortrait</h3><span class="field">content_id</span><p>视频人群画像。</p></div>
+          <div class="card gray"><h3>AccountPortrait</h3><span class="field">account_id</span><p>作者粉丝画像。</p></div>
+          <div class="card gray"><h3>互动表现</h3><span class="field">statistics.*</span><p>点赞、评论、分享等质量信号。</p></div>
+          <div class="card purple"><h3>判断信号汇总</h3><span class="field">EvidenceBundle</span><p>画像、互动、相关性、风险一起判断。</p></div>
+          <div class="card purple"><h3>规则包决策</h3><span class="field">Decision</span><p>输出入池、扩展、停止或观察。</p></div>
+          <div class="card red"><h3>沉淀结果</h3><span class="field">OutputAsset</span><p>候选内容、作者资产、观察线索、淘汰记录。</p></div>
+        </div>
+      </section>
+
+      <section>
+        <h2>2. 节点类型图:全部可跳路径一眼看懂</h2>
+        <p class="note">绿色线会产生新节点;橙色虚线是分页或 tag 循环;灰色虚线是判断信号,必须先汇总,再由规则包输出决策动作。</p>
+        <div class="diagram">
+          <svg viewBox="0 0 1600 930" role="img" aria-labelledby="svgTitle svgDesc">
+            <title id="svgTitle">抖音游走节点类型图</title>
+            <desc id="svgDesc">展示 PatternSeed、Query、SearchPage、SearchCursor、Video、Author、AuthorWorksPage、AuthorWorksCursor、Hashtag、Portrait、互动表现、判断信号汇总、规则包决策、沉淀结果的边关系。</desc>
+            <defs>
+              <marker id="arrow" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto" markerUnits="strokeWidth">
+                <path d="M2,2 L10,6 L2,10 Z" fill="#40516a"></path>
+              </marker>
+              <marker id="arrow-green" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto" markerUnits="strokeWidth">
+                <path d="M2,2 L10,6 L2,10 Z" fill="#0f8a5f"></path>
+              </marker>
+              <marker id="arrow-orange" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto" markerUnits="strokeWidth">
+                <path d="M2,2 L10,6 L2,10 Z" fill="#c66a12"></path>
+              </marker>
+              <marker id="arrow-gray" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto" markerUnits="strokeWidth">
+                <path d="M2,2 L10,6 L2,10 Z" fill="#748196"></path>
+              </marker>
+            </defs>
+
+            <text class="svg-title" x="34" y="42">核心图:搜索、作者作品、tag 三类循环分开表达</text>
+
+            <g transform="translate(40 90)">
+              <rect x="0" y="0" width="160" height="86" rx="12" fill="#e8f7ef" stroke="#55b98b" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">PatternSeed</text>
+              <text class="node-sub" x="18" y="60">策略种子</text>
+            </g>
+            <g transform="translate(250 90)">
+              <rect x="0" y="0" width="140" height="86" rx="12" fill="#e8f7ef" stroke="#55b98b" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">Query</text>
+              <text class="node-field" x="18" y="60">keyword</text>
+            </g>
+            <g transform="translate(450 82)">
+              <rect x="0" y="0" width="200" height="102" rx="12" fill="#e8f7ef" stroke="#55b98b" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">SearchPage</text>
+              <text class="node-field" x="18" y="60">搜索结果页</text>
+              <text class="node-sub" x="18" y="82">产出 video / author</text>
+            </g>
+            <g transform="translate(730 82)">
+              <rect x="0" y="0" width="190" height="102" rx="12" fill="#eaf1ff" stroke="#88a8e8" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">Video</text>
+              <text class="node-field" x="18" y="60">aweme_id</text>
+              <text class="node-sub" x="18" y="82">带 desc/tag/author</text>
+            </g>
+            <g transform="translate(1010 82)">
+              <rect x="0" y="0" width="190" height="102" rx="12" fill="#eaf1ff" stroke="#88a8e8" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">Author</text>
+              <text class="node-field" x="18" y="60">author.sec_uid</text>
+              <text class="node-sub" x="18" y="82">可拉作者作品</text>
+            </g>
+            <g transform="translate(1280 82)">
+              <rect x="0" y="0" width="230" height="102" rx="12" fill="#e8f7ef" stroke="#55b98b" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">AuthorWorksPage</text>
+              <text class="node-field" x="18" y="60">blogger results</text>
+              <text class="node-sub" x="18" y="82">返回作品视频</text>
+            </g>
+
+            <path class="edge green" d="M200 133 H250"></path>
+            <path class="edge green" d="M390 133 H450"></path>
+            <path class="edge green" d="M650 133 H730"></path>
+            <path class="edge green" d="M920 133 H1010"></path>
+            <path class="edge green" d="M1200 133 H1280"></path>
+
+            <g transform="translate(452 278)">
+              <rect x="0" y="0" width="196" height="90" rx="12" fill="#fff4df" stroke="#e5a04b" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">SearchCursor</text>
+              <text class="node-field" x="18" y="60">next_cursor</text>
+            </g>
+            <path class="edge orange" d="M620 184 V278"></path>
+            <path class="edge orange" d="M452 323 C310 320 310 190 450 146"></path>
+
+            <g transform="translate(782 278)">
+              <rect x="0" y="0" width="190" height="90" rx="12" fill="#fff4df" stroke="#e5a04b" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">Hashtag</text>
+              <text class="node-field" x="18" y="60">desc / text_extra</text>
+            </g>
+            <path class="edge orange" d="M825 184 V278"></path>
+            <path class="edge orange" d="M782 323 C660 508 230 500 318 176"></path>
+
+            <g transform="translate(1290 278)">
+              <rect x="0" y="0" width="220" height="90" rx="12" fill="#fff4df" stroke="#e5a04b" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">AuthorWorksCursor</text>
+              <text class="node-field" x="18" y="60">next_cursor</text>
+            </g>
+            <path class="edge orange" d="M1490 184 V278"></path>
+            <path class="edge orange" d="M1290 323 C1190 326 1180 210 1280 152"></path>
+
+            <path class="edge orange" d="M1280 184 C1210 252 980 252 920 184"></path>
+
+            <g transform="translate(130 408)">
+              <rect x="0" y="0" width="330" height="54" rx="12" fill="#fffaf0" stroke="#e5a04b" stroke-width="2"></rect>
+              <text class="node-sub" x="18" y="34">搜索分页循环:Query + next_cursor</text>
+            </g>
+            <g transform="translate(520 408)">
+              <rect x="0" y="0" width="410" height="54" rx="12" fill="#fffaf0" stroke="#e5a04b" stroke-width="2"></rect>
+              <text class="node-sub" x="18" y="34">Tag 循环:#xxx 生成新 Query</text>
+            </g>
+            <g transform="translate(1040 408)">
+              <rect x="0" y="0" width="390" height="54" rx="12" fill="#fffaf0" stroke="#e5a04b" stroke-width="2"></rect>
+              <text class="node-sub" x="18" y="34">作者作品循环:sec_uid + next_cursor</text>
+            </g>
+
+            <g transform="translate(720 520)">
+              <rect x="0" y="0" width="205" height="94" rx="12" fill="#eef2f6" stroke="#aeb9c8" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">ContentPortrait</text>
+              <text class="node-field" x="18" y="60">content_id=aweme_id</text>
+            </g>
+            <g transform="translate(1008 520)">
+              <rect x="0" y="0" width="205" height="94" rx="12" fill="#eef2f6" stroke="#aeb9c8" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">AccountPortrait</text>
+              <text class="node-field" x="18" y="60">account_id=sec_uid</text>
+            </g>
+            <g transform="translate(1294 520)">
+              <rect x="0" y="0" width="160" height="94" rx="12" fill="#eef2f6" stroke="#aeb9c8" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">互动表现</text>
+              <text class="node-field" x="18" y="60">statistics.*</text>
+            </g>
+            <g transform="translate(785 668)">
+              <rect x="0" y="0" width="255" height="105" rx="12" fill="#f1eaff" stroke="#aa8df2" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">判断信号汇总</text>
+              <text class="node-sub" x="18" y="60">画像 + 互动 + 相关性</text>
+              <text class="node-sub" x="18" y="84">风险 + trace</text>
+            </g>
+            <g transform="translate(1120 632)">
+              <rect x="0" y="0" width="360" height="198" rx="14" fill="#f1eaff" stroke="#aa8df2" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="34">规则包决策</text>
+              <text class="node-sub" x="18" y="62">视频是否入池</text>
+              <text class="node-sub" x="18" y="88">作者是否值得扩展</text>
+              <text class="node-sub" x="18" y="114">tag 是否生成新 query</text>
+              <text class="node-sub" x="18" y="140">当前路径是否停止</text>
+              <text class="node-sub" x="18" y="166">是否降预算继续观察</text>
+            </g>
+            <g transform="translate(1120 860)">
+              <rect x="0" y="0" width="360" height="56" rx="12" fill="#fff0f0" stroke="#ea9191" stroke-width="2.5"></rect>
+              <text class="node-title" x="18" y="36">沉淀结果:候选 / 作者 / 线索 / 淘汰</text>
+            </g>
+
+            <path class="edge gray" d="M825 184 C800 330 805 420 820 520"></path>
+            <text class="edge-label" x="704" y="470">画像信号</text>
+            <path class="edge gray" d="M1105 184 C1090 330 1090 420 1110 520"></path>
+            <text class="edge-label" x="1128" y="470">账号画像</text>
+            <path class="edge gray" d="M1410 184 C1400 330 1380 420 1370 520"></path>
+            <text class="edge-label" x="1398" y="470">互动信号</text>
+            <path class="edge gray" d="M822 614 C850 642 870 655 900 668"></path>
+            <path class="edge gray" d="M1110 614 C1070 650 1016 660 1002 668"></path>
+            <path class="edge gray" d="M1370 614 C1260 655 1130 680 1040 720"></path>
+            <path class="edge" d="M1040 720 H1120"></path>
+            <path class="edge" d="M1300 830 V860"></path>
+          </svg>
+        </div>
+      </section>
+
+      <section>
+        <h2>3. 边规则表:每条边从哪个字段来,是否产生新节点</h2>
+        <p class="note">这张表是最准确的定义,后续代码也可以直接按它变成 graph traversal 配置。</p>
+        <div class="table-wrap">
+          <table>
+            <thead>
+              <tr>
+                <th>From</th>
+                <th>To</th>
+                <th>字段 / 输入</th>
+                <th>接口 / 动作</th>
+                <th>是否产生新节点</th>
+                <th>是否可循环</th>
+                <th>控制策略</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr>
+                <td>PatternSeed</td>
+                <td>Query</td>
+                <td>seed terms</td>
+                <td>本地组合</td>
+                <td><span class="ok">是</span></td>
+                <td>否</td>
+                <td>query 数量上限</td>
+              </tr>
+              <tr>
+                <td>Query</td>
+                <td>SearchPage</td>
+                <td>`keyword`, `cursor=0`</td>
+                <td>`/crawler/dou_yin/keyword`</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="ok">是</span></td>
+                <td>单 query 页数上限</td>
+              </tr>
+              <tr>
+                <td>SearchPage</td>
+                <td>SearchCursor</td>
+                <td>`next_cursor`</td>
+                <td>读取分页状态</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="ok">是</span></td>
+                <td>cursor 去重、页数上限</td>
+              </tr>
+              <tr>
+                <td>SearchCursor</td>
+                <td>SearchPage</td>
+                <td>`keyword`, `next_cursor`</td>
+                <td>`/crawler/dou_yin/keyword`</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="ok">是</span></td>
+                <td>连续低质页停止</td>
+              </tr>
+              <tr>
+                <td>SearchPage</td>
+                <td>Video</td>
+                <td>`aweme_id`</td>
+                <td>结果拆分</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="ok">是</span></td>
+                <td>视频去重</td>
+              </tr>
+              <tr>
+                <td>SearchPage</td>
+                <td>Author</td>
+                <td>`author.sec_uid`</td>
+                <td>结果拆分</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="ok">是</span></td>
+                <td>作者去重</td>
+              </tr>
+              <tr>
+                <td>Video</td>
+                <td>Author</td>
+                <td>`author.sec_uid`</td>
+                <td>读取视频字段</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="ok">是</span></td>
+                <td>作者预算</td>
+              </tr>
+              <tr>
+                <td>Author</td>
+                <td>AuthorWorksPage</td>
+                <td>`account_id = sec_uid`</td>
+                <td>`/crawler/dou_yin/blogger`</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="ok">是</span></td>
+                <td>单作者作品数上限</td>
+              </tr>
+              <tr>
+                <td>AuthorWorksPage</td>
+                <td>AuthorWorksCursor</td>
+                <td>`next_cursor`</td>
+                <td>读取分页状态</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="ok">是</span></td>
+                <td>同作者 cursor 去重</td>
+              </tr>
+              <tr>
+                <td>AuthorWorksCursor</td>
+                <td>AuthorWorksPage</td>
+                <td>`account_id`, `next_cursor`</td>
+                <td>`/crawler/dou_yin/blogger`</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="ok">是</span></td>
+                <td>作品页上限</td>
+              </tr>
+              <tr>
+                <td>AuthorWorksPage</td>
+                <td>Video</td>
+                <td>`aweme_id`</td>
+                <td>作品拆分</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="ok">是</span></td>
+                <td>视频去重、作者去重</td>
+              </tr>
+              <tr>
+                <td>Video</td>
+                <td>Hashtag</td>
+                <td>`desc`, `cha_list`, `text_extra`</td>
+                <td>本地提取</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="risk">强循环</span></td>
+                <td>tag 数上限、回扣判断</td>
+              </tr>
+              <tr>
+                <td>Hashtag</td>
+                <td>Query</td>
+                <td>tag 文本</td>
+                <td>本地生成 query</td>
+                <td><span class="ok">是</span></td>
+                <td><span class="risk">强循环</span></td>
+                <td>漂移停止、query 去重</td>
+              </tr>
+              <tr>
+                <td>Video</td>
+                <td>ContentPortrait</td>
+                <td>`content_id = aweme_id`</td>
+                <td>热点宝内容画像</td>
+                <td><span class="no">否</span></td>
+                <td>否</td>
+                <td>只做评分信号</td>
+              </tr>
+              <tr>
+                <td>Author</td>
+                <td>AccountPortrait</td>
+                <td>`account_id = sec_uid`</td>
+                <td>热点宝账号画像</td>
+                <td><span class="no">否</span></td>
+                <td>否</td>
+                <td>只做作者预算信号</td>
+              </tr>
+              <tr>
+                <td>Video</td>
+                <td>互动表现</td>
+                <td>`statistics.*`</td>
+                <td>读取指标</td>
+                <td><span class="no">否</span></td>
+                <td>否</td>
+                <td>只做质量信号,不能单独决定入池</td>
+              </tr>
+              <tr>
+                <td>ContentPortrait / AccountPortrait / 互动表现 / 文本相关性 / 风险信号</td>
+                <td>判断信号汇总</td>
+                <td>画像 + 互动 + 相关性 + 风险 + trace</td>
+                <td>合并判断证据</td>
+                <td><span class="no">否</span></td>
+                <td>否</td>
+                <td>先汇总,不能单信号拍板</td>
+              </tr>
+              <tr>
+                <td>判断信号汇总</td>
+                <td>规则包决策</td>
+                <td>EvidenceBundle</td>
+                <td>硬门槛 + 软评分</td>
+                <td><span class="no">否</span></td>
+                <td>否</td>
+                <td>输出五类决策动作</td>
+              </tr>
+              <tr>
+                <td>规则包决策</td>
+                <td>沉淀结果 / 下一跳</td>
+                <td>视频入池、作者扩展、tag 扩展、路径停止、降预算观察</td>
+                <td>写入 trace 或触发下一跳</td>
+                <td><span class="no">否</span></td>
+                <td>否</td>
+                <td>保留 source_evidence 和决策原因</td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </section>
+
+      <section>
+        <h2>4. 三条典型循环路径</h2>
+        <p class="note">只要不加预算和去重,下面三条都会持续展开;其中 tag 派生 query 扩散最快。</p>
+        <div class="cycle-grid">
+          <div class="cycle-card">
+            <h3>Query 分页循环</h3>
+            <pre>Query
+-> SearchPage(cursor=0)
+-> SearchCursor(next_cursor)
+-> SearchPage(cursor=10)
+-> SearchCursor(next_cursor)
+-> ...</pre>
+          </div>
+          <div class="cycle-card">
+            <h3>作者作品循环</h3>
+            <pre>Video
+-> Author
+-> AuthorWorksPage
+-> Video
+-> Author
+-> ...</pre>
+          </div>
+          <div class="cycle-card">
+            <h3>tag 派生 Query 循环</h3>
+            <pre>Video
+-> Hashtag
+-> Query
+-> SearchPage
+-> Video
+-> Hashtag
+-> ...</pre>
+          </div>
+        </div>
+      </section>
+
+      <section>
+        <h2>5. 最小可执行 JSON 形态</h2>
+        <p class="note">这不是接口请求体,只是后续代码可读取的策略图配置雏形。</p>
+        <div class="cycle-card">
+          <pre>{
+  "nodes": [
+    "PatternSeed", "Query", "SearchPage", "SearchCursor",
+    "Video", "Author", "AuthorWorksPage", "AuthorWorksCursor",
+    "Hashtag", "ContentPortrait", "AccountPortrait",
+    "互动表现", "判断信号汇总", "规则包决策", "沉淀结果"
+  ],
+  "loop_edges": [
+    "SearchPage -> SearchCursor -> SearchPage",
+    "AuthorWorksPage -> AuthorWorksCursor -> AuthorWorksPage",
+    "Video -> Hashtag -> Query -> SearchPage -> Video",
+    "Video -> Author -> AuthorWorksPage -> Video"
+  ],
+  "non_traversal_signals": [
+    "ContentPortrait", "AccountPortrait", "互动表现"
+  ],
+  "decision_outputs": [
+    "视频是否入池",
+    "作者是否值得扩展",
+    "tag 是否生成新 query",
+    "当前路径是否停止",
+    "是否降预算继续观察"
+  ],
+  "budget": {
+    "max_depth": 6,
+    "max_pages_per_query": 3,
+    "max_works_pages_per_author": 3,
+    "max_tags_per_video": 3,
+    "max_tag_expansion_hops": 10
+  }
+}</pre>
+        </div>
+      </section>
+
+      <div class="summary">
+        <strong>最终读法:</strong>
+        `next_cursor` 要进图,但以 `SearchCursor / AuthorWorksCursor` 表达为分页状态节点。
+        真正会继续游走的是 Query、SearchPage、Video、Author、AuthorWorksPage、Hashtag;
+        ContentPortrait、AccountPortrait、互动表现不会单独决定结果,必须先进入判断信号汇总,再由规则包输出入池、扩展、停止或观察。
+      </div>
+    </div>
+  </main>
+</body>
+</html>

+ 512 - 0
product_documents/旧版和上下游理解/DemandAgent与PatternTree游走整合理解.md

@@ -0,0 +1,512 @@
+# DemandAgent 与 Pattern Tree / 权重策略整合理解
+
+生成时间:2026-06-01  
+整合来源:`demandagent.md`、`demandagent-pattern-tree-walkthrough.md`、数据工程 demand-agent workflow-v4、Pattern V2 到 DemandAgent bridge 结论。  
+阅读目标:让人真正理解 DemandAgent 的业务逻辑,而不是只记住一堆表名。
+
+## 1. 先用一句话理解 DemandAgent
+
+DemandAgent 不是写内容,也不是简单找热点。
+
+它更像一个“需求池编辑 + 数据策略员”:先看已有内容为什么有效,再看哪些方向值得继续补供给,最后把这些证据翻译成下游可以领取的需求。
+
+可以把它想成这样:
+
+```text
+已有内容表现
+-> 内容被拆出来的元素、分类、组合
+-> 权重计算:哪些东西更值得看
+-> Pattern Tree 游走:这些东西在结构上是什么,和谁稳定共现
+-> LLM 业务判断:这是不是人的真实需求,内容能不能满足
+-> 需求池:demand_content / feature_point_data
+-> ContentFindAgent 或后续生产系统继续消费
+```
+
+这个顺序很重要。DemandAgent 不是凭空想一个需求名,而是让“内容表现证据 + 权重证据 + Pattern Tree 结构证据 + LLM 业务判断”一起证明需求成立。
+
+## 2. 它解决的业务问题
+
+如果只看代码,DemandAgent 像是在调 SQL、跑 Pattern、调用 Agent、写表。但从业务上看,它一直在做一件事:
+
+> 把“已有内容为什么有效”翻译成“接下来应该寻找或生产什么需求”。
+
+比如某个二级品类现在缺内容,或者某个长文账号过去 30 天表现很好,或者某个增长合作方的视频带来了分享和回流。系统不应该只说“这个视频不错”,而要进一步问:
+
+```text
+这类内容到底满足了什么人群需求?
+它里面哪些元素反复有效?
+这些元素是不是孤立出现,还是能组成稳定的内容模式?
+这个模式能不能被下游继续寻找内容、找作者或生产新内容?
+```
+
+DemandAgent 的价值就是把这些问题变成需求池里的需求卡片。
+
+四条来源流可以这样理解:
+
+| 来源流 | 服务对象 | 业务问题 | 最终落点 |
+|---|---|---|---|
+| `piaoquan` | 有供需缺口的二级品类 | 哪些票圈内容方向现在值得补更多需求和供给? | 普通流写 `demand_content`,并尽力同步 `dwd_multi_demand_pool_di` |
+| `全局树` | 固定的全局结构对象 | 从全局内容结构里,哪些泛化特征点值得沉淀? | 写 `feature_point_data`,不进普通 `demand_content` |
+| `changwen` | 公众号 / 长文账号 | 某个账号表现好的内容能不能提炼成继续寻找或生产的需求? | 源码上作为普通流写 `demand_content` 和 `dwd_multi_demand_pool_di` |
+| `zengzhang` | 增长合作方 | 合作方视频表现能不能沉淀出可继续供给的需求? | 代码路径存在,但正式入口状态要保守看待 |
+
+一句话:不同来源流回答的是“今天为谁生成需求”,后面的权重计算、Pattern Tree 游走和 Agent 判断,回答的是“哪些东西真的值得变成需求”。
+
+## 3. 两条同等重要的主线
+
+DemandAgent 里有两条同等重要的主线。
+
+第一条是 **权重计算**。它回答:
+
+```text
+先看谁?
+哪些元素、分类、内容方向更值得被优先考虑?
+```
+
+第二条是 **Pattern Tree 游走**。它回答:
+
+```text
+它是什么?
+它在树上的位置在哪里?
+它和谁稳定共现?
+它能不能组成更具体的需求?
+```
+
+权重像是先把“值得看的候选”排出来;Pattern Tree 像是给这些候选找身份证和关系网。只有权重没有树,容易变成“高分热词”;只有树没有权重,容易在结构里乱逛,不知道先看什么。
+
+所以真正的判断链路是:
+
+```text
+权重决定优先级
+Pattern Tree 决定结构位置和组合证据
+support 证明组合不是偶然出现
+LLM 判断这是不是人的真实需求
+```
+
+`score/weight` 和 `support` 不是同一件事:
+
+| 概念 | 回答的问题 | 例子 |
+|---|---|---|
+| `score` / `weight` | 这个元素或分类值不值得优先看? | 某分类因为 ROV、曝光、互动、回流表现好,所以分数高 |
+| `support` / `absolute_support` | 这个组合是不是在多条内容里稳定出现? | 一个 itemset 被 4 条帖子支持,说明它不是孤立偶然 |
+
+这也是为什么权重计算和 Pattern Tree 游走必须并列讲,不能只讲其中一边。
+
+## 4. 权重计算:先判断谁值得看
+
+权重计算是 DemandAgent 的“注意力分配器”。
+
+上游内容很多,元素、分类、组合也很多。Agent 不可能把所有东西都当成同等重要。权重的作用,就是先告诉 Agent:哪些元素或分类更值得优先看,哪些方向更可能代表当前供需缺口或内容机会。
+
+权重通常来自内容表现和业务表现,例如:
+
+```text
+曝光
+realplay
+分享
+回流
+ROV
+分发表现
+供需缺口
+```
+
+这些表现数据会被整理成本地权重 JSON。Agent 不直接靠感觉猜“哪个元素重要”,而是通过工具读取这些权重。
+
+核心工具是:
+
+| 工具 | 用人话解释 |
+|---|---|
+| `get_weight_score_topn` | 从权重文件里取高分元素或分类,告诉 Agent “先看这些” |
+| `get_weight_score_by_name` | 给定一个名字,查它对应的权重分,判断它是不是有表现支撑 |
+
+当 Agent 最后生成需求并写入普通需求池时,`demand_content.score` 也不是 LLM 随便填的。代码会按需求名里的元素或分类去权重 JSON 里查分,再做聚合。组合需求会按名称拆分后取平均,没查到的按 0 算。
+
+这意味着:
+
+```text
+score 高,不代表这个需求一定成立;
+但它说明这个需求背后的元素或分类,在现有内容表现里值得被看见。
+```
+
+业务上可以这样理解:
+
+```text
+权重计算 = 先把“值得看的候选”排出来
+```
+
+但权重只解决“值得看”,还没有解决“它到底是什么、和谁能组成需求”。这就要进入 Pattern Tree 游走。
+
+## 5. Pattern Tree 游走:再判断它是什么、和谁有关
+
+Pattern Tree 游走不是代码里写死的一套 DFS/BFS,也不是 LLM 随便想象一棵树。
+
+更准确地说:
+
+```text
+代码提供真实地图、工具和计算结果;
+LLM 在 prompt 约束下,沿着真实节点、权重、共现、support 和 matched posts 做策略判断。
+```
+
+DemandAgent 主要读取 MySQL `open_aigc_pattern` 里的 Pattern 快照。核心表可以先记这几张:
+
+| 表 | 关键字段 | 业务含义 |
+|---|---|---|
+| `topic_pattern_execution` | `id, cluster_name, merge_leve2, platform, status, post_count, itemset_count` | 一次 Pattern 构建或挖掘执行,决定本次使用哪个快照 |
+| `topic_pattern_category` | `id, execution_id, source_type, name, path, level, parent_id, element_count` | Pattern Tree 的分类节点,也就是树上的位置 |
+| `topic_pattern_element` | `id, execution_id, post_id, point_type, element_type, name, category_id, category_path` | 帖子里的元素,能连到分类节点和具体帖子 |
+| `topic_pattern_itemset` | `id, execution_id, combination_type, item_count, support, absolute_support, dimensions, matched_post_ids` | 稳定出现的频繁组合,也就是 pattern 证据 |
+| `topic_pattern_itemset_item` | `itemset_id, point_type, dimension, category_id, category_path, element_name` | itemset 里面具体包含哪些分类、元素和维度 |
+
+游走的典型路径可以这样讲:
+
+```text
+高权重元素或分类
+-> 定位它在 Pattern Tree 上的位置
+-> 看它的祖先、子节点、兄弟节点和元素
+-> 横向看它和谁共现
+-> 查频繁 itemset,看组合是否稳定
+-> 下钻 itemset detail,看 support、matched_post_ids 和 items
+-> LLM 判断是否能生成 DemandItem
+```
+
+核心工具也可以按“人会怎么查”来理解:
+
+| 工具 | 它帮 Agent 做什么 |
+|---|---|
+| `get_category_tree` | 先看整张树的大概地图 |
+| `search_elements` | 从一个元素名回到它所在的分类节点 |
+| `get_element_category_chain` | 从元素向上回溯祖先链,知道它挂在哪条树枝上 |
+| `search_categories` | 从分类名定位 tree node |
+| `get_category_detail` | 看某个分类的 ancestors、children、siblings、elements |
+| `get_category_co_occurrences` | 看某个分类常和哪些分类一起出现在同一批帖子里 |
+| `get_element_co_occurrences` | 看某个具体元素常和哪些元素一起出现 |
+| `get_frequent_itemsets` | 查已经挖掘好的稳定组合,按 `support`、`absolute_support` 等返回 |
+| `get_itemset_detail` | 查看某个组合到底由哪些 items 构成,匹配了哪些帖子 |
+| `get_post_elements` | 下钻到帖子,确认具体内容里有哪些元素 |
+
+这里最关键的是:LLM 可以决定“接下来查哪条路”,但节点、分类链、权重、support、post_id 都必须由工具返回。它不能凭空制造 tree 节点。
+
+## 6. LLM 在中间做什么判断
+
+LLM 在 DemandAgent 里更像一个业务编辑,而不是数据库执行器。
+
+它拿到高权重元素、分类节点、共现关系、itemset 和 matched posts 之后,要判断:
+
+```text
+这个元素能不能单独成为需求?
+这个分类是不是太泛?
+这几个元素组合在一起,是不是更像一个具体需求?
+这个组合背后有没有人的渴求?
+内容能不能满足这种渴求?
+它是不是只是一组高频词,但不能指导下游找内容?
+```
+
+这也是 prompt 的核心口径:
+
+```text
+需求 = 人的渴求 x 内容的可满足性
+```
+
+比如:
+
+```text
+祈福
+```
+
+它不只是一个词。如果它有内容表现,有人群心理诉求,有可以被内容满足的表达形式,就可能成为需求。
+
+再比如:
+
+```text
+命理信仰 + 经验忠告
+```
+
+单看任何一个词都可能偏泛,但组合起来可能代表一种更具体的内容需求:用户希望通过某种信仰或经验解释,获得生活判断和心理安慰。
+
+所以 DemandAgent 不是纯规则,也不是 LLM 自由生成。它是:
+
+```text
+工具真实数据 + 权重优先级 + Pattern Tree 结构证据 + LLM 业务判断
+```
+
+## 7. 从判断到落库
+
+Agent 判断成立后,不是直接写最终表。它先通过 `create_demand_item(s)` 写本地 `DemandItem JSON`。
+
+`DemandItem JSON` 是中间产物,不是最终需求池。它通常包含:
+
+```text
+element_names
+reason
+desc
+type
+```
+
+后续代码再读取这些 JSON,统一写入业务输出。
+
+普通流的主结果是 MySQL:
+
+```text
+content-deconstruction-supply.demand_content
+```
+
+核心字段是:
+
+| 字段 | 来源 | 说明 |
+|---|---|---|
+| `merge_leve2` | 当前 `merge_level2` / `cluster_name` | 二级品类或业务对象 |
+| `name` | `DemandItem.element_names` 逗号拼接 | 需求名 |
+| `reason` | `DemandItem.reason` | 为什么这个需求成立 |
+| `suggestion` | `DemandItem.desc` | 下游如何理解这个需求 |
+| `score` | 权重 JSON 查分后聚合 | 需求优先级信号 |
+| `ext_data` | JSON 字符串 | 包含 `reason`、`desc`、`type`、`video_ids` |
+| `dt` | 北京时间当天 | 分区日期 |
+
+`ext_data` 大概长这样:
+
+```json
+{
+  "reason": "为什么这个需求成立",
+  "desc": "需求描述",
+  "type": "元素 / 分类L2 / pattern / 关系",
+  "video_ids": ["..."]
+}
+```
+
+普通流写完 MySQL 后,会尽力同步到 ODPS/Hive:
+
+```text
+loghubods.dwd_multi_demand_pool_di
+```
+
+这张表可以理解成数仓侧需求池同步。主要字段包括:
+
+```text
+strategy, demand_id, demand_name, weight, type, video_count, video_list, extend, dt
+```
+
+全局树是特殊分支。`merge_level2 == "全局树"` 时,不写普通 `demand_content`,而是写:
+
+```text
+feature_point_data
+```
+
+当前代码实际写入的核心字段是:
+
+```text
+特征点, 总分发曝光pv=5000, 质bn_rovn=0.1, dt
+```
+
+数据工程复核到 `feature_point_data` schema 还有更多字段,例如 `type`、`需求时间周期`、`特征点id`、`点维度`、`vid_list` 等。但当前写入分支不是所有字段都填。
+
+最终链路可以这样记:
+
+```text
+普通流:
+DemandItem JSON
+-> demand_content
+-> dwd_multi_demand_pool_di
+-> ContentFindAgent / 后续生产系统
+
+全局树:
+DemandItem JSON
+-> feature_point_data
+```
+
+`demand_task` 只是任务台账,不是最终需求池。`DemandItem JSON` 是落库前中间产物,也不是最终需求池。
+
+## 8. 真实 case:`地貌水文,互动设计` 是怎么来的
+
+下面用一个真实 case 把前面的话串起来。
+
+本次旧报告里选择的普通入池 case 是:
+
+```text
+demand_content.id=46331
+dt: 20260601
+merge_leve2: 罕见画面
+name: 地貌水文,互动设计
+score: 0.486613
+type: pattern
+reason: 频繁项集支持度4个帖子,共现9个帖子。地貌水文分类权重0.48,互动设计分类权重0.50。引导分享的自然景观是用户高频互动的罕见内容
+suggestion: 用户想分享地貌水文的罕见画面
+ext_data.video_ids: 33 个
+```
+
+用人话讲,这条需求不是“LLM 觉得地貌水文有意思”。它背后有三层证据。
+
+第一层是权重。`地貌水文` 和 `互动设计` 的分类权重都在 0.5 左右,最终 `score=0.486613`。这说明在当前 `罕见画面` 这个业务对象下,这两个方向不是边缘噪声,而是值得 Agent 优先看的候选。
+
+第二层是树上位置。按 `merge_leve2=罕见画面` 找到当天最新成功 Pattern execution:
+
+```text
+topic_pattern_execution.id: 1987
+cluster_name: 罕见画面
+status: success
+post_count: 49
+itemset_count: 80
+```
+
+在这个 execution 里,`地貌水文` 是实质维度的分类节点:
+
+```text
+category_id: 163405
+source_type: 实质
+path: /展示主体/自然景观/地貌水文
+level: 3
+```
+
+`互动设计` 是形式维度的分类节点:
+
+```text
+category_id: 163457
+source_type: 形式
+path: /互动设计
+level: 1
+```
+
+这就解释了这条需求的结构:它不是单纯“自然景观”,而是“自然景观里的地貌水文”加上“促使用户互动分享的表达设计”。
+
+第三层是组合稳定性。旧报告里找到一个同时包含 `地貌水文` 和 `互动设计` 的 itemset:
+
+```text
+topic_pattern_itemset.id: 377530
+execution_id: 1987
+combination_type: 关键点×灵感点_混合
+item_count: 3
+support: 0.0816327
+absolute_support: 4
+dimensions: ["形式", "实质"]
+matched_post_ids: ["69361193", "69448206", "69480613", "69481772"]
+```
+
+这说明这个组合不是单条内容里的偶然现象,而是至少有 4 条帖子支撑。`demand_content.reason` 里写的“频繁项集支持度4个帖子”,和 `absolute_support=4` 可以候选对应。
+
+所以这条需求可以被叙事成:
+
+```text
+在“罕见画面”里,自然景观下的地貌水文内容有表现价值;
+其中带有互动设计的表达,更容易让用户产生分享行为;
+这个组合有权重、有树上位置、有 itemset 和 matched_post_ids 支撑;
+LLM 判断它不是泛词,而是“用户想分享地貌水文罕见画面”的需求。
+```
+
+不过,这里有一个非常重要的边界:当前只能候选闭合,不能强外键闭合。
+
+原因是 `demand_content` 没有保存这些字段:
+
+```text
+execution_id
+category_id
+category_path
+itemset_id
+support
+absolute_support
+matched_post_ids
+```
+
+因此我们可以按同日、同品类、同名称,在 Pattern 库里反查到高度吻合的 execution、category 和 itemset,但不能证明 `demand_content.id=46331` 唯一来自 `topic_pattern_itemset.id=377530`。
+
+这句话很关键:
+
+> 当前 `demand_content` 不能精确反查唯一 itemset/category lineage;真实 case 可以候选闭合,但不是强外键闭合。
+
+## 9. 上下游血缘:它给 ContentFindAgent 什么,又缺什么
+
+ContentFindAgent 当前直接消费的是普通需求池:
+
+```text
+content-deconstruction-supply.demand_content
+```
+
+它调度时主要读取:
+
+```text
+demand_content.id
+demand_content.name
+demand_content.suggestion
+demand_content.score
+demand_content.merge_leve2
+demand_content.dt
+```
+
+然后把 `demand_content.id` 当作 `demand_id / demand_content_id` 传给内容寻找流程。最终结果写到:
+
+```text
+demand_find_task
+demand_find_content_result
+demand_find_author
+```
+
+所以对下游来说,`demand_content` 像一张“可以领取的需求卡片”。它告诉 ContentFindAgent:
+
+```text
+需求叫什么
+为什么大概成立
+建议怎么理解
+优先级分数是多少
+有没有关联 video_ids
+```
+
+但它没有告诉 ContentFindAgent:
+
+```text
+这条需求来自哪次 Pattern execution
+命中了哪些 category_id/category_path
+是哪个 itemset 支撑的
+support 和 absolute_support 是多少
+matched_post_ids 是哪些
+LLM 当时丢弃了哪些候选
+```
+
+这就解释了为什么后续 ContentFindAgent 不应该自己再猜完整 Pattern lineage。更合理的方向是让 DemandAgent 输出 evidence pack,例如:
+
+```text
+execution_id
+source_type
+element_names
+element_type
+point_type
+category_paths
+itemset_ids
+support
+absolute_support
+matched_post_ids
+video_ids
+reason / desc / type
+```
+
+这样 ContentFindAgent 才能把需求和 Pattern Tree、Decode case、召回 query、投放反馈串起来。
+
+另一个上游边界也要说清楚:数据工程的 Pattern V2 到 DemandAgent bridge 结论是谨慎的。当前能看到 Pattern V2 PG `pattern_mining_*` 和 DemandAgent MySQL `topic_pattern_*` 有一些名称、post_id、support、itemset 层面的弱重叠,但还没有强证据证明 MySQL `topic_pattern_*` 一定是从 PG `pattern_mining_*` 物化来的。要证明这件事,需要稳定的 writer/sync 代码路径,或者字段级 case bridge。
+
+因此在当前文档里,DemandAgent 的 Pattern 事实边界应写成:
+
+```text
+DemandAgent 当前直接消费的是 MySQL open_aigc_pattern.topic_pattern_*。
+Pattern V2 PG 可以作为上游背景,但不能被写成已强闭合的数据来源。
+```
+
+## 10. 一句话总结
+
+DemandAgent 的业务逻辑可以压缩成一句话:
+
+> 它先用权重计算找出值得看的元素和分类,再沿 Pattern Tree 查它们的位置、关系、共现和稳定组合,最后让 LLM 判断这些证据能不能变成“人的渴求 x 内容可满足性”的需求,并把结果写成下游可领取的需求池。
+
+再短一点:
+
+```text
+权重回答:谁值得看。
+Pattern Tree 回答:它是什么,和谁组成稳定需求。
+support 回答:这个组合是不是偶然。
+LLM 回答:这是不是人的真实需求。
+demand_content 回答:下游现在可以领取什么。
+```
+
+当前最大缺口也很明确:
+
+```text
+demand_content 保存了需求表达,但没有保存完整 Pattern lineage。
+```
+
+所以未来如果要让 ContentFindAgent 更准确地消费 DemandAgent,就不应该让 ContentFindAgent 自己猜 Pattern 树,而应该让 DemandAgent 把 evidence pack 给足。

+ 774 - 0
product_documents/旧版和上下游理解/oldContentAgentAnalysis.md

@@ -0,0 +1,774 @@
+# 旧版 ContentFindAgent 分析
+
+生成时间:2026-06-01  
+整合来源:`oldcontentfindagent.md`、`全局评价.md`。  
+阅读目标:用通俗、叙事的方式讲清旧版 ContentFindAgent 真实在做什么、为什么能跑通、为什么还不适合继续承载策略迭代。
+
+## 1. 一句话理解旧 ContentFindAgent
+
+旧版 ContentFindAgent 是一个能跑通业务闭环的 MVP。换句话说,旧系统能跑通,但还不能稳定回答“哪一路策略有效、下一轮该怎么优化”。
+
+它从 DemandAgent 产出的 `demand_content` 里领取需求,理解需求,生成搜索词,去抖音找视频和作者,再用热点宝画像筛选 50+ 用户更可能喜欢的内容,最后把结果写进 `demand_find_content_result` 和 `demand_find_author`,并创建 AIGC crawler plan。
+
+它的真实主链路可以压缩成:
+
+```text
+demand_content
+-> demand_find_task
+-> Agent 需求理解
+-> case 出发 / 特征出发 / 作者召回
+-> 抖音搜索 / 作者作品
+-> 热点宝画像
+-> LLM 最终筛选
+-> output.json
+-> demand_find_content_result / demand_find_author
+-> AIGC crawler plan
+```
+
+这条链路的问题不是“完全不能用”。相反,它已经能完成从需求到内容入库的闭环。
+
+但它还不是一个可复盘、可学习、可投放反馈驱动的内容供给策略系统。最大问题是:很多关键策略都藏在 LLM 判断和 prompt 里,候选、召回路径、淘汰原因、排序分、投放反馈没有结构化成一等数据。
+
+一句话说:
+
+```text
+旧系统能找到内容,但很难解释“为什么找到、为什么淘汰、为什么这一路有效、下一轮该怎么调策略”。
+```
+
+## 2. 旧系统的真实主链路
+
+旧系统从需求池开始。
+
+`demand_content` 是 DemandAgent 普通流写入的 MySQL 需求池。ContentFindAgent 调度器按日期和任务状态领取未处理需求,然后创建 `demand_find_task`。这个任务记录负责防重复、记录 trace、记录运行状态和 token 成本。
+
+进入 Agent 后,系统把需求信息注入 prompt:
+
+```text
+%query%      -> demand_content.name
+%suggestion% -> demand_content.suggestion
+%demand_id%  -> demand_content.id
+```
+
+Agent 接着按 skills 做几件事:
+
+1. 先理解需求,把它拆成实质、形式、上层、下层。
+2. 用下层特征查历史高赞 case。
+3. 根据 case 点和特征生成搜索词。
+4. 调抖音关键词搜索。
+5. 从历史作者池找可复用作者,再拉作者作品。
+6. 批量查热点宝内容画像和账号画像。
+7. 让 LLM 综合相关性、case 对齐、画像、热度、来源可靠性,选出最终内容。
+8. 写 `output.json`,再入库。
+
+最终内容结果写:
+
+```text
+content-deconstruction-supply.demand_find_content_result
+```
+
+作者资产写:
+
+```text
+content-deconstruction-supply.demand_find_author
+```
+
+任务状态写:
+
+```text
+content-deconstruction-supply.demand_find_task
+```
+
+从业务上看,`demand_content` 像一张“需求卡片”,ContentFindAgent 的任务是拿着这张卡片去外部平台找可投放的内容供给。
+
+## 3. 旧系统实际消费的数据源
+
+旧系统已经接了不少数据源,覆盖了“能找”和“能筛”的最低闭环。
+
+| 类型 | 库 / 表 / API | 旧系统怎么用 |
+|---|---|---|
+| 需求池 | `content-deconstruction-supply.demand_content` | 读取 `id/name/suggestion/score/merge_leve2/dt`,作为任务输入 |
+| 调度台账 | `content-deconstruction-supply.demand_find_task` | 防重复、恢复 running 任务、记录成功/失败和成本 |
+| 高赞 case 索引 | `open_aigc_pattern.topic_pattern_element` | 按下层特征的 `name` 查 `post_id` |
+| case 解构点 | `content-deconstruction-supply.workflow_decode_task_result` | 用 `post_id=channel_content_id` 查 `purpose_points/key_points/inspiration_points` |
+| 历史作者池 | `content-deconstruction-supply.demand_find_author` | 用 `content_tags LIKE query` 召回历史优质作者 |
+| 抖音关键词搜索 | `/crawler/dou_yin/keyword` | 通过 `douyin_search` 拉候选视频 |
+| 作者作品接口 | `/crawler/dou_yin/blogger` | 通过 `douyin_user_videos` 拉作者近期作品 |
+| 热点辅助 | `/crawler/jin_ri_re_bang/content_rank` | 校准搜索词和时效表达,不是最终结果源 |
+| 热点宝画像 | `video_like_portrait` / `account_fans_portrait` | 判断内容或账号是否适合 50+ 人群 |
+| 本地中间文件 | `OUTPUT_DIR/<trace_id>/output.json` | LLM 最终筛选结果,后续入库读取它 |
+
+这里有一个重要边界:
+
+```text
+ContentFindAgent 消费的是 DemandAgent 普通流的 MySQL 需求池 demand_content。
+ODPS/Hive 的 dwd_multi_demand_pool_di 和 feature_point_data 是 DemandAgent 的数仓输出/全局树输出,目前不是 ContentFindAgent 直接读取路径。
+```
+
+这意味着旧版 ContentFindAgent 并没有直接拿到 DemandAgent 生成需求时用过的完整 Pattern evidence。它拿到的是 `name/suggestion/score` 这类需求表达,再自己用工具去补 case 和内容证据。
+
+## 4. 需求理解:LLM 拆实质、形式、上层、下层
+
+旧系统的需求理解主要依赖 LLM。
+
+LLM 会按 `demand_analysis` 的口径把需求拆成四类:
+
+| 维度 | 含义 | 旧系统怎么用 |
+|---|---|---|
+| 实质特征 | 内容讲什么主题 | 用来理解需求语义 |
+| 形式特征 | 用什么表达方式、结构、节奏 | 用来筛选候选内容是否符合表达方式 |
+| 上层特征 | 比较宽泛,不适合直接搜 | 更多用于理解和归类 |
+| 下层特征 | 更具体,可直接搜或查 case | 用来调用 `get_goodcase_topic_point` |
+
+比如需求是:
+
+```json
+{
+  "demand_content.id": 42890,
+  "name": "证据文书,口播,三段式",
+  "suggestion": "用户想了解腐败案件中的证据文书和法律文件,适合中老年用户理解。",
+  "merge_leve2": "贪污腐败"
+}
+```
+
+LLM 可能拆成:
+
+```json
+{
+  "实质特征": ["证据文书"],
+  "形式特征": ["口播", "三段式"],
+  "下层特征": ["证据文书"],
+  "上层特征": []
+}
+```
+
+如果输入更完整,比如:
+
+```text
+反腐,证据文书,口播,三段式
+```
+
+更合理的拆法是:
+
+```json
+{
+  "实质特征": ["反腐", "证据文书"],
+  "形式特征": ["口播", "三段式"],
+  "上层特征": ["反腐"],
+  "下层特征": ["证据文书"]
+}
+```
+
+这里的风险在于:拆解依赖 LLM 遵守规则。它被要求不要造词,但没有代码级 schema validator 强制它一定只输出受控词。
+
+所以旧系统有一个真实风险:
+
+```text
+LLM 拆下层特征可能查不到 Pattern element。
+```
+
+只读抽样里,最近一批 `demand_content.name` 拆出的 32 个 token,能在 `topic_pattern_element.name` 精确命中的只有 13 个,命中率约 `40.6%`。这说明 `demand_content.name` 和 Pattern 库词表之间不是天然强绑定。
+
+## 5. case 出发路径:`get_goodcase_topic_point`
+
+旧系统里最重要的 case 工具是:
+
+```python
+get_goodcase_topic_point(features="证据文书", limit=8)
+```
+
+它不是外部接口,而是直接查 DB。
+
+第一步查 Pattern 库:
+
+```text
+database: open_aigc_pattern
+table: topic_pattern_element
+```
+
+SQL 逻辑是:
+
+```sql
+SELECT DISTINCT post_id
+FROM topic_pattern_element
+WHERE name = %s
+```
+
+也就是说,输入是下层特征原词,例如:
+
+```text
+name = "证据文书"
+```
+
+如果有多个下层特征,例如:
+
+```text
+["证据文书", "腐败案件"]
+```
+
+当前逻辑会分别查每个词的 `post_id` 集合,再取交集。这个策略很严格,容易漏召回:只要其中一个词不在库里,或者多个词没有共同 case,case 路径就可能变空。
+
+第二步查历史 case 的解构结果:
+
+```text
+database: content-deconstruction-supply
+table: workflow_decode_task_result
+join: channel_content_id = post_id
+```
+
+返回字段是:
+
+```text
+purpose_points      目的点
+key_points          关键点
+inspiration_points  灵感点
+```
+
+这一步拿到的不是最终候选视频,而是历史高赞 case 的内容表达证据。它帮助 LLM 生成 case 出发搜索词、筛选规则和推荐理由。
+
+一个真实小样本是 `feature=福报`:
+
+```text
+topic_pattern_element 命中 post_id:
+65115960
+64764085
+```
+
+再查 `workflow_decode_task_result`,能拿到类似:
+
+```text
+channel_content_id: 64764085
+purpose_points: 分享小年习俗禁忌与祝福
+key_points: 民俗专家式讲解, 禁忌与福报关联, 排比式祈福文案, 强引导转发机制, 家庭和谐主题
+inspiration_points: 小年禁忌与祈福
+```
+
+以及:
+
+```text
+channel_content_id: 65115960
+purpose_points: 引导社交转发
+key_points: 因果对比叙事, 拟人化神祇形象, 分段式字幕引导, 民间传说场景, 祈福式话术结尾
+inspiration_points: 观音菩萨下凡故事, 转发积攒功德福报
+```
+
+这里必须明确:
+
+```text
+get_goodcase_topic_point 不是 DemandAgent 精确 lineage。
+```
+
+原因是:
+
+- 查询 `topic_pattern_element` 时只按 `name=%s`,没有 `execution_id`。
+- 不按 `category_id/category_path/element_type` 限定。
+- 不从 `demand_content.ext_data.video_ids` 读取 DemandAgent 写入时解析出的 post_id。
+- 不知道当前 `demand_content` 对应哪次 `topic_pattern_execution`。
+
+所以它是“按特征词从历史 Pattern element 中召回高赞 case”,不是“消费 DemandAgent 生成该需求时用到的同一个 Pattern tree 节点”。
+
+## 6. 抖音搜索、作者召回和画像筛选
+
+LLM 拿到 case 点之后,会组合两类搜索词。
+
+以 `证据文书` 为例,它可能输出:
+
+```json
+{
+  "起点策略": {
+    "高赞case出发搜索词": ["腐败案件证据链", "贪腐案卷宗曝光"],
+    "特征出发搜索词": ["证据文书", "法律文件"]
+  }
+}
+```
+
+然后调用:
+
+```python
+douyin_search(keyword="腐败案件证据链", cursor="0")
+douyin_search(keyword="证据文书", cursor="0")
+```
+
+背后 API 是:
+
+```text
+POST http://crawapi.piaoquantv.com/crawler/dou_yin/keyword
+```
+
+返回给 LLM 的核心结构包括:
+
+```json
+{
+  "aweme_id": "视频ID",
+  "desc": "视频标题/描述",
+  "author": {
+    "nickname": "作者名",
+    "sec_uid": "完整作者sec_uid"
+  },
+  "statistics": {
+    "digg_count": 120000,
+    "comment_count": 3000,
+    "share_count": 8000
+  }
+}
+```
+
+除了关键词搜索,旧系统还会从历史作者池召回作者:
+
+```python
+find_authors_from_db(query="证据文书", limit=3)
+```
+
+查的是:
+
+```text
+content-deconstruction-supply.demand_find_author
+```
+
+匹配方式大致是:
+
+```sql
+WHERE content_tags LIKE "%证据文书%"
+```
+
+返回的作者会带上:
+
+```text
+author_nickname
+author_url
+author_sec_uid
+age_50_plus_ratio
+age_50_plus_tgi
+remark
+trace_id
+```
+
+这个作者库本质上是 ContentFindAgent 自己跑出来的历史优质作者沉淀。
+
+拿到 `author_sec_uid` 后,系统会继续调用:
+
+```python
+douyin_user_videos(account_id="MS4wLjABAAAA...", sort_type="最新", cursor="")
+```
+
+背后 API 是:
+
+```text
+POST http://crawapi.piaoquantv.com/crawler/dou_yin/blogger
+```
+
+这条路径的业务含义是:如果某个作者过去被证明适合 50+ 人群,就继续从他的新作品里找供给。
+
+画像筛选统一走热点宝画像:
+
+```python
+batch_fetch_portraits(candidates_json="[...]")
+```
+
+背后主要查两个接口:
+
+```text
+内容点赞画像:
+video_like_portrait
+
+账号粉丝画像:
+account_fans_portrait
+```
+
+旧系统的画像策略是:
+
+- 先查内容点赞画像。
+- 内容画像没有时,再按允许条件查账号粉丝画像兜底。
+- 重点看 `50+` 占比和 TGI。
+- `content_like` 优于 `account_fans`,`account_fans` 优于 `none`。
+
+业务上,`50+ 占比` 表示这条内容或这个账号在中老年用户中的实际分布;`TGI > 100` 表示高于平均水平。对于面向中老年用户的视频平台,这是非常关键的筛选信号。
+
+但旧系统最终内容筛选没有硬编码稳定阈值。比如候选 A:
+
+```text
+标题:贪腐案卷宗曝光,证据链如何坐实?
+来源:case 出发搜索
+点赞:120000,评论:3000,分享:8000
+50+ 占比:45.3%
+50+ TGI:168.2
+```
+
+LLM 可能判断保留,因为主题强相关、证据文书/卷宗/证据链命中、分享数据高、50+ TGI 高。
+
+候选 B:
+
+```text
+标题:学生提交证据材料模板
+点赞:30000
+50+ TGI:90
+```
+
+LLM 可能淘汰,因为虽然有“证据”字样,但语义是学生材料模板,不是腐败案件证据文书,50+ 画像也弱。
+
+这说明旧系统可以做判断,但判断过程主要在 LLM 的上下文里完成,没有变成稳定的 scorecard。
+
+## 7. LLM 在旧系统里承担了什么
+
+LLM 在旧系统里不是只负责“写推荐理由”。它实际上承担了很多隐形职责:
+
+```text
+需求拆解
+搜索词生成
+case 点和特征词组合
+候选内容相关性判断
+画像和热度权衡
+作者作品是否可用判断
+最终排序
+淘汰理由
+推荐理由
+```
+
+LLM 的输入大概包括:
+
+```json
+{
+  "需求": {
+    "query": "证据文书,口播,三段式",
+    "suggestion": "用户想了解腐败案件中的证据文书和法律文件"
+  },
+  "需求分析JSON": {
+    "实质特征": ["证据文书"],
+    "形式特征": ["口播", "三段式"],
+    "下层特征": ["证据文书"],
+    "筛选方案": {
+      "目的点对齐规则": ["必须围绕腐败案件/法律文件/证据链"],
+      "形式规则": ["口播", "三段式"]
+    }
+  },
+  "case_points": [],
+  "douyin_search_results": [],
+  "author_videos": [],
+  "portrait_results": []
+}
+```
+
+它最后输出 `output.json`,核心结构包括:
+
+```json
+{
+  "trace_id": "真实trace_id",
+  "query": "证据文书,口播,三段式",
+  "demand_id": 42890,
+  "summary": {
+    "candidate_count": 20,
+    "portrait_content_like_count": 6,
+    "portrait_account_fans_count": 2,
+    "portrait_none_count": 1,
+    "filtered_in_count": 10
+  },
+  "good_account_expansion": {
+    "enabled": true,
+    "accounts": []
+  },
+  "contents": [
+    {
+      "title": "贪腐案卷宗曝光,证据链如何坐实?",
+      "aweme_id": "7390000000000000000",
+      "strategy_type": "case出发",
+      "from_case_aweme_id": "历史case_post_id",
+      "from_case_point": "证据链/卷宗/法律文件",
+      "search_keyword": "腐败案件证据链",
+      "channel": "抖音",
+      "find_way": "搜索",
+      "rank": 1,
+      "author_nickname": "某反腐解说账号",
+      "author_sec_uid": "MS4wLjABAAAA...",
+      "statistics": {
+        "digg_count": 120000,
+        "comment_count": 3000,
+        "share_count": 8000
+      },
+      "portrait_data": {
+        "source": "content_like",
+        "age_50_plus_ratio": "45.3%",
+        "age_50_plus_tgi": "168.2"
+      },
+      "decision_basis": "内容点赞用户画像",
+      "reason": "命中证据文书/腐败案件语义,分享和评论较高,50+ TGI 高。"
+    }
+  ]
+}
+```
+
+然后 `store_results_mysql(trace_id)` 读取这个文件,把入选内容写入 `demand_find_content_result`,把优质账号写入 `demand_find_author`。
+
+核心问题是:
+
+```text
+当前最大问题是候选、淘汰、排序、归因、反馈没有结构化。
+```
+
+最终表里能看到“选中了什么”,但看不到“为什么没选别的”。对于投放表现学习来说,这个缺口非常致命。
+
+## 8. 旧系统的策略评价
+
+旧系统的结论可以分两半看。
+
+好的部分是:链路能跑通。
+
+它已经完成了:
+
+```text
+领取需求
+理解需求
+召回 case
+生成搜索词
+搜抖音
+扩作者
+查画像
+筛选内容
+入库
+沉淀作者
+创建 AIGC plan
+```
+
+但策略层偏薄。旧系统不是可学习的策略系统。
+
+主要问题有几个。
+
+第一,关键词靠 LLM 猜。  
+LLM 拆出来的下层特征未必是 Pattern 库里的受控词,导致 case 查询 miss 或跑偏。
+
+第二,case 查询太脆。  
+`topic_pattern_element.name = 特征名` 是精确查;多个特征还会取 `post_id` 交集。这对召回很严格,也很容易空。
+
+第三,ContentFindAgent 不应再游走 Pattern/Cluster 特征树。  
+上游 DemandAgent 已经做过 Pattern Tree / Clustering 侧的需求发现。ContentFindAgent 应该解决的是“为了这个需求,去哪里找表现更好的内容和作者”,而不是重新回答“这个需求从哪些 Pattern 来”。
+
+第四,候选池不可见。  
+旧系统直接从候选走到最终 10 条,缺少全量候选沉淀。
+
+第五,游走路径不可复盘。  
+我们不知道某条候选到底来自 case 搜索、特征搜索、作者扩展、热点辅助,还是历史作者池。
+
+第六,投放反馈没有反哺策略。  
+后续平台上的观看时长、完播、点赞、分享、回流没有按 `channel/query/author/candidate` 回流到策略。
+
+第七,LLM 做了太多隐形排序。  
+相关性、画像、热度、来源可靠性都由 LLM 综合判断,但没有统一 scorecard 和可比较的分数。
+
+因此,旧系统适合作为 MVP,但不适合作为下一阶段“游走策略 + 投放表现学习”的最终形态。
+
+## 9. 技术实现评价
+
+内部自研 Agent 框架不是不能用。
+
+它有一些有价值的能力:
+
+```text
+ReAct loop
+ToolRegistry
+skills
+FileSystemTraceStore
+GoalTree
+上下文压缩
+工具白名单
+trace/message 持久化
+```
+
+这些能力对调试和复盘很有帮助。
+
+但它现在更像“通用 LLM 工具调用 runtime”,不是业务 workflow 编排器。旧 ContentFindAgent 的问题也不主要是“换不换 LangGraph / AutoGen / CrewAI”。问题更本质:业务状态没有显式化。
+
+对比 LangGraph 这类框架,Graph API 强调 State、Nodes、Edges,把流程、状态和恢复点显式写出来。CrewAI 也倾向把生产应用拆成 Flow 管状态和控制,Crew/Agent 负责智能任务。
+
+所以建议不是立刻推倒自研框架,而是把 ContentFindAgent 的业务内核重做成显式流程:
+
+```text
+Demand Intake
+Recall Graph
+Candidate Pool
+Normalization
+Scorecard / Ranker
+Walker
+Feedback
+Agent Explanation
+```
+
+旧 Agent 可以继续做“聪明的操作员”,但不能继续做“隐形数据库和排序模型”。
+
+## 10. 具体例子走读:社会保障
+
+再用一个完整例子把旧系统串起来。
+
+假设需求是:
+
+```text
+demand_id: 45417
+name: 社会保障
+suggestion: 了解社会保障体系的整体框架和各项制度
+merge_leve2: 民生政策
+```
+
+第一步,LLM 拆需求:
+
+```text
+实质特征: 社会保障、养老金、医保、养老制度
+形式特征: 政策科普、生活经验、案例讲述
+上层特征: 民生政策、老年生活保障
+下层特征: 社会保障
+```
+
+第二步,用下层特征查 Pattern element:
+
+```sql
+SELECT DISTINCT post_id
+FROM topic_pattern_element
+WHERE name = '社会保障'
+```
+
+只读抽样里,这个词能查到历史 case,例如:
+
+```text
+55385641
+58401208
+64415939
+```
+
+第三步,用这些 `post_id` 查 `workflow_decode_task_result`,拿到:
+
+```text
+purpose_points:
+分享养老经验与医疗困境
+
+key_points:
+高额退休金背景、警示性叙事风格、数据驱动的逻辑支撑、
+利益损益对比、社会保障公平性讨论
+
+inspiration_points:
+万元退休金老人的医疗报销门槛困境、2400元医疗报销门槛费
+```
+
+第四步,LLM 把“特征”和“历史 case 点”组合成搜索和筛选策略:
+
+```text
+高赞 case 出发搜索词:
+退休金 医疗报销 门槛费
+养老金 医保 报销 门槛
+社保待遇 老年人 医疗困境
+
+特征出发搜索词:
+社会保障
+社保制度
+养老金 医保
+养老保障 政策
+
+筛选规则:
+1. 内容必须围绕养老金、医保、报销、养老保障等社会保障问题。
+2. 优先选择有具体金额、具体制度、老年人生活场景的视频。
+3. 排除社保代缴广告、泛职场五险一金、和老年保障无关的政策泛谈。
+4. 如果有热点宝画像,优先 50+ 占比/TGI 更高的内容或账号。
+```
+
+第五步,系统调用 `douyin_search`,比如搜:
+
+```text
+退休金 医疗报销 门槛费
+```
+
+返回候选:
+
+```json
+{
+  "aweme_id": "真实抖音视频ID",
+  "desc": "养老金医保报销门槛,很多老人不知道...",
+  "author": {
+    "nickname": "某养老政策账号",
+    "sec_uid": "..."
+  },
+  "stats": {
+    "digg_count": 12000,
+    "comment_count": 800,
+    "share_count": 1300
+  }
+}
+```
+
+第六步,进入热点宝画像:
+
+```text
+优先查内容画像
+如果内容画像没有,再用账号粉丝画像兜底
+看 50+ 占比、TGI,以及内容主题是否和需求对齐
+```
+
+第七步,LLM 决定是否入选。如果入选,会写到 `output.json`,再落库到:
+
+```text
+demand_find_content_result
+```
+
+里面通常包含:
+
+```text
+demand_content_id
+aweme_id
+video_url
+title / desc
+author_name
+author_id / sec_uid
+digg_count
+comment_count
+share_count
+portrait_source
+elderly_ratio
+elderly_tgi
+recommendation_reason
+process_trace
+```
+
+所以,“一个需求最终落库一条视频”是成立的,但更准确地说是:一个需求会产生多个候选视频,经过 LLM + case/feature + 画像筛选后,入选视频逐条落入 `demand_find_content_result`。
+
+## 11. 旧系统最大缺口和新方案启示
+
+旧系统缺的不是某一个工具,而是策略闭环。
+
+下一版应该把 ContentFindAgent 从“LLM 猜词 + 工具调用 + 最终入库”升级成“有召回通道、有候选池、有日志、有 scorecard、有反馈归因”的系统。
+
+建议方向是:
+
+| 能力 | 为什么需要 |
+|---|---|
+| DemandAgent evidence 消费 | 不再让 ContentFindAgent 自己猜 Pattern lineage,而是消费上游证据包 |
+| 多召回 channel 预算调度 | 每条召回路径都有预算、来源、预期作用和后验效果 |
+| 抖音内容图游走 | 游走对象从特征树切到视频、作者、作者作品、画像和投放表现 |
+| 候选池 | 记录所有召回候选,不只最终入选 10 条 |
+| 召回日志 | 记录为什么找到:demand、channel、seed、query、video、author |
+| scorecard | 把相关性、case/decode 命中、画像、作者质量、风险变成可比较分数 |
+| 投放反馈 | 用观看时长、完播、点赞、分享、回流倒推策略是否有效 |
+
+最核心的边界是:
+
+```text
+ContentFindAgent 的游走对象应该从“特征树”切到“内容图”。
+```
+
+也就是:
+
+```text
+需求
+-> Evidence / case / query / author seed
+-> 视频
+-> 作者
+-> 作者作品
+-> 内容画像 / 账号画像
+-> 候选池
+-> 投放表现
+-> 反向调整召回策略
+```
+
+旧系统已经证明“需求到内容入库”这件事能跑通。下一步要做的是让每一步都可见、可统计、可归因、可学习。
+
+最后用一句话收束:
+
+```text
+旧 ContentFindAgent 是一个能工作的内容寻找 MVP;
+但它把太多策略判断藏在 LLM 里。
+下一版要把候选、召回、淘汰、排序、归因、反馈全部结构化,
+让系统从能找内容,升级为能迭代策略。
+```

+ 110 - 0
product_documents/旧版和上下游理解/新旧对比文档.md

@@ -0,0 +1,110 @@
+# 新旧对比文档
+
+## 1. 背景与问题定义
+
+当前 ContentFindAgent 已经能跑通内容寻找闭环:
+
+```text
+LLM 拆词 -> case / 特征搜索 -> 抖音召回 -> 画像筛选 -> output.json -> 入库 -> AIGC plan
+```
+
+它的问题不是“不能找内容”,而是策略不可复盘、不可评估、不可学习:
+
+| 问题 | 说明 |
+|---|---|
+| 关键词靠猜 | LLM 从 `demand_content.name/suggestion` 临场拆实质、形式、上层、下层,未绑定回 DemandAgent 生成需求时的 Pattern 证据 |
+| case 查库容易 miss | 当前按 `topic_pattern_element.name = 下层词` 精确查,且多词取交集;LLM 拆出的词不是受控词时容易空召回 |
+| 候选池不可见 | 只沉淀最终入选内容,没有完整记录 80/100 条候选从哪里来、为什么淘汰 |
+| 召回路径不可复盘 | 不知道某条内容来自 case、特征、作者、热点还是历史成功策略 |
+| 投放反馈没反哺策略 | 自有平台已有观看时长、点赞、分享、回流等结果,但没有按召回通道回流到策略 |
+
+本轮新方案的核心判断:
+
+```text
+ContentFindAgent 不再只是 LLM 搜索执行器,
+而要升级成以投放表现为目标的内容供给策略系统。
+```
+
+## 2. 目标与非目标
+
+### 2.1 目标
+
+| 目标 | 说明 |
+|---|---|
+| 召回通道调度 | 关键词只是 seed,真正要管理的是 case、evidence、作者、历史赢家、特征探索、热点修饰等 channel 的预算 |
+| DemandAgent evidence 消费 | ContentFindAgent 不再猜 Pattern 下层词,优先消费 DemandAgent 给出的需求证据 |
+| case / decode 正交 | Pattern 回答“是什么”,workflow decode 回答“成功视频怎么表达”,二者组合生成搜索 seed 和筛选依据 |
+| 抖音游走 | V1 只做抖音,策略为 `seed/search_term -> video -> author -> author_works` |
+| 候选池 | 记录所有召回候选,不只记录最终 10 条 |
+| 召回日志 | 记录每条候选为什么出现、从哪个 seed/channel 来 |
+| 投放反馈 | 把观看时长、完播、分享、回流等结果回流到 channel/query/author/case |
+
+### 2.2 非目标
+
+| 非目标 | 原因 |
+|---|---|
+| 不自建完整 Pattern 树 | Pattern tree、itemset、support、category path 属于 DemandAgent/Pattern 侧 |
+| 不做多平台 | 当前已验证的主要能力是抖音搜索、作者作品、画像和热榜 |
+| 不做强化学习 | 当前缺候选池、动作日志和稳定 reward,先不具备 RL 条件 |
+| 不做无限图游走 | V1 要可控、可归因,先限制深度 |
+| 不让 LLM 隐形排序 | 基础过滤、画像归一、去重、状态、排序分需要结构化 |
+
+## 3. 当前系统与数据流
+
+### 3.1 当前主链路
+
+```text
+demand_content
+-> demand_find_task
+-> content_finder Agent
+-> demand_analysis
+-> get_goodcase_topic_point
+-> douyin_search / douyin_user_videos
+-> batch_fetch_portraits
+-> output.json
+-> demand_find_content_result / demand_find_author
+-> AIGC plan
+```
+
+### 3.2 当前已使用的数据
+
+| 数据 | 当前用途 |
+|---|---|
+| `demand_content.id/name/suggestion` | 作为需求输入,注入 prompt |
+| `demand_content.score/merge_leve2/dt` | 调度、分品类、AIGC plan 映射 |
+| `topic_pattern_element.name/post_id` | 用下层特征反查历史 case |
+| `workflow_decode_task_result` | 取目的点、关键点、灵感点 |
+| 抖音关键词搜索 | 拉候选视频 |
+| 抖音作者作品 | 对优质作者做作品扩展 |
+| 热点宝内容/账号画像 | 判断 50+ 用户适配 |
+| `demand_find_author` | 历史作者复用 |
+| `demand_find_content_result` | 最终入选内容沉淀 |
+
+### 3.3 当前没用好的数据
+
+| 数据 | 问题 |
+|---|---|
+| `demand_content.ext_data.video_ids` | DemandAgent 已给出支撑需求的视频,但当前没优先作为 case seed |
+| DemandAgent `reason/type` | 能解释需求来源,但当前主链路没充分消费 |
+| `category_path/element_type` | 可稳定绑定 Pattern 语义,但当前让 LLM 临场拆 |
+| `itemset/support/matched_post_ids` | 可作为高置信 evidence seed,但没有完整 handoff |
+| 历史投放表现 | 没进入搜索词、作者、排序策略 |
+| 内容状态 | 不知道是否已发布、已用过、不可爬、表现差 |
+| 全量候选过程 | 缺候选池、召回路径、淘汰原因 |
+
+## 4. 新旧方案对比
+
+| 模块 | 原方案 | 原方案问题 | 新方案 | 类型 | 优先级 |
+|---|---|---|---|---|---|
+| Pattern 边界 | ContentFindAgent 用下层词查 `topic_pattern_element.name` | 只是在按词查历史 case,不是消费 DemandAgent 生成需求时的真实 lineage | Pattern 树归 DemandAgent/Pattern;ContentFindAgent 消费 evidence pack | 调整 | P0 |
+| 关键词构建 | LLM 生成 case 搜索词和特征搜索词 | 搜索词来源不可控,缺预算和后验效果 | 关键词构建升级为召回通道调度,每个 seed 记录来源、预算、表现 | 调整 | P0 |
+| case 出发 | 下层词精确查 Pattern,再 join decode | 下层词可能 miss;多词交集太严;最多 3 条 case 太薄 | 优先用 `video_ids/matched_post_ids`,再 join decode 生成成功表达 seed | 调整 | P0 |
+| 特征出发 | LLM 拆上层/下层后生成特征搜索词 | 本质是关键词搜索,噪声大,缺 Pattern 置信 | 降级为小预算探索/兜底 channel | 调整 | P1 |
+| DemandAgent evidence | 新增 | 原方案只读 `name/suggestion`,没有完整消费需求证据 | 增加 evidence pack:execution、category、itemset、support、matched posts、video_ids | 新增 | P0 |
+| case / decode 正交 | decode 只辅助 LLM 生成搜索词 | Pattern 和 decode 的角色没有分清 | Pattern 回答“是什么”,decode 回答“怎么表达”,组合成搜索 seed 和筛选依据 | 增强 | P0 |
+| 召回 channel | case/特征/作者三路为主 | channel 少,且缺预算控制 | 六路:evidence、case/decode、作者、历史赢家、特征探索、热点修饰 | 增强 | P0 |
+| 抖音游走 | 搜到视频后可做作者扩展 | 扩展条件不够结构化,深度和停止条件不清楚 | V1 固定 `seed/search_term -> video -> author -> author_works`,按画像/相似作品/历史表现决定是否扩 | 增强 | P0 |
+| 候选池 | 新增 | 原方案只保存最终结果 | 记录全部候选、来源、画像、状态、分数、决策 | 新增 | P0 |
+| 召回日志 | 新增 | 无法知道候选为什么出现 | 记录 `demand -> seed -> search_term -> video -> author -> author_works` 的边 | 新增 | P0 |
+| 投放反馈 | 新增 | 观看时长、分享、回流没有反哺策略 | 按 channel/query/author/case 回流线上效果,调整预算和排序 | 新增 | P0 |
+| 规则 scorecard | LLM 综合判断 | 稳定性差,无法统一比较候选 | 用可解释分数:相关性、case/decode、画像、作者、热度、历史表现、风险惩罚 | 新增 | P0 |

+ 188 - 0
product_documents/旧版和上下游理解/旧版ContentFindAgent外部数据源接口审计.md

@@ -0,0 +1,188 @@
+# 旧版 ContentFindAgent 外部数据源接口清单
+
+用途:作为新版开发时核对旧版数据依赖、接口参数、验证状态和迁移边界的工作清单。
+
+边界:只记录开发需要的信息;不写明文密码、token、AK/SK 或完整 DSN;不执行写库、创建计划、上传 OSS 等有副作用动作。
+
+## 1. 总结论
+
+旧版 ContentFindAgent 不只使用 `demand_content` 需求池,还依赖 Pattern/Decode 库、作者库、抖音 crawapi、热点宝、今日热榜、TikHub fallback、OpenRouter、AIGC plan 和 OSS。
+
+主召回与筛选依赖:
+
+| 数据源 | 作用 | 状态 |
+|---|---|---|
+| `demand_content` | 需求输入 | 已实测 |
+| `topic_pattern_element` | 按特征词找历史 case `post_id` | 已实测 |
+| `workflow_decode_task_result` | 取 case 的目的点、关键点、灵感点 | 已实测 |
+| `demand_find_author` | 历史作者复用 | 已实测 |
+| 抖音关键词搜索 | 拉候选视频 | 已实测 |
+| 抖音账号作品 | 作者作品扩展 | 已实测 |
+| 热点宝内容画像 / 账号画像 | 判断 50+ 人群适配 | 已实测 |
+| 今日热榜 | 热点表达辅助 | 已实测 |
+| TikHub | 抖音搜索 fallback | 缺 token,未实连 |
+| OpenRouter | LLM 需求理解、策略、筛选 | 源码定位,未调用 |
+| AIGC plan | 入库后创建/绑定计划 | 有副作用,未实连 |
+| OSS | 上传 log HTML | 有副作用,未实连 |
+
+Apifox 对比结论:
+
+| 项 | 结论 |
+|---|---|
+| 用户给的 Apifox 页 | `https://s.apifox.cn/a2261aa2-b040-4d26-b615-af23d3a9cf15/324871124e0` |
+| 是否同一接口 | 是,与旧代码 `douyin_search` 同为 `POST http://crawapi.piaoquantv.com/crawler/dou_yin/keyword` |
+| 参数差异 | Apifox 示例 `content_type=综合` 实测失败;旧代码 `content_type=视频` 实测成功 |
+| 开发建议 | 新版以实测可用参数为准,保留 Apifox 作为接口说明参考 |
+
+## 2. 旧版主链路
+
+```text
+demand_content / demand_find_task / demand_task_oprate
+-> get_goodcase_topic_point
+-> topic_pattern_element
+-> workflow_decode_task_result
+-> hot_topic_search
+-> douyin_search
+-> douyin_search_tikhub fallback
+-> find_authors_from_db
+-> douyin_user_videos
+-> batch_fetch_portraits
+-> output.json
+-> demand_find_content_result / demand_find_author
+-> AIGC plan / OSS log html
+```
+
+主链路分层:
+
+| 层级 | 数据/接口 | 开发定位 |
+|---|---|---|
+| 需求层 | `demand_content`, `demand_find_task`, `demand_task_oprate` | 领取需求、去重、调度、状态 |
+| case 层 | `topic_pattern_element`, `workflow_decode_task_result` | 从历史 case 生成搜索 seed 和筛选依据 |
+| 召回层 | 抖音关键词搜索、今日热榜、TikHub fallback | 拉候选视频和热点表达 |
+| 作者层 | `demand_find_author`, 抖音账号作品 | 复用作者资产并扩展作品 |
+| 画像层 | 热点宝内容画像、账号画像 | 判断 50+ 适配 |
+| 结果层 | `output.json`, `demand_find_content_result`, `demand_find_author` | 沉淀最终内容和作者 |
+| 后处理层 | AIGC plan, OSS | 后置交接和复盘展示 |
+
+## 3. DB 数据源清单
+
+| 数据源 | 旧版用途 | 关键字段 | 代码证据 | Env key | 只读验证 |
+|---|---|---|---|---|---|
+| `content-deconstruction-supply.demand_content` | 上游需求池 | `id`, `name`, `suggestion`, `score`, `merge_leve2`, `dt` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/db/schedule.py` | `CONTENT_SUPPLY_DB_*` | `27838` 行 |
+| `content-deconstruction-supply.demand_find_task` | 任务台账、防重复、状态、成本 | `trace_id`, `demand_content_id`, `status`, `token_coast`, `created_at` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/db/schedule.py` | `CONTENT_SUPPLY_DB_*` | `2281` 行 |
+| `content-deconstruction-supply.demand_task_oprate` | 调度开关、日成本限制 | `is_open`, `day_limit_coast`, `update_time` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/db/schedule.py` | `CONTENT_SUPPLY_DB_*` | 源码定位 |
+| `open_aigc_pattern.topic_pattern_element` | 用特征词找历史 case | `name`, `post_id`, `execution_id`, `element_type` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:91` | `PATTERN_GLOBAL_DB_*` | `565571` 行 |
+| `content-deconstruction-supply.workflow_decode_task_result` | 用 case `post_id` 找解构点 | `channel_content_id`, `purpose_points`, `key_points`, `inspiration_points` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:144` | `CONTENT_SUPPLY_DB_*` | `21070` 行 |
+| `content-deconstruction-supply.demand_find_author` | 历史作者池 | `author_name`, `author_link`, `author_sec_uid`, `elderly_ratio`, `elderly_tgi`, `content_tags`, `trace_id` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/find_authors_from_db.py:34` | `CONTENT_SUPPLY_DB_*` | `2830` 行 |
+| `content-deconstruction-supply.demand_find_content_result` | 最终内容结果表 | `trace_id`, `demand_content_id`, `content_id`, `crawler_plan_id`, `produce_plan_id`, `publish_plan_id`, `web_html_url` | 旧版入库链路 `store_results_mysql` / AIGC 后处理 | `CONTENT_SUPPLY_DB_*` | `19074` 行 |
+
+DB 注意事项:
+
+| 项 | 说明 |
+|---|---|
+| 旧代码业务库连接 | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/db/connection.py:8` 读取 `DB_*` |
+| 旧代码 Pattern 库连接 | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/db/open_aigc_pattern_connection.py:11` 读取 `OPEN_AIGC_PATTERN_DB_*` |
+| 硬编码风险 | `connection.py:13-17`、`open_aigc_pattern_connection.py:17-21` 存在 fallback 凭证风险;只标位置,不搬运明文 |
+| lineage 边界 | `topic_pattern_element.name -> post_id -> workflow_decode_task_result.channel_content_id` 是弱 lineage,不是当前需求 execution-scoped 精确血缘 |
+
+只读样本:
+
+| 样本 | 结果 |
+|---|---|
+| 最新 `demand_content` | `id=47242/name=腐败案件,宣判/dt=20260602`;`id=47241/name=基层腐败,警示/dt=20260602` |
+| 作者库 `content_tags LIKE '%福利%'` | 命中 2 条,返回作者名、50+ 占比、TGI、内容标签 |
+| `topic_pattern_element.name='福报'` join decode | 命中 2 条,可返回 `purpose_points/key_points/inspiration_points` |
+
+## 4. HTTP API 清单
+
+| 接口 | 旧版工具 | Method / Endpoint | 请求字段 | 返回字段 | 验证状态 |
+|---|---|---|---|---|---|
+| 抖音关键词搜索 | `douyin_search` | `POST http://crawapi.piaoquantv.com/crawler/dou_yin/keyword` | `keyword`, `content_type`, `sort_type`, `publish_time`, `cursor`, `account_id` | `aweme_id`, `desc`, `author.nickname`, `author.sec_uid`, `statistics.*`, `has_more`, `next_cursor` | HTTP `200`,业务 `code=0`,返回 `9-10` 条 |
+| TikHub 搜索 fallback | `douyin_search_tikhub` | `POST https://api.tikhub.io/api/v1/douyin/search/fetch_video_search_v2` | `keyword`, `cursor`, `sort_type`, `publish_time`, `filter_duration`, `content_type`, `search_id`, `backtrace` | `business_data[].data.aweme_info`, `next_page`, `backtrace` | `TIKHUB_API_KEY` 缺失,未实连 |
+| 抖音账号作品 | `douyin_user_videos` | `POST http://crawapi.piaoquantv.com/crawler/dou_yin/blogger` | `account_id`, `sort_type`, `cursor` | `aweme_id`, `desc`, `author.*`, `statistics.*`, `has_more`, `next_cursor` | HTTP `200`,业务 `code=0`,返回 `21` 条 |
+| 热点宝内容画像 | `get_content_fans_portrait` / `batch_fetch_portraits` | `POST http://crawapi.piaoquantv.com/crawler/dou_yin/re_dian_bao/video_like_portrait` | `content_id`, `need_age`, `need_gender`, `need_province` | 年龄、性别、省份画像;`percentage`, `preference` | HTTP `200`,业务 `code=0` |
+| 热点宝账号画像 | `get_account_fans_portrait` / `batch_fetch_portraits` | `POST http://crawapi.piaoquantv.com/crawler/dou_yin/re_dian_bao/account_fans_portrait` | `account_id`, `need_age`, `need_gender`, `need_province` | 年龄、性别、省份画像;`percentage`, `preference` | HTTP `200`,业务 `code=0`,返回年龄桶 |
+| 今日热榜 | `hot_topic_search` | `POST http://crawapi.piaoquantv.com/crawler/jin_ri_re_bang/content_rank` | `sort_type`, `cursor` | `source`, `jump_url`, `type`, `rankList[].title`, `rankList[].heat`, `has_more`, `next_cursor` | HTTP `200`,业务 `code=0`,返回 `12` 个来源块 |
+| OpenRouter LLM | Agent LLM | 外部 LLM API | prompt / messages | 需求理解、搜索策略、筛选、输出 | 成本接口,未调用 |
+| AIGC plan | `create_crawler_plan_by_douyin_content_id` | `POST https://aigc-api.aiddit.com/aigc/crawler/plan/save`; `POST /aigc/produce/plan/detail`; `POST /aigc/produce/plan/save` | `aweme_id`, `merge_leve2`, plan ids | `crawler_plan_id`, `produce_plan_id`, `publish_plan_id` | 有写侧副作用,未实连 |
+| OSS log HTML | `upload_html_to_oss` / `render_log_html` | Aliyun OSS SDK | HTML log、对象 key | `web_html_url` | 有上传副作用,未实连 |
+
+HTTP 代码证据:
+
+| 工具 | 代码位置 |
+|---|---|
+| `douyin_search` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/douyin_search.py:27` |
+| `douyin_search_tikhub` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/douyin_search_tikhub.py:38` |
+| `douyin_user_videos` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/douyin_user_videos.py:23` |
+| `hotspot_profile` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/hotspot_profile.py:27` |
+| `hot_topic_search` | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/hot_topic_search.py:39` |
+| OpenRouter | `/Users/samlee/Documents/works/ContentFindAgent/agent/llm/openrouter.py:623` |
+| AIGC plan | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/aigc_platform_api.py:67` |
+| OSS | `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/utils/oss_upload.py:46` |
+
+## 5. 抖音关键词搜索与 Apifox 对比
+
+| 对比项 | 代码口径 | Apifox 口径 | 实测结论 |
+|---|---|---|---|
+| Endpoint | `POST /crawler/dou_yin/keyword` | `POST /crawler/dou_yin/keyword` | 同一个接口 |
+| `keyword` | 必传 | 必传 | 一致 |
+| `content_type` | `视频` | `综合/视频/图文` | `视频` 成功;`综合` 失败 |
+| `sort_type` | `综合排序` | `默认排序/最新发布/点赞最多` | `综合排序` 成功;`默认排序` 在 `content_type=视频` 时成功 |
+| `publish_time` | `不限` | `不限/近1日/近7日/近1月` | `不限` 成功 |
+| `duration` | 旧代码不传 | Apifox 示例传 `不限` | 在有效 `content_type=视频` 时可接受或被忽略 |
+| `cursor` | `0` | 示例为空字符串 | 两种口径在部分组合下都可用 |
+| `account_id` | `771431222` | 示例同类字段 | 一致 |
+
+实测 payload:
+
+| Payload | 结果 |
+|---|---|
+| `content_type=视频`, `sort_type=综合排序`, `cursor=0` | HTTP `200`,业务 `code=0`,返回 `9-10` 条 |
+| `content_type=综合`, `sort_type=默认排序`, `duration=不限`, `cursor=""` | HTTP `200`,业务 `code=10001`,参数校验失败 |
+| `content_type=视频`, `sort_type=默认排序`, `cursor=""` | HTTP `200`,业务 `code=0`,返回 `9` 条 |
+| `content_type=视频`, `sort_type=综合排序`, `duration=不限` | HTTP `200`,业务 `code=0`,返回 `9` 条 |
+
+## 6. Env Key 清单
+
+| 用途 | 旧代码 key | 数据工程/开发 key | 说明 |
+|---|---|---|---|
+| 业务库 | `DB_*` | `CONTENT_SUPPLY_DB_*` | 用于 `demand_content`、`demand_find_task`、`workflow_decode_task_result`、`demand_find_author` 等 |
+| Pattern 库 | `OPEN_AIGC_PATTERN_DB_*` | `PATTERN_GLOBAL_DB_*` | 用于 `topic_pattern_element` |
+| crawapi | 旧代码硬编码 base URL | `CONTENTFIND_API_CRAWAPI_*` | 旧代码未统一抽 env |
+| TikHub | `TIKHUB_API_KEY` | `TIKHUB_API_KEY` | 未配置时不能实连 fallback |
+| OpenRouter | `OPEN_ROUTER_API_KEY` | `OPEN_ROUTER_API_KEY` | LLM 成本接口 |
+| AIGC | 旧代码存在 token 风险 | `CONTENTFIND_API_AIGC_*` | 不使用硬编码 token 做测试 |
+| OSS | `ALIYUN_OSS_*` | `OSS_ACCESS_KEY_ID/SECRET` 或 `ALIYUN_OSS_*` | 上传有副作用 |
+
+## 7. 数据工程验证对照
+
+| 数据工程验证对象 | 状态 | 证据位置 |
+|---|---|---|
+| `contentfind_business_mysql` | `pass` | `/Users/samlee/Documents/works/数据工程/domains/contentfindagent/runs/2026-05-31-workflow-v2-human-context-zh/verification/db-readonly-results.json:88` |
+| `contentfind_pattern_mysql` | `pass` | `/Users/samlee/Documents/works/数据工程/domains/contentfindagent/runs/2026-05-31-workflow-v2-human-context-zh/verification/db-readonly-results.json:1137` |
+| `contentfind_crawapi_douyin_keyword` | `pass` | `/Users/samlee/Documents/works/数据工程/domains/contentfindagent/runs/2026-05-31-workflow-v2-human-context-zh/verification/db-readonly-results.json:1370` |
+| `contentfind_hot_topic_api` | `pass` | `/Users/samlee/Documents/works/数据工程/domains/contentfindagent/runs/2026-05-31-workflow-v2-human-context-zh/verification/db-readonly-results.json:1397` |
+| `contentfind_hotspot_content_profile_api` | `pass` | `/Users/samlee/Documents/works/数据工程/domains/contentfindagent/runs/2026-05-31-workflow-v2-human-context-zh/verification/db-readonly-results.json:1423` |
+| `contentfind_douyin_account_videos_api` | `pass` | `/Users/samlee/Documents/works/数据工程/domains/contentfindagent/runs/2026-05-31-workflow-v2-human-context-zh/verification/db-readonly-results.json:1448` |
+| `contentfind_tikhub_api` | `blocked` | `/Users/samlee/Documents/works/数据工程/domains/contentfindagent/runs/2026-05-31-workflow-v2-human-context-zh/verification/db-readonly-results.json:1474` |
+| `contentfind_aigc_produce_plan_detail_api` | `blocked` | `/Users/samlee/Documents/works/数据工程/domains/contentfindagent/runs/2026-05-31-workflow-v2-human-context-zh/verification/db-readonly-results.json:1483` |
+| `contentfind_oss` | `blocked` | `/Users/samlee/Documents/works/数据工程/domains/contentfindagent/runs/2026-05-31-workflow-v2-human-context-zh/verification/db-readonly-results.json:1490` |
+
+## 8. Subagent 交叉验证结论
+
+| 角色 | 验证范围 | 结论 |
+|---|---|---|
+| Subagent A | 旧版 `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder` | 主链路、DB/API/tool、输出表、硬编码风险位置与主 agent 结论一致 |
+| Subagent B | `/Users/samlee/Documents/works/数据工程/domains/contentfindagent`、env、verification、Apifox | 数据工程已有验证、blocked 项、Apifox endpoint 对照与主 agent 结论一致 |
+
+## 9. 开发迁移注意
+
+| 注意项 | 处理建议 |
+|---|---|
+| 候选池缺失 | 新版要沉淀所有候选、召回来源、淘汰原因和 scorecard,不只存最终结果 |
+| case 弱 lineage | 不把 `topic_pattern_element.name -> post_id -> decode` 写成 DemandAgent 精确血缘 |
+| Apifox 参数漂移 | 抖音关键词搜索以实测 payload 为准,接口文档只作参考 |
+| TikHub fallback | 需要 token 后再补只读实测 |
+| AIGC / OSS | 默认按后处理能力接入,开发测试不能误触发写侧动作 |
+| 硬编码凭证 | 新版必须迁移到 env/secret 管理,旧代码只保留风险行号 |
+| OpenRouter | 属于成本接口,开发时需要显式开关和预算保护 |

+ 509 - 0
product_documents/规则包/douyin_rule_packs.v1.json

@@ -0,0 +1,509 @@
+{
+  "schema_version": "rule_pack.v1",
+  "package_id": "douyin_rule_packs_v1",
+  "package_name": "抖音规则包 V1",
+  "description": "核心对象说明:EvidenceBundle 是判断证据包,RuleDecision 是规则判断结果,aweme_id 是抖音视频 ID,sec_uid 是抖音作者 ID,trace_id 是本次运行 ID,source_evidence 是来源证据,effect_status 是搜索词效果状态,rule_decisions.jsonl 是规则判断结果文件,source_edges.jsonl 是来源记录文件;POOL 是入池,CANDIDATE 是候选,PENDING 是待观察,REJECT 是淘汰。",
+  "updated_at": "2026-06-04",
+  "strategy_binding": {
+    "strategy_id": "douyin_available_walk_strategy_v1",
+    "strategy_version": "V1",
+    "platform": "douyin",
+    "source": "PatternSeed",
+    "decision_input_node": "EvidenceBundle",
+    "decision_node": "RuleDecision",
+    "input_edge_id": "e18",
+    "output_edge_id": "e19"
+  },
+  "decision_output_catalog": [
+    {
+      "key": "video_pool_decision",
+      "label": "视频是否入池",
+      "allowed_actions": ["POOL", "CANDIDATE", "PENDING", "REJECT"]
+    },
+    {
+      "key": "author_expand_decision",
+      "label": "作者是否值得扩展",
+      "allowed_actions": ["EXPAND_AUTHOR", "EXPAND_AUTHOR_SMALL_BUDGET", "PENDING_AUTHOR", "NO_EXPAND"]
+    },
+    {
+      "key": "tag_query_decision",
+      "label": "tag 是否生成新 query",
+      "allowed_actions": ["GENERATE_QUERY", "NO_GENERATE_QUERY", "STOP_PATH", "LOW_BUDGET_PENDING"],
+      "v1_default_enabled": true
+    },
+    {
+      "key": "path_stop_decision",
+      "label": "当前路径是否停止",
+      "allowed_actions": ["CONTINUE", "STOP_PATH"]
+    },
+    {
+      "key": "budget_observe_decision",
+      "label": "是否降预算待观察",
+      "allowed_actions": ["NORMAL_BUDGET", "LOW_BUDGET_PENDING"]
+    },
+    {
+      "key": "author_asset_decision",
+      "label": "作者是否沉淀",
+      "allowed_actions": ["STORE_AUTHOR", "PENDING_AUTHOR", "REJECT_AUTHOR"]
+    }
+  ],
+  "shared_contracts": {
+    "input_type": "EvidenceBundle",
+    "required_input_fields": [
+      "portrait_signal",
+      "account_signal",
+      "interaction_signal",
+      "relevance_signal",
+      "risk_signal",
+      "trace",
+      "source_evidence"
+    ],
+    "required_output_fields": [
+      "matched_hard_gates",
+      "scorecard",
+      "final_action",
+      "reason_code",
+      "trace_id",
+      "source_evidence",
+      "input_snapshot_ref",
+      "evidence_refs",
+      "replay_fields"
+    ],
+    "nullable_output_fields": [
+      "score",
+      "age_50_plus_level",
+      "effect_status"
+    ],
+    "source_evidence_required": true,
+    "single_signal_decision_allowed": false,
+    "enums": {
+      "effect_status": ["success", "weak_effective", "pending", "blocked", "failed"],
+      "origin_source": ["pattern_itemset"],
+      "immediate_source": ["pattern_query", "query_direct", "query_from_tag", "author_work"],
+      "age_50_plus_level": ["strong", "medium", "weak", "missing"]
+    }
+  },
+  "global_budgets": {
+    "max_tag_expansion_hops": 10,
+    "max_queries_per_pattern": 5,
+    "max_pages_per_query": 3,
+    "max_expand_authors": 10,
+    "max_author_work_pages": 3,
+    "max_author_works_per_author": 20,
+    "max_depth": 3
+  },
+  "rule_packs": [
+    {
+      "pack_id": "douyin_video_candidate_rule_pack_v1",
+      "pack_name": "视频候选判断规则包",
+      "version": "1.0.0",
+      "enabled": true,
+      "applies_to": {
+        "target_entity": "Video",
+        "input_node": "EvidenceBundle",
+        "decision_node": "RuleDecision",
+        "edge_ids": ["e18", "e19"],
+        "decision_output_keys": ["video_pool_decision", "author_expand_decision", "path_stop_decision", "budget_observe_decision"]
+      },
+      "input_contract": {
+        "required_fields": [
+          "entity.aweme_id",
+          "source_evidence",
+          "trace.trace_id",
+          "relevance_signal.pattern_recall",
+          "relevance_signal.category_or_element_binding",
+          "risk_signal.risk_level",
+          "interaction_signal.statistics",
+          "portrait_signal",
+          "portrait_signal.age_50_plus_level"
+        ],
+        "missing_policy": "fail_hard_gate"
+      },
+      "hard_gates": [
+        {
+          "gate_id": "missing_aweme_id",
+          "label": "无 aweme_id",
+          "when": {"field": "entity.aweme_id", "op": "is_empty"},
+          "action": "REJECT",
+          "reason_code": "missing_aweme_id",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "missing_source_evidence",
+          "label": "无 source_evidence",
+          "when": {"field": "source_evidence", "op": "is_empty"},
+          "action": "REJECT",
+          "reason_code": "missing_source_evidence",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "pattern_recall_required",
+          "label": "视频必须回扣 Pattern",
+          "when": {"field": "relevance_signal.pattern_recall", "op": "not_in", "value": ["strong", "matched"]},
+          "action": "REJECT",
+          "reason_code": "video_pattern_recall_required",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "category_or_element_binding_required",
+          "label": "视频必须绑定分类或元素",
+          "when": {"field": "relevance_signal.category_or_element_binding", "op": "not_in", "value": ["direct_match", "tree_walk_match", "matched"]},
+          "action": "REJECT",
+          "reason_code": "category_or_element_binding_required",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "obvious_drift",
+          "label": "明显跑偏",
+          "when": {"field": "relevance_signal.level", "op": "in", "value": ["drift", "unrelated"]},
+          "action": "REJECT",
+          "reason_code": "obvious_drift",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "high_risk_content",
+          "label": "高风险内容",
+          "when": {"field": "risk_signal.risk_level", "op": "in", "value": ["high", "blocked"]},
+          "action": "REJECT",
+          "reason_code": "high_risk_content",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "not_safe_for_50_plus",
+          "label": "明显不适合 50+ 安全消费",
+          "when": {"field": "risk_signal.age_50_plus_safety", "op": "in", "value": ["unsafe", "misleading"]},
+          "action": "REJECT",
+          "reason_code": "not_safe_for_50_plus",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "missing_content_portrait",
+          "label": "无内容画像",
+          "when": {"field": "portrait_signal", "op": "is_empty"},
+          "action": "REJECT",
+          "reason_code": "missing_content_portrait",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "age_50_plus_weak",
+          "label": "50+ 内容画像弱或缺失",
+          "when": {"field": "portrait_signal.age_50_plus_level", "op": "in", "value": ["weak", "missing"]},
+          "action": "REJECT",
+          "reason_code": "age_50_plus_weak",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "missing_author_sec_uid",
+          "label": "无作者 sec_uid",
+          "when": {"field": "entity.author.sec_uid", "op": "is_empty"},
+          "action": "NO_EXPAND",
+          "reason_code": "missing_author_sec_uid",
+          "severity": "partial",
+          "stop_scoring": false,
+          "affects_outputs": ["author_expand_decision"]
+        }
+      ],
+      "scorecard": {
+        "total_score": 100,
+        "dimensions": [
+          {
+            "key": "douyin_tone",
+            "label": "抖音调性",
+            "max_score": 25,
+            "evidence_paths": ["relevance_signal.platform_fit"]
+          },
+          {
+            "key": "interaction_performance",
+            "label": "互动表现",
+            "max_score": 15,
+            "evidence_paths": ["interaction_signal.statistics"]
+          },
+          {
+            "key": "age_50_plus_portrait",
+            "label": "50+ 内容画像",
+            "max_score": 30,
+            "evidence_paths": ["portrait_signal.age_50_plus_level", "portrait_signal.age_distribution", "portrait_signal.tgi"]
+          },
+          {
+            "key": "adaptability",
+            "label": "可改编性",
+            "max_score": 10,
+            "evidence_paths": ["relevance_signal.adaptability"]
+          },
+          {
+            "key": "freshness_available",
+            "label": "新鲜度 / 可用状态",
+            "max_score": 20,
+            "evidence_paths": ["risk_signal.availability", "entity.create_time"]
+          }
+        ]
+      },
+      "thresholds": [
+        {"min_score": 70, "action": "POOL", "reason_code": "video_score_pool"},
+        {"min_score": 60, "max_score": 69, "action": "CANDIDATE", "reason_code": "video_score_candidate"},
+        {"min_score": 50, "max_score": 59, "action": "PENDING", "reason_code": "video_score_pending"},
+        {"max_score": 49, "action": "REJECT", "reason_code": "video_score_reject"}
+      ]
+    },
+    {
+      "pack_id": "douyin_author_expand_rule_pack_v1",
+      "pack_name": "作者扩展判断规则包",
+      "version": "1.0.0",
+      "enabled": true,
+      "applies_to": {
+        "target_entity": "Author",
+        "input_node": "EvidenceBundle",
+        "decision_node": "RuleDecision",
+        "edge_ids": ["e07", "e08", "e18", "e19"],
+        "decision_output_keys": ["author_expand_decision", "author_asset_decision", "path_stop_decision", "budget_observe_decision"]
+      },
+      "input_contract": {
+        "required_fields": ["entity.author.sec_uid", "account_signal", "account_signal.age_50_plus_level", "risk_signal", "trace.trace_id", "source_evidence"],
+        "missing_policy": "fail_hard_gate"
+      },
+      "hard_gates": [
+        {
+          "gate_id": "missing_sec_uid",
+          "label": "无 sec_uid",
+          "when": {"field": "entity.author.sec_uid", "op": "is_empty"},
+          "action": "NO_EXPAND",
+          "reason_code": "missing_sec_uid",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "works_unavailable",
+          "label": "作品不可爬",
+          "when": {"field": "risk_signal.author_works_available", "op": "eq", "value": false},
+          "action": "NO_EXPAND",
+          "reason_code": "works_unavailable",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "author_high_risk",
+          "label": "账号风险明显",
+          "when": {"field": "risk_signal.author_risk_level", "op": "in", "value": ["high", "blocked"]},
+          "action": "NO_EXPAND",
+          "reason_code": "author_high_risk",
+          "severity": "fatal",
+          "stop_scoring": true
+        },
+        {
+          "gate_id": "author_homepage_drift",
+          "label": "主页明显跑偏",
+          "when": {"field": "relevance_signal.author_homepage_level", "op": "in", "value": ["drift", "unrelated"]},
+          "action": "NO_EXPAND",
+          "reason_code": "author_homepage_drift",
+          "severity": "fatal",
+          "stop_scoring": true
+        }
+      ],
+      "scorecard": {
+        "total_score": 100,
+        "dimensions": [
+          {"key": "account_50_plus_portrait", "label": "账号 50+ 画像", "max_score": 35, "evidence_paths": ["account_signal.age_50_plus_level", "account_signal.age_distribution", "account_signal.tgi"]},
+          {"key": "vertical_stability", "label": "垂类稳定性", "max_score": 20, "evidence_paths": ["relevance_signal.vertical_stability"]},
+          {"key": "similar_work_count", "label": "相似作品数量", "max_score": 20, "evidence_paths": ["relevance_signal.similar_work_count"]},
+          {"key": "hit_stability", "label": "爆款稳定性", "max_score": 10, "evidence_paths": ["interaction_signal.author_hit_stability"]},
+          {"key": "update_frequency", "label": "更新频率", "max_score": 10, "evidence_paths": ["interaction_signal.update_frequency"]},
+          {"key": "crawl_stability", "label": "可爬稳定性", "max_score": 5, "evidence_paths": ["risk_signal.crawl_stability"]}
+        ]
+      },
+      "thresholds": [
+        {"min_score": 80, "action": "EXPAND_AUTHOR", "reason_code": "author_score_expand"},
+        {"min_score": 60, "max_score": 79, "action": "EXPAND_AUTHOR_SMALL_BUDGET", "reason_code": "author_score_small_budget"},
+        {"min_score": 45, "max_score": 59, "action": "PENDING_AUTHOR", "reason_code": "author_score_pending"},
+        {"max_score": 44, "action": "NO_EXPAND", "reason_code": "author_score_no_expand"}
+      ],
+      "author_asset_rule": {
+        "enabled": true,
+        "decision_output_key": "author_asset_decision",
+        "required_conditions": [
+          {"field": "account_signal.works_sample_count", "op": "gte", "value": 9, "reason_code": "author_sample_count_required"},
+          {"field": "relevance_signal.pattern_recall_work_count", "op": "gte", "value": 3, "reason_code": "author_pattern_work_count_required"},
+          {"field": "relevance_signal.pattern_recall_work_ratio", "op": "gte", "value": 0.3334, "reason_code": "author_pattern_ratio_required"},
+          {"field": "account_signal.age_50_plus_level", "op": "in", "value": ["strong", "medium"], "reason_code": "author_50_plus_required"},
+          {"field": "risk_signal.author_risk_level", "op": "not_in", "value": ["high", "blocked"], "reason_code": "author_low_risk_required"}
+        ],
+        "actions": [
+          {"when": "all_required_conditions_pass", "action": "STORE_AUTHOR"},
+          {"when": "expandable_but_deposit_conditions_not_met", "action": "PENDING_AUTHOR"},
+          {"when": "hard_gate_failed", "action": "REJECT_AUTHOR"}
+        ]
+      }
+    },
+    {
+      "pack_id": "douyin_tag_expansion_rule_pack_v1",
+      "pack_name": "tag 扩散规则包",
+      "version": "1.0.0",
+      "enabled": true,
+      "applies_to": {
+        "target_entity": "Hashtag",
+        "input_node": "EvidenceBundle",
+        "decision_node": "RuleDecision",
+        "edge_ids": ["e12", "e13", "e18", "e19"],
+        "decision_output_keys": ["tag_query_decision", "path_stop_decision", "budget_observe_decision"]
+      },
+      "budget": {
+        "max_tag_expansion_hops": 10,
+        "hop_definition": "Video -> Hashtag -> Query"
+      },
+      "input_contract": {
+        "required_fields": ["entity.tag_text", "relevance_signal.tag_recall_level", "risk_signal", "trace.tag_expansion_hop_count", "source_evidence"],
+        "missing_policy": "no_generate_query"
+      },
+      "hard_gates": [
+        {
+          "gate_id": "empty_tag",
+          "label": "空 tag",
+          "when": {"field": "entity.tag_text", "op": "is_empty"},
+          "action": "NO_GENERATE_QUERY",
+          "reason_code": "empty_tag"
+        },
+        {
+          "gate_id": "generic_tag",
+          "label": "泛 tag",
+          "when": {"field": "entity.tag_text", "op": "in", "value": ["#热门", "#上热门", "#搞笑段子", "#推荐", "#流量"]},
+          "action": "NO_GENERATE_QUERY",
+          "reason_code": "generic_tag"
+        },
+        {
+          "gate_id": "risk_tag",
+          "label": "风险 tag",
+          "when": {"field": "risk_signal.tag_risk_level", "op": "in", "value": ["high", "blocked"]},
+          "action": "NO_GENERATE_QUERY",
+          "reason_code": "risk_tag"
+        },
+        {
+          "gate_id": "tag_not_recall_pattern",
+          "label": "tag 必须回扣 Pattern / seed",
+          "when": {"field": "relevance_signal.tag_recall_level", "op": "not_in", "value": ["strong", "matched"]},
+          "action": "NO_GENERATE_QUERY",
+          "reason_code": "tag_not_recall_pattern"
+        },
+        {
+          "gate_id": "similar_query_seen",
+          "label": "已生成过相同或近似 query",
+          "when": {"field": "trace.similar_query_seen", "op": "eq", "value": true},
+          "action": "NO_GENERATE_QUERY",
+          "reason_code": "similar_query_seen"
+        },
+        {
+          "gate_id": "tag_hop_limit_reached",
+          "label": "tag 扩散跳跃已达 10 次",
+          "when": {"field": "trace.tag_expansion_hop_count", "op": "gte", "value": 10},
+          "action": "STOP_PATH",
+          "reason_code": "tag_hop_limit_reached"
+        }
+      ],
+      "scorecard": {
+        "total_score": 100,
+        "dimensions": [
+          {"key": "tag_specificity", "label": "tag 具体度", "max_score": 35, "evidence_paths": ["relevance_signal.tag_specificity"]},
+          {"key": "tag_platform_fit", "label": "抖音搜索可用性", "max_score": 25, "evidence_paths": ["relevance_signal.platform_fit"]},
+          {"key": "tag_risk_clean", "label": "风险干净度", "max_score": 20, "evidence_paths": ["risk_signal.tag_risk_level"]},
+          {"key": "tag_trace_value", "label": "路径探索价值", "max_score": 20, "evidence_paths": ["trace.prior_tag_result_quality"]}
+        ]
+      },
+      "thresholds": [
+        {"min_score": 80, "action": "GENERATE_QUERY", "reason_code": "tag_strong_generate_query"},
+        {"min_score": 60, "max_score": 79, "action": "LOW_BUDGET_PENDING", "reason_code": "tag_low_budget_pending"},
+        {"max_score": 59, "action": "NO_GENERATE_QUERY", "reason_code": "tag_score_no_generate"}
+      ]
+    },
+    {
+      "pack_id": "douyin_path_stop_rule_pack_v1",
+      "pack_name": "路径停止规则包",
+      "version": "1.0.0",
+      "enabled": true,
+      "applies_to": {
+        "target_entity": "Path",
+        "input_node": "EvidenceBundle",
+        "decision_node": "RuleDecision",
+        "edge_ids": ["e02", "e04", "e08", "e10", "e12", "e13", "e18", "e19"],
+        "decision_output_keys": ["path_stop_decision"]
+      },
+      "hard_gates": [
+        {"gate_id": "continuous_low_quality_pages", "label": "连续 3 页低质", "when": {"field": "trace.continuous_low_quality_pages", "op": "gte", "value": 3}, "action": "STOP_PATH", "reason_code": "continuous_low_quality_pages"},
+        {"gate_id": "continuous_empty_recall", "label": "连续 3 页空召回", "when": {"field": "trace.continuous_empty_recall", "op": "gte", "value": 3}, "action": "STOP_PATH", "reason_code": "continuous_empty_recall"},
+        {"gate_id": "continuous_three_pages", "label": "连续三页即停", "when": {"field": "trace.continuous_pages", "op": "gte", "value": 3}, "action": "STOP_PATH", "reason_code": "continuous_three_pages"},
+        {"gate_id": "author_works_low_relevance", "label": "作者作品连续 3 条低相关", "when": {"field": "trace.author_works_low_relevance_count", "op": "gte", "value": 3}, "action": "STOP_PATH", "reason_code": "author_works_low_relevance"},
+        {"gate_id": "tag_hop_limit_reached", "label": "tag 扩散达到 10 次", "when": {"field": "trace.tag_expansion_hop_count", "op": "gte", "value": 10}, "action": "STOP_PATH", "reason_code": "tag_hop_limit_reached"},
+        {"gate_id": "obvious_path_drift", "label": "明显漂移", "when": {"field": "relevance_signal.path_drift_level", "op": "in", "value": ["high", "blocked"]}, "action": "STOP_PATH", "reason_code": "obvious_path_drift"}
+      ],
+      "default_action": "CONTINUE"
+    },
+    {
+      "pack_id": "douyin_budget_observe_rule_pack_v1",
+      "pack_name": "预算待观察规则包",
+      "version": "1.0.0",
+      "enabled": true,
+      "applies_to": {
+        "target_entity": "Budget",
+        "input_node": "EvidenceBundle",
+        "decision_node": "RuleDecision",
+        "edge_ids": ["e02", "e04", "e08", "e10", "e12", "e13", "e18", "e19"],
+        "decision_output_keys": ["budget_observe_decision"]
+      },
+      "hard_gates": [
+        {"gate_id": "high_duplicate_rate", "label": "重复率过高", "when": {"field": "trace.duplicate_rate", "op": "gte", "value": 0.6}, "action": "LOW_BUDGET_PENDING", "reason_code": "high_duplicate_rate"},
+        {"gate_id": "pending_not_reject", "label": "上游视频判断为待观察但未淘汰", "when": {"field": "upstream_rule_decisions.video_pool.final_action", "op": "eq", "value": "PENDING"}, "action": "LOW_BUDGET_PENDING", "reason_code": "pending_not_reject"},
+        {"gate_id": "near_tag_hop_limit", "label": "接近 tag 扩散上限", "when": {"field": "trace.tag_expansion_hop_count", "op": "gte", "value": 8}, "action": "LOW_BUDGET_PENDING", "reason_code": "near_tag_hop_limit"}
+      ],
+      "default_action": "NORMAL_BUDGET"
+    }
+  ],
+  "trace_policy": {
+    "emit_event": "rule_decision_evaluated",
+    "rule_output_files": ["rule_decisions.jsonl"],
+    "include_fields": [
+      "trace_id",
+      "pack_id",
+      "pack_version",
+      "edge_id",
+      "entity_type",
+      "entity_id",
+      "matched_hard_gates",
+      "scorecard",
+      "final_action",
+      "reason_code",
+      "source_evidence",
+      "input_snapshot_ref",
+      "evidence_refs",
+      "replay_fields",
+      "effect_status",
+      "origin_source",
+      "immediate_source",
+      "age_50_plus_level",
+      "tag_expansion_hop_count"
+    ],
+    "related_runtime_outputs": {
+      "source_edges.jsonl": "运行记录模块根据 RuleDecision 和 WalkAction 追加来源记录;规则包不直接写",
+      "search_clues.jsonl": "运行记录模块按 query/tag 聚合 RuleDecision 计数和效果状态;规则包不直接写",
+      "trace_events.jsonl": "运行编排记录 rule_decision_evaluated 事件;规则包只提供事件字段",
+      "final_output.json": "结果沉淀与来源反查模块读取 RuleDecision 后汇总最终输出;规则包不直接写"
+    }
+  },
+  "source_evidence_policy": {
+    "required": true,
+    "must_include": [
+      "pattern_execution_id",
+      "itemset_items",
+      "category_bindings_or_element_bindings",
+      "origin_source",
+      "immediate_source",
+      "origin_edge_id",
+      "trace_id"
+    ]
+  }
+}

+ 362 - 0
product_documents/规则包/抖音规则包V1.md

@@ -0,0 +1,362 @@
+# 抖音规则包 V1
+
+更新时间:2026-06-04
+
+## 阅读约定
+
+本文第一次出现核心字段或枚举时,会用括号补一句中文解释。常见字段包括:`aweme_id`(抖音视频 ID)、`sec_uid`(抖音作者 ID)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`effect_status`(搜索词效果状态)、`POOL`(入池)、`CANDIDATE`(候选)、`PENDING`(待观察)、`REJECT`(淘汰)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 总原则
+
+V1 规则包只服务一条链路:
+
+```text
+Pattern -> Query -> 抖音搜索 -> 视频 / 作者 / tag -> 判断 -> 游走或沉淀
+```
+
+核心原则:
+
+| 原则 | 说明 |
+|---|---|
+| 相关性是强规则 | 所有视频和 tag 都必须回扣 Pattern,无法回扣直接不通过 |
+| 50+ 是平台底线 | 先保证适老、安全、易判断,再看画像分 |
+| 风险先一票否决 | 高风险内容不入库,也不扩作者 |
+| 信号先汇总 | 内容画像、账号画像、互动表现不能单独拍板 |
+| tag 可以扩散 | V1 启用 tag 扩散,但 tag 必须回扣 Pattern,最多 10 次跳跃 |
+| 必须可追溯 | 所有结果保留 `source_evidence`(来源证据)和 `trace`(运行追踪记录) |
+
+一次 tag 扩散跳跃定义为:
+
+```text
+Video -> Hashtag -> Query
+```
+
+单次 V1 run 最多允许:
+
+```text
+max_tag_expansion_hops = 10
+```
+
+## 2. 规则包怎么组合
+
+规则包不是随便自由拼。V1 使用固定顺序:
+
+```text
+来源和运行追踪记录(trace)检查
+-> Pattern 回扣检查
+-> 风险和 50+ 安全检查
+-> 软评分
+-> 输出动作
+-> 记录运行追踪记录(trace)
+```
+
+五个规则包全部启用:
+
+| 规则包 | 作用 |
+|---|---|
+| 视频候选判断规则包 | 判断视频入池、候选、待观察(PENDING)或淘汰 |
+| 作者扩展判断规则包 | 判断作者是否扩作品 |
+| tag 扩散规则包 | 判断 hashtag 是否生成新 query |
+| 路径停止规则包 | 判断 query、作者、tag 路径是否停止 |
+| 预算待观察规则包 | 判断是否降预算待观察(PENDING) |
+
+## 3. 视频候选判断规则包
+
+它回答:这条视频能不能成为候选内容。
+
+### 硬门槛
+
+| 门槛 | 动作 |
+|---|---|
+| 无 `aweme_id` | 淘汰 |
+| 无 `source_evidence` | 淘汰 |
+| 不能回扣 Pattern / seed | 淘汰 |
+| 不能绑定分类或元素 | 淘汰 |
+| 明显跑偏 | 淘汰 |
+| 高风险内容 | 淘汰 |
+| 内容画像拿不到 | 淘汰 |
+| 50+ 内容画像弱或缺失 | 淘汰 |
+| 明显不适合 50+ 安全消费 | 淘汰 |
+| 无作者 `sec_uid` | 视频可继续判断,但不扩作者 |
+
+高风险包括:
+
+- 医疗、养生、保健品的神效承诺。
+- 金融、补贴、中奖、返利诱导。
+- 恐吓式转发、诅咒式传播。
+- 违法违规、低俗、暴力、隐私泄露。
+
+### 软评分
+
+Pattern 回扣不参与软评分。它是必选硬门槛:先判断有没有回扣,回扣成立后才进入下面的评分。
+
+满分 100 分:
+
+| 维度 | 分值 |
+|---|---:|
+| 抖音调性 | 25 |
+| 互动表现 | 15 |
+| 50+ 内容画像 | 30 |
+| 可改编性 | 10 |
+| 新鲜度 / 可用状态 | 20 |
+
+### 动作
+
+| 分数 | 动作 |
+|---:|---|
+| 70+ | 入池 |
+| 60-69 | 候选 |
+| 50-59 | 待观察(PENDING) |
+| <50 | 淘汰 |
+
+### 例子
+
+Pattern 是“早上好祝福 + 好运健康”。
+
+- 视频标题是“早上好,愿你今天好运连连”:可进入评分。
+- 视频是纯搞笑段子:即使点赞高,也因为跑偏淘汰。
+- 视频说“三天治好高血压”:即使 50+ 画像强,也因为风险淘汰。
+
+## 4. 作者扩展判断规则包
+
+它回答:这个作者值不值得拉更多作品。
+
+### 硬门槛
+
+| 门槛 | 动作 |
+|---|---|
+| 无 `sec_uid` | 不扩 |
+| 作品不可爬 | 不扩 |
+| 账号风险明显 | 不扩 |
+| 主页明显跑偏 | 不扩 |
+
+### 软评分
+
+满分 100 分:
+
+| 维度 | 分值 |
+|---|---:|
+| 账号 50+ 画像 | 35 |
+| 垂类稳定性 | 20 |
+| 相似作品数量 | 20 |
+| 爆款稳定性 | 10 |
+| 更新频率 | 10 |
+| 可爬稳定性 | 5 |
+
+### 动作
+
+| 分数 | 动作 |
+|---:|---|
+| 80+ | 扩作者 |
+| 60-79 | 小预算扩 |
+| 45-59 | 待观察作者(PENDING_AUTHOR) |
+| <45 | 不扩 |
+
+### 作者沉淀规则
+
+作者不是“扩了就沉淀”。要同时满足:
+
+| 条件 | 口径 |
+|---|---|
+| 样本数 | 已看作品 `>=9` |
+| 回扣作品数 | 能回扣 Pattern 的作品 `>=3` |
+| 回扣率 | 回扣作品数 / 样本数 `>=1/3` |
+| 账号 50+ | 账号画像为 strong 或 medium |
+| 风险 | 账号低风险 |
+
+满足则 `STORE_AUTHOR`;可扩但暂不满足则 `PENDING_AUTHOR`;硬门槛失败则 `REJECT_AUTHOR`。
+
+### 例子
+
+- 作者长期发早安祝福、节日问候,粉丝画像偏 50+:扩作者。
+- 视频本身不错,但作者主页大多是年轻娱乐混剪:视频待观察(PENDING)或不扩。
+- 作者作品不可爬:不扩。
+
+## 5. tag 扩散规则包
+
+它回答:视频里的 hashtag 要不要生成新 query。
+
+V1 启用 tag 扩散,但必须严格控制。
+
+### 硬门槛
+
+| 门槛 | 动作 |
+|---|---|
+| 空 tag | 不扩 |
+| 泛 tag | 不扩 |
+| 风险 tag | 不扩 |
+| 不能回扣 Pattern / seed | 不扩 |
+| 已生成过相同或近似 query | 不扩 |
+| tag 扩散跳跃已达 10 次 | 停止 tag 扩散 |
+
+泛 tag 示例:
+
+```text
+#热门
+#上热门
+#搞笑段子
+#推荐
+#流量
+```
+
+风险 tag 示例:
+
+```text
+#领补贴
+#神药
+#包治百病
+#不转发倒霉
+```
+
+### 软评分
+
+tag 回扣 Pattern 不参与软评分。它是必选硬门槛:tag 不能回扣 Pattern,就不能生成新 query。
+
+满分 100 分:
+
+| 维度 | 分值 |
+|---|---:|
+| tag 具体度 | 35 |
+| 抖音搜索可用性 | 25 |
+| 风险干净度 | 20 |
+| 路径探索价值 | 20 |
+
+### 动作
+
+| 条件 | 动作 |
+|---|---|
+| 强相关 | 生成新 query |
+| 弱相关 | 不生成 query |
+| 达到 10 次跳跃 | 停止路径 |
+| 不确定但有价值 | 降预算待观察 |
+
+### 例子
+
+Pattern 是“早上好祝福”。
+
+- `#早上好`、`#每日问候`:可以生成新 query。
+- `#热门`、`#上热门`:太泛,不扩。
+- `#领养老金补贴`:有风险,不扩。
+
+## 6. 路径停止规则包
+
+它回答:当前这条路径还要不要继续。
+
+停止条件:
+
+| 条件 | 动作 |
+|---|---|
+| 连续 3 页低质 | 停止当前 query 或作者作品分页 |
+| 连续 3 页空召回 | 停止当前 query |
+| 连续三页 | 停止继续翻页 |
+| 作者作品连续 3 条低相关 | 停止当前作者 |
+| tag 扩散达到 10 次 | 停止 tag 扩散 |
+| 明显漂移 | 停止路径 |
+
+例子:
+
+- 一个 query 连续三页都低质:停止这个 query。
+- 一个 query 连续三页没有有效召回:停止这个 query。
+- 同一个 query 或同一个作者作品连续翻到第三页:停止继续翻页。
+- 一个作者连续 3 条作品都不回扣 Pattern:停止这个作者。
+- 从祝福语一路扩到直播带货:漂移停止。
+
+## 7. 预算待观察规则包
+
+它回答:要不要降预算待观察。
+
+降预算条件:
+
+| 条件 | 动作 |
+|---|---|
+| 重复率过高 | 降预算待观察 |
+| 视频待观察(PENDING)但未淘汰 | 降预算待观察 |
+| tag 扩散接近 10 次 | 降预算待观察 |
+
+例子:
+
+- 某个 tag 召回的视频相关但质量一般:降预算继续。
+- 某作者有少量相关作品,但不稳定:小预算扩。
+- tag 扩散已经到第 8 次:后续只允许强相关继续。
+
+## 8. 运行追踪记录(trace)要记录什么
+
+每次规则判断都要记录:
+
+| 字段 | 说明 |
+|---|---|
+| `trace_id` | 本次运行 ID |
+| `pack_id` | 使用哪个规则包 |
+| `pack_version` | 规则包版本 |
+| `edge_id` | 当前来自哪条边 |
+| `entity_type` | 被判断对象类型;EvidenceBundle 输入里使用小写 `video / author / tag`,规则判断结果里使用 `Video / Author / Hashtag / Path / Budget` |
+| `entity_id` | 被判断对象 ID,例如 `aweme_id`(抖音视频 ID)、`sec_uid`(抖音作者 ID)或 tag 文本 |
+| `matched_hard_gates` | 命中的硬门槛 |
+| `scorecard` | 每个维度得分 |
+| `final_action` | 最终动作 |
+| `reason_code` | 原因码 |
+| `source_evidence` | 来源证据,包含 Pattern、itemset、分类或元素绑定 |
+| `input_snapshot_ref` | EvidenceBundle(判断证据包)快照引用 |
+| `evidence_refs` | 本次判断实际读取的证据字段 |
+| `replay_fields` | 规则版本、命中门槛、阈值区间等复盘字段 |
+| `tag_expansion_hop_count` | 当前 tag 扩散次数 |
+
+规则包直接写入:
+
+```text
+rule_decisions.jsonl(规则判断结果文件)
+```
+
+后续会读取这些结果的模块:
+
+| 文件 | 写入边界 |
+|---|---|
+| `source_edges.jsonl` | 运行记录模块根据 `RuleDecision`(规则判断结果)和 `WalkAction`(下一步动作)追加 |
+| `search_clues.jsonl` | 运行记录模块按 query/tag 聚合 `RuleDecision` 计数和效果状态 |
+| `trace_events.jsonl` | 运行记录模块记录 `rule_decision_evaluated` 等事件 |
+| `final_output.json` | 结果沉淀与来源反查模块读取 `RuleDecision` 后汇总最终输出 |
+
+## 9. 简单例子
+
+### 例子 1:视频入池
+
+```text
+Pattern:早上好祝福 + 好运健康
+视频:早上好,愿你今天好运连连
+结果:相关、低风险、适合 50+、互动尚可
+动作:入池
+```
+
+### 例子 2:作者扩展
+
+```text
+视频作者:长期发早安祝福、节日问候
+账号画像:50+ 明显
+作品:稳定、可爬
+动作:扩作者
+```
+
+### 例子 3:tag 扩散
+
+```text
+视频 tag:#早上好 #每日问候
+判断:能回扣 Pattern,适合生成搜索词
+动作:生成新 query
+```
+
+### 例子 4:tag 不扩
+
+```text
+视频 tag:#热门 #上热门
+判断:太泛,不能解释回 Pattern
+动作:不生成 query
+```
+
+### 例子 5:停止路径
+
+```text
+路径:早上好祝福 -> #热门 -> 搞笑段子
+判断:明显漂移
+动作:停止路径
+```

+ 629 - 0
tech_documents/Pattern和分类树反查手册.md

@@ -0,0 +1,629 @@
+# Pattern 和分类树反查手册
+
+## 元信息
+
+- run_id: `2026-06-04-pattern-1158558-post-55157577`
+- 生成时间: `2026-06-04T17:04:12`
+- 主事实库: `postgresql://aiddit_aigc:***@gp-t4n72471pkmt4b9q7o-master.gpdbmaster.singapore.rds.aliyuncs.com:5432/open_aigc`,schema: `public`
+- 只读校验: `transaction_read_only=on`
+- 页面锚点: `pattern_itemset.id=1158558`,`post_id=55157577`,`execution_id=401`,`mining_config_id=1301`
+
+## 0. 先用业务语言理解这套东西
+
+Pattern 不是一个孤立 ID。对业务和产品来说,它是一组内容特征的组合规律:比如一类祝福视频经常同时出现 `祝福词句`、`福袋`、`分享`、`动态捕捉`、`堆叠排列` 这些特征。
+
+`itemset` 是这组特征的组合本身;`pattern item` 是组合里的一个特征 chip;`matched post` 是被系统记录为支撑这个组合规律的帖子。
+
+分类树也不是装饰性的目录。它负责回答:这个特征在内容理解里到底是什么位置。比如 `福袋` 是节庆道具/实物元素,`分享` 是传播意图,`动态捕捉` 是画面表现手法。
+
+本手册只讲已经由 PostgreSQL `open_aigc.public` 和代码路径确认过的链路:
+
+```text
+Post/Case -> Pattern -> Pattern item -> Pattern V2 快照分类树
+Pattern item -> 快照分类树节点 -> 父链/子节点 -> global 回指
+```
+
+## 1. 使用前约束:先定 scope,别直接查
+
+| 必须确认 | 为什么 | 本案例值 |
+|---|---|---|
+| `execution_id` | 快照树和元素都按 execution 隔离 | `401`,snapshot `2026-04-23`,`status=failed`,`is_current=True` |
+| `mining_config_id` | 决定挖掘范围、算法、depth、merge 语义 | `1301`,`topic_full_dmax`,`algorithm=fpgrowth` |
+| `target_depth` | 决定 item 是保留原路径、截断父层,还是展开祖先 | `max` |
+| `merge` | 决定 matched post 是否可能是 soft coverage | `启用` |
+| Pattern 类型 | 不同 pattern 表的语义不同 | 本案例是 `pattern_itemset*` 的 topic/category FP-Growth,不是 group/sequence pattern |
+
+## 2. Source-of-truth Map
+
+| 业务问题 | 主表 | 关键字段 | 人话解释 |
+|---|---|---|---|
+| Pattern 本体 | `pattern_itemset` | `id`, `execution_id`, `mining_config_id`, `item_count`, `absolute_support` | 一行就是一个 Pattern。 |
+| Pattern 的 item | `pattern_itemset_item` | `itemset_id`, `category_id`, `point_type`, `dimension`, `category_path` | `category_id` 是 execution-local `pattern_mining_category.id`。 |
+| Pattern 的帖子 | `pattern_itemset_post` | `itemset_id`, `post_id`, `execution_id` | 反查 post -> pattern 就从这里走。 |
+| Post 自己的快照元素 | `pattern_mining_element` | `post_id`, `execution_id`, `category_id`, `point_type`, `element_type`, `point_text` | 这是帖子本身被快照出来的元素事实,不等同于它命中某个 Pattern。 |
+| 快照分类树 | `pattern_mining_category` | `id`, `execution_id`, `parent_id`, `source_stable_id`, `name`, `path` | 父链和子节点都在这张表里查。 |
+| 全局回指 | `global_category` | `stable_id`, `source_type`, `path`, `retired_at_execution_id` | 只能用于回指/漂移,不要用当前树覆盖历史快照。 |
+| 帖子信息 | `post` | `post_id`, `title`, `platform` | 只负责展示帖子业务上下文。 |
+| 下游需求证据 | `demand_content.ext_data.evidence_pack` | `pattern_source_system`, `source_kind`, `case_id_type`, `source_post_id`, `pattern_execution_id`, `mining_config_id`, `itemset_ids`, `itemset_items[]`, `matched_post_ids`, `source_certainty`, `validation_status` | 这是 ContentFindAgent 要从 `post_id` 唯一反查 Pattern 时必须继承的上游证据;DemandAgentNew 方案要求它必须由真实 DB 强校验后写入。 |
+| 下游结果证据 | `demand_find_content_result` 或 sidecar/source edge artifact | `demand_content_id`, `aweme_id/post_id`, `source_evidence`, `trace_id` | 最终入库内容要能回到上游 Pattern evidence;不能只剩一段 `process_trace` 文本。 |
+
+## 3. 反查链路 A:已知 `post_id` -> 这个帖子支撑了哪些 Pattern
+
+**这条链路回答什么业务问题**:拿到一条帖子,想知道它被系统归入了哪些内容规律。产品页面里的“这个帖子出现在某个 Pattern 下”,底层就是这条映射。
+
+**怎么查**
+
+```text
+post.post_id
+  -> pattern_itemset_post.post_id
+  -> pattern_itemset_post.itemset_id
+  -> pattern_itemset.id
+  -> pattern_itemset_item.itemset_id
+  -> pattern_mining_category.id
+```
+
+**DB 证据**
+
+| 表 | 字段 | 本 case 结果 | 说明 |
+|---|---|---|---|
+| `post` | `post_id`, `title`, `platform` | `55157577` / `周日早上好今天是6月29日最美的祝福送给你` / `piaoquan` | 帖子业务上下文。 |
+| `pattern_itemset_post` | `post_id`, `itemset_id` | `post_id=55157577` 反查到 `67` 个 itemset | 说明这条帖子支撑多个 Pattern。 |
+| `pattern_itemset` | `id`, `execution_id`, `mining_config_id`, `item_count`, `absolute_support` | 包含 `id=1158558`,`execution_id=401`,`mining_config_id=1301`,`item_count=10`,`absolute_support=27` | 说明这个 Pattern 由 10 个特征组成,并有 27 条支撑帖子。 |
+
+**代码证据**
+
+- ORM:`PatternItemsetPost.itemset_id/post_id/execution_id` 在 [models.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/db/pattern/models.py:219) 定义。
+- API:详情页 `GET /executions/{execution_id}/itemsets/{itemset_id}` 在 [topic.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/api/routes/pattern/topic.py:796) 分页读取 `PatternItemsetPost.post_id`。
+- Tool/debug:`get_itemset_detail` 在 [topic_tools_debug.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/api/routes/pattern/topic_tools_debug.py:825) 也按 itemset 查 `post_ids`。
+
+**业务解释**:`55157577` 这条早安祝福帖子出现在 `Pattern #1158558` 下,表示它被系统记录为支撑“祝福表达 + 节庆道具 + 分享意图 + 视觉表现手法”这类组合规律的一条样本。
+
+**这个结果不能说明什么**:它不能单独证明这条帖子严格包含 Pattern 的全部 10 个 item;严格程度要走链路 E 计算 exact hit ratio。
+
+## 4. 反查链路 B:已知 `pattern_itemset.id` -> Pattern items -> 快照分类树
+
+**这条链路回答什么业务问题**:一个 Pattern 卡片上那一排 chip 到底是什么业务特征,它们分别落在分类树的哪个位置。
+
+**怎么查**
+
+```text
+pattern_itemset.id
+  -> pattern_itemset_item.itemset_id
+  -> pattern_itemset_item.category_id
+  -> pattern_mining_category.id
+```
+
+**DB 证据**
+
+| 表 | 字段 | 本 case 结果 | 说明 |
+|---|---|---|---|
+| `pattern_itemset` | `id`, `item_count`, `absolute_support` | `1158558` / `10` / `27` | Pattern 本体。 |
+| `pattern_itemset_item` | `itemset_id`, `category_id`, `point_type`, `dimension`, `category_path` | 10 行 item | 每一行就是页面上的一个 chip。 |
+| `pattern_mining_category` | `id`, `name`, `path`, `source_stable_id`, `source_type`, `parent_id` | 10 个 item 全部能 join 到快照分类节点 | 说明 Pattern item 来自 execution 401 的快照分类树。 |
+
+| item | 点类型/维度 | 快照 category_id | 业务含义 |
+|---|---|---:|---|
+| 祝福词句 | 关键点/实质 | 43925 | 内容中出现的祝福表达素材,比如早安、好运、健康、快乐这类祝愿文字。 |
+| 吉祥民俗 | 关键点/实质 | 44644 | 带有吉祥、节庆、民俗寓意的文化符号,用来让内容显得喜庆、有传统祝福意味。 |
+| 动态捕捉 | 关键点/形式 | 46795 | 视频或画面表现手法,强调把烟花、粒子、礼物喷涌等动态瞬间拍出来。 |
+| 堆叠排列 | 关键点/形式 | 45975 | 画面构图手法,把祝福语、装饰元素或物品密集叠放,形成热闹、饱满的视觉效果。 |
+| 轴线对称 | 关键点/形式 | 46294 | 画面构图手法,用中轴或对称关系让画面看起来稳定、仪式感强。 |
+| 配色组合 | 关键点/形式 | 46641 | 色彩搭配手法,说明内容靠多种颜色组合制造节日感或视觉冲击。 |
+| 单色饱和度 | 关键点/形式 | 46097 | 色彩强化手法,强调某一类高饱和颜色,让画面更醒目、更有情绪。 |
+| 福袋 | 灵感点/实质 | 44587 | 画面里的节庆道具或实物元素,常用来表达好运、财富、祝福。 |
+| 节日祝福 | 目的点/实质 | 43447 | 内容的祝愿主题,把节日、日期或特殊时刻和祝福表达连在一起。 |
+| 分享 | 目的点/意图 | 47134 | 内容的传播意图,表示作者想把祝福、歌曲、景观或信息传递给别人。 |
+
+**代码证据**
+
+- ORM:`PatternItemsetItem.category_id/element_id/element_name/post_count` 在 [models.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/db/pattern/models.py:240) 定义。
+- 落库:`write_itemsets` 把挖掘结果写入 `pattern_itemset`、`pattern_itemset_item`、`pattern_itemset_post`,见 [itemset_writer.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/pattern/persistence/itemset_writer.py:126)。
+- 挖掘:`run_topic_fpgrowth` 从 `PatternMiningElement.category_path/category_id` 构造 itemset,见 [service.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/pattern/topic_fpgrowth/service.py:105)。
+
+**业务解释**:这条 Pattern 不是“一个帖子”,而是 10 个内容特征一起出现的规律。`祝福词句`、`节日祝福`、`福袋` 偏内容素材;`动态捕捉`、`堆叠排列`、`轴线对称` 偏视觉表达;`分享` 是传播意图。
+
+**这个结果不能说明什么**:它说明 Pattern item 对应哪些快照分类节点,但不说明每条 matched post 都完整命中这些 item。
+
+## 5. 反查链路 C:已知 `category_id` -> 父链、子节点、是否叶子、global 回指
+
+**这条链路回答什么业务问题**:一个 chip 在分类树里到底是大类、中间层,还是叶子;它上面是谁,下面还有没有更细的业务概念。
+
+**怎么查**
+
+```text
+pattern_mining_category.id
+  -> recursive parent_id chain
+  -> direct children: where parent_id = category_id
+  -> global back-reference: source_stable_id + source_type
+```
+
+**DB 证据**
+
+| 字段 | 来自表 | 说明 |
+|---|---|---|
+| `id`, `parent_id` | `pattern_mining_category` | 在 execution 内构成快照树父子关系。 |
+| `source_stable_id`, `source_type` | `pattern_mining_category` | 回指长期全局分类树节点。 |
+| `stable_id`, `retired_at_execution_id` | `global_category` | 判断当前 global 是否仍 active,或只是历史回指。 |
+
+| 示例节点 | 是否叶子 | 父链 | 直接子节点 | 业务解释 |
+|---|---|---|---|---|
+| 祝福词句 | 否 | 表象 > 符号 > 表达符号 > 祝福语 > 吉祥用语 > 祝福词句 | 吉祥话(45124, count=68);序列祝福(44199, count=31);特色祝福语(45473, count=29);财运祝福(44910, count=24) | 内容中出现的祝福表达素材,比如早安、好运、健康、快乐这类祝愿文字。 |
+| 吉祥民俗 | 是 | 表象 > 符号 > 装饰符号 > 吉祥图案 > 综合意象 > 符号概念 > 吉祥民俗 | 无直接子节点,叶子节点 | 带有吉祥、节庆、民俗寓意的文化符号,用来让内容显得喜庆、有传统祝福意味。 |
+| 动态捕捉 | 是 | 呈现 > 视觉 > 影像制作 > 实景拍摄 > 拍摄方式 > 即时捕捉 > 动态捕捉 | 无直接子节点,叶子节点 | 视频或画面表现手法,强调把烟花、粒子、礼物喷涌等动态瞬间拍出来。 |
+| 堆叠排列 | 是 | 呈现 > 视觉 > 构图编排 > 空间布局 > 元素编排 > 排列节奏 > 堆叠排列 | 无直接子节点,叶子节点 | 画面构图手法,把祝福语、装饰元素或物品密集叠放,形成热闹、饱满的视觉效果。 |
+| 轴线对称 | 是 | 呈现 > 视觉 > 构图编排 > 空间布局 > 构图方式 > 对称分割 > 中轴对称 > 轴线对称 | 无直接子节点,叶子节点 | 画面构图手法,用中轴或对称关系让画面看起来稳定、仪式感强。 |
+| 配色组合 | 是 | 呈现 > 视觉 > 视觉气质 > 色彩调性 > 背景底色 > 配色组合 | 无直接子节点,叶子节点 | 色彩搭配手法,说明内容靠多种颜色组合制造节日感或视觉冲击。 |
+| 单色饱和度 | 是 | 呈现 > 视觉 > 视觉气质 > 色彩调性 > 色彩强化 > 饱和度调节 > 单色饱和度 | 无直接子节点,叶子节点 | 色彩强化手法,强调某一类高饱和颜色,让画面更醒目、更有情绪。 |
+| 福袋 | 是 | 表象 > 实体 > 物品 > 器物 > 道具 > 节庆饰物 > 福袋 | 无直接子节点,叶子节点 | 画面里的节庆道具或实物元素,常用来表达好运、财富、祝福。 |
+| 节日祝福 | 否 | 表象 > 符号 > 表达符号 > 祝福语 > 节日祝福 | 时令节庆(44574, count=110);纪念日祝福(44088, count=32);年节贺岁(47211, count=0) | 内容的祝愿主题,把节日、日期或特殊时刻和祝福表达连在一起。 |
+| 分享 | 是 | 信息传递 > 信息扩散 > 分享 | 无直接子节点,叶子节点 | 内容的传播意图,表示作者想把祝福、歌曲、景观或信息传递给别人。 |
+
+**代码证据**
+
+- 快照树写入:`_collect_active_categories` 按 `(stable_id, source_type)` 收集节点和祖先,见 [snapshot_builder.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/pattern/persistence/snapshot_builder.py:101)。
+- 父子关系回填:`_write_categories` 写 `source_stable_id/parent_source_stable_id` 并回填 `parent_id`,见 [snapshot_builder.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/pattern/persistence/snapshot_builder.py:162)。
+- 树查询 API:`GET /api/pattern/executions/{execution_id}/category-tree` 在 [base.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/api/routes/pattern/base.py:371)。
+
+**业务解释**:`祝福词句`、`节日祝福` 在这个快照里还有直接子节点,所以它们是父层概念;`福袋`、`分享` 没有直接子节点,所以在这个快照里是叶子特征。
+
+**这个结果不能说明什么**:不能用当前 `global_category` 直接覆盖历史 Pattern 里的树位置;Pattern 的树位置以 `pattern_mining_category` 快照为准。
+
+## 6. 反查链路 D:已知 `post_id + execution_id` -> 帖子自己的快照元素
+
+**这条链路回答什么业务问题**:这条帖子本身被系统解构出了哪些内容元素,每个元素落在什么分类树节点。
+
+**怎么查**
+
+```text
+pattern_mining_element.post_id + execution_id
+  -> pattern_mining_element.category_id
+  -> pattern_mining_category.id
+```
+
+**DB 证据**
+
+| 表 | 字段 | 本 case 结果 | 说明 |
+|---|---|---|---|
+| `pattern_mining_element` | `post_id`, `source_table`, `source_element_id`, `point_type`, `element_type`, `name`, `point_text`, `category_id` | 12 行快照元素 | 说明帖子自己有哪些元素事实。 |
+| `pattern_mining_category` | `id`, `name`, `path`, `source_stable_id`, `source_type` | 12 行都能映射分类节点 | 说明这些元素在快照树里的位置。 |
+
+| 元素 | point_text | 点类型/维度 | 快照节点 | 业务解释 |
+|---|---|---|---|---|
+| 祝福语 | 祝福语堆叠 | 关键点/实质 | 祝福词句 | 内容中出现的祝福表达素材,比如早安、好运、健康、快乐这类祝愿文字。 |
+| 吉祥意象 | 吉祥意象组合 | 关键点/实质 | 吉祥意象 | 内容里被讲到或被看见的对象/主题,出现在 `关键点` 这类解构点中。 |
+| 组合 | 吉祥意象组合 | 关键点/形式 | 搭配手法 | 内容的表达方式、视觉手法或组织方式,出现在 `关键点` 这类解构点中。 |
+| 粒子特效 | 动态粒子特效 | 关键点/形式 | 粒子效果 | 内容的表达方式、视觉手法或组织方式,出现在 `关键点` 这类解构点中。 |
+| 动态 | 动态粒子特效 | 关键点/形式 | 动态捕捉 | 视频或画面表现手法,强调把烟花、粒子、礼物喷涌等动态瞬间拍出来。 |
+| 堆叠 | 祝福语堆叠 | 关键点/形式 | 堆叠排列 | 画面构图手法,把祝福语、装饰元素或物品密集叠放,形成热闹、饱满的视觉效果。 |
+| 对称式构图 | 对称式构图 | 关键点/形式 | 轴线对称 | 画面构图手法,用中轴或对称关系让画面看起来稳定、仪式感强。 |
+| 高饱和度配色 | 高饱和度配色 | 关键点/形式 | 配色饱和度 | 内容的表达方式、视觉手法或组织方式,出现在 `关键点` 这类解构点中。 |
+| 福袋 | 福袋喷涌烟花 | 灵感点/实质 | 福袋 | 画面里的节庆道具或实物元素,常用来表达好运、财富、祝福。 |
+| 喷涌烟花 | 福袋喷涌烟花 | 灵感点/实质 | 场景烟花 | 内容里被讲到或被看见的对象/主题,出现在 `灵感点` 这类解构点中。 |
+| 早安祝福 | 分享早安祝福 | 目的点/实质 | 早安问候 | 内容里被讲到或被看见的对象/主题,出现在 `目的点` 这类解构点中。 |
+| 分享 | 分享早安祝福 | 目的点/意图 | 分享 | 内容的传播意图,表示作者想把祝福、歌曲、景观或信息传递给别人。 |
+
+**代码证据**
+
+- topic 元素快照:`_write_topic_elements` 把 `post_decode_topic_point_element` 写成 `PatternMiningElement`,见 [snapshot_builder.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/pattern/persistence/snapshot_builder.py:248)。
+- script/段落元素快照:`_write_script_field_elements` 等写入脚本侧元素,见 [snapshot_builder.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/pattern/persistence/snapshot_builder.py:302)。
+- 帖子详情:`build_post_details_batch` 按 `post_id + source_table + source_element_id` 还原解构详情,见 [builder.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/pattern/post_detail/builder.py:145)。
+
+**业务解释**:`55157577` 这条帖子自身包含祝福语、福袋、烟花、早安祝福、分享等内容元素,也包含堆叠、对称、动态、配色等视觉手法。
+
+**这个结果不能说明什么**:它证明帖子自身有哪些元素,但不证明帖子属于哪些 Pattern;属于哪些 Pattern 要查 `pattern_itemset_post`。
+
+## 7. 反查链路 E:已知 `post + Pattern` -> exact hit ratio
+
+**这条链路回答什么业务问题**:这个帖子只是被记录为支撑 Pattern,还是它真的命中了 Pattern 的全部特征。
+
+**怎么查**
+
+```text
+pattern_itemset_item
+  left join pattern_mining_element
+    on same category_id
+   and same point_type
+   and same dimension/element_type
+exact_hit_ratio = exact_hits / pattern_item_count
+```
+
+**DB 证据**
+
+| Pattern item | exact hit | post 快照元素行 | source_table/source_element_id | point_text |
+|---|---|---|---|---|
+| 祝福词句 (关键点/实质) | 是 | 祝福语 | post_decode_topic_point_element/61640 | 祝福语堆叠 |
+| 吉祥民俗 (关键点/实质) | 否 | - | - | - |
+| 堆叠排列 (关键点/形式) | 是 | 堆叠 | post_decode_topic_point_element/61641 | 祝福语堆叠 |
+| 单色饱和度 (关键点/形式) | 否 | - | - | - |
+| 轴线对称 (关键点/形式) | 是 | 对称式构图 | post_decode_topic_point_element/61637 | 对称式构图 |
+| 配色组合 (关键点/形式) | 否 | - | - | - |
+| 动态捕捉 (关键点/形式) | 是 | 动态 | post_decode_topic_point_element/61638 | 动态粒子特效 |
+| 福袋 (灵感点/实质) | 是 | 福袋 | post_decode_topic_point_element/61632 | 福袋喷涌烟花 |
+| 节日祝福 (目的点/实质) | 否 | - | - | - |
+| 分享 (目的点/意图) | 是 | 分享 | post_decode_topic_point_element/61635 | 分享早安祝福 |
+
+本 case 结果:`55157577` 对 `Pattern #1158558` 精确命中 `6/10`,ratio=`0.6`。
+
+**代码证据**
+
+- merge 语义:`_materialize_merged` 用 `tau_item_coverage` 过滤 item、用 `tau_coverage` 过滤 post,见 [merge_service.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/pattern/topic_fpgrowth/merge_service.py:202)。
+- 调用点:`run_topic_fpgrowth` 调用 `merge_itemsets(...)` 并把 merge 参数记录到 config,见 [service.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/pattern/topic_fpgrowth/service.py:367)。
+- 落库:`write_itemsets` 把 merge 后的 `matched_post_ids` 写入 `pattern_itemset_post`,见 [itemset_writer.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/pattern/persistence/itemset_writer.py:189)。
+
+**业务解释**:`55157577` 是 `1158558` 的支撑帖子,但它并不是完整复刻这个 Pattern 的所有特征。它命中了祝福词句、动态捕捉、堆叠排列、轴线对称、福袋、分享;没有精确命中吉祥民俗、配色组合、单色饱和度、节日祝福。
+
+**这个结果不能说明什么**:不能因为某个 post 出现在 matched posts 里,就对业务说“它完整包含这个 Pattern”。更准确的说法是:它支撑这个 Pattern;严格命中程度要看 exact hit ratio。
+
+## 8. ContentFindAgent 下游反查:Candidate Mode、MVP Exact Mode 与 PG 主事实边界
+
+这一节专门回答下游视角的问题:ContentFindAgent 只拿到一个 `caseid`,且这个 `caseid` 在本链路中就是 `post_id`,能不能追回“具体这次 execution 生成的哪个 Pattern 下的哪个帖子、哪个分类树节点”?
+
+答案分三层:
+
+```text
+只有 post_id
+-> 只能候选反查,不能唯一定位 Pattern。
+
+DemandAgent 写入 DB 校验通过的 evidence_pack,CFA 继承并沉淀 source_evidence
+-> 可以 exact 追到 DemandAgent 当前消费的 MySQL Pattern Tree。
+
+要 exact 追到 PG Pattern V2 主事实和页面
+-> evidence_pack 还要保存 PG 侧 ID,或补齐 PG -> MySQL 同步映射证明。
+```
+
+### 8.1 Candidate Mode:只有 `post_id`,只能得到候选 Pattern
+
+**这条链路回答什么业务问题**:下游已经有一个帖子 ID,想知道它可能属于哪些 Pattern。
+
+**怎么查**
+
+```text
+ContentFindAgent caseid(post_id)
+  -> pattern_itemset_post.post_id
+  -> pattern_itemset.id
+  -> pattern_itemset_item
+  -> pattern_mining_category
+```
+
+**DB 证据**
+
+| 证据点 | 表/字段 | 本轮结论 | 业务解释 |
+|---|---|---|---|
+| 支撑关系存在 | `pattern_itemset_post.post_id`, `itemset_id`, `execution_id` | `(itemset_id, post_id)` 是精确映射行 | 一行表示“这个帖子被记录为这个 Pattern 的支撑样本”。 |
+| 但不是唯一 | `pattern_itemset_post.post_id` 聚合 | PG 只读验证显示同一个 post 平均命中约 `315.52` 个 itemset,最高 `7388` 个 | 帖子通常同时支撑很多组合规律,所以不能靠 post 反推出唯一 Pattern。 |
+| 本案例 | `post_id=55157577` | 在 `mining_config_id=1301` 下命中 `67` 个 itemset,其中包含 `1158558` | 这就是“候选集合”,不是“生成血缘”。 |
+
+**代码证据**
+
+- Pattern 支撑帖子表:`PatternItemsetPost` 定义 `itemset_id/post_id/execution_id`,见 [models.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/db/pattern/models.py:219)。
+- Pattern 详情 API 读取 `PatternItemsetPost.post_id` 展示 matched posts,见 [topic.py](/Users/samlee/Documents/works/image_article_comprehension/aiddit/pattern_global_v2/api/routes/pattern/topic.py:796)。
+- 旧 CFA 的 case 工具不是这条强链路,而是按 `topic_pattern_element.name` 查 `post_id`,见 [get_goodcase_topic_point.py](/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:91)。
+
+**业务解释**:这像拿到一条视频后问“它被哪些规律收录过”。答案可能很多,因为一条早安祝福视频既可以属于“祝福词句 + 分享”,也可以属于“福袋 + 动态捕捉”,还可能属于更宽的“节日祝福 + 配色”组合。
+
+**这个结果不能说明什么**:不能说明“这个 ContentFindAgent case 是由某一个 Pattern 生成的”。它只能说明“这个 post 在 Pattern 库里支持过这些 Pattern”。
+
+### 8.2 MVP Exact Mode:DemandAgent 写 `evidence_pack`,CFA 继承 `source_evidence`
+
+**这条链路回答什么业务问题**:ContentFindAgent 最终拿到一条入库内容,要追溯它来自哪张需求单、哪次 Pattern execution、哪个 itemset、哪个 source post,以及这个 Pattern itemset 对应分类树上的哪些节点。
+
+**完整闭合链路**
+
+```text
+DemandAgent 生成需求
+  -> 代码用真实 DB 校验 evidence_refs
+  -> demand_content.ext_data.evidence_pack
+  -> ContentFindAgent 领取 demand_content 时解析 evidence_pack
+  -> 数据源 / Query / 判断 / 游走 / 入库全程继承 source_evidence
+  -> demand_find_content_result.source_evidence 或 sidecar/source edge artifact
+  -> source_post_id + pattern_execution_id + mining_config_id + itemset_id
+  -> topic_pattern_itemset.matched_post_ids
+  -> topic_pattern_itemset_item.category_id
+  -> topic_pattern_category.id
+  -> 父链、直接子节点、source_type、category_path
+```
+
+**必须保存的 evidence 字段**
+
+| 字段 | 为什么必须有 | 没有它会怎样 |
+|---|---|---|
+| `pattern_source_system` | 标记证据来自 `mysql_topic_pattern` 还是 PG Pattern V2 主事实 | 容易把 DemandAgent MySQL 兼容层误写成 PG 主事实。 |
+| `source_kind` | 区分来源是 Pattern itemset、直接 Case、聚类结果还是搜索结果 | 下游不知道该走 Pattern 反查还是 Case 反查。 |
+| `case_id_type=post_id` | 标准化你说的 `caseid` 是 `post_id`,不是结果表 id 或 decode 行 id | ID 语义混乱,无法稳定 join。 |
+| `source_post_id` | 标记这个需求证据中的原始支撑帖子 | 各表里的 `post_id/channel_content_id/aweme_id` 无法对齐。 |
+| `pattern_execution_id` | 标记来源是哪次 Pattern Tree 执行 | 同名元素跨 execution 会混淆。 |
+| `mining_config_id` | 标记具体挖掘配置 | 同一 execution 可能有多个 Pattern 配置。 |
+| `itemset_ids` | 标记具体 Pattern 本体 | 没有它只能从 post 反查一堆候选 Pattern。 |
+| `itemset_items[]` | 标记 itemset 内每个 item 对应的分类节点 | 不用下游再猜 chip 对应哪个 tree node。 |
+| `matched_post_ids` | 标记这个 Pattern 的支撑帖子集合 | `video_ids` 不等于 itemset 的 matched posts。 |
+| `category_bindings` | 标记分类树节点、路径、层级、source_type | 下游入库后无法解释内容属于哪棵树、哪个节点。 |
+| `element_bindings` | 标记具体元素、点类型、维度和所属 post | Query 和判断只能拿到宽泛词,不能回扣到真实元素。 |
+| `seed_terms` | 标记真正用于 Query 的元素词或分类词 | 宽泛 `name` 会被误当搜索词。 |
+| `trace_id/demand_task_id/demand_content_id` | 串起 DemandAgent -> CFA -> 入库结果 | 无法复盘哪条需求、哪次任务产生了这条内容。 |
+| `source_certainty=db_validated` | 标记证据已经由代码从真实 DB 校验通过 | 下游无法区分 LLM 猜的证据和真实证据。 |
+| `validation_status=passed` | 标记该需求证据链通过校验 | 失败证据会流入下游。 |
+
+**DemandAgent 侧强校验口径**
+
+DemandAgentNew 的方案把 `evidence_refs` 和 `evidence_pack` 分开:LLM 只能给候选 `evidence_refs`,真正进入 `demand_content.ext_data.evidence_pack` 的,必须由代码按当前 `execution_id` 从真实 DB 校验并补齐。
+
+文档证据:
+
+- DemandAgent 输出调整方案明确:本次只改普通需求池 `demand_content.ext_data`,新增结构化 `evidence_pack`,并要求它是真实 DB 校验过的上游 exact evidence 基础,见 [demand_output_improve_plan.md](/Users/samlee/Documents/works/DemandAgentNew/demand_output_improve_plan.md:5)。
+- DemandAgent 给下游的迭代需求明确:CFA 不能只靠旧反查机制恢复 Pattern/Case/分类树血缘,`evidence_pack` 是必要条件但不是充分条件,CFA 还要继承并沉淀 `source_evidence`,见 [demandagent给下游的迭代需求.md](/Users/samlee/Documents/works/DemandAgentNew/demandagent给下游的迭代需求.md:11)。
+
+对于 `source_kind=pattern_itemset`,至少要确认:
+
+| 校验项 | 必须满足 |
+|---|---|
+| `itemset_ids` | 每个 itemset 属于当前 `pattern_execution_id`,且能查到 `mining_config_id/support/absolute_support/matched_post_ids`。 |
+| `source_post_id` | 必须出现在对应 itemset 的 `matched_post_ids` 中。 |
+| `itemset_items[]` | 必须来自 `topic_pattern_itemset_item`,且每个 `category_id` 能 join 到当前 execution 的 `topic_pattern_category`。 |
+| `category_bindings` | `category_id` 必须属于当前 `pattern_execution_id`,不能跨 execution 借节点。 |
+| `element_bindings` | 必须能在当前 execution 下绑定到 `topic_pattern_element`,只查到候选词不算通过。 |
+| `source_certainty/validation_status` | 进入 `demand_content` 时只能是 `db_validated/passed`。 |
+
+任一必需字段查不到、跨 execution、post 不在 matched posts、category 不在当前 execution,当前 DemandItem 都不能写入 `demand_content`;只能进入 reject 清单。
+
+**ContentFindAgent 侧继承口径**
+
+DemandAgent 补 `evidence_pack` 只是 Exact Mode 的上游基础。ContentFindAgent 后续还必须:
+
+| 阶段 | 必须做什么 |
+|---|---|
+| 领取需求 | 从 `demand_content.ext_data` 解析 `evidence_pack`,不能只取 `id/name/suggestion/score/merge_leve2/dt`。 |
+| 数据源 | 展示 Pattern、Case、分类/元素绑定和原始支撑素材。 |
+| Query | 使用 `seed_terms`,不直接把宽泛 `name` 当 Query。 |
+| 判断 | 用分类/元素绑定、Case 原文、support、absolute_support 做回扣判断。 |
+| 游走 | 每次游走结果绑定回分类或元素节点,避免无限漂移。 |
+| 入库 | 最终写 `demand_find_content_result` 时保存结构化 `source_evidence`,或写 sidecar/source edge artifact。 |
+
+如果 CFA 最终只写:
+
+```text
+aweme_id + demand_content_id + process_trace
+```
+
+那链路仍然会在最终结果处断开,无法 exact 回到 Pattern 和分类树节点。
+
+**业务解释**:Exact Mode 的本质是“不要让下游猜”。DemandAgent 在生成需求时已经知道自己看了哪次 Pattern Tree、用了哪个 itemset、有哪些 matched posts、哪些分类节点参与了判断;这些证据必须随需求一起交给 ContentFindAgent。CFA 之后发现、筛选、入库内容时,只要继承这份 evidence,就能从最终 case 回到具体 Pattern 和分类树。
+
+### 8.3 新找到的内容:只能继承“需求来源 Pattern”,不能自动宣称 exact 命中
+
+ContentFindAgent 后续可能用这个需求去搜索、游走,并找到新的内容。这里要分清两种帖子:
+
+| 帖子类型 | 能说明什么 | 不能说明什么 |
+|---|---|---|
+| `source_post_id` 或 `matched_post_ids` 中的帖子 | 它是上游 itemset 的支撑素材,可以 exact 回到 `pattern_execution_id + itemset_id + 分类树节点` | 若启用 merge,仍需 exact hit ratio 判断是否严格命中全部 item。 |
+| CFA 新找到的 `aweme_id/post_id` | 它是被这个需求引导找到的下游内容,可以继承“需求来源 Pattern”的证据 | 不能自动声称这个新 post exact 命中该 Pattern 节点。 |
+
+如果新找到的内容不在上游 `source_post_id/matched_post_ids` 中,只能标成:
+
+```text
+derived_from_pattern_demand
+candidate_pattern_related
+```
+
+除非 CFA 额外完成新 post 的解构、分类绑定,并用同一套 `pattern_itemset_item/topic_pattern_category` 或 PG `pattern_mining_element` 规则重新校验,否则不能把它写成“exact 属于该 Pattern”。
+
+补充说明:CFA 后续的“解构 + 分类树匹配”可以作为新候选视频的 Pattern 回扣判断证据,用来决定规则包 hard gate 是否放行;但它仍然不是上游 Exact Mode。也就是说,它可以证明“新候选能解释回当前需求 Pattern 的相近分类路径”,不能证明“新候选本来就是上游 itemset 的 `source_post_id/matched_post_ids` 支撑素材”。具体接入流程见 [03_Pattern回扣流程.md](content_agent_code_blueprint/03_Pattern回扣流程.md)。
+
+### 8.4 旧 CFA 当前实际链路:弱召回,不是精确血缘
+
+**怎么查**
+
+```text
+features/name
+  -> open_aigc_pattern.topic_pattern_element.name = feature
+  -> topic_pattern_element.post_id
+  -> workflow_decode_task_result.channel_content_id
+  -> purpose_points / key_points / inspiration_points
+```
+
+**DB/代码证据**
+
+| 证据点 | 表/代码 | 说明 |
+|---|---|---|
+| 只按名字查 post | `topic_pattern_element.name = %s` | [get_goodcase_topic_point.py](/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:91) |
+| 回 decode case | `workflow_decode_task_result.channel_content_id in post_ids` | [get_goodcase_topic_point.py](/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:144) |
+| 不带 Pattern 主键 | 没有 `execution_id/category_id/itemset_id` 过滤 | 所以不能恢复“当时具体哪个 Pattern 生成了这个 case”。 |
+
+**业务解释**:旧 CFA 更像“用一个特征词去找历史好 case”,不是“消费某个 Pattern itemset 的完整证据包”。它能帮业务找例子,但不能承担可审计的数据血缘。
+
+### 8.5 PG Pattern V2 与 DemandAgent MySQL Pattern Tree 的边界
+
+如果反查目标是 `library.aiddit.com/pattern/overview` 里的 PG Pattern V2 页面,本手册用的是:
+
+```text
+open_aigc.public.pattern_itemset
+open_aigc.public.pattern_itemset_post
+open_aigc.public.pattern_itemset_item
+open_aigc.public.pattern_mining_category
+```
+
+如果反查目标是 DemandAgent 当前消费的 Pattern Tree,则它主要读 MySQL:
+
+```text
+open_aigc_pattern.topic_pattern_execution
+open_aigc_pattern.topic_pattern_category
+open_aigc_pattern.topic_pattern_element
+open_aigc_pattern.topic_pattern_itemset
+open_aigc_pattern.topic_pattern_itemset_item
+```
+
+这里有一个关键差异:
+
+```text
+PG Pattern V2:
+  pattern_itemset_post 表记录 itemset -> post
+
+DemandAgent MySQL Pattern Tree:
+  没有 topic_pattern_itemset_post 表
+  支撑帖子集合来自 topic_pattern_itemset.matched_post_ids
+```
+
+所以:
+
+| 目标 | MVP 是否足够 | 必要条件 |
+|---|---|---|
+| 追到 DemandAgent 当前消费的 MySQL Pattern Tree | 足够 | `evidence_pack.pattern_source_system=mysql_topic_pattern`,并保存 `topic_pattern_itemset.id/execution_id/mining_config_id/matched_post_ids`、`topic_pattern_itemset_item.category_id`、`topic_pattern_category.id`。 |
+| 追到 PG Pattern V2 主事实和页面 | 还不够 | `evidence_pack` 必须保存 PG 侧 `pattern_execution_id/itemset_id/category_id`,或补齐 PG -> MySQL 的同步映射证明。 |
+
+没有 PG ID 或同步映射时,只能保守说“可追到 DemandAgent 当前消费的 MySQL Pattern Tree”,不能说“已 exact 追到 PG Pattern V2 主事实”。
+
+## 9. Examples
+
+### Example 1:Pattern #1158558 / Post #55157577
+
+这个例子对应截图里的 `Pattern 详情 #1158558`。它是一条祝福类内容 Pattern:一组帖子经常一起出现祝福表达、节庆道具、分享意图,以及若干视觉表现手法。
+
+#### 1. Case 元信息
+
+| 字段 | 值 | 业务含义 |
+|---|---|---|
+| `pattern_itemset.id` | `1158558` | 这条 Pattern 本体。 |
+| `execution_id` | `401` | 这条 Pattern 所属的快照/挖掘执行。 |
+| `mining_config_id` | `1301` / `topic_full_dmax` | 这套挖掘配置,决定它是 topic 分类路径 Pattern。 |
+| `snapshot_date` | `2026-04-23` | 快照日期,分类树位置以这个 execution 为准。 |
+| `execution status` | `is_current=True`, `status=failed` | 当前 UI 选中但执行状态异常,报告保留漂移说明。 |
+| `item_count` / `absolute_support` | `10` / `27` | 这条 Pattern 有 10 个 item,由 27 条帖子支撑。 |
+
+#### 2. Pattern 详情反查:10 个 item 在哪棵树上
+
+这 10 个 chip 全部来自 `pattern_itemset_item`,并通过 `category_id` 指向 `pattern_mining_category.id`。也就是说,它们来自 execution 401 的 Pattern V2 快照分类树。
+
+| # | chip 名称 | 点类型 | 维度 | 快照 category_id | 父链 | 直接子节点 | 叶子 | 业务含义 | 当前 global 状态 |
+|---:|---|---|---|---:|---|---|---|---|---|
+| 1 | 祝福词句 | 关键点 | 实质 | 43925 | 表象 > 符号 > 表达符号 > 祝福语 > 吉祥用语 > 祝福词句 | 吉祥话(45124, count=68);序列祝福(44199, count=31);特色祝福语(45473, count=29);财运祝福(44910, count=24) | 否 | 内容中出现的祝福表达素材,比如早安、好运、健康、快乐这类祝愿文字。 | 当前无 active;历史行 retired_at_execution_id=1889 |
+| 2 | 吉祥民俗 | 关键点 | 实质 | 44644 | 表象 > 符号 > 装饰符号 > 吉祥图案 > 综合意象 > 符号概念 > 吉祥民俗 | 无直接子节点,叶子节点 | 是 | 带有吉祥、节庆、民俗寓意的文化符号,用来让内容显得喜庆、有传统祝福意味。 | 当前无 active;历史行 retired_at_execution_id=1893,1894 |
+| 3 | 动态捕捉 | 关键点 | 形式 | 46795 | 呈现 > 视觉 > 影像制作 > 实景拍摄 > 拍摄方式 > 即时捕捉 > 动态捕捉 | 无直接子节点,叶子节点 | 是 | 视频或画面表现手法,强调把烟花、粒子、礼物喷涌等动态瞬间拍出来。 | 当前无 active;历史行 retired_at_execution_id=2103 |
+| 4 | 堆叠排列 | 关键点 | 形式 | 45975 | 呈现 > 视觉 > 构图编排 > 空间布局 > 元素编排 > 排列节奏 > 堆叠排列 | 无直接子节点,叶子节点 | 是 | 画面构图手法,把祝福语、装饰元素或物品密集叠放,形成热闹、饱满的视觉效果。 | 当前无 active;历史行 retired_at_execution_id=2103 |
+| 5 | 轴线对称 | 关键点 | 形式 | 46294 | 呈现 > 视觉 > 构图编排 > 空间布局 > 构图方式 > 对称分割 > 中轴对称 > 轴线对称 | 无直接子节点,叶子节点 | 是 | 画面构图手法,用中轴或对称关系让画面看起来稳定、仪式感强。 | 当前无 active;历史行 retired_at_execution_id=2103 |
+| 6 | 配色组合 | 关键点 | 形式 | 46641 | 呈现 > 视觉 > 视觉气质 > 色彩调性 > 背景底色 > 配色组合 | 无直接子节点,叶子节点 | 是 | 色彩搭配手法,说明内容靠多种颜色组合制造节日感或视觉冲击。 | 当前无 active;历史行 retired_at_execution_id=2087 |
+| 7 | 单色饱和度 | 关键点 | 形式 | 46097 | 呈现 > 视觉 > 视觉气质 > 色彩调性 > 色彩强化 > 饱和度调节 > 单色饱和度 | 无直接子节点,叶子节点 | 是 | 色彩强化手法,强调某一类高饱和颜色,让画面更醒目、更有情绪。 | 当前无 active;历史行 retired_at_execution_id=2103 |
+| 8 | 福袋 | 灵感点 | 实质 | 44587 | 表象 > 实体 > 物品 > 器物 > 道具 > 节庆饰物 > 福袋 | 无直接子节点,叶子节点 | 是 | 画面里的节庆道具或实物元素,常用来表达好运、财富、祝福。 | 当前无 active;历史行 retired_at_execution_id=2099 |
+| 9 | 节日祝福 | 目的点 | 实质 | 43447 | 表象 > 符号 > 表达符号 > 祝福语 > 节日祝福 | 时令节庆(44574, count=110);纪念日祝福(44088, count=32);年节贺岁(47211, count=0) | 否 | 内容的祝愿主题,把节日、日期或特殊时刻和祝福表达连在一起。 | 当前无 active;历史行 retired_at_execution_id=1889 |
+| 10 | 分享 | 目的点 | 意图 | 47134 | 信息传递 > 信息扩散 > 分享 | 无直接子节点,叶子节点 | 是 | 内容的传播意图,表示作者想把祝福、歌曲、景观或信息传递给别人。 | 当前 active,path=/信息传递/信息扩散/分享 |
+
+业务读法:`祝福词句` 和 `节日祝福` 是祝福内容本身;`福袋` 是画面里的节庆道具;`分享` 是传播意图;`动态捕捉/堆叠排列/轴线对称/配色组合/单色饱和度` 是视觉表达方式。
+
+#### 3. Post #55157577 反查 Pattern
+
+帖子 `55157577`:`周日早上好今天是6月29日最美的祝福送给你`,平台 `piaoquan`。
+
+- 从 `pattern_itemset_post.post_id=55157577` 出发,在 `mining_config_id=1301` 下反查到 `67` 个 itemset。
+- 这些 itemset 中包含 `Pattern #1158558`。
+- `Pattern #1158558` 自己的 matched posts 共 `27` 条,并包含 `55157577`。
+
+业务读法:这条早安祝福帖子不是孤立地出现在页面上,它被系统记录为支撑“祝福表达 + 节庆道具 + 分享意图 + 视觉手法”这类 Pattern 的样本。
+
+#### 4. Post #55157577 自身快照元素
+
+| # | point_type | element_type | 元素名 | point_text | 快照节点 | 父链 | 业务含义 |
+|---:|---|---|---|---|---|---|---|
+| 1 | 关键点 | 实质 | 祝福语 | 祝福语堆叠 | 祝福词句 | 表象 > 符号 > 表达符号 > 祝福语 > 吉祥用语 > 祝福词句 | 内容中出现的祝福表达素材,比如早安、好运、健康、快乐这类祝愿文字。 |
+| 2 | 关键点 | 实质 | 吉祥意象 | 吉祥意象组合 | 吉祥意象 | 表象 > 符号 > 装饰符号 > 吉祥图案 > 综合意象 > 意象概念 > 吉祥意象 | 内容里被讲到或被看见的对象/主题,出现在 `关键点` 这类解构点中。 |
+| 3 | 关键点 | 形式 | 组合 | 吉祥意象组合 | 搭配手法 | 呈现 > 视觉 > 形象塑造 > 造型装扮 > 穿搭呈现 > 搭配手法 | 内容的表达方式、视觉手法或组织方式,出现在 `关键点` 这类解构点中。 |
+| 4 | 关键点 | 形式 | 粒子特效 | 动态粒子特效 | 粒子效果 | 呈现 > 视觉 > 影像制作 > 后期处理 > 动态特效 > 粒子光效 > 粒子效果 | 内容的表达方式、视觉手法或组织方式,出现在 `关键点` 这类解构点中。 |
+| 5 | 关键点 | 形式 | 动态 | 动态粒子特效 | 动态捕捉 | 呈现 > 视觉 > 影像制作 > 实景拍摄 > 拍摄方式 > 即时捕捉 > 动态捕捉 | 视频或画面表现手法,强调把烟花、粒子、礼物喷涌等动态瞬间拍出来。 |
+| 6 | 关键点 | 形式 | 堆叠 | 祝福语堆叠 | 堆叠排列 | 呈现 > 视觉 > 构图编排 > 空间布局 > 元素编排 > 排列节奏 > 堆叠排列 | 画面构图手法,把祝福语、装饰元素或物品密集叠放,形成热闹、饱满的视觉效果。 |
+| 7 | 关键点 | 形式 | 对称式构图 | 对称式构图 | 轴线对称 | 呈现 > 视觉 > 构图编排 > 空间布局 > 构图方式 > 对称分割 > 中轴对称 > 轴线对称 | 画面构图手法,用中轴或对称关系让画面看起来稳定、仪式感强。 |
+| 8 | 关键点 | 形式 | 高饱和度配色 | 高饱和度配色 | 配色饱和度 | 呈现 > 视觉 > 视觉气质 > 色彩调性 > 色彩强化 > 饱和度调节 > 配色饱和度 | 内容的表达方式、视觉手法或组织方式,出现在 `关键点` 这类解构点中。 |
+| 9 | 灵感点 | 实质 | 福袋 | 福袋喷涌烟花 | 福袋 | 表象 > 实体 > 物品 > 器物 > 道具 > 节庆饰物 > 福袋 | 画面里的节庆道具或实物元素,常用来表达好运、财富、祝福。 |
+| 10 | 灵感点 | 实质 | 喷涌烟花 | 福袋喷涌烟花 | 场景烟花 | 表象 > 符号 > 装饰符号 > 装饰元素 > 动态装饰 > 烟花特效 > 场景烟花 | 内容里被讲到或被看见的对象/主题,出现在 `灵感点` 这类解构点中。 |
+| 11 | 目的点 | 实质 | 早安祝福 | 分享早安祝福 | 早安问候 | 表象 > 符号 > 表达符号 > 祝福语 > 日常问候 > 早安问候 | 内容里被讲到或被看见的对象/主题,出现在 `目的点` 这类解构点中。 |
+| 12 | 目的点 | 意图 | 分享 | 分享早安祝福 | 分享 | 信息传递 > 信息扩散 > 分享 | 内容的传播意图,表示作者想把祝福、歌曲、景观或信息传递给别人。 |
+
+#### 5. Exact hit 校验
+
+`55157577` 对 `Pattern #1158558` 精确命中 `6/10`,ratio=`0.6`。
+
+| Pattern item | exact hit | post 里的元素 | source_table/source_element_id | point_text |
+|---|---|---|---|---|
+| 祝福词句 (关键点/实质) | 是 | 祝福语 | post_decode_topic_point_element/61640 | 祝福语堆叠 |
+| 吉祥民俗 (关键点/实质) | 否 | - | - | - |
+| 堆叠排列 (关键点/形式) | 是 | 堆叠 | post_decode_topic_point_element/61641 | 祝福语堆叠 |
+| 单色饱和度 (关键点/形式) | 否 | - | - | - |
+| 轴线对称 (关键点/形式) | 是 | 对称式构图 | post_decode_topic_point_element/61637 | 对称式构图 |
+| 配色组合 (关键点/形式) | 否 | - | - | - |
+| 动态捕捉 (关键点/形式) | 是 | 动态 | post_decode_topic_point_element/61638 | 动态粒子特效 |
+| 福袋 (灵感点/实质) | 是 | 福袋 | post_decode_topic_point_element/61632 | 福袋喷涌烟花 |
+| 节日祝福 (目的点/实质) | 否 | - | - | - |
+| 分享 (目的点/意图) | 是 | 分享 | post_decode_topic_point_element/61635 | 分享早安祝福 |
+
+本 case 结论:`55157577` 是 `1158558` 的支撑帖子,但不是严格包含全部 10 个 item 的帖子。业务表达上应该说“它支撑这个 Pattern”,不要说“它完整命中这个 Pattern”。
+
+## 10. Validation:多 case 压测
+
+本轮用 6 个 `mining_config_id=1301` 的 itemset 压测同一套反查方法。结论是:`pattern_itemset_item.category_id -> pattern_mining_category` 的树节点解析稳定;但 `pattern_itemset_post` 里的 matched posts 同时存在严格全命中和 partial/soft coverage。
+
+| itemset_id | item_count | support posts | 叶子/父层 | exact ratio min-max-avg | strict posts | partial posts | 语义判断 |
+|---:|---:|---:|---|---|---:|---:|---|
+| 1158558 | 10 | 27 | 8/2 | 0.6-0.9-0.6803 | 0 | 27 | soft_coverage_partial |
+| 1165384 | 3 | 510 | 2/1 | 0.6667-1-0.8958 | 350 | 160 | mixed_strict_and_partial |
+| 1155829 | 4 | 487 | 2/2 | 0.75-1-0.827 | 150 | 337 | mixed_strict_and_partial |
+| 1158667 | 6 | 302 | 4/2 | 0.6667-1-0.7234 | 12 | 290 | mixed_strict_and_partial |
+| 1165858 | 8 | 82 | 5/3 | 0.625-0.875-0.6723 | 0 | 82 | soft_coverage_partial |
+| 1150227 | 4 | 10 | 3/1 | 0.75-0.75-0.75 | 0 | 10 | soft_coverage_partial |
+
+叶子/父层列的格式是 `leaf_count/non_leaf_count`。可以看到 6 个 case 都不是“只含叶子”或“只含父节点”的简单形态;分类路径 Pattern 里父层和叶子可以共存。
+
+### best/worst sample posts
+
+| itemset_id | sample | post_id | exact hit | ratio | title |
+|---:|---|---|---|---:|---|
+| 1158558 | best | 57627005 | 9/10 | 0.9 | 9月1日开门红,早安!愿你所遇皆美好,红红火火 |
+| 1158558 | worst | 55157577 | 6/10 | 0.6 | 周日早上好今天是6月29日最美的祝福送给你 |
+| 1165384 | best | 21653502 | 3/3 | 1 |  一首好听的歌,说的好,唱的更好!,《知心朋友》 |
+| 1165384 | worst | 45493439 | 2/3 | 0.6667 | 🔴终于找到这首歌!景美歌更美 |
+| 1155829 | best | 21862937 | 4/4 | 1 | 相互问候,胜过亲身相见!️ |
+| 1155829 | worst | 16078129 | 3/4 | 0.75 | 🧡花车游北京,太美了! |
+| 1158667 | best | 56160200 | 6/6 | 1 | 💐💐今天是7月24日,早上好,最美的祝福送给你! |
+| 1158667 | worst | 13454544 | 4/6 | 0.6667 | 💗今天正月十五!祝大家元宵节快乐!💕家庭美满!🎉幸福安康! |
+| 1165858 | best | 55366311 | 7/8 | 0.875 | 🌺早上好!健康长久,送给我牵挂的人~ |
+| 1165858 | worst | 45536951 | 5/8 | 0.625 | 🔴新的一年里!我要每天祝福你!分享给老友 |
+| 1150227 | best | 55157577 | 3/4 | 0.75 | 周日早上好今天是6月29日最美的祝福送给你 |
+
+因此,通用手册只能写:`pattern_itemset_post` 是 Pattern 的支撑帖子映射;如果配置启用 merge,需要再查 exact hit ratio。不能写成“每个 matched post 一定严格包含 Pattern 的全部 item”。
+
+## 11. 四个小问题
+
+### 1. `Pattern 详情 #1158558` 是不是来自 `pattern_itemset / pattern_itemset_item / pattern_itemset_post`?
+
+是。`#1158558` 是 `pattern_itemset.id`;详情页的 10 个 chip 来自 `pattern_itemset_item where itemset_id=1158558`;“匹配帖子 27”来自 `pattern_itemset_post where itemset_id=1158558`。前端 `selectItemset()` 请求 `/api/pattern/executions/{execution_id}/itemsets/{itemset_id}`,然后用返回的 `items` 和 `matched_post_ids` 渲染页面。
+
+### 2. 这是不是“分类版 Pattern 库”?
+
+可以这么叫,更准确叫“分类路径 Pattern 库”。因为 `mining_config_id=1301` 是 `topic_full_dmax`,算法输入 item 来自 `pattern_mining_element.category_path`,落库时 `pattern_itemset_item.category_id/category_path` 有值,而 `element_id/element_name` 为空。也就是说它挖的是分类节点路径组合,不是具体标准元素名组合。
+
+### 3. `itemset` 指 `Pattern #1158558` 那 10 个 item 的组合,还是一个个帖子?
+
+`itemset` 指一个 Pattern 的 item 组合,不是帖子。`pattern_itemset.id=1158558` 是一条 Pattern;`item_count=10` 表示这条 Pattern 由 10 个 item 组成;`absolute_support=27` 表示有 27 个 matched posts 支撑它。
+
+### 4. `item` 在代码里到底指 pattern item 还是帖子?
+
+在这条链路里,`item` 指 Pattern item,不是帖子。帖子在代码和表里叫 `post`、`matched_post_ids` 或 `pattern_itemset_post`。FP-Growth 代码里:`transactions` 是帖子级交易集合;`iset` 是一个挖出的 itemset tuple;`items` / `parsed_items` 是 itemset 里的 item;`matched` / `matched_post_ids` 是支撑该 itemset 的帖子集合。
+
+## 12. 证据索引
+
+- 结构化 DB 结果见 [case-pack.json](/Users/samlee/Documents/works/数据工程/domains/pattern-v2/runs/2026-06-04-pattern-1158558-post-55157577/verification/case-pack.json)。
+- 可复用 SQL 见 [readonly-pattern-1158558-post-55157577.sql](/Users/samlee/Documents/works/数据工程/domains/pattern-v2/runs/2026-06-04-pattern-1158558-post-55157577/sql/readonly-pattern-1158558-post-55157577.sql)。
+- 漂移记录见 [drift-register.md](/Users/samlee/Documents/works/数据工程/domains/pattern-v2/runs/2026-06-04-pattern-1158558-post-55157577/verification/drift-register.md)。
+- 代码证据已经嵌入每条链路;本节只保留索引,不再集中堆放。
+
+## Subagent 交叉核验结果
+
+| agent | 状态 | 核验结论 |
+|---|---|---|
+| `db_case_agent` | `completed_readonly` | Verified execution 401/config 1301 counts, selected itemsets, leaf/non-leaf mix, and strict vs partial exact-hit behavior. Confirmed selected item categories resolve 100% into the snapshot tree. |
+| `code_api_agent` | `completed_readonly` | Verified frontend overview/detail routes, backend itemset APIs, ORM models, category tree API, snapshot writer, FP-Growth writer, and merge service. No first-class public REST endpoint was found whose primary contract is post_id -> all itemsets; use the table path or add a public API if needed. |
+| `review_agent` | `completed_readonly` | Recommended separating generic method from this case, making execution/config scope explicit, and warning that matched posts are support rows whose containment semantics depend on mining/merge settings. |
+
+DB case agent 还额外抽查了 strict all leaf-only、strict all mixed tree、soft/tree partial 三类样本,用来确认本报告没有从 `1158558/55157577` 一个单例过度外推。
+
+## 13. 漂移与边界
+
+- `execution_id=401` 在 DB 中 `is_current=true`,但 `status=failed`,错误是 SQLAlchemy `DetachedInstanceError`。本报告只说明 `1301/1158558` 这些已落库结果的血缘,不把 execution 401 说成一次完整成功 run。
+- 多数 item 的 `source_stable_id + source_type` 当前没有 active `global_category` 行,只有历史 retired 行;因此分类树位置以 `pattern_mining_category` 快照为准,当前 `global_category` 只用于回指和漂移说明。
+- `open_aigc_pattern` 没有参与本 case 主事实证明;这里不讨论下游 DemandAgent/build 工作台。
+- 从 ContentFindAgent 只拿 `post_id` 只能进入 Candidate Mode,不能唯一确定具体 Pattern;Exact Mode 必须依赖 DemandAgent 或上游 evidence 保留 `pattern_execution_id + itemset_id + source_post_id + category_bindings`。

+ 194 - 0
tech_documents/content_agent_architecture/00_架构总览.md

@@ -0,0 +1,194 @@
+# Content Agent 架构总览
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 目标
+
+本文档包用于沉淀新版 Content Agent 的长期架构设计,不直接替代 `product_documents/prd/V1落地版本细化版.md`。
+
+当前 V1 是最小闭环:
+
+```text
+Pattern evidence_pack
+-> Pattern 策略种子
+-> Query
+-> 抖音关键词搜索
+-> 视频候选
+-> 判断信号汇总
+-> 规则包决策
+-> 作者作品一跳扩展
+-> 内容 / 作者 / 淘汰 / 运行追踪记录(trace)沉淀
+-> 策略学习复盘
+```
+
+长期目标是支持:
+
+- 多数据源:Pattern、Case、历史搜索、历史作者、热点、养号等。
+- 多平台:抖音、小红书、快手、B 站、视频号、票圈等。
+- 可组装策略:判断规则、游走边、预算、停止条件可独立迭代。
+- 可学习闭环:从运行追踪记录(trace)、结果和表现中学习下一轮 query、规则、预算和数据源优先级。
+- 高吞吐执行:未来可以支撑每天几万个视频级别的候选召回、判断、游走和复盘。
+
+## 2. 设计原则
+
+七阶段流程是执行视角,不直接等于模块边界:
+
+```text
+数据源 -> Query -> Platform -> 判断 -> 游走 -> 资产清洗沉淀 -> 策略学习
+```
+
+模块边界按业务责任和数据责任划分:
+
+- 谁负责定义这个概念。
+- 谁可以修改这个数据。
+- 谁只能读取或引用。
+- 谁负责做判断。
+- 谁只负责对接平台或外部系统。
+
+最重要的解耦原则:
+
+- 平台接入模块不负责判断逻辑。
+- 判断规则模块不直接查平台、不直接决定下一跳。
+- 游走策略模块不直接做质量评分。
+- 结果沉淀与来源反查模块不重新解释规则结果,只沉淀结果和来源关系。
+- 策略学习模块不回写篡改历史,只基于不可变运行追踪记录(trace) 产出下一轮建议。
+
+## 3. 第一版业务模块
+
+| 编号 | 业务模块 | 定位 |
+|---|---|---|
+| 1 | 数据源与种子模块 | 接入来源证据,验证 Pattern / Case / 作者 / 热点等源是否可进入流程 |
+| 2 | 搜索意图模块 | 把 seed 转成可执行搜索意图和平台 query |
+| 3 | 平台接入模块 | 对接平台动作,做字段归一、分页、限流和错误处理 |
+| 4 | 候选与证据模块 | 承载候选池,组装 EvidenceBundle |
+| 5 | 判断规则模块 | 根据规则包输出入池、候选、pending、淘汰、扩展、停止等决策 |
+| 6 | 游走策略模块 | 管理从视频、作者、tag、作品继续扩展的路径、预算和停止条件 |
+| 7 | 策略版本管理模块 | 管理规则包、游走策略、query 生成、平台动作的版本和组合 |
+| 8 | 结果沉淀与来源反查模块 | 沉淀内容资产、作者资产、搜索线索资产和稳定来源视图 |
+| 9 | 运行记录模块 | 记录 run、事件、source edge、搜索线索效果、错误、幂等和复盘证据 |
+| 10 | 策略学习模块 | 基于运行追踪记录(trace)和结果产出下一轮可执行调整建议 |
+
+核心模块优先级:
+
+1. 判断规则模块
+2. 游走策略模块
+3. 结果沉淀与来源反查模块
+4. 策略学习模块
+5. 策略版本管理模块
+
+## 4. 文档包结构
+
+```text
+tech_documents/content_agent_architecture/
+  核心主线:
+  00_架构总览.md
+  01_统一叫法与术语表.md
+  02_业务模块划分.md
+  03_模块关系与数据责任.md
+  04_核心运行链路.md
+  05_规则包与游走策略设计.md
+  06_判断证据包与候选模型.md
+  07_运行记录与来源路径.md
+  08_结果沉淀与来源反查.md
+  09_模块接口与外部接入说明.md
+
+  后续演进:
+  future/
+    01_策略学习与实验闭环.md
+    02_高吞吐执行架构预研.md
+
+  审查归档:
+  review/archive/
+    2026-06-05_架构交叉验证报告.md
+
+  决策记录:
+  ADR/
+    0001_为什么规则包独立于游走策略.md
+    0002_为什么保留全量候选池.md
+    0003_为什么要单独记录source_edge.md
+    0004_source_edges写入边界.md
+    0005_先模块化单体后服务化队列化.md
+    0006_规则包只负责产出RuleDecision.md
+    0007_V1引入薄FastAPI和LangGraph.md
+```
+
+## 5. 写代码前必须完成的三步
+
+### 第一步:统一叫法表
+
+时机:现在立即开始,写代码前必须完成第一版。
+
+目标:
+
+- 明确定义 `Candidate`、`ContentAsset`、`SearchClueObservation`、`SearchClueAsset`、`RuleDecision`、`WalkEdge` 等词。
+- 禁止同一个词跨模块表达不同含义。
+- 明确哪些词是过程数据,哪些是最终资产。
+
+产物:
+
+- `01_统一叫法与术语表.md`
+
+### 第二步:核心对象和数据责任
+
+时机:统一叫法第一版确认后,进入技术架构和数据表设计前完成。
+
+目标:
+
+- 给每个模块定核心对象。
+- 明确谁负责写入或维护 `source_edges`、`rule_decisions`、`candidate_pool`、`trace_events`、`search_clues`、`final_output`。
+- 明确哪些模块只能追加,哪些模块可以更新状态。
+
+产物:
+
+- `02_业务模块划分.md`
+- `03_模块关系与数据责任.md`
+
+### 第三步:可插拔模块接口
+
+时机:数据责任确认后,写任何规则引擎、游走策略执行器、平台接入模块代码前完成。
+
+目标:
+
+- 判断规则最小输入输出:
+
+```text
+EvidenceBundle + RulePackVersion -> RuleDecision
+```
+
+- 游走策略最小输入输出:
+
+```text
+PathState + RuleDecision + WalkStrategyVersion -> WalkAction
+```
+
+- 所有接口说明必须包含最小输入、最小输出、版本号和可复盘字段。
+
+产物:
+
+- `05_规则包与游走策略设计.md`
+- `07_运行记录与来源路径.md`
+- `09_模块接口与外部接入说明.md`
+
+## 6. 后续讨论顺序
+
+建议按以下顺序推进,不直接进入服务拆分:
+
+1. `01_统一叫法与术语表.md`:统一叫法表。
+2. `02_业务模块划分.md`:业务模块划分和模块关系。
+3. `03_模块关系与数据责任.md`:数据责任和核心对象。
+4. `04_核心运行链路.md`:核心运行链路。
+5. `05_规则包与游走策略设计.md`:判断规则输入输出和游走策略输入输出。
+6. `06_判断证据包与候选模型.md`:EvidenceBundle / Candidate 模型。
+7. `07_运行记录与来源路径.md`:运行追踪、来源记录和运行记录。
+8. `08_结果沉淀与来源反查.md`:结果沉淀和来源反查。
+9. `09_模块接口与外部接入说明.md`:模块接口与外部接入方式。
+10. `ADR/`:只记录关键架构决策,不承担主设计说明。
+
+`future/` 下的学习闭环和高吞吐预研是后续演进材料,不属于写代码前的核心必读链路。

+ 140 - 0
tech_documents/content_agent_architecture/01_统一叫法与术语表.md

@@ -0,0 +1,140 @@
+# 统一叫法与术语表
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 目的
+
+本文定义 Content Agent 的统一叫法,避免不同文档、代码、规则包、运行记录对同一个词使用不同含义。
+
+原则:
+
+- 产品、代码、规则配置、运行记录使用同一组词。
+- 一个词只能有一个主含义。
+- 如果两个阶段都用同一个词,必须说明它在哪个模块负责解释。
+
+## 2. 核心词表
+
+| 词 | 中文名 | 所属模块 | 定义 | 不是 |
+|---|---|---|---|---|
+| `SourceContext` | 来源信息 | 数据源与种子模块 | 本次运行 的上游来源、需求单、Pattern / Case 证据和启动参数 | 不是候选池 |
+| `EvidencePack` | 上游证据包 | 数据源与种子模块 | DemandAgent 或上游写入的 Pattern / Case 来源证据 | 不是判断用 EvidenceBundle |
+| `SeedPack` | 种子包 | 数据源与种子模块 | 从来源证据提取出的 Pattern item、分类、元素、seed_terms | 不是 query |
+| `SeedTerm` | 种子词 | 数据源与种子模块 | 可用于生成 query 的原始搜索素材 | 不是最终执行 query |
+| `SearchIntent` | 搜索意图 | 搜索意图模块 | 从 seed 生成的搜索方向,尚未绑定具体平台动作 | 不是平台请求 |
+| `ExecutableQuery` | 可执行 query | 搜索意图模块 | 已经过裁剪、去重、可投放到平台搜索的 query | 不是搜索结果 |
+| `SearchClueObservation` | 搜索线索观测 | 运行记录模块 | 记录某个 query、tag query 或历史路径在一次运行 中的效果 | 不是正式资产 |
+| `SearchClueAsset` | 搜索线索资产 | 结果沉淀与来源反查模块 | 经复盘或人工确认后可复用的 query、tag 或搜索路径线索 | 不是本次执行 query |
+| `PlatformAction` | 平台动作 | 平台接入模块 | 抖音关键词搜索、作者作品、小红书笔记搜索等动作 | 不是业务判断 |
+| `PlatformResult` | 平台返回结果 | 平台接入模块 | 平台 API 或页面采集返回的原始或归一化结果 | 不是候选最终结论 |
+| `Candidate` | 候选 | 候选与证据模块 | 从平台结果抽出的可被判断的内容、作者、tag 或路径过程对象 | 不是已沉淀资产 |
+| `CandidatePool` | 候选池 | 候选与证据模块 | 全量候选集合,包含未判断、candidate、pending、rejected 等过程对象 | 不是 final output |
+| `MediaEvidence` | 媒体状态记录 | 候选与证据模块 | 视频媒体元数据、下载状态、OSS 状态或不可用原因 | 不是结果沉淀与来源反查模块的业务资产 |
+| `EvidenceBundle` | 判断证据包 | 候选与证据模块 | 输入规则包的标准证据结构 | 不是最终决策 |
+| `RulePack` | 规则包 | 判断规则模块 | 一组硬门槛、软评分、输出动作和证据要求 | 不是游走策略 |
+| `RuleDecision` | 规则决策 | 判断规则模块 | 规则包对候选、作者、tag 或路径的判断结果 | 不是下一跳计划 |
+| `RejectReason` | 淘汰原因 | 判断规则模块 | 标准化淘汰或停止原因码 | 不是自然语言备注 |
+| `WalkStrategy` | 游走策略 | 游走策略模块 | 从一个节点继续扩展到另一个节点的策略配置 | 不是规则包 |
+| `WalkEdge` | 游走边 | 游走策略模块 | 如 `Video -> Author`、`Author -> AuthorWorksPage` 的可执行扩展边 | 不是 source edge |
+| `PathState` | 路径状态 | 游走策略模块 | 当前游走深度、预算、连续空召回、连续低质等状态 | 不是 trace 日志 |
+| `PolicyBundle` | 策略组合包 | 策略版本管理模块 | 某次运行 使用的规则包、游走策略、query 生成和平台动作版本组合 | 不是单个规则包 |
+| `Asset` | 资产 | 结果沉淀与来源反查模块 | 经清洗、判断引用和提交规则后沉淀的内容、作者或线索 | 不是临时候选 |
+| `ContentAsset` | 内容资产 | 结果沉淀与来源反查模块 | 最终输出中的内容结果,使用 `asset_status` 表示入池或候选资产状态 | 不是平台原始视频对象 |
+| `AuthorAsset` | 作者资产 | 结果沉淀与来源反查模块 | 满足作者沉淀规则的作者结果 | 不是搜索返回作者线索 |
+| `SourceEdge` | 来源记录 | 运行记录模块 | 记录 Source、Seed、Query、Candidate、Author、Tag、Asset 之间的运行事实边 | 不是游走边配置 |
+| `SourceLineage` | 稳定来源路径 | 结果沉淀与来源反查模块 | 基于 `SourceEdge` 归并出的资产可查询来源路径 | 不是新的运行事实 |
+| `RunRecord` | 运行记录 | 运行记录模块 | 一次运行 的结构化运行记录集合 | 不是普通日志 |
+| `TraceEvent` | 追踪事件 | 运行记录模块 | 每一步输入、输出、状态、错误的事件记录 | 不是规则决策 |
+| `LearningSignal` | 学习信号 | 策略学习模块 | 从运行追踪记录(trace)、结果和表现提取出的可解释信号 | 不是自动训练结果 |
+| `StrategyAdjustmentProposal` | 策略调整建议 | 策略学习模块 | 下一轮可执行的 query、规则、预算、数据源优先级调整建议 | 不是直接修改历史 |
+
+## 3. 容易混用的词
+
+### Candidate 和 Asset
+
+`Candidate` 是等待判断或已经被判断过的过程对象。
+
+`Asset` 是经过清洗、绑定和沉淀规则后保留下来的可复用对象。
+
+不能把平台搜索返回的视频直接称为内容资产。
+
+### SearchClueObservation、SearchClueAsset 和 Query
+
+`ExecutableQuery` 是本次实际执行的搜索词。
+
+`SearchClueObservation` 是本次运行 的线索效果记录,可能来自有效 query、失败 query、tag 扩散或历史搜索路径。
+
+`SearchClueAsset` 是被确认可复用后的线索资产,由结果沉淀与来源反查模块负责。
+
+搜索意图模块只负责 `SearchIntent` 和 `ExecutableQuery`,不负责线索资产。
+
+### RuleDecision 和 WalkEdge
+
+`RuleDecision` 回答“值不值得留、扩、停、淘汰”。
+
+`WalkEdge` 回答“从哪里可以走到哪里”。
+
+规则包不直接负责游走边,游走策略也不直接打分。
+
+### WalkEdge 和 SourceEdge
+
+`WalkEdge` 是策略配置,例如 `Video -> Hashtag -> Query`。
+
+`SourceEdge` 是运行事实,例如某个具体 `query_id` 找到了某个 `aweme_id`。
+
+前者是计划,后者是事实。`SourceEdge` 由运行记录模块追加,结果沉淀与来源反查模块只读取并归并成 `SourceLineage`。
+
+### EvidencePack 和 EvidenceBundle
+
+`EvidencePack` 是上游来源证据。
+
+`EvidenceBundle` 是进入规则包的判断证据。
+
+它们不能合并成一个大 JSON,否则会混淆来源证明和判断输入。
+
+## 4. 状态词
+
+### 4.1 candidate_status
+
+`candidate_status` 只用于候选与证据模块的过程对象。
+
+| 状态 | 所属对象 | 含义 |
+|---|---|---|
+| `unreviewed` | 候选 | 已进入候选池,尚未完成规则判断 |
+| `candidate` | 候选 | 分数或证据不足以入池,但允许后续人工或补证 |
+| `pending` | 候选 / trace | 不进入正式资产表,只保留运行追踪记录(trace) 和判断记录 |
+| `rejected` | 候选 | 淘汰 |
+| `blocked` | 候选 / 路径 | 外部接口、权限、媒体不可用等导致无法继续 |
+
+### 4.2 asset_status
+
+`asset_status` 只用于结果沉淀与来源反查模块的最终输出或稳定资产视图。
+
+| 状态 | 所属对象 | 含义 |
+|---|---|---|
+| `pooled` | 内容资产 | 已入池 |
+| `candidate_asset` | 内容资产 | 可进入候选资产视图,但不是正式入池 |
+| `stored_author` | 作者资产 | 已沉淀作者 |
+| `clue_only` | 搜索线索资产 | 只作为可复用线索保存 |
+
+### 4.3 path_status
+
+| 状态 | 所属对象 | 含义 |
+|---|---|---|
+| `expanded` | 作者 / tag / 路径 | 已触发扩展 |
+| `stopped` | 路径 | 因预算、低质、空召回、风险等停止 |
+| `blocked` | 运行 / 路径 | 外部接口、权限、媒体不可用等导致无法继续 |
+
+## 5. 已收敛结论
+
+- `candidate` 作为过程状态只写候选池;进入最终输出时改用 `asset_status`。
+- `SearchClueObservation` 写入运行记录,`SearchClueAsset` 由结果沉淀与来源反查模块沉淀。
+- `MediaEvidence` 沿用 V1 文件名表示媒体状态记录,不表示业务资产;代码中可优先命名为 `MediaEvidence` 或 `MediaObject`。
+- `PolicyBundle` 在一次运行 内固定一个版本引用;后续阶段差异通过新的 `PolicyBundleVersion` 表达。
+- 淘汰不属于资产,淘汰摘要属于候选池、规则决策和 trace 复盘。

+ 161 - 0
tech_documents/content_agent_architecture/02_业务模块划分.md

@@ -0,0 +1,161 @@
+# 业务模块划分
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 划分原则
+
+本系统按业务责任和数据责任划分模块,不按页面、平台或七阶段流程直接切分。
+
+判定一个模块是否应该独立,看四个问题:
+
+1. 它是否负责一组稳定业务概念。
+2. 它是否需要独立演化。
+3. 它是否对外输出明确接口说明。
+4. 它是否不应该被平台接口或单次流程绑死。
+
+## 2. 业务模块总表
+
+| 模块 | 核心问题 | 负责的主要对象 | 对外输出 |
+|---|---|---|---|
+| 数据源与种子模块 | 从哪里开始,来源是否有效 | `SourceContext`、`EvidencePack`、`SeedPack` | `source_context`、`pattern_seed_pack` |
+| 搜索意图模块 | 搜什么,为什么搜 | `SearchIntent`、`ExecutableQuery` | `queries`、query 来源说明 |
+| 平台接入模块 | 在哪个平台执行,拿到什么字段 | `PlatformAction`、`PlatformResult` | 归一化平台结果 |
+| 候选与证据模块 | 哪些对象可以进入判断 | `Candidate`、`CandidatePool`、`EvidenceBundle`、`MediaEvidence` | `candidate_pool`、`media_assets`、`EvidenceBundle` |
+| 判断规则模块 | 值不值得留、扩、停、淘汰 | `RulePack`、`RuleDecision`、`Scorecard`、`RejectReason` | `rule_decisions` |
+| 游走策略模块 | 从这个点还能去哪 | `WalkStrategy`、`WalkEdge`、`PathState`、`Budget` | 下一跳动作或停止 |
+| 策略版本管理模块 | 本次运行 用哪套策略组合 | `PolicyBundle`、`RulePackVersion`、`WalkStrategyVersion` | 版本引用 |
+| 结果沉淀与来源反查模块 | 沉淀什么,如何反查来源 | `ContentAsset`、`AuthorAsset`、`SearchClueAsset`、`SourceLineage` | `final_output`、稳定来源视图 |
+| 运行记录模块 | 本次运行发生了什么 | `Run`、`TraceEvent`、`RunRecord`、`SourceEdge`、`SearchClueObservation` | `trace_events`、`source_edges`、`search_clues`、运行状态 |
+| 策略学习模块 | 下一轮怎么改 | `LearningSignal`、`Experiment`、`StrategyAdjustmentProposal` | 学习报告和调整建议 |
+
+## 3. 模块说明
+
+### 3.1 数据源与种子模块
+
+负责接入 Pattern、Case、历史搜索、历史作者、热点、养号等来源。
+
+它只证明“可以从这里开始”,不负责搜索、不负责判断、不负责入库。
+
+V1 中它主要读取 `demand_content.ext_data.evidence_pack`,产出:
+
+- `source_context.json`
+- `pattern_seed_pack.json`
+
+### 3.2 搜索意图模块
+
+负责把 seed 转成搜索意图和最终执行 query。
+
+它不能直接调用平台,也不能根据平台结果调整历史 query。平台结果的效果复盘由策略学习模块给出建议,再进入新的版本。
+
+V1 中它产出:
+
+- `queries.jsonl`
+
+query 的执行效果由运行记录模块写入 `search_clues.jsonl`,搜索意图模块只提供 query 来源和执行意图。
+
+### 3.3 平台接入模块
+
+负责平台动作和字段归一。
+
+抖音、小红书、快手等都应该在这里转换成统一的 `PlatformResult`,不要让核心规则依赖平台原始字段。
+
+平台接入模块不判断内容好坏。
+
+### 3.4 候选与证据模块
+
+负责从平台结果中抽取候选,并组装 EvidenceBundle。
+
+它是判断规则模块的上游,必须保证 EvidenceBundle 结构稳定。
+
+它不输出入池结论。
+
+### 3.5 判断规则模块
+
+负责规则包执行。
+
+规则包应尽量是纯决策:
+
+```text
+EvidenceBundle + RulePackVersion -> RuleDecision
+```
+
+它不自己拉平台数据,不自己决定下一跳。
+
+### 3.6 游走策略模块
+
+负责根据路径状态和规则结果决定是否继续扩展:
+
+```text
+PathState + RuleDecision + WalkStrategyVersion -> WalkAction
+```
+
+游走策略模块不打分,不沉淀资产。
+
+### 3.7 策略版本管理模块
+
+负责记录本次运行 使用哪套策略组合:
+
+- Query 生成版本
+- 平台动作版本
+- RulePack 版本
+- WalkStrategy 版本
+- Budget 版本
+
+没有版本,后续策略学习无法比较。
+
+### 3.8 结果沉淀与来源反查模块
+
+负责最终沉淀和来源关系。
+
+它不能重新解释规则结论,只能基于 `RuleDecision`、`Candidate`、`SourceEdge` 和清洗规则沉淀资产。
+
+它读取 `SourceEdge`,归并成稳定 `SourceLineage`;不直接追加运行期 `source_edges.jsonl`。
+
+### 3.9 运行记录模块
+
+负责不可变运行事实。
+
+它不是普通日志,而是复盘和学习的结构化事实层。
+
+它负责追加 `trace_events.jsonl`、`source_edges.jsonl` 和 `search_clues.jsonl`,保证运行事实只有一个写入边界。
+
+### 3.10 策略学习模块
+
+负责从运行事实中提取学习信号。
+
+第一阶段不做自动训练模型,而是产出可执行建议:
+
+- 调整 query 生成。
+- 调整规则包阈值。
+- 调整游走预算。
+- 调整数据源优先级。
+- 标记高价值 Pattern、作者或平台动作。
+
+## 4. 核心边界
+
+### 规则包和游走策略必须分离
+
+规则包回答“这个东西如何判断”。
+
+游走策略回答“判断之后去哪”。
+
+混在一起会导致未来无法独立调整规则和路径。
+
+### 平台接入和业务判断必须分离
+
+平台字段会变,业务判断应该稳定。
+
+平台接入模块要把平台结果转换成标准候选和证据,不把平台特有逻辑渗透进规则包。
+
+### 资产沉淀和候选判断必须分离
+
+候选判断可以频繁迭代,资产沉淀需要稳定和可审计。
+
+结果沉淀与来源反查模块只接受明确的候选、规则决策和来源记录。

+ 122 - 0
tech_documents/content_agent_architecture/03_模块关系与数据责任.md

@@ -0,0 +1,122 @@
+# 模块关系与数据责任
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 模块关系图
+
+```mermaid
+flowchart LR
+  Upstream["DemandAgent / Pattern / Case"] --> Source["数据源与种子模块"]
+  Source --> Query["搜索意图模块"]
+  Query --> Platform["平台接入模块"]
+  Platform --> Candidate["候选与证据模块"]
+  Candidate --> Rule["判断规则模块"]
+  Rule --> Walk["游走策略模块"]
+  Walk --> Platform
+  Rule --> Asset["结果沉淀与来源反查模块"]
+  Source --> Trace["运行记录模块"]
+  Query --> Trace
+  Platform --> Trace
+  Candidate --> Trace
+  Rule --> Trace
+  Walk --> Trace
+  Asset --> Trace
+  Trace --> Learning["策略学习模块"]
+  Learning --> Catalog["策略版本管理模块"]
+  Catalog --> Query
+  Catalog --> Rule
+  Catalog --> Walk
+```
+
+## 2. 数据责任总表
+
+| 数据 / 产物 | 负责模块 | 写入方式 | 其他模块权限 |
+|---|---|---|---|
+| `source_context.json` | 数据源与种子模块 | 创建一次,必要时补充校验状态 | 只读 |
+| `pattern_seed_pack.json` | 数据源与种子模块 | 创建一次 | 搜索意图模块只读 |
+| `queries.jsonl` | 搜索意图模块 | 追加 | 平台接入模块只读执行 |
+| `candidate_pool.jsonl` | 候选与证据模块 | 追加 + 状态补充 | 判断规则模块只读 |
+| `media_assets.jsonl` | 候选与证据模块 | 追加 + 媒体状态更新 | 判断规则模块只读 |
+| `rule_decisions.jsonl` | 判断规则模块 | 只追加,不覆盖 | 游走策略模块、结果沉淀与来源反查模块只读 |
+| `source_edges.jsonl` | 运行记录模块 | 只追加,不覆盖 | 结果沉淀与来源反查模块读取并归并为 `SourceLineage` |
+| `search_clues.jsonl` | 运行记录模块 | 只追加,不覆盖 | 搜索意图模块、结果沉淀与来源反查模块、策略学习模块只读 |
+| `trace_events.jsonl` | 运行记录模块 | 只追加,不覆盖 | 全模块通过 `RunRecordRecorder` 提交事件请求;运行记录模块统一追加,不可改旧事件 |
+| `final_output.json` | 结果沉淀与来源反查模块 | 运行结束汇总写入 | 策略学习模块只读 |
+| `PolicyBundleVersion` | 策略版本管理模块 | 发布版本 | 执行模块只读 |
+| `LearningSignal` | 策略学习模块 | 从历史 run 生成 | 策略版本管理模块读取后决定是否采纳 |
+
+## 3. 核心对象初稿
+
+| 模块 | 过程核心对象 | 稳定对象 / 稳定视图 | 说明 |
+|---|---|---|---|
+| 数据源与种子模块 | `SourceContext` | `EvidencePack` / `SeedPack` | 一次运行 的来源证据入口和可复用 seed |
+| 搜索意图模块 | `QueryPlan` | `ExecutableQuery` | 管理 seed 到 query 的生成、去重和执行预算 |
+| 平台接入模块 | `PlatformActionExecution` | `PlatformResult` | 一次平台动作执行和归一化返回 |
+| 候选与证据模块 | `CandidateBatch` | `Candidate` / `EvidenceBundle` | 批量组装 EvidenceBundle,同时保留单个候选生命周期 |
+| 判断规则模块 | `RuleEvaluationRun` | `RuleDecision` / `RulePackVersion` | 某个 RulePackVersion 对一批 EvidenceBundle 的判断过程 |
+| 游走策略模块 | `WalkRun` | `WalkPath` / `PathState` | 一次游走运行和其中的可追踪路径 |
+| 策略版本管理模块 | `PolicyBundleVersion` | `PolicyBundleVersion` | 一组 query、规则、游走、预算版本组合 |
+| 结果沉淀与来源反查模块 | `AssetCommitRun` | `ContentAsset` / `AuthorAsset` / `SearchClueAsset` / `SourceLineage` | 一次资产沉淀提交,以及稳定资产来源路径 |
+| 运行记录模块 | `Run` | `RunRecord` / `TraceEvent` / `SourceEdge` | 一次完整运行及其不可变运行记录 |
+| 策略学习模块 | `LearningReview` | `LearningSignal` / `StrategyAdjustmentProposal` | 一次复盘分析和调整建议 |
+
+## 4. 文件名到业务术语映射
+
+| 文件 / 产物 | 业务术语 | 说明 |
+|---|---|---|
+| `source_context.json` | `SourceContext` | 本次运行的来源信息 |
+| `pattern_seed_pack.json` | `SeedPack` | 从上游证据提取的策略种子 |
+| `queries.jsonl` | `ExecutableQuery` | 本次实际执行的 query |
+| `candidate_pool.jsonl` | `CandidatePool` / `Candidate` | 全量候选和候选过程状态 |
+| `media_assets.jsonl` | `MediaObject` / `MediaEvidence` | 媒体元数据、下载、OSS、不可用状态;不是结果沉淀与来源反查模块的业务资产 |
+| `rule_decisions.jsonl` | `RuleDecision` | 规则包唯一输出 |
+| `source_edges.jsonl` | `SourceEdge` | 运行期来源事实边 |
+| `search_clues.jsonl` | `SearchClueObservation` | query / tag query 的运行期效果观测 |
+| `trace_events.jsonl` | `TraceEvent` | 运行事件 |
+| `final_output.json` | `ContentAsset` / `AuthorAsset` / `SearchClueAsset` / `SourceLineage` | 最终输出和稳定资产视图 |
+
+## 5. 写入原则
+
+### 5.1 追加优先
+
+以下数据默认只追加,不覆盖:
+
+- `rule_decisions.jsonl`
+- `source_edges.jsonl`
+- `trace_events.jsonl`
+- `queries.jsonl`
+- `search_clues.jsonl`
+
+原因:
+
+- 便于复盘。
+- 便于比较策略版本。
+- 避免后续学习阶段污染历史事实。
+
+### 5.2 状态更新要受限
+
+以下数据允许状态补充,但需要保留历史:
+
+- `candidate_pool.jsonl`
+- `media_assets.jsonl`
+
+例如媒体状态可以从 `metadata_only` 变成 `downloaded_local`,但要在 trace 中记录变化。
+
+### 5.3 结果汇总可以覆盖同一次运行 的最终文件
+
+`final_output.json` 是 运行结束时的汇总快照,可以由结果沉淀与来源反查模块在 运行内重算。
+
+但它不能替代 `rule_decisions`、`source_edges` 和 `trace_events`。
+
+## 6. 已收敛写入边界
+
+- `source_edges.jsonl` 由运行记录模块追加;游走策略模块只产出 `WalkAction`,结果沉淀与来源反查模块只读取并归并。
+- `search_clues.jsonl` 由运行记录模块追加;搜索意图模块不回写线索效果,结果沉淀与来源反查模块只沉淀 promoted 后的 `SearchClueAsset`。promotion 来源可以是人工复盘、`LearningReview` 建议或资产提交规则,但必须经结果沉淀与来源反查模块提交。
+- `candidate_pool.jsonl` 不允许判断规则模块回写状态;判断规则模块只写新的 `RuleDecision`,候选状态补充由候选与证据模块根据 decision 引用追加或记录 correction event。

+ 77 - 0
tech_documents/content_agent_architecture/04_核心运行链路.md

@@ -0,0 +1,77 @@
+# 核心运行链路
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. V1 最小链路
+
+```text
+SourceContext
+-> SeedPack
+-> QueryPlan
+-> PlatformActionExecution
+-> CandidateBatch
+-> EvidenceBundle
+-> RuleEvaluation
+-> WalkPath
+-> AssetCommit
+-> LearningReview
+```
+
+V1 对应业务链路:
+
+```text
+Pattern evidence_pack
+-> Pattern 策略种子
+-> Query
+-> 抖音关键词搜索
+-> 视频候选
+-> 判断信号汇总
+-> 规则包决策
+-> 作者作品一跳扩展
+-> 内容 / 作者 / 淘汰 / 运行追踪记录(trace)沉淀
+-> 策略学习复盘
+```
+
+## 2. 阶段和模块的关系
+
+| 七阶段 | 主要模块 | 说明 |
+|---|---|---|
+| 数据源 | 数据源与种子模块 | 验证来源,产出 seed |
+| Query | 搜索意图模块 | 生成执行 query |
+| Platform | 平台接入模块 | 执行平台动作并归一字段 |
+| 判断 | 候选与证据模块、判断规则模块 | 组装 EvidenceBundle 并执行规则 |
+| 游走 | 游走策略模块、平台接入模块 | 根据决策继续扩展 |
+| 资产清洗沉淀 | 结果沉淀与来源反查模块 | 资产提交和稳定来源路径归并 |
+| 策略学习 | 策略学习模块、策略版本管理模块 | 复盘和生成调整建议 |
+
+## 3. 一次运行 的关键事件
+
+| 顺序 | 事件 | 主要写入 |
+|---|---|---|
+| 1 | 创建运行 | `source_context.json`、`trace_events.jsonl` |
+| 2 | 提取 seed | `pattern_seed_pack.json` |
+| 3 | 生成 query | `queries.jsonl`、`trace_events.jsonl` |
+| 4 | 执行平台搜索 | `candidate_pool.jsonl`、`trace_events.jsonl` |
+| 5 | 组装 EvidenceBundle | `candidate_pool.jsonl` 或临时判断输入 |
+| 6 | 执行规则包 | `rule_decisions.jsonl` |
+| 7 | 决定游走 | `trace_events.jsonl`,由运行记录模块追加对应 `source_edges.jsonl` |
+| 8 | 作者作品或 tag 扩散 | `candidate_pool.jsonl`、`queries.jsonl`、`source_edges.jsonl`、`search_clues.jsonl` |
+| 9 | 资产沉淀 | `final_output.json`,读取 `source_edges.jsonl` 归并稳定来源路径 |
+| 10 | 复盘 | 学习报告或调整建议 |
+
+## 4. 不变量
+
+- 每次运行 必须有 `trace_id`。
+- 每个候选必须能反查到来源路径。
+- 每个规则决策必须有 `rule_pack_version`。
+- 每个游走动作必须有 `walk_strategy_version`。
+- 每个入池资产必须能反查到 `source_evidence` 和 `rule_decision`。
+- 每条 `SourceEdge` 必须由运行记录模块追加,不能由规则包或结果沉淀模块直接写。
+- 每个学习建议必须指向历史 run 和指标证据。

+ 136 - 0
tech_documents/content_agent_architecture/05_规则包与游走策略设计.md

@@ -0,0 +1,136 @@
+# 规则包与游走策略设计
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 核心原则
+
+判断规则和游走策略必须独立。
+
+规则包回答:
+
+```text
+这个候选、作者、tag 或路径是否值得保留、扩展、待观察(PENDING)、淘汰或停止?
+```
+
+游走策略回答:
+
+```text
+基于当前路径状态和规则结果,下一步去哪,或者是否停止?
+```
+
+## 2. 规则包最小输入输出
+
+```text
+EvidenceBundle + RulePackVersion -> RuleDecision
+```
+
+### 输入
+
+| 字段 | 说明 |
+|---|---|
+| `trace_id` | 本次运行 |
+| `candidate_id` | 被判断对象 |
+| `candidate_type` | `video / author / tag / path` |
+| `evidence_bundle` | 标准判断证据 |
+| `rule_pack_id` | 规则包 ID |
+| `rule_pack_version` | 规则包版本 |
+| `policy_bundle_version` | 本次运行 的策略组合版本 |
+| `idempotency_key` | 同一候选、证据快照和规则版本的幂等键 |
+| `input_snapshot_ref` | EvidenceBundle 快照引用 |
+
+### 输出
+
+| 字段 | 说明 |
+|---|---|
+| `decision_id` | 决策 ID |
+| `final_action` | V1 使用规则包真实动作枚举:`POOL / CANDIDATE / PENDING / REJECT / STORE_AUTHOR / REJECT_AUTHOR / EXPAND_AUTHOR / EXPAND_AUTHOR_SMALL_BUDGET / PENDING_AUTHOR / NO_EXPAND / GENERATE_QUERY / NO_GENERATE_QUERY / STOP_PATH / CONTINUE / LOW_BUDGET_PENDING / NORMAL_BUDGET` |
+| `matched_hard_gates` | 命中的硬门槛 |
+| `scorecard` | 软评分结果 |
+| `score` | 总分 |
+| `reason_code` | 标准原因码 |
+| `evidence_refs` | 使用了哪些证据字段 |
+| `replay_fields` | 复盘所需的规则版本、命中条件和关键证据引用 |
+| `created_at` | 判断时间 |
+
+`final_action` 是规则判断信号,不是执行命令。实际扩展、停止、预算变更必须由游走策略消费 `RuleDecision` 后产出 `WalkAction`,再由运行编排执行。
+
+规则包只负责产出 `RuleDecision`。它不能直接写 `source_edges.jsonl`、`trace_events.jsonl` 或 `final_output.json`。
+
+## 3. 游走策略最小输入输出
+
+```text
+PathState + RuleDecision + WalkStrategyVersion -> WalkAction
+```
+
+停止是 `WalkAction.next_action = STOP`,不是单独的返回类型。
+
+### 输入
+
+| 字段 | 说明 |
+|---|---|
+| `trace_id` | 本次运行 |
+| `current_node` | 当前节点,如视频、作者、tag、query |
+| `path_state` | 深度、预算、连续空召回、连续低质、tag 扩散次数 |
+| `rule_decision` | 判断规则输出 |
+| `walk_strategy_id` | 游走策略 ID |
+| `walk_strategy_version` | 游走策略版本 |
+| `policy_bundle_version` | 策略组合版本 |
+
+### 输出
+
+| 字段 | 说明 |
+|---|---|
+| `walk_action_id` | 游走动作 ID |
+| `next_action` | `SEARCH / FETCH_AUTHOR_WORKS / GENERATE_TAG_QUERY / STOP` |
+| `walk_edge_id` | 绑定的游走边配置 |
+| `target_node_ref` | 下一步目标节点或平台动作引用 |
+| `budget_delta` | 本次消耗或分配的预算 |
+| `stop_reason` | 停止原因 |
+| `source_edge_basis` | 追加 `SourceEdge` 所需的来源和目标引用,由运行记录模块接口消费 |
+
+游走策略只产出 `WalkAction`。实际 `SourceEdge` 由运行编排通过 `RunRecordRecorder` 追加,避免游走策略模块直接写运行记录。
+
+## 4. 可组装方式
+
+`PolicyBundle` 负责组合:
+
+```text
+QueryStrategyVersion
++ PlatformActionVersion
++ RulePackVersion[]
++ WalkStrategyVersion[]
++ BudgetVersion
+```
+
+这样可以单独调整:
+
+- query 生成。
+- 视频候选规则。
+- 作者扩展规则。
+- tag 扩散规则。
+- 停止规则。
+- 作者作品游走预算。
+
+## 5. V1 保留的规则包类型
+
+| 规则包 | 主要对象 | 说明 |
+|---|---|---|
+| 视频候选判断 | video | Pattern 回扣、画像、互动、风险、质量 |
+| 作者扩展判断 | author | 50+ 画像、垂类稳定、相似作品、可爬稳定 |
+| tag 扩散判断 | tag | tag 是否强相关、是否能回扣 Pattern、是否风险泛化 |
+| 路径停止判断 | path | 连续低质、连续空召回、连续三页、tag 跳数上限 |
+| 预算待观察 判断 | path / candidate | 低预算保留运行追踪记录(trace),但不进入正式资产 |
+
+## 6. 已收敛结论
+
+- V1 保留 `CANDIDATE` 和 `PENDING` 两个不同动作;`PENDING` 不进入正式资产表,只保留运行追踪记录(trace) 和判断记录。
+- tag 扩散保留独立规则包,便于后续单独调整预算和停止条件。
+- 作者扩展规则包只决定是否扩展或 待观察作者(PENDING_AUTHOR);作者是否沉淀为资产由结果沉淀与来源反查模块基于 `RuleDecision` 和清洗规则提交。
+- V1 不引入复合 action;如果未来需要同时表达 `REJECT_VIDEO` 和 `EXPAND_AUTHOR`,应拆成多条 `RuleDecision`,并共享同一个 `input_snapshot_ref`。

+ 75 - 0
tech_documents/content_agent_architecture/06_判断证据包与候选模型.md

@@ -0,0 +1,75 @@
+# 判断证据包与候选模型
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. Candidate
+
+`Candidate` 是被判断的过程对象。
+
+候选类型:
+
+| 类型 | 示例 |
+|---|---|
+| `video` | 抖音视频、作者作品视频 |
+| `note` | 小红书笔记 |
+| `author` | 抖音作者、小红书作者 |
+| `tag` | 视频 tag、话题 |
+| `query` | tag 扩散 query 或历史 query |
+| `path` | 一条游走路径或路径状态 |
+
+Candidate 不等于 Asset。
+
+## 2. CandidatePool
+
+`CandidatePool` 保存全量候选,包括:
+
+- `unreviewed`。
+- `candidate`。
+- `pending`。
+- `rejected`。
+- 未判断。
+- 平台失败或媒体不可用。
+
+它是策略学习的基础,不能只保存最终结果。
+
+`CandidatePool` 使用 `candidate_status` 表达过程状态;最终输出中的内容或作者使用 `asset_status`。
+
+## 3. EvidenceBundle
+
+`EvidenceBundle` 是规则包输入,不是最终判断结果。
+
+V1 已验证的 9 个块:
+
+| 块 | 说明 |
+|---|---|
+| `source_evidence` | 来源证据 |
+| `entity` | 被判断对象 |
+| `relevance_signal` | 与 Pattern / 需求相关性 |
+| `interaction_signal` | 互动指标 |
+| `portrait_signal` | 内容或账号画像 |
+| `account_signal` | 作者账号信息 |
+| `risk_signal` | 风险信号 |
+| `walk_context` | 游走信息 |
+| `trace` | trace 和版本引用 |
+
+## 4. 组装原则
+
+- 平台接入模块提供原始和归一字段。
+- 候选与证据模块负责组装 EvidenceBundle。
+- 判断规则模块只能消费 EvidenceBundle,不自己拼证据。
+- 缺少硬要求字段时,由规则包输出明确原因码。
+
+V1 中 `portrait_signal` 是视频判断硬要求;内容画像缺失时直接淘汰。
+
+## 5. 已收敛结论
+
+- V1 可以不单独落盘完整 EvidenceBundle,但 `RuleDecision` 必须记录 `input_snapshot_ref`、`evidence_refs` 和可复盘摘要。
+- 小红书笔记后续使用 `entity.type = note` 分支,不复用 video 的平台字段语义。
+- `risk_signal` 第一阶段由候选与证据模块组装;若风险规则复杂化,再拆独立风险模块。

+ 107 - 0
tech_documents/content_agent_architecture/07_运行记录与来源路径.md

@@ -0,0 +1,107 @@
+# 运行记录与来源路径
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 定位
+
+运行记录是本地和未来服务化运行的事实层。
+
+它不是普通日志,也不是 show 后端数据格式。
+
+它要回答:
+
+- 本次运行 从哪里开始。
+- 每个 query 为什么出现。
+- 每个候选从哪里来。
+- 每个规则决策用了什么证据。
+- 每个游走动作为什么继续或停止。
+- 每个资产能不能反查回来源。
+
+## 2. V1 运行记录文件
+
+| 文件 | 负责模块 |
+|---|---|
+| `source_context.json` | 数据源与种子模块 |
+| `pattern_seed_pack.json` | 数据源与种子模块 |
+| `queries.jsonl` | 搜索意图模块 |
+| `candidate_pool.jsonl` | 候选与证据模块 |
+| `media_assets.jsonl` | 候选与证据模块 |
+| `rule_decisions.jsonl` | 判断规则模块 |
+| `source_edges.jsonl` | 运行记录模块 |
+| `search_clues.jsonl` | 运行记录模块 |
+| `trace_events.jsonl` | 运行记录模块 |
+| `final_output.json` | 结果沉淀与来源反查模块 |
+
+## 3. 来源记录 SourceEdge
+
+`SourceEdge` 是运行事实,不是游走策略配置。
+
+示例:
+
+```text
+PatternItem -> Query
+Query -> SearchPage
+SearchPage -> Video
+Video -> Author
+Author -> AuthorWorksPage
+AuthorWorksPage -> Video
+Video -> Hashtag
+Hashtag -> Query
+Video -> ContentAsset
+Author -> AuthorAsset
+```
+
+最小字段:
+
+| 字段 | 说明 |
+|---|---|
+| `edge_id` | 来源记录 ID |
+| `trace_id` | 本次运行 |
+| `from_node_type` | 来源节点类型 |
+| `from_id` | 来源节点 ID |
+| `to_node_type` | 目标节点类型 |
+| `to_id` | 目标节点 ID |
+| `edge_type` | `pattern_to_query / query_to_video / video_to_author / author_to_work / video_to_tag / tag_to_query / decision_to_asset` |
+| `immediate_source` | 直接来源 |
+| `origin_source` | 最早来源 |
+| `rule_decision_id` | 相关规则决策 |
+| `walk_edge_id` | 相关游走边配置 |
+| `created_at` | 创建时间 |
+
+`SourceEdge` 的追加写入只能通过运行记录模块的模块接口完成。游走策略模块可以提供 `source_edge_basis`,结果沉淀与来源反查模块可以读取并归并,但二者都不直接写 `source_edges.jsonl`。
+
+## 4. TraceEvent
+
+`TraceEvent` 记录过程事件。
+
+最小字段:
+
+| 字段 | 说明 |
+|---|---|
+| `trace_id` | 本次运行 |
+| `event_id` | 事件 ID |
+| `stage` | 阶段 |
+| `context` | 所属模块或阶段 |
+| `input_ref` | 输入引用 |
+| `output_ref` | 输出引用 |
+| `status` | `started / completed / failed / skipped` |
+| `error_code` | 错误码 |
+| `policy_bundle_version` | 策略版本 |
+| `created_at` | 事件时间 |
+
+## 5. 不可变原则
+
+- `source_edges` 只追加,不覆盖。
+- `search_clues` 只追加,不覆盖。
+- `trace_events` 只追加,不覆盖。
+- `rule_decisions` 只追加,不覆盖。
+- 学习阶段只能读历史,不能改历史。
+
+如果需要修正,写一条新的 correction event。

+ 66 - 0
tech_documents/content_agent_architecture/08_结果沉淀与来源反查.md

@@ -0,0 +1,66 @@
+# 结果沉淀与来源反查
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 资产类型
+
+| 资产 | 定义 |
+|---|---|
+| `ContentAsset` | 经判断和清洗后可复用的内容结果 |
+| `AuthorAsset` | 经作者沉淀规则确认的作者结果 |
+| `SearchClueAsset` | 可复用 query、tag、失败 query、历史搜索路径等线索 |
+| `SourceLineage` | 基于运行期 `SourceEdge` 归并出的稳定来源路径 |
+
+## 2. 资产沉淀输入
+
+结果沉淀与来源反查模块读取:
+
+- `Candidate`
+- `RuleDecision`
+- `SourceEdge`
+- `TraceEvent`
+- `SearchClueObservation`
+- 清洗规则
+
+它不重新执行判断。
+
+它写入 `final_output.json`,并可以在后续稳定存储中维护 `ContentAsset`、`AuthorAsset`、`SearchClueAsset` 和 `SourceLineage`。
+
+## 3. 入池与候选
+
+| 状态 | 说明 |
+|---|---|
+| `pooled` | 正式入池,可进入内容资产 |
+| `candidate_asset` | 允许后续人工或补证,可以进入候选资产视图 |
+| `stored_author` | 已沉淀作者 |
+| `clue_only` | 只沉淀为搜索线索资产 |
+
+`pending` 和 `rejected` 不是资产状态:前者只保留运行追踪记录(trace) 和判断记录,后者只进入原因统计和复盘。
+
+## 4. 来源反查要求
+
+每个入池内容或作者至少要能反查:
+
+- 来源需求或 Pattern。
+- seed / query。
+- 平台动作。
+- 候选 ID。
+- RuleDecision。
+- SourceEdge / SourceLineage。
+- TraceEvent。
+
+如果只能留下 `aweme_id + demand_content_id`,则不能支撑策略学习。
+
+## 5. 已收敛结论
+
+- 过程状态 `candidate` 只属于候选池;最终输出若需要候选资产视图,使用 `asset_status = candidate_asset`。
+- V1 不强制作者资产必须多条作品回扣 Pattern,但必须保留 `sample_count` 和 `pattern_recall_work_count` 供复盘。
+- `SearchClueAsset` 使用 `asset_status = clue_only`。
+- 淘汰候选不作为资产长期沉淀;保留在候选池、规则决策和 trace 中,保存周期由后续数据保留策略决定。

+ 103 - 0
tech_documents/content_agent_architecture/09_模块接口与外部接入说明.md

@@ -0,0 +1,103 @@
+# 模块接口与外部接入说明
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 目的
+
+本文定义 Content Agent 第一阶段需要稳定下来的模块接口和外部接入方式。
+
+业务核心只依赖模块接口,不依赖抖音、小红书、文件系统、数据库、队列或具体 HTTP SDK。接入模块可以替换,但输入、输出、幂等、失败语义和复盘字段不能漂移。
+
+模块接口定义属于应用内侧;平台、文件、数据库、队列、对象存储等具体实现属于外部接入实现。
+
+## 2. 通用字段
+
+所有模块接口调用至少携带这些字段:
+
+| 字段 | 说明 |
+|---|---|
+| `trace_id` | 本次运行 ID,用来串起一次执行的全部记录 |
+| `policy_bundle_version` | 本次运行使用的策略组合版本 |
+| `idempotency_key` | 防重复键,同一请求重试时必须保持不变 |
+| `input_ref` | 输入快照或上游产物引用 |
+| `created_at` | 产生时间 |
+
+失败、限流或异步处理时,返回里至少要能说明这些信息:
+
+| 字段 | 说明 |
+|---|---|
+| `status` | 处理状态:`success` 成功、`pending` 待观察、`blocked` 被阻断、`failed` 失败、`rate_limited` 被限流 |
+| `output_ref` | 输出引用 |
+| `error_code` | 标准错误码,可空 |
+| `retry_after` | 被限流或暂不可用时的建议重试时间,可空 |
+| `quota_scope` | 限流或配额所属范围,如平台、账号、query、IP,可空 |
+| `throttle_reason` | 触发降速或暂停的原因,可空 |
+| `pause_until` | 暂停扩展或暂停抓取的时间,可空 |
+| `max_inflight` | 当前边界允许的最大并发,可空 |
+| `event_id` | 模块接口调用产生的事件 ID,可空 |
+| `causation_id` | 触发本次输出的上游事件或决策 ID,可空 |
+| `sequence_no` | 同一 trace 内用于乱序重放的序号,可空 |
+| `attempt_no` | 当前重试次数,可空 |
+| `dedupe_scope` | 幂等去重范围,可空 |
+| `dead_letter_reason` | 无法继续重试时的归档原因,可空 |
+| `replay_fields` | 复盘所需字段 |
+
+## 3. 模块接口清单
+
+| 模块接口 | 所属边界 | 最小输入 | 最小输出 | 失败语义 |
+|---|---|---|---|---|
+| `PlatformSearchInterface` | 平台接入模块 | `ExecutableQuery`、平台、分页、预算 | `PlatformResult[]`、分页游标、限流信息 | `rate_limited`、`blocked`、`failed` |
+| `PlatformFetchInterface` | 平台接入模块 | 平台对象 ID、对象类型、抓取动作 | 归一化 `PlatformResult` 或媒体状态 | `rate_limited`、`blocked`、`unavailable`、`failed` |
+| `PortraitSignalProvider` | 候选与证据模块外部依赖 | 内容或账号引用、画像版本 | `portrait_signal`、`account_signal` | `missing`、`blocked`、`failed` |
+| `EvidenceBundleBuilder` | 候选与证据模块 | `Candidate`、平台结果、画像、路径信息 | `EvidenceBundle`、`input_snapshot_ref` | `blocked`、`failed` |
+| `RuleDecisionRecorder` | 判断规则模块 | `RuleDecision` | `rule_decisions.jsonl` 引用 | `failed` |
+| `WalkActionPlanner` | 游走策略模块 | `PathState`、`RuleDecision`、策略版本 | `WalkAction`、`source_edge_basis` | `stopped`、`failed` |
+| `RunRecordRecorder` | 运行记录模块 | `TraceEvent`、`SourceEdge`、`SearchClueObservation` | 追加后的记录引用 | `failed` |
+| `ResultCommitter` | 结果沉淀与来源反查模块 | `RuleDecision`、`SourceEdge`、清洗规则、资产提交策略 | `final_output.json` 引用、资产快照、`SourceLineage` | `duplicate`、`blocked`、`failed` |
+| `PolicyBundleStore` | 策略版本管理模块 | 策略组合 ID 或版本 | 固定 `PolicyBundleVersion` 快照 | `not_found`、`blocked` |
+
+## 4. 写入规则
+
+| 产物 | 唯一负责写入的模块 | 说明 |
+|---|---|---|
+| `queries.jsonl` | Query 规划用例 | 搜索意图模块追加 |
+| `candidate_pool.jsonl` | 候选写入用例 | 候选与证据模块追加或状态补充 |
+| `media_assets.jsonl` | 媒体状态写入用例 | 候选与证据模块记录媒体状态 |
+| `rule_decisions.jsonl` | `RuleDecisionRecorder` | 规则包唯一输出 |
+| `source_edges.jsonl` | `RunRecordRecorder` | 来源记录文件,由运行记录模块追加 |
+| `search_clues.jsonl` | `RunRecordRecorder` | query / tag query 效果观测 |
+| `trace_events.jsonl` | `RunRecordRecorder` | 运行事件 |
+| `final_output.json` | `ResultCommitter` | 结果沉淀与来源反查模块汇总写入 |
+
+## 5. 边界约束
+
+- 规则包不能调用平台接口,也不能写 `source_edges.jsonl`、`trace_events.jsonl`、`final_output.json`。
+- 游走策略不能打分,也不能直接写 `source_edges.jsonl`;它只产出 `WalkAction` 和 `source_edge_basis`。
+- 结果沉淀与来源反查模块不能重新解释规则结果;它读取 `RuleDecision` 和 `SourceEdge`,提交资产和稳定来源路径。
+- 策略学习模块只能读历史运行记录和最终输出,不能修改历史事实。
+
+## 6. 信任边界
+
+| 边界 | 风险 | 接口要求 |
+|---|---|---|
+| 外部平台 API | 限流、封禁、字段漂移、返回污染 | 平台接入模块必须归一化字段、返回限流语义,不把平台原始结构泄漏到规则包 |
+| 内容画像 / 账号画像服务 | 缺失、过期、画像口径变化 | `PortraitSignalProvider` 必须返回画像版本、缺失原因和可复盘引用 |
+| DemandAgent / Pattern evidence | 上游证据不完整或来源链断裂 | 数据源与种子模块必须校验 `source_evidence`,缺失时不能入池 |
+| 运行记录存储 | 历史事实被覆盖、重复写、断链 | `RunRecordRecorder` 必须只追加、不覆盖,并支持幂等写入和 correction event;correction event 只能追加,必须引用原 `event_id` 或 `source_edge_id` |
+| 策略配置和规则包发布 | 错误版本、未审配置直接上线 | `PolicyBundleStore` 必须返回固定版本快照、`policy_bundle_hash`、发布人、审批来源、发布时间和回滚引用;学习建议必须人工确认后发布 |
+| secrets / cookie / API key | 凭据泄露、日志污染 | 接入模块不得把 secrets 写入 `TraceEvent`、`SourceEdge`、`RuleDecision`、error message 或 `final_output`,只允许记录 `secret_ref` |
+
+## 7. 高吞吐预留
+
+- 所有模块接口必须支持幂等重试。
+- 平台接口必须显式返回限流范围和重试建议。
+- 批量规则评估可以优化吞吐,但输出仍必须是一条条 `RuleDecision`。
+- 运行记录模块的底层实现可以从 JSONL 换成表或事件日志,但模块接口说明不变。
+- 当下游写入、画像服务或平台接口返回 `rate_limited / blocked` 时,上游执行器必须降速或停止扩散,不能继续扩大游走队列。

+ 19 - 0
tech_documents/content_agent_architecture/ADR/0001_为什么规则包独立于游走策略.md

@@ -0,0 +1,19 @@
+# ADR 0001:为什么规则包独立于游走策略
+
+状态:接受
+
+名词说明:`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 决策
+
+规则包只负责产出 `RuleDecision`;游走策略消费 `RuleDecision`、`PathState` 和 `WalkStrategyVersion`,产出 `WalkAction`。
+
+## 原因
+
+规则阈值、路径预算、下一跳策略的迭代节奏不同,混在一起会让调参、复盘和学习归因变困难。
+
+## 后果
+
+- 好处:规则和游走可以独立调参、独立回放、独立评估。
+- 代价:需要显式维护 `RuleDecision -> WalkAction` 的映射。

+ 19 - 0
tech_documents/content_agent_architecture/ADR/0002_为什么保留全量候选池.md

@@ -0,0 +1,19 @@
+# ADR 0002:为什么保留全量候选池
+
+状态:接受
+
+名词说明:`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 决策
+
+保留全量 `CandidatePool`,包括入池、candidate、pending、rejected、平台失败和媒体不可用记录。
+
+## 原因
+
+只保留最终结果会丢失空召回、误杀、低质候选、作者扩展失败和规则过严/过松的复盘证据。
+
+## 后果
+
+- 好处:支持策略学习、淘汰原因分析、去重和来源路径复盘。
+- 代价:存储量上升,需要生命周期和清理策略。

+ 19 - 0
tech_documents/content_agent_architecture/ADR/0003_为什么要单独记录source_edge.md

@@ -0,0 +1,19 @@
+# ADR 0003:为什么要单独记录 SourceEdge(来源记录)
+
+状态:接受
+
+名词说明:`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 决策
+
+`SourceEdge` 独立记录来源事实;它不是 `WalkEdge` 配置,也不是最终资产字段。
+
+## 原因
+
+Content Agent 的结果必须能反查来源 Pattern、seed、query、平台动作、作者/tag 扩展路径和规则决策。
+
+## 后果
+
+- 好处:支持资产反查、策略学习、多路径归因和 query/tag/作者效果评估。
+- 代价:需要维护 edge 类型、节点引用、幂等和重复路径处理。

+ 19 - 0
tech_documents/content_agent_architecture/ADR/0004_source_edges写入边界.md

@@ -0,0 +1,19 @@
+# ADR 0004:source_edges.jsonl(来源记录文件)写入方式
+
+状态:接受
+
+名词说明:`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 决策
+
+`source_edges.jsonl` 只由运行记录模块追加;游走策略只产出 `source_edge_basis`,结果沉淀与来源反查模块只读取并归并为稳定来源路径。
+
+## 原因
+
+运行事实必须只有一个写入边界,避免游走策略、结果沉淀模块和规则包分别写来源记录。
+
+## 后果
+
+- 好处:事实层可审计,资产来源路径可重算,游走策略不绑定运行记录存储。
+- 代价:运行编排需要把 `WalkAction` 显式转换为 `SourceEdge`。

+ 20 - 0
tech_documents/content_agent_architecture/ADR/0005_先模块化单体后服务化队列化.md

@@ -0,0 +1,20 @@
+# ADR 0005:先模块化单体后服务化队列化
+
+状态:接受
+
+名词说明:`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 决策
+
+第一阶段先做模块化单体和模块接口说明,不立即拆微服务或强行引入队列。
+
+## 原因
+
+当前主要风险是模块词、数据责任、规则接口说明和游走接口说明不稳定;过早服务化会放大耦合和运维复杂度。
+
+## 后果
+
+- 好处:先稳定边界,本地 JSONL、表存储和事件日志未来可共用模块接口。
+- 代价:第一阶段不解决全部高并发运维问题;迁移到队列/表存储需要另开 ADR。
+- 补充:V1 加薄 FastAPI 和 LangGraph 仍然属于模块化单体;它们只做内部入口和运行编排,不代表拆服务或上复杂队列。

+ 19 - 0
tech_documents/content_agent_architecture/ADR/0006_规则包只负责产出RuleDecision.md

@@ -0,0 +1,19 @@
+# ADR 0006:规则包只负责产出 RuleDecision(规则判断结果)
+
+状态:接受
+
+名词说明:`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 决策
+
+规则包唯一负责产出的结果是 `RuleDecision`,对应写入 `rule_decisions.jsonl`。
+
+## 原因
+
+规则包不应该同时负责来源记录、运行事件和最终输出,否则会绕过运行记录模块和结果沉淀模块。
+
+## 后果
+
+- 好处:规则包更容易独立调参、测试和回放。
+- 代价:运行编排需要根据 `RuleDecision` 触发 `TraceEvent`(运行事件)、`WalkAction` 和资产提交。

+ 20 - 0
tech_documents/content_agent_architecture/ADR/0007_V1引入薄FastAPI和LangGraph.md

@@ -0,0 +1,20 @@
+# ADR 0007:V1 引入薄 FastAPI 和 LangGraph
+
+状态:接受
+
+名词说明:`trace_id`(本次运行 ID)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`source_edges.jsonl`(来源记录文件)、`final_output.json`(最终结果文件)。
+
+## 决策
+
+V1 直接加入薄 FastAPI 内部接口和 LangGraph 运行编排。
+
+FastAPI 只负责启动运行、查询运行和服务内部面板;LangGraph 只负责编排节点顺序和失败分支。10 个业务模块仍然是业务逻辑的唯一承载位置。
+
+## 原因
+
+上线第一天需要在 `show/` 内部面板看到真实 case、真实拉取数据、规则判断、游走过程和策略复盘建议。只用 CLI 不够直观,也不利于后续后台化。
+
+## 后果
+
+- 好处:第一天就能运行、查看和复盘真实 case;后续改成 worker 或数据库时,业务模块不用重写。
+- 代价:V1 多了 FastAPI 和 LangGraph 两层工程复杂度,必须用依赖规则防止业务逻辑流入 API 或编排节点。

+ 91 - 0
tech_documents/content_agent_architecture/future/01_策略学习与实验闭环.md

@@ -0,0 +1,91 @@
+# 策略学习与实验闭环
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 第一阶段学习定义
+
+第一阶段不做自动训练模型。
+
+策略学习先做可解释复盘:
+
+```text
+收集运行追踪记录(trace)
+-> 计算指标
+-> 识别原因
+-> 产出调整建议
+-> 小范围再试
+```
+
+## 2. 学习输入
+
+| 输入 | 看什么 |
+|---|---|
+| `source_context` | 哪些数据源有效 |
+| `queries` | 哪些 query 有召回、哪些空召回 |
+| `candidate_pool` | 候选规模、重复率、低质率 |
+| `rule_decisions` | 规则命中、误杀、放水、原因分布 |
+| `source_edges` | 哪些路径产生有效结果 |
+| `search_clues` | 哪些 query 或 tag query 产生有效、弱有效或空召回 |
+| `trace_events` | 错误、耗时、失败阶段 |
+| `final_output` | 入池、候选、pending、淘汰汇总 |
+| 后续表现 | 播放、互动、回流、人工反馈 |
+
+## 3. 学习输出
+
+| 输出 | 例子 |
+|---|---|
+| Query 调整建议 | 增加某类 item 组合,减少泛词 |
+| 规则包调整建议 | 提高风险门槛,降低某类互动权重 |
+| 游走预算建议 | 某类作者多给一跳,某类 tag 停止扩散 |
+| 数据源优先级建议 | 某 Pattern 产出稳定,优先跑 |
+| 平台动作建议 | 抖音作者作品有效,小红书待验证 |
+
+## 4. 实验闭环
+
+每次策略调整都应该进入 `PolicyBundle`:
+
+```text
+旧 PolicyBundle
+-> LearningReview
+-> StrategyAdjustmentProposal
+-> 新 PolicyBundle
+-> 新一轮运行
+-> 对比结果
+```
+
+学习阶段只产生 `StrategyAdjustmentProposal`,不能直接改历史 `RuleDecision`、`SourceEdge`、`TraceEvent` 或 `SearchClueObservation`。
+
+## 5. 指标
+
+V1 已有评价面:
+
+- query 有效率。
+- 视频 Pattern 回扣率。
+- 入池率。
+- 待观察率。
+- 淘汰率。
+- 作者扩展有效率。
+- tag 扩散有效率。
+- 50+ 命中情况。
+- trace 反查完整率。
+
+后续可加入:
+
+- 平台动作稳定率。
+- 单候选成本。
+- 单入池资产成本。
+- source edge 完整率。
+- 同策略多 run 波动。
+
+## 6. 已收敛结论
+
+- 第一阶段学习建议必须人工确认后才能发布为新的 `PolicyBundleVersion`。
+- 学习产物同时保留结构化 JSON 和 Markdown 摘要:JSON 给后续算法和工具消费,Markdown 给产品和策略复盘。
+- 线上表现数据通过 `trace_id`、`asset_id`、`source_edge_ids` 和 `policy_bundle_version` 关联本地运行追踪记录(trace)。

+ 85 - 0
tech_documents/content_agent_architecture/future/02_高吞吐执行架构预研.md

@@ -0,0 +1,85 @@
+# 高吞吐执行架构预研
+
+版本:v0.2
+
+状态:收敛稿
+
+## 阅读约定
+
+核心对象第一次出现时会带中文解释:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`SourceEdge`(来源记录)、`trace_id`(本次运行 ID)、`source_evidence`(来源证据)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+
+## 1. 问题定义
+
+未来目标可能达到每天几万个视频级别的搜索、判断、游走和复盘。
+
+高吞吐架构不能只考虑并发,还要保证:
+
+- 来源可追溯。
+- 规则可复盘。
+- 游走可控。
+- 策略版本可比较。
+- 平台接口可限流和降级。
+
+## 2. 关键压力点
+
+| 压力点 | 说明 |
+|---|---|
+| 平台搜索 | 多 query、多页、多平台,容易触发限流 |
+| 候选去重 | 同一视频可能来自多个 query、tag、作者路径 |
+| 画像获取 | 内容画像和账号画像可能成为瓶颈 |
+| 规则判断 | 大量 EvidenceBundle 批量评分 |
+| 游走扩展 | tag、作者作品容易指数膨胀 |
+| trace 写入 | 高频 追加,要保证低成本和可查询 |
+| 学习复盘 | 需要按策略版本和路径聚合 |
+
+## 3. 架构方向
+
+第一阶段先保持本地运行记录和小规模服务化兼容。
+
+未来可拆为:
+
+```text
+Source Loader
+-> Query Planner
+-> Platform Fetch Workers
+-> Candidate Normalizer
+-> Evidence Builder
+-> Rule Evaluator
+-> Walk Planner
+-> 运行记录模块
+-> Asset Committer
+-> Learning Analyzer
+```
+
+这些是运行组件,不等于模块边界。运行记录模块是贯穿整个流程的记录组件,不只是最后一步;Asset Committer 必须读取已经追加的 `SourceEdge`,再归并 `SourceLineage` 和提交最终资产。
+
+## 4. 必须先稳定的模块接口说明
+
+在讨论队列、数据库、服务拆分之前,必须先稳定:
+
+- `EvidenceBundle` 接口说明。
+- `RuleDecision` 接口说明。
+- `WalkStrategy` 接口说明。
+- `SourceEdge` 接口说明。
+- `TraceEvent` 接口说明。
+- `PolicyBundleVersion` 接口说明。
+- 模块接口与外部接入方式。
+
+否则高吞吐只会放大耦合和数据污染。
+
+## 5. 执行约束
+
+- 平台 API 限流通过 `PlatformSearchInterface` 和 `PlatformFetchInterface` 返回 `rate_limited`、`retry_after`、`quota_scope`,不能埋在平台接入模块私有日志里。
+- 候选池、规则决策、SourceEdge、TraceEvent 都必须有幂等键,允许失败重试但不重复提交资产。
+- 规则评估可以批量化,但输出必须仍然是一条条可复盘的 `RuleDecision`。
+- 游走扩展必须受 `PathState`、预算和停止规则约束,不能由平台召回量直接驱动指数扩散。
+- Trace 写入第一阶段保持 只追加文件接口说明;未来替换为事件日志或表存储时,模块接口说明不变。
+- 策略学习第一阶段离线批处理;近实时学习必须另开 ADR。
+
+## 6. 后续技术选型待定
+
+- 候选池用 JSONL、MySQL、Postgres、ClickHouse 还是对象存储加索引。
+- Trace 用 只追加 表还是事件日志。
+- 平台抓取是否按平台独立队列。
+- 是否需要多租户或多业务线隔离。

+ 185 - 0
tech_documents/content_agent_architecture/review/archive/2026-06-05_架构交叉验证报告.md

@@ -0,0 +1,185 @@
+# 架构交叉验证报告
+
+本文是归档审查记录,不参与核心必读链路。当前核心架构主线以 `00_架构总览.md` 到 `09_模块接口与外部接入说明.md` 为准。
+
+名词说明:`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`TraceEvent`(运行事件)、`SourceEdge`(来源记录)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)。
+
+本文用于对 `00-09` 架构文档做第二轮质量校验。校验视角来自四类方法:
+
+- 业务模块划分检查:业务模块、统一语言、核心对象、业务事件、数据责任。
+- 架构模式检查:Clean / Hexagonal / 模块接口与外部接入方式、依赖方向、可测试性。
+- Senior Architect Review:规模化、演进路径、运行风险、ADR 完整性。
+- Security Boundary Precheck:只做轻量边界预检,不替代正式威胁建模。
+
+## 总体结论
+
+当前 10 个业务模块的划分方向是成立的,尤其是以下设计是值得保留的:
+
+- 判断规则模块和游走策略模块分开,有利于规则包和路径策略分别迭代。
+- 候选与证据模块不直接负责最终判断,只提供 `EvidenceBundle`,避免把事实采集和业务判断混在一起。
+- `SourceEdge` 被提升为一等数据对象,有利于复盘、学习和路径优化。
+- 策略目录与版本模块独立存在,有利于后续做实验、灰度、回滚和对比评估。
+- 保留完整候选池,而不是只保留命中结果,有利于后续学习算法使用正负样本。
+
+本文列出的 P1 已在 v0.2 收敛计划中处理:统一语言、数据责任、规则与游走接口说明、模块接口说明和 ADR 会共同约束后续实现。
+
+## 历史 P1 与 v0.2 收敛结果
+
+### 1. v0.1 中 `source_edges` 的负责方存在冲突
+
+v0.1 架构文档把 `source_edges.jsonl` 放到结果沉淀与来源反查模块下,但 V1 运行记录中它更像由游走模块直接产生:节点之间的来源记录和下一跳,天然发生在游走执行过程中。
+
+这会带来一个边界问题:如果结果沉淀与来源反查模块负责 `source_edges`,游走策略模块是否可以直接写入?如果游走策略模块直接写入,结果沉淀与来源反查模块的数据责任就会被绕过。
+
+v0.2 收敛为:
+
+- 游走策略模块负责产生 `source_edge_basis`。
+- 运行记录模块负责追加写入运行期事实。
+- 结果沉淀与来源反查模块负责读取、归并、查询和对外暴露稳定来源路径。
+
+### 2. 规则包的写入范围过宽
+
+原 V1 规则包的 `trace_policy.write_to` 包含 `rule_decisions.jsonl`、`source_edges.jsonl`、`trace_events.jsonl`、`final_output.json`。从业务模块职责和依赖方向看,这会让判断规则模块越权写游走边、运行记录和最终资产输出。
+
+建议改成:
+
+- 判断规则模块只产出 `RuleDecision`。
+- 运行编排层根据 `RuleDecision` 和 `WalkStrategy` 产出 `TraceEvent`、`SourceEdge`。
+- 结果沉淀与来源反查模块负责 `final_output` 或最终资产提交。
+
+规则包可以声明“希望记录哪些复盘字段”,但不应该直接拥有所有输出文件。
+
+### 3. v0.1 中搜索线索概念不能长期承担两种含义
+
+原文档中 `SearchClue` 同时承担运行观测和资产沉淀两种含义,这是合理的过渡状态,但不适合作为长期业务模型。双重含义会导致线索生成、线索复用、线索资产化、线索效果评估都可能被多个业务模块同时改。
+
+v0.2 拆成两个概念:
+
+- `ExecutableQuery` / `SearchPlan`:由 Query 生成模块负责,表示本次要执行的搜索意图。
+- `SearchClueObservation`:由运行记录模块负责,表示本次运行 的线索效果。
+- `SearchClueAsset`:由结果沉淀与来源反查模块负责,表示经过验证、有复用价值的搜索线索资产。
+
+中间通过 `CluePromotionDecision` 或 `ClueCommitEvent` 连接。
+
+### 4. `Candidate` 和 `ContentAsset` 的状态语义要分开
+
+当前语言表里 `Candidate` 有时表示候选对象,有时又接近“内容资产的候选状态”。这会让后续资产沉淀、负样本保留、命中结果输出变得混乱。
+
+建议统一:
+
+- `Candidate` 是采集过程对象,属于候选与证据模块。
+- `ContentAsset` 是沉淀后的业务资产,属于结果沉淀与来源反查模块。
+- 如果需要状态字段,使用 `candidate_status` 和 `asset_status`,不要都叫 `status`。
+
+## P2:v0.2 后仍需实现阶段细化的问题
+
+### 5. 模块接口与外部接入说明需要映射到代码模块
+
+v0.2 已新增模块接口说明,当前核心路径为 `09_模块接口与外部接入说明.md`。
+
+进入代码设计前,还需要把这些模块接口映射到具体模块、测试替身和文件接入模块。
+
+### 6. 核心对象需要区分“业务生命周期”和“批处理过程”
+
+当前文档里有 `CandidateBatch`、`RuleEvaluation`、`AssetCommit` 这类过程记录对象。它们有用,但要避免替代真正的长期业务对象。
+
+v0.2 已保留过程记录对象和长期业务对象的区别。进入实现时建议沿用:
+
+- 过程记录对象:`SearchRun`、`WalkRun`、`RuleEvaluationRun`、`AssetCommitRun`。
+- 长期业务对象:`Candidate`、`EvidenceBundle`、`PolicyBundleVersion`、`ContentAsset`、稳定来源路径。
+
+这样既能支持一次运行复盘,也能支持长期资产学习。
+
+### 7. 高吞吐架构已预留容量和背压语义,仍需实现验证
+
+`future/02_高吞吐执行架构预研.md` 和 `09_模块接口与外部接入说明.md` 已经预留幂等、限流、重试和背压语义。
+
+后续实现阶段还需要把这些语义落到模块、测试替身和接入模块行为中,并通过压测或真实平台额度明确:
+
+- 平台 API 限流如何表达成业务约束。
+- 候选池写入的幂等键和去重窗口。
+- 规则评估和游走执行是否允许异步乱序。
+- `TraceEvent`、`RuleDecision`、`SourceEdge` 的落盘顺序要求。
+- 失败重试后如何避免重复资产提交。
+
+这些不是马上要微服务化,而是要在模块接口接入模块和执行器中落成可测试行为。
+
+### 8. 需要补轻量安全与信任边界
+
+v0.2 已在 `09_模块接口与外部接入说明.md` 标出轻量信任边界:
+
+- 外部平台 API 边界。
+- 热点画像 / 账号画像边界。
+- DemandAgent / Pattern evidence 边界。
+- 运行记录存储边界。
+- 策略配置和规则包发布边界。
+- API key、cookie、账号凭据等 secrets 边界。
+
+正式威胁建模仍需后续单独执行,它会影响日志脱敏、证据保存、策略回放和平台接入模块的实现方式。
+
+## 推荐执行顺序
+
+### 第一步:统一语言表收敛
+
+先处理:
+
+- `Candidate` vs `ContentAsset`
+- `SearchClueObservation` vs `ExecutableQuery` vs `SearchClueAsset`
+- `SourceEdge` vs `source_edge_basis`
+- `RuleDecision` vs `TraceEvent`
+
+目标是让每个词只属于一个业务模块,跨模块只通过事件或模块接口传递。
+
+### 第二步:数据责任表修订
+
+重点定清:
+
+- 谁负责 `source_edges`
+- 谁负责 `rule_decisions`
+- 谁负责 `trace_events`
+- 谁负责 `final_output`
+- 谁能追加写,谁只能读取,谁能归并成稳定视图
+
+这一步完成后,模块边界才真正可落代码。
+
+### 第三步:规则包和游走策略接口说明落地
+
+分别定义:
+
+- `RulePackVersion + EvidenceBundle -> RuleDecision`
+- `WalkStrategyVersion + PathState + RuleDecision -> WalkAction`
+
+并强制包含:
+
+- version
+- idempotency_key
+- input_snapshot_ref
+- rationale / matched_rule / confidence
+- replay_fields
+- produced_at
+
+### 第四步:确认模块接口说明和 ADR
+
+已补:
+
+- 模块接口与外部接入说明文档。
+- ADR:为什么先做模块化单体,而不是立即微服务化。
+- ADR:`source_edges` 的负责写入方和写入边界。
+
+## 质量评分
+
+| 维度 | 评价 | 风险 |
+| --- | --- | --- |
+| 业务模块划分 | 方向正确 | v0.2 已拆分线索观测和线索资产 |
+| 数据责任 | 大体可用 | v0.2 已收敛 `source_edges`、`search_clues` 负责方 |
+| 规则可插拔性 | 方向正确 | v0.2 已限定规则包只拥有 `RuleDecision` |
+| 游走策略可演进性 | 方向正确 | `WalkRun`、`PathState` 接口说明还需细化 |
+| 学习闭环 | 基础扎实 | 正负样本和延迟反馈字段需补 |
+| 高吞吐扩展 | 有预研方向 | 模块接口层接口说明已预留,模块映射和测试替身待实现 |
+| 安全边界 | 轻量边界已标注 | 正式威胁建模或安全 ADR 待真实平台/API key 实现前补 |
+
+## 结论
+
+不建议推翻当前 10 个业务模块划分。它已经能支撑 V1 到长期演进的主方向。
+
+下一步不是重新划分业务模块,而是把边界变硬:统一语言、数据责任、规则与游走接口说明、模块接口说明。这四件事完成后,再进入代码结构设计会稳很多。

+ 332 - 0
tech_documents/content_agent_code_blueprint/01_V1代码落地蓝图.md

@@ -0,0 +1,332 @@
+# V1 代码落地蓝图
+
+版本:v0.6
+
+状态:V1 后端骨架已落地,真实数据最小闭环进行中
+
+名词说明:`trace_id`(本次运行 ID)、`aweme_id`(抖音视频 ID)、`EvidenceBundle`(判断证据包)、`RuleDecision`(规则判断结果)、`WalkAction`(下一步动作)、`source_edges.jsonl`(来源记录文件)、`rule_decisions.jsonl`(规则判断结果文件)、`final_output.json`(最终结果文件)。
+
+## 1. 目标
+
+本文只服务于第一版代码实现,不替代产品方案和架构文档。
+
+V1 直接采用:
+
+```text
+薄 FastAPI 内部接口
++ LangGraph 运行编排
++ Python 模块化单体
++ 本地 JSON / JSONL 运行记录
+```
+
+核心要求:
+
+- V1 后端必须能通过 FastAPI 接口和运行文件验证真实 case、真实拉取数据、规则判断、游走过程和策略复盘建议。
+- 代码目录必须直截了当地体现 [02_业务模块划分.md](../content_agent_architecture/02_业务模块划分.md) 的 10 个业务模块。
+- FastAPI 只做内部控制和查询,不写业务判断。
+- LangGraph 只做运行编排,不拥有业务逻辑。
+- 业务模块不导入 FastAPI、LangGraph、HTTP SDK 或本地文件写入实现。
+- 可视化前端不进入 V1 代码蓝图,等后端链路跑通后再单独设计。
+
+## 2. 运行形态
+
+```text
+本地管理脚本 / 后续内部后台
+  -> FastAPI 内部接口
+    -> RunService
+      -> LangGraph RunGraph
+        -> 10 个业务模块
+          -> 模块接口
+            -> 外部接入实现
+              -> runtime/v1/{trace_id}/
+```
+
+三层必须分清:
+
+| 层 | 负责什么 | 不能做什么 |
+|---|---|---|
+| FastAPI | 启动运行、查询运行、返回运行结果 | 不写规则、不直接改运行记录、不调用平台 |
+| LangGraph | 定义运行节点、节点顺序、失败分支、重试入口 | 不写规则、不归一化平台字段、不沉淀资产 |
+| 业务模块 | 完成各自业务责任,产出标准对象和运行文件 | 不依赖 FastAPI / LangGraph / 具体平台 SDK |
+
+## 3. 目录结构
+
+V1 先用精简目录。目录表达边界,文件表达第一版实现。不要提前把每个模块拆成多个小文件。
+
+```text
+content_agent/
+  api.py
+  schemas.py
+  run_service.py
+  graph.py
+  models.py
+  interfaces.py
+  business_modules/
+    source_seed/
+      __init__.py
+      source_context.py
+    search_intent.py
+    platform_access.py
+    candidate_evidence/
+      __init__.py
+      candidate_builder.py
+      source_evidence.py
+    rule_judgment/
+      __init__.py
+      evaluator.py
+    walk_strategy.py
+    policy_version.py
+    result_source_lookup.py
+    run_record/
+      __init__.py
+      recorder.py
+      validation.py
+    learning_review.py
+  integrations/
+    douyin.py
+    mock_platform.py
+    policy_json.py
+    runtime_files.py
+  cli.py
+tests/
+  fixtures/
+    real_case_source/
+    mock_platform_results/
+  test_v1_graph.py
+  test_api.py
+  test_douyin_client.py
+  test_rule_pack_reading.py
+  test_runtime_files.py
+  test_source_evidence.py
+runtime/
+  v1/
+    {trace_id}/
+```
+
+目录说明:
+
+| 位置 | 职责 |
+|---|---|
+| `api.py` | FastAPI 内部接口,只处理请求、响应和权限边界 |
+| `schemas.py` | FastAPI 请求和响应结构 |
+| `run_service.py` | 运行入口,组装依赖并启动 LangGraph |
+| `graph.py` | LangGraph 运行图、运行状态和节点函数 |
+| `models.py` | 核心数据结构,不导入框架 |
+| `interfaces.py` | 模块接口定义,用 `Protocol` 或抽象类表达 |
+| `business_modules/` | 10 个业务模块,一一对应 02 文档 |
+| `integrations/` | 抖音、mock、规则 JSON、本地运行文件等具体接入实现 |
+| `cli.py` | 本地调试入口,调用同一个 `RunService` |
+
+## 4. 10 个业务模块与代码位置
+
+| 02 里的业务模块 | V1 代码位置 | 主要产物 |
+|---|---|---|
+| 数据源与种子模块 | `business_modules/source_seed/` | `source_context.json`、`pattern_seed_pack.json` |
+| 搜索意图模块 | `business_modules/search_intent.py` | `queries.jsonl` |
+| 平台接入模块 | `business_modules/platform_access.py` | 归一化平台结果 |
+| 候选与证据模块 | `business_modules/candidate_evidence/` | `candidate_pool.jsonl`、`media_assets.jsonl`、`EvidenceBundle` |
+| 判断规则模块 | `business_modules/rule_judgment/` | `rule_decisions.jsonl` |
+| 游走策略模块 | `business_modules/walk_strategy.py` | `WalkAction`、`source_edge_basis` |
+| 策略版本管理模块 | `business_modules/policy_version.py` | `PolicyBundleVersion` 快照 |
+| 结果沉淀与来源反查模块 | `business_modules/result_source_lookup.py` | `final_output.json`、稳定来源路径 |
+| 运行记录模块 | `business_modules/run_record/` | `trace_events.jsonl`、`source_edges.jsonl`、`search_clues.jsonl`、运行文件校验 |
+| 策略学习模块 | `business_modules/learning_review.py` | 策略复盘建议 |
+
+`RunService`、FastAPI 和 LangGraph 都不属于这 10 个业务模块。它们只是入口和编排层。
+
+模块文件拆分规则:
+
+- 单个业务模块超过约 300 行,或出现两个以上稳定子职责时,再拆成业务模块子目录。
+- 当前已拆目录:`source_seed/`、`candidate_evidence/`、`rule_judgment/`、`run_record/`。
+- `integrations/douyin.py` 只有在搜索、作者作品、画像三个接入都变复杂后,才拆成 `integrations/douyin/` 目录。
+- `models.py` 和 `interfaces.py` 先保持单文件;字段稳定后再按对象拆分。
+- 测试先按链路和接口分文件,不按每个小函数建目录。
+
+## 5. 依赖规则
+
+必须遵守:
+
+```text
+api
+  -> run_service
+    -> graph
+      -> business_modules
+        -> models / interfaces
+
+integrations
+  -> interfaces / models
+```
+
+禁止:
+
+- `business_modules/` 导入 `fastapi`。
+- `business_modules/` 导入 `langgraph`。
+- `business_modules/` 直接读写 `runtime/v1/{trace_id}/` 文件。
+- `business_modules/` 直接调用抖音 HTTP SDK。
+- `api.py` 直接写 `rule_decisions.jsonl`、`source_edges.jsonl` 或 `final_output.json`。
+- `graph.py` 直接写运行文件或调用抖音 HTTP SDK。
+- 后续可视化前端直接读取本地运行文件或硬编码事实。
+
+允许:
+
+- `api.py` 调用 `RunService`。
+- `RunService` 调用 `graph.py`。
+- `graph.py` 的节点调用业务模块。
+- 业务模块通过 `interfaces.py` 使用外部接入实现。
+
+## 6. FastAPI 内部接口
+
+V1 的 FastAPI 是内部控制面,先服务本地管理脚本,后续再服务可视化前端。
+
+| 方法 | 路径 | 用途 | 返回重点 |
+|---|---|---|---|
+| `POST` | `/runs` | 启动一次运行 | `trace_id`、状态、策略版本 |
+| `GET` | `/runs/{trace_id}` | 查看运行摘要 | 当前节点、产物文件、错误 |
+| `GET` | `/runs/{trace_id}/candidates` | 查看真实拉取候选 | `aweme_id`、作者、来源、状态 |
+| `GET` | `/runs/{trace_id}/rule-decisions` | 查看规则判断 | `final_action`、原因、分数 |
+| `GET` | `/runs/{trace_id}/source-edges` | 查看来源记录和游走路径 | 来源节点、目标节点、动作 |
+| `GET` | `/runs/{trace_id}/final-output` | 查看最终输出 | 内容、作者、淘汰、摘要 |
+| `GET` | `/runs/{trace_id}/strategy-review` | 查看策略复盘建议 | 搜索词、规则、游走预算建议 |
+| `GET` | `/runs/{trace_id}/validation` | 校验运行文件是否完整一致 | `pass/fail` 和问题列表 |
+
+第一版可以只做本机访问和简单 token。不要把它设计成对外用户 API。
+
+`POST /runs` 支持 `platform_mode`:
+
+- `mock`:默认值,使用固定 mock 数据,测试必须稳定。
+- `real`:读取 `.env` 中的 Crawapi 配置,真实调用抖音关键词搜索和内容点赞画像。
+
+真实模式当前默认每条 query 只处理前 3 条候选,可用 `CONTENTFIND_DOUYIN_MAX_RESULTS_PER_QUERY` 调整。这个限制用于避免内容画像请求把一次调试拖得过慢。
+
+Pattern 回扣的真实接口流程见:[03_Pattern回扣流程.md](03_Pattern回扣流程.md)。主链路接入后,应在 `build_candidates` 和 `evaluate_rules` 之间增加回扣判断,把解构和分类树匹配结果写入 `EvidenceBundle.relevance_signal`。
+
+## 7. LangGraph 运行图
+
+LangGraph 的节点要直观对应业务模块,不新增隐藏业务逻辑。一个节点可以调用同一个业务模块里的组合入口,例如候选与证据模块会在一次调用里产出候选、媒体状态和判断证据包。
+
+```text
+load_source
+-> plan_queries
+-> search_platform
+-> build_candidates
+-> load_policy
+-> evaluate_rules
+-> plan_walk
+-> record_run
+-> commit_results
+-> review_strategy
+```
+
+节点对应关系:
+
+| LangGraph 节点 | 调用的业务模块 | 主要输入 | 主要输出 |
+|---|---|---|---|
+| `load_source` | 数据源与种子模块 | 上游 case 或 source 文件 | `source_context.json`、`pattern_seed_pack.json` |
+| `plan_queries` | 搜索意图模块 | `pattern_seed_pack` | `queries.jsonl` |
+| `search_platform` | 平台接入模块 | query、平台、分页预算 | 归一化平台结果 |
+| `build_candidates` | 候选与证据模块 | 平台结果、来源信息 | `candidate_pool.jsonl`、`media_assets.jsonl`、`EvidenceBundle` |
+| `load_policy` | 策略版本管理模块 | 策略组合版本 | 规则包和游走策略版本快照 |
+| `evaluate_rules` | 判断规则模块 | `EvidenceBundle`、规则版本 | `rule_decisions.jsonl` |
+| `plan_walk` | 游走策略模块 | `RuleDecision`、路径状态 | `WalkAction`、`source_edge_basis` |
+| `record_run` | 运行记录模块 | 事件、来源记录、搜索词效果 | `trace_events.jsonl`、`source_edges.jsonl`、`search_clues.jsonl` |
+| `commit_results` | 结果沉淀与来源反查模块 | 候选、判断结果、来源记录 | `final_output.json` |
+| `review_strategy` | 策略学习模块 | 本次运行记录和最终输出 | 策略复盘建议 |
+
+`RunState` 最少包含:
+
+| 字段 | 说明 |
+|---|---|
+| `trace_id` | 本次运行 ID |
+| `policy_bundle_version` | 本次策略组合版本 |
+| `current_step` | 当前运行节点 |
+| `source_ref` | `source_context.json` 引用 |
+| `seed_ref` | `pattern_seed_pack.json` 引用 |
+| `query_refs` | `queries.jsonl` 引用 |
+| `candidate_refs` | `candidate_pool.jsonl` 引用 |
+| `decision_refs` | `rule_decisions.jsonl` 引用 |
+| `source_edge_refs` | `source_edges.jsonl` 引用 |
+| `final_output_ref` | `final_output.json` 引用 |
+| `status` | `running / success / blocked / failed` |
+| `errors` | 可复盘错误 |
+
+## 8. 运行记录文件由谁生成
+
+| 文件 | 生成模块 | V1 是否生成 |
+|---|---|---|
+| `source_context.json` | 数据源与种子模块 | 是 |
+| `pattern_seed_pack.json` | 数据源与种子模块 | 是 |
+| `queries.jsonl` | 搜索意图模块 | 是 |
+| `candidate_pool.jsonl` | 候选与证据模块 | 是 |
+| `media_assets.jsonl` | 候选与证据模块 | 是 |
+| `rule_decisions.jsonl` | 判断规则模块 | 是 |
+| `trace_events.jsonl` | 运行记录模块 | 是 |
+| `source_edges.jsonl` | 运行记录模块 | 是 |
+| `search_clues.jsonl` | 运行记录模块 | 是 |
+| `final_output.json` | 结果沉淀与来源反查模块 | 是 |
+
+策略复盘建议第一版可以由 API 读取这些文件后即时计算,不新增运行记录文件。后续如果需要保存为文件,再更新 `runtime_v1_records_schema.md`。
+
+## 9. 第一条可运行链路
+
+第一条链路先跑真实 case,但保留 mock 模式做测试。
+
+建议命令:
+
+```bash
+uvicorn content_agent.api:app --host 127.0.0.1 --port 8000
+```
+
+启动运行:
+
+```bash
+curl -X POST http://127.0.0.1:8000/runs \
+  -H 'Content-Type: application/json' \
+  -d '{
+    "source": "tests/fixtures/real_case_source/source_context.json",
+    "platform": "douyin",
+    "platform_mode": "real",
+    "trace_id": "v1_trace_001",
+    "policy_bundle_version": "douyin_policy_bundle_v1"
+  }'
+```
+
+运行完成后,FastAPI 接口和运行文件必须能验证:
+
+- 真实 case 来源。
+- 生成的 query。
+- 抖音真实返回候选。V1 当前只做关键词搜索一页结果。
+- 候选的 `aweme_id`、作者、互动数据和画像状态。
+- `RuleDecision` 的 `final_action`、分数和原因。
+- 来源记录和游走路径。
+- 搜索词效果和策略复盘建议。
+
+## 10. 测试切片
+
+| 测试 | 目的 |
+|---|---|
+| 业务模块单元测试 | 不启动 FastAPI、不启动 LangGraph,直接测模块逻辑 |
+| LangGraph 集成测试 | 使用 mock 平台跑完整图,检查 10 个运行文件 |
+| FastAPI 接口测试 | 调 `/runs` 和查询接口,确认返回格式 |
+| 真实客户端单元测试 | 用假 HTTP 响应测关键词搜索、画像成功、画像缺失和 HTTP 错误 |
+| 端到端接口冒烟测试 | 启动 FastAPI,确认能查询真实运行结果 |
+| 字段一致性测试 | 校验 JSON / JSONL 字段和规则包枚举一致 |
+
+当前测试状态:`uv run pytest -q` 通过,覆盖 mock 完整链路、真实 source fixture、规则包读取、运行文件校验、FastAPI 查询接口和真实 Crawapi 客户端字段映射。
+
+## 11. 暂不做
+
+- 暂不做对外用户 API。
+- 暂不做多进程 worker。
+- 暂不做复杂队列。
+- 暂不做数据库持久化。
+- 暂不让策略学习自动改规则。
+- 暂不做可视化前端。
+- 暂不做作者一跳、账号画像兜底、tag 扩散。
+
+## 12. 写代码前最后确认
+
+开始写代码前,只需要确认:
+
+1. Python 包管理方式默认使用 `uv` 还是普通 `requirements.txt`。
+2. 抖音真实接口凭据从哪里读取,只能用 `secret_ref`,不能写入运行记录。
+3. 第一条真实 case 使用哪份 `source_context.json`:当前使用 `tests/fixtures/real_case_source/source_context.json`。

+ 60 - 0
tech_documents/content_agent_code_blueprint/02_待决策事项.md

@@ -0,0 +1,60 @@
+# V1 待决策事项
+
+状态:真实数据最小闭环实施中
+
+本文只记录需要你后续拍板的口径,不承担主设计说明。
+
+## 1. Pattern 回扣口径
+
+当前实现:真实抖音关键词搜索返回的新视频默认标记为 `candidate_related`(候选相关),不会自动写成 `matched`(已命中 Pattern)。
+
+影响:现有规则包要求 `relevance_signal.pattern_recall` 必须是 `strong` 或 `matched`,所以真实候选即使互动和画像可用,也可能先被 `video_pattern_recall_required` 淘汰。
+
+待决策:是否允许标题、标签或描述文本命中 seed terms 时,把候选升级为 `matched`。如果允许,需要明确:
+
+- 哪些字段参与匹配:`desc`、`item_title`、`cha_list`、`text_extra`。
+- 匹配到几个词才算通过。
+- 是否必须同时命中分类或元素词。
+- 是否需要二次模型判断。
+
+## 2. 50+ 内容画像分级
+
+当前实现:内容点赞画像可用时,根据年龄分布中 50+ 占比和 TGI 做临时分级:
+
+- `strong`:50+ 占比不低于 25%,或 TGI 不低于 130。
+- `medium`:50+ 占比不低于 10%,或 TGI 不低于 100。
+- `weak`:低于上述条件。
+- `missing`:接口拿不到画像或年龄维度为空。
+
+待决策:这些阈值是否作为 V1 业务口径保留,还是只作为工程冒烟时的临时计算。
+
+## 3. Crawapi 鉴权方式
+
+当前实现:沿用旧项目实测方式,只发 `Content-Type: application/json`,不发送 Bearer 或 API Key。`.env` 中 `CONTENTFIND_API_CRAWAPI_KEY` 为空不阻断真实模式。
+
+待决策:如果后续 Crawapi 要求鉴权,需要明确 header 名称和传值方式。运行文件里只能记录 `auth_mode` 或 `secret_ref`,不能写入真实密钥。
+
+## 4. 真实模式范围
+
+当前实现:`platform_mode=real` 只做抖音关键词搜索一页结果和内容点赞画像。
+
+暂未实现:
+
+- 作者作品一跳。
+- 账号粉丝画像兜底。
+- tag 扩散生成新 query。
+- 数据库写入。
+
+待决策:下一步优先补作者一跳,还是先补文本匹配回扣 Pattern。
+
+## 5. 真实候选数量和画像耗时
+
+当前实现:真实关键词搜索会读取一页结果,但默认每条 query 只处理前 3 条候选,对应配置项是 `CONTENTFIND_DOUYIN_MAX_RESULTS_PER_QUERY`。
+
+原因:真实冒烟时,两条 query 返回十几条候选,如果逐条补内容画像,一次调试会明显变慢。
+
+待决策:
+
+- V1 上线第一天每条 query 默认处理几条候选。
+- 内容画像是否需要独立短超时。
+- 画像失败时是保留候选并标记 `missing`,还是直接淘汰。

+ 292 - 0
tech_documents/content_agent_code_blueprint/03_Pattern回扣流程.md

@@ -0,0 +1,292 @@
+# V1 Pattern 回扣流程
+
+状态:真实接口已验证,待接入主链路
+
+本文只记录后续写代码需要的最小流程。Pattern 回扣的目标是回答一个问题:新搜到的抖音视频,是否能通过解构结果和分类树路径解释回本次需求的 Pattern。
+
+本文和 [Pattern和分类树反查手册.md](../Pattern和分类树反查手册.md) 不冲突。反查手册讲的是上游证据的 Exact Mode:`source_post_id` 或 `matched_post_ids` 中的帖子如何精确追到具体 Pattern、itemset 和分类树节点。本文讲的是 CFA 新搜到的候选视频如何通过“解构 + 分类树匹配”获得判断证据。它可以让候选通过规则包里的 Pattern 回扣硬门槛,但不能把新候选写成上游 Pattern 的原始支撑帖子。
+
+## 1. 输入和输出
+
+输入:
+
+- `Candidate`:来自抖音关键词搜索或作者作品的一条候选视频。
+- `source_context.ext_data.evidence_pack`:本次需求的 Pattern 来源证据。
+- `pattern_seed_pack`:本次 Pattern 的 itemset、分类、元素和 seed terms。
+
+输出写入 `EvidenceBundle.relevance_signal`:
+
+```json
+{
+  "pattern_recall": "matched",
+  "category_or_element_binding": "tree_walk_match",
+  "matched_category_paths": [
+    "/理念/知识/公共管理/政策法规/治理监督/反腐案例"
+  ],
+  "matched_terms": ["贪污公款", "贪污案例"],
+  "decode_status": "SUCCESS",
+  "match_status": "matched"
+}
+```
+
+这里的 `matched` 表示“本次候选视频通过了解构回扣判断”,不是“这个 `aweme_id` 已经出现在上游 Pattern 的 `matched_post_ids` 里”。后者仍然只能由上游 `evidence_pack` 或 Pattern 数据库事实证明。
+
+如果解构未完成:
+
+```json
+{
+  "pattern_recall": "candidate_related",
+  "category_or_element_binding": "candidate_related",
+  "decode_status": "RUNNING",
+  "match_status": "pending"
+}
+```
+
+## 2. 流程
+
+```text
+Candidate
+-> 提交解构任务
+-> 轮询解构结果
+-> 抽取解构元素
+-> 调分类树 match-paths
+-> 判断是否回扣 Pattern
+-> 写入 EvidenceBundle.relevance_signal
+-> 规则包 hard gate 判断
+```
+
+当前不能直接用标题、tag 或 desc 文本判定回扣成功。标题词只能作为解构输入或辅助证据,不能直接把候选升级成 `matched`。
+
+## 3. 解构接口
+
+提交接口:
+
+```text
+POST https://aigc-api.aiddit.com/aigc/api/task/decode
+```
+
+查询接口:
+
+```text
+POST https://aigc-api.aiddit.com/aigc/api/task/decode/result
+```
+
+固定参数:
+
+```json
+{
+  "configId": 58
+}
+```
+
+候选视频请求体示例:
+
+```json
+{
+  "params": {
+    "configId": 58,
+    "skipCompleted": false,
+    "posts": [
+      {
+        "channelContentId": "7386896932536438066",
+        "title": "【警示教育】基层公职人员贪污公款数千万元 只为满足私欲 #警示教育 #遵纪守法 (来源:共产党员网)",
+        "bodyText": "【警示教育】基层公职人员贪污公款数千万元 只为满足私欲 #警示教育 #遵纪守法 (来源:共产党员网)",
+        "images": [],
+        "video": null,
+        "contentModal": 4,
+        "channel": 2,
+        "mergeLeve1": "",
+        "mergeLeve2": "贪污腐败"
+      }
+    ]
+  }
+}
+```
+
+状态处理:
+
+| 状态 | 处理 |
+|---|---|
+| `SUCCESS` | 解析 `dataContent`,继续分类树匹配 |
+| `PENDING` / `RUNNING` | 本轮标记 `match_status=pending`,候选进入 `PENDING` 或继续等待 |
+| `FAILED` | 标记 `decode_status=FAILED`,保留错误原因摘要,不进入 Pattern 回扣通过 |
+
+V1 不能同步阻塞太久。建议第一版把解构做成可轮询状态:本次运行先记 `PENDING`,后续补跑回扣和规则判断。
+
+## 4. 解构元素抽取
+
+优先使用这些字段里的词:
+
+| 来源 | 用途 |
+|---|---|
+| `目的点[].点` | 最重要,通常是业务实质摘要 |
+| `目的点[].实质.*[].名称` | 重要,代表内容实质对象 |
+| `关键点[].点` | 次重要,代表内容关键特征 |
+| `关键点[].实质.*[].名称` | 次重要,可作为辅助实质 |
+| `分词结果[].词` | 辅助,不单独决定回扣 |
+| `contribution_results[].词` | 辅助,适合排序或补充证据 |
+
+不要把所有词一股脑都当成 Pattern 回扣证据。形式词、泛词、噪声词要降权,例如:
+
+- `标题`
+- `结构`
+- `标签`
+- `语气`
+- `官方来源`
+
+## 5. 分类树匹配接口
+
+接口:
+
+```text
+POST https://library.aiddit.com/api/search/categories/match-paths
+```
+
+请求体:
+
+```json
+{
+  "source_type": "实质",
+  "top_k": 10,
+  "min_score": 0.6,
+  "items": [
+    {
+      "term": "贪污公款",
+      "description": "解构实质元素"
+    }
+  ]
+}
+```
+
+真实验证结果:
+
+```text
+警示公职人员贪污公款案例
+-> /理念/知识/公共管理/政策法规/治理监督/反腐案例
+score = 0.6346
+
+贪污公款
+-> /理念/知识/公共管理/政策法规/治理监督/反腐案例
+score = 0.6364
+
+贪污案例
+-> /理念/知识/公共管理/政策法规/治理监督/反腐案例
+score = 0.6668
+```
+
+噪声示例:
+
+```text
+公职人员 -> /理念/知识/思想/概念范畴/关系结构/构成要素
+警示教育 -> /理念/知识/身心科学/健康养生/身体健康/健康警示
+遵纪守法 -> /理念/知识/法律/法律规范
+私欲动机 -> 无命中
+```
+
+因此不能按“任意词命中”判断回扣成功。
+
+## 6. 回扣判断规则
+
+V1 建议先用保守规则:
+
+1. 至少一个强证据词命中分类树路径,强证据词来自:
+   - `目的点[].点`
+   - `目的点[].实质.*[].名称`
+   - `关键点[].点` 中类型为实质的点
+2. 命中的分类路径必须和本次 `evidence_pack.category_bindings`、`itemset_items` 或 `seed_terms` 可解释地相连。
+3. 命中路径如果只是泛概念、形式词、法律常识词,不算回扣通过。
+4. 只有通过上述检查,才允许:
+
+```text
+pattern_recall = matched
+category_or_element_binding = direct_match 或 tree_walk_match
+```
+
+否则保持:
+
+```text
+pattern_recall = candidate_related
+category_or_element_binding = candidate_related
+```
+
+回扣通过后仍要保留来源边界:
+
+- 可以说:这个新候选被当前需求 Pattern 引导找到,并且解构结果能解释回相近分类路径。
+- 不可以说:这个新候选是上游 itemset 的原始支撑帖子。
+- 不可以改写:`source_evidence.source_post_id`、`source_evidence.matched_post_ids`。
+- 必须另存:候选自己的 `pattern_recall_evidence`。
+
+## 7. 接入位置
+
+代码建议新增业务模块:
+
+```text
+content_agent/business_modules/pattern_recall/
+  __init__.py
+  decode.py
+  category_match.py
+  recall_decision.py
+```
+
+主链路位置:
+
+```text
+search_platform
+-> build_candidates
+-> recall_pattern
+-> load_policy
+-> evaluate_rules
+```
+
+职责边界:
+
+| 模块 | 负责 |
+|---|---|
+| `integrations/decode_api.py` | 调解构提交、查询接口 |
+| `integrations/category_match.py` | 调分类树 match-paths 接口 |
+| `business_modules/pattern_recall/` | 抽取元素、判断是否回扣 Pattern |
+| `candidate_evidence` | 把回扣结果装进 `EvidenceBundle` |
+| `rule_judgment` | 只读取 `EvidenceBundle`,继续按规则包 hard gate 执行 |
+
+规则包不直接调用解构接口,也不直接调分类树接口。
+
+## 8. 运行记录
+
+V1 接入后需要补充记录:
+
+- `trace_events.jsonl` 增加 `decode_submitted`、`decode_completed`、`category_matched`。
+- `rule_decisions.jsonl.replay_fields` 增加回扣依据摘要。
+- `source_evidence` 增加候选视频的回扣证据引用,但不能改写上游 `source_post_id` 或 `matched_post_ids`。
+
+建议新增字段:
+
+```json
+{
+  "pattern_recall_evidence": {
+    "decode_status": "SUCCESS",
+    "matched_terms": ["贪污公款", "贪污案例"],
+    "matched_category_paths": [
+      "/理念/知识/公共管理/政策法规/治理监督/反腐案例"
+    ],
+    "match_api": "library.aiddit.com/api/search/categories/match-paths"
+  }
+}
+```
+
+## 9. 当前验证结论
+
+已验证:
+
+- 解构提交接口可达。
+- 解构查询接口可达。
+- 真实候选视频 `7386896932536438066` 返回 `SUCCESS`。
+- 解构结果能抽出实质词和关键点。
+- 分类树 `match-paths` 接口可达。
+- 解构词能命中反腐相关路径。
+
+待实现:
+
+- 将解构异步状态接入 V1 运行。
+- 将分类树匹配结果写入 `EvidenceBundle.relevance_signal`。
+- 将回扣证据写入运行记录。
+- 让规则包 hard gate 基于真实回扣结果判断,而不是一直停留在 `candidate_related`。

+ 684 - 0
tech_documents/data_interface.md

@@ -0,0 +1,684 @@
+# Content Find Agent v2 Data Interface
+
+更新时间:2026-06-04
+
+## 0. 证据口径
+
+本文只记录旧版代码、旧版接口审计、DB-first schema inventory、ContentFindAgent 数据工程 run 和 subagent 交叉验证过的数据接口。
+
+| 状态 | 含义 |
+|---|---|
+| 已验证 | 已有 DB / API 只读验证或旧版实测结果 |
+| 源码定位 | 旧版代码能定位到接口或写入逻辑,但本次未实连 |
+| blocked | 因 token、只读条件或副作用限制未实连 |
+| pending / 待接入 | DB 字段存在,产品应接入,但旧版 ContentFindAgent 未作为主链路实现 |
+| source-only | 已有配置来源或源码来源,但本轮未作为主链路实连验证 |
+| missing | 变量、key、表或 API 未找到可用真实值 |
+| 缺口 | 没有真实表、真实接口或稳定字段,不能写成已接入 |
+
+不把 `show` 静态样例、小红书搜索、共创作者、相似作者、候选池、淘汰原因表写成已验证接口。
+
+### 0.1 阅读方式
+
+每个阶段都按“短索引 + 详情块”组织:
+
+- 短索引只看接口、用途和状态,方便快速扫读。
+- 详情块再看输入字段、输出字段和边界,避免长表在窄屏下挤成一行。
+- 缺口统一放在最后,避免和已验证接口混在一起。
+
+### 0.2 环境配置和只读验证摘要
+
+本节吸收原环境缺口报告的确认结果,只记录变量名、能力和验证状态,不记录密码、token、AK/SK 或完整 DSN。
+
+#### 已验证配置
+
+- `.env` 结构:pass
+  - 对象:根目录 `.env`
+  - 注释:共 `139` 个变量,无重复 key;本地私有文件,不进入代码仓库。
+- 旧版 env 覆盖:pass
+  - 对象:旧仓 Python `os.getenv(...)`
+  - 注释:旧代码中发现的 `52` 个 getenv 变量都已在 `.env` 中覆盖。
+- 业务库:pass
+  - 变量:`DB_*`, `CONTENT_SUPPLY_DB_*`, `CONTENTFIND_DB_*`
+  - 注释:`demand_content`, `workflow_decode_task_result`, `demand_find_author`, `demand_find_content_result`, `demand_find_task`, `demand_task_oprate` 均只读通过。
+- Pattern 库:pass
+  - 变量:`OPEN_AIGC_PATTERN_DB_*`, `PATTERN_GLOBAL_DB_*`, `CONTENTFIND_PATTERN_DB_*`
+  - 注释:`topic_pattern_element`, `topic_pattern_itemset`, `topic_pattern_category` 均只读通过。
+- Crawapi 抖音接口:pass
+  - 变量:`CONTENTFIND_API_CRAWAPI_BASE_URL`, `CONTENTFIND_DOUYIN_*`, `CONTENTFIND_HOT_TOPIC_PATH`
+  - 注释:抖音关键词、作者作品、内容画像、账号画像、今日热榜均只读通过。
+- AIGC plan detail:pass
+  - 变量:`AIGC_TOKEN`, `CONTENTFIND_API_AIGC_TOKEN`, `CONTENTFIND_API_READONLY_TOKEN`, `CONTENTFIND_AIGC_READONLY_PRODUCE_PLAN_ID`
+  - 注释:只查 produce plan detail,未创建或保存计划。
+- OpenRouter:pass
+  - 变量:`OPEN_ROUTER_API_KEY`, `OPENROUTER_API_KEY`, `OPENROUTER_BASE_URL`
+  - 注释:两个 key 别名已同步;只读 key metadata probe HTTP 200;未调用 chat completion 或 embeddings。
+- 上游 open_aigc MySQL:pass
+  - 变量:`OPEN_AIGC_MYSQL_*`
+  - 注释:本轮只读 `SELECT 1` 通过,并完成 5 张上游表 `LIMIT 1` 探测。
+- 上游 open_aigc PG:pass
+  - 变量:`OPEN_AIGC_PG_*`, `PGVECTOR_DSN`, `DATA_ENGINEERING_OPEN_AIGC_PG_DSN`
+  - 注释:`DATA_ENGINEERING_OPEN_AIGC_PG_DSN` 和同步后的 `OPEN_AIGC_PG_*` 均只读 `SELECT 1` 通过;Pattern 相关表 `LIMIT 1` 通过。
+- ODPS 表现数据:pass
+  - 变量:`ODPS_ACCESS_ID`, `ODPS_ACCESS_KEY`, `ODPS_ENDPOINT`, `ODPS_PROJECT`
+  - 注释:本轮 `SELECT 1` 通过,`video_dimension_detail_add_column`, `dwd_multi_demand_pool_di`, `feature_point_data` schema 可读。
+
+#### 待补或仅有来源配置
+
+- TikHub fallback:missing
+  - 变量:`TIKHUB_API_KEY`, `TIKHUB_BASE_URL`, `TIKHUB_DOUYIN_SEARCH_PATH`
+  - 注释:URL 和 path 已有,旧仓未找到真实 key;只能作为待补备用通道。
+- OSS:missing
+  - 变量:`ALIYUN_OSS_*`, `OSS_ACCESS_KEY_ID`, `OSS_ACCESS_KEY_SECRET`
+  - 注释:变量名已定位,真实 AK/SK、bucket、region、prefix、public base URL 缺失。
+- Feishu:source-only
+  - 变量:`FEISHU_APP_ID`, `FEISHU_APP_SECRET`
+  - 注释:旧版有来源,但不是 v2 主链路;本轮未调用。
+- 前端联调 API:missing
+  - 变量:`VITE_API_BASE_URL`, `VITE_CONTENTFIND_API_BASE_URL`
+  - 注释:show 当前仍是静态沙盘,尚未接真实后端。
+
+## 1. 数据源
+
+数据源阶段回答:从哪里拿原始素材、原始种子和可搜索线索。
+
+### 1.1 索引
+
+| 编号 | 接口/表 | 用途 | 状态 |
+|---|---|---|---|
+| S1 | `content-deconstruction-supply.demand_content` | 需求输入 | 已验证 |
+| S2 | `open_aigc_pattern.topic_pattern_element` | Pattern / 特征词到历史 Case | 已验证 |
+| S3 | `content-deconstruction-supply.workflow_decode_task_result` | 直接 Case 原始素材和解构点 | 已验证 |
+| S4 | `topic_pattern_itemset -> workflow_decode_task_result` | Pattern Item Set 派生多个 Case | pending / 待接入 |
+| S5 | `open_aigc_pattern.topic_pattern_category` | Pattern 类目上下文 | 已验证 |
+| S6 | `content-deconstruction-supply.demand_find_author` | 历史沉淀账号 | 已验证 |
+| S7 | 今日热榜 `/crawler/jin_ri_re_bang/content_rank` | 热点入口 | 已验证 |
+| S8 | `open_aigc.post` | 上游历史内容素材 | 已验证 |
+| S9 | `open_aigc.post_decode_topic_point_element` | 上游选题点元素 | 已验证 |
+| S10 | `open_aigc.post_script_paragraph_field_element` | 上游脚本段落元素 | 已验证 |
+
+### 1.2 详情
+
+#### S1. 数据库:`content-deconstruction-supply`,表:`demand_content`
+
+- 当前输入字段:`id`, `name`, `suggestion`, `score`, `merge_leve2`, `dt`, `ext_data`
+- 新版必解析字段:`ext_data.evidence_pack`
+- 输出字段:需求词、需求解释、品类、分数、日期。
+- 边界:这是需求源,不是 Case 素材源;旧版只取基础字段,新版不能丢掉证据包。
+
+`ext_data.evidence_pack` 需要包含并向下游传递:
+
+| 字段 | 用途 |
+|---|---|
+| `source_kind` | 区分 Pattern、Case、聚类、历史搜索等来源 |
+| `pattern_source_system` | 区分 Pattern 来源系统或桥接口径 |
+| `case_id_type` | 说明 Case ID / post ID / channel content ID 的口径 |
+| `source_post_id` | 绑定原始素材 ID |
+| `pattern_execution_id` | 回查 Pattern 执行 |
+| `mining_config_id` | 回查 Pattern 挖掘配置 |
+| `itemset_ids` | 回查频繁项集 |
+| `itemset_items[]` | 回查 itemset 内的分类、元素和维度 |
+| `category_bindings` | 绑定分类树父节点 |
+| `element_bindings` | 绑定分类树元素节点 |
+| `matched_post_ids` | 记录 Pattern 支撑素材 |
+| `seed_terms` | 给 Query 使用的策略种子 |
+| `trace_id` | 串起上游和下游 trace |
+| `source_certainty` | 标记来源确定性 |
+| `validation_status` | 标记已验证、待接入或候选反查 |
+
+这些字段要进入数据源、Query、判断、游走、资产清洗沉淀和策略学习。目标是从某个 `case_id` 或 `post_id` 能反查到 Pattern、itemset、分类节点或元素节点。
+
+#### S2. 数据库:`open_aigc_pattern`,表:`topic_pattern_element`
+
+- 输入字段:`name`, `execution_id`, `element_type`, `category_path`
+- 输出字段:`post_id`, `point_type`, `point_text`, `element_type`, `name`
+- 边界:旧版实际按 `name` 精确查,不是严格 Pattern itemset 路径。
+
+#### S3. 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
+
+- 输入字段:`channel_content_id`
+- 输出字段:`title`, `body_text`, `images`, `video_url`, `channel`, `channel_account_id`, `channel_account_name`
+- 解构字段:`purpose_points`, `key_points`, `inspiration_points`, `topic_fusion_result`, `merge_leve2`
+- 边界:Case 的最终落点;可由直接 Case 或 Pattern 派生 Case 回查。
+
+#### S4. 数据库:`open_aigc_pattern`,表:`topic_pattern_itemset` -> 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
+
+- 输入字段:`execution_id`, `combination_type`, `item_count`, `support`, `absolute_support`, `matched_post_ids`
+- 输出字段:用 `matched_post_ids` 回查 `workflow_decode_task_result.channel_content_id`
+- 边界:DB 字段存在,旧版未作为主链路实现;不能写成已跑通。
+
+#### S5. 数据库:`open_aigc_pattern`,表:`topic_pattern_category`
+
+- 输入字段:`execution_id`, `id`, `path`, `source_type`
+- 输出字段:`name`, `description`, `category_nature`, `level`, `parent_id`, `element_count`
+- 边界:辅助解释 Pattern,不直接召回内容。
+
+#### S6. 数据库:`content-deconstruction-supply`,表:`demand_find_author`
+
+- 输入字段:`content_tags`, `channel`, `author_id`, `author_name`
+- 输出字段:`trace_id`, `author_name`, `author_link`, `author_id`, `elderly_ratio`, `elderly_tgi`, `is_good`, `remark`, `content_tags`, `channel`
+- 边界:旧版可按 `content_tags LIKE query` 找历史作者。
+
+#### S7. 接口:今日热榜 `/crawler/jin_ri_re_bang/content_rank`
+
+- 输入字段:`sort_type`, `cursor`
+- 输出字段:`source`, `jump_url`, `type`, `rankList[].title`, `rankList[].heat`, `has_more`, `next_cursor`
+- 边界:热点只做修饰和探索入口,不等于最终内容源。
+
+#### S8. 数据库:`open_aigc`,表:`post`
+
+- 输入字段:`post_id`, `platform`, `merge_leve1`, `merge_leve2`, `import_date`
+- 输出字段:`title`, `body_text`, `platform_account_id`, `platform_account_name`, `publish_timestamp`, `like_count`, `comment_count`, `collect_count`, `images`
+- 边界:数据工程上游素材表,不是旧版 ContentFindAgent 主读表。
+
+#### S9. 数据库:`open_aigc`,表:`post_decode_topic_point_element`
+
+- 输入字段:`post_id`, `topic_point_id`, `element_type`, `element_sub_type`
+- 输出字段:`element_name`, `element_description`, `element_source`, `element_reason`
+- 边界:用于理解上游元素,不替代 `workflow_decode_task_result`。
+
+#### S10. 数据库:`open_aigc`,表:`post_script_paragraph_field_element`
+
+- 输入字段:`post_id`, `paragraph_id`, `field_type`, `element_type`, `element_sub_type`
+- 输出字段:`element_name`, `element_description`
+- 边界:用于策略学习和素材理解。
+
+## 2. Query
+
+Query 阶段回答:哪些输入会被转成平台可执行搜索词或采集动作。
+
+### 2.1 索引
+
+| 编号 | 接口/表 | 用途 | 状态 |
+|---|---|---|---|
+| Q1 | `demand_content` | 需求转 Query 输入 | 已验证 |
+| Q2 | `workflow_decode_task_result` | Case 解构点转 Query 输入 | 已验证 |
+| Q3 | `topic_pattern_element` | Pattern 词 / 元素转 Query 输入 | 已验证 |
+| Q4 | `topic_pattern_itemset` | Pattern 组合转 Query 输入 | pending / 待接入 |
+| Q5 | OpenRouter | LLM Query Builder / 判断辅助 | 只读鉴权通过 |
+| Q6 | `demand_find_task` | Query 执行 trace 台账 | 已验证 |
+
+### 2.2 详情
+
+#### Q1. 数据库:`content-deconstruction-supply`,表:`demand_content`
+
+- 输入字段:`name`, `suggestion`, `merge_leve2`, `score`, `dt`, `ext_data.evidence_pack.seed_terms`, `ext_data.evidence_pack.source_kind`
+- 输出字段:Query 原始需求、品类、解释、策略种子。
+- 边界:旧版从需求池出发;新版有证据包时优先使用 `seed_terms`,不能只把宽泛 `name` 当 Query 输入。
+
+#### Q2. 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
+
+- 输入字段:`purpose_points`, `key_points`, `inspiration_points`, `topic_fusion_result`, `title`, `body_text`, `merge_leve2`
+- 输出字段:Case 策略种子、筛选点、回扣依据。
+- 边界:v2 以解构出的策略种子为主,不直接用账号名做 Query。
+
+#### Q3. 数据库:`open_aigc_pattern`,表:`topic_pattern_element`
+
+- 输入字段:`name`, `element_type`, `category_path`, `point_type`, `point_text`
+- 输出字段:Pattern 词、维度、类目路径。
+- 边界:旧版用 `name` 找历史 case;v2 可用于 Pattern seed。
+
+#### Q4. 数据库:`open_aigc_pattern`,表:`topic_pattern_itemset`
+
+- 输入字段:`combination_type`, `item_count`, `support`, `absolute_support`, `dimensions`, `matched_post_ids`
+- 输出字段:Pattern 组合、支持度、多 Case 命中集合。
+- 边界:字段存在;旧版未用它生成 Query。
+
+#### Q5. 能力:OpenRouter
+
+- 输入字段:`model`, `messages`, `tools`, `system`, `max_tokens`, `temperature`
+- 输出字段:`content`, `tool_calls`, `prompt_tokens`, `completion_tokens`, `finish_reason`, `cost`, `usage`
+- 边界:本轮只做 key metadata 鉴权探测,未调用 chat completion 或 embeddings;真实生成仍需显式预算开关。
+
+#### Q6. 数据库:`content-deconstruction-supply`,表:`demand_find_task`
+
+- 输入字段:`trace_id`, `demand_content_id`, `status`, `created_at`, `token_coast`
+- 输出字段:任务状态、成本、需求关联。
+- 边界:不是 Query 内容表,但承载执行追踪。
+
+## 3. Platform
+
+Platform 阶段回答:同一个 Query 在平台上用什么动作执行,返回什么字段。
+
+### 3.1 索引
+
+| 编号 | 接口/表 | 用途 | 状态 |
+|---|---|---|---|
+| P1 | 抖音关键词搜索 `/crawler/dou_yin/keyword` | Query -> 视频候选 | 已验证 |
+| P2 | TikHub 搜索 fallback | 抖音搜索备用 | blocked |
+| P3 | 抖音账号作品 `/crawler/dou_yin/blogger` | 作者 -> 作者作品 | 已验证 |
+| P4 | 热点宝内容画像 | 视频画像 | 已验证 |
+| P5 | 热点宝账号画像 | 作者画像 | 已验证 |
+| P6 | `demand_find_content_result` | 平台结果沉淀字段 | 已验证 |
+| P7 | `demand_find_author` | 作者平台身份 | 已验证 |
+
+### 3.2 详情
+
+#### P1. 接口:抖音关键词搜索 `/crawler/dou_yin/keyword`
+
+- 输入字段:`keyword`, `content_type`, `sort_type`, `publish_time`, `cursor`, `account_id`
+- 输出字段:`aweme_id`, `desc`, `author.nickname`, `author.sec_uid`, `statistics.*`, `has_more`, `next_cursor`
+- 边界:实测 `content_type=视频` 成功;`综合` 口径不能直接照搬。
+
+#### P2. 接口:TikHub 搜索 fallback
+
+- 输入字段:`keyword`, `cursor`, `sort_type`, `publish_time`, `filter_duration`, `content_type`, `search_id`, `backtrace`
+- 输出字段:`business_data[].data.aweme_info`, `next_page`, `backtrace`
+- 边界:缺 `TIKHUB_API_KEY`,不能写成已接入。
+
+#### P3. 接口:抖音账号作品 `/crawler/dou_yin/blogger`
+
+- 输入字段:`account_id`, `sort_type`, `cursor`
+- 输出字段:`aweme_id`, `desc`, `author.*`, `statistics.*`, `has_more`, `next_cursor`
+- 边界:已验证抖音作者作品;小红书作者作品仍待验证。
+
+#### P4. 接口:热点宝内容画像
+
+- 输入字段:`content_id`, `need_age`, `need_gender`, `need_province`
+- 输出字段:年龄、性别、省份画像,`percentage`, `preference`
+- 边界:用于判断,不是召回接口。
+
+#### P5. 接口:热点宝账号画像
+
+- 输入字段:`account_id`, `need_age`, `need_gender`, `need_province`
+- 输出字段:年龄、性别、省份画像,`percentage`, `preference`
+- 边界:可作为作者是否值得扩展的判断信号。
+
+#### P6. 数据库:`content-deconstruction-supply`,表:`demand_find_content_result`
+
+- 输入字段:`channel`, `aweme_id`, `video_url`, `author_id`, `author_name`, `author_link`
+- 输出字段:平台、内容 ID、作者 ID、链接。
+- 边界:这是结果表字段,不是平台 API。
+
+#### P7. 数据库:`content-deconstruction-supply`,表:`demand_find_author`
+
+- 输入字段:`channel`, `author_id`, `author_name`, `author_link`, `content_tags`
+- 输出字段:作者资产、内容标签、平台身份。
+- 边界:可作为后续作者起点。
+
+## 4. 判断
+
+判断阶段回答:内容、作者、Pattern 派生 Case 是否值得留下或继续扩展。
+
+### 4.1 索引
+
+| 编号 | 接口/表 | 用途 | 状态 |
+|---|---|---|---|
+| J1 | 热点宝内容画像 | 50+ 内容适配 | 已验证 |
+| J2 | 热点宝账号画像 | 50+ 作者适配 | 已验证 |
+| J3 | `workflow_decode_task_result` | Case 回扣判断 | 已验证 |
+| J4 | `topic_pattern_element` | Pattern 回扣判断 | 已验证 |
+| J5 | `topic_pattern_itemset` | Pattern 来源 Case 支持度判断 | pending / 待接入 |
+| J6 | `demand_find_content_result` | 判断结果沉淀 | 已验证 |
+| J7 | `dwd_multi_demand_pool_di` | 下游需求池表现参考 | 已验证 |
+| J8 | `feature_point_data` | 特征表现参考 | 已验证 |
+
+### 4.2 详情
+
+#### J1. 接口:热点宝内容画像
+
+- 输入字段:`content_id`, `need_age`, `need_gender`, `need_province`
+- 输出字段:年龄、性别、省份画像,`percentage`, `preference`
+- 边界:判断信号,不直接代表内容质量。
+
+#### J2. 接口:热点宝账号画像
+
+- 输入字段:`account_id`, `need_age`, `need_gender`, `need_province`
+- 输出字段:年龄、性别、省份画像,`percentage`, `preference`
+- 边界:作者强不代表作品必然强。
+
+#### J3. 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
+
+- 输入字段:`purpose_points`, `key_points`, `inspiration_points`, `topic_fusion_result`
+- 输出字段:目的点、关键点、灵感点、选题描述。
+- 边界:直接 Case 和 Pattern 派生 Case 都回到这张表判断。
+
+#### J4. 数据库:`open_aigc_pattern`,表:`topic_pattern_element`
+
+- 输入字段:`name`, `element_type`, `category_path`, `point_type`, `point_text`
+- 输出字段:元素词、维度、分类路径、点位文本。
+- 边界:旧版是弱 lineage,不是 execution-scoped 精确血缘。
+
+#### J5. 数据库:`open_aigc_pattern`,表:`topic_pattern_itemset`
+
+- 输入字段:`support`, `absolute_support`, `item_count`, `matched_post_ids`, `combination_type`
+- 输出字段:支持度、帖子集合、组合类型。
+- 边界:用于 Pattern -> 多 Case 判断;旧版未主链路使用。
+
+#### J6. 数据库:`content-deconstruction-supply`,表:`demand_find_content_result`
+
+- 输入字段:`digg_count`, `comment_count`, `share_count`, `portrait_source`, `elderly_ratio`, `elderly_tgi`, `recommendation_reason`, `process_trace`
+- 输出字段:互动、画像来源、50+ 指标、推荐理由、过程 trace。
+- 边界:旧版只沉淀最终结果,没有全量候选判断表。
+
+#### J7. 数据库:`ODPS`,表:`dwd_multi_demand_pool_di`
+
+- 输入字段:`strategy`, `demand_id`, `demand_name`, `weight`, `type`, `video_count`, `video_list`, `extend`, `dt`
+- 输出字段:需求池权重和视频列表。
+- 边界:用于策略学习/复盘,不是旧版判断实时输入。
+
+#### J8. 数据库:`ODPS`,表:`feature_point_data`
+
+- 输入字段:`特征点`, `总分发曝光pv`, `bn_总回流`, `质bn_rovn`, `分发视频量`, `总日回流uv`, `vid_list`, `dt`
+- 输出字段:特征点表现、回流、视频列表。
+- 边界:用于后续策略学习,不是旧版实时评分表。
+
+## 5. 游走
+
+游走阶段回答:从视频、作者、Pattern、Case 还能走到哪里。
+
+### 5.1 索引
+
+| 编号 | 接口/表 | 用途 | 状态 |
+|---|---|---|---|
+| W1 | 抖音关键词搜索返回作者 | 视频 -> 作者 | 已验证 |
+| W2 | 抖音账号作品 | 作者 -> 作者作品 | 已验证 |
+| W3 | `demand_find_author` | 历史作者 -> 作者作品 | 已验证 |
+| W4 | `topic_pattern_element -> workflow_decode_task_result` | 特征词 -> 历史 Case | 已验证 |
+| W5 | `topic_pattern_itemset -> workflow_decode_task_result` | Pattern -> Item Set -> 多 Case | pending / 待接入 |
+| W6 | 相关搜索 / 标签 / 共创 / 相似作者 | 内容继续扩展 | 缺口 |
+| W7 | 小红书笔记/作者/话题游走 | 小红书扩展 | 缺口 |
+
+### 5.2 详情
+
+#### W1. 接口:抖音关键词搜索返回作者
+
+- 输入字段:`aweme_id`, `desc`, `author.nickname`, `author.sec_uid`, `statistics.*`
+- 输出字段:作者昵称、`sec_uid`、互动指标。
+- 边界:只是进入作者判断,不代表作者自动入库。
+
+#### W2. 接口:抖音账号作品
+
+- 输入字段:`account_id` / `author.sec_uid`, `sort_type`, `cursor`
+- 输出字段:`aweme_id`, `desc`, `author.*`, `statistics.*`, `has_more`, `next_cursor`
+- 边界:作品需要重新过判断。
+
+#### W3. 数据库:`content-deconstruction-supply`,表:`demand_find_author`
+
+- 输入字段:`author_id`, `author_link`, `content_tags`, `channel`
+- 输出字段:可复用作者身份、标签、画像指标。
+- 边界:旧版按作者资产复用;作品仍走平台接口。
+
+#### W4. 数据库:`open_aigc_pattern`,表:`topic_pattern_element` -> 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
+
+- 输入字段:`topic_pattern_element.name`, `post_id`
+- 输出字段:`workflow_decode_task_result.channel_content_id`, Case 解构点。
+- 边界:旧版已实现的弱链路。
+
+#### W5. 数据库:`open_aigc_pattern`,表:`topic_pattern_itemset` -> 数据库:`content-deconstruction-supply`,表:`workflow_decode_task_result`
+
+- 输入字段:`matched_post_ids`, `support`, `absolute_support`
+- 输出字段:多个 Case 的原始素材和解构点。
+- 边界:新版重要路径;旧版未实现主链路。
+
+#### W6. 缺口:相关搜索 / 标签 / 共创 / 相似作者
+
+- 输入字段:暂无稳定字段。
+- 输出字段:暂无稳定返回。
+- 边界:不写成已验证接口。
+
+#### W7. 缺口:小红书笔记/作者/话题游走
+
+- 输入字段:暂无旧版实测字段。
+- 输出字段:暂无稳定返回。
+- 边界:目前只有静态 Case 字段,不等于平台接口接入。
+
+## 6. 资产清洗沉淀
+
+资产清洗沉淀阶段回答:召回和判断后的内容、作者、后处理链接沉到哪里。
+
+### 6.1 索引
+
+| 编号 | 接口/表 | 用途 | 状态 |
+|---|---|---|---|
+| A1 | `demand_find_content_result` | 内容资产入库 | 已验证 |
+| A2 | `demand_find_content_result` | 内容质量和画像沉淀 | 已验证 |
+| A3 | `demand_find_author` | 作者资产入库 | 已验证 |
+| A4 | AIGC plan | 后处理计划绑定 | 源码定位 / blocked |
+| A5 | OSS log HTML | 过程可视化链接 | 源码定位 / blocked |
+| A6 | 关系资产 | 来源、query、作者、标签、游走路径关系 | 缺口 |
+| A7 | 搜索线索 | 有效 query、失败 query、标签、话题 | 缺口 |
+
+### 6.2 详情
+
+#### A1. 数据库:`content-deconstruction-supply`,表:`demand_find_content_result`(内容资产入库)
+
+- 输入字段:`trace_id`, `query`, `rank_no`, `video_url`, `title`, `author_name`, `author_link`, `author_id`, `aweme_id`, `demand_content_id`, `channel`, `dt`
+- 输出字段:内容资产、排序、来源需求、平台身份。
+- 新版必须补:结构化 `source_evidence`,或 sidecar/source edge artifact。
+- 边界:旧版沉淀最终内容,不沉淀全量候选池;只靠 `aweme_id + demand_content_id + process_trace` 不能 exact 回溯到 Pattern 和分类树节点。
+
+#### A2. 数据库:`content-deconstruction-supply`,表:`demand_find_content_result`(内容质量和画像沉淀)
+
+- 输入字段:`digg_count`, `comment_count`, `share_count`, `portrait_source`, `elderly_ratio`, `elderly_tgi`, `recommendation_reason`, `process_trace`
+- 输出字段:互动指标、画像、推荐理由、过程记录。
+- 边界:`process_trace` 可复盘,但不是结构化全量 trace 表;不能替代 `source_evidence`。
+
+#### A3. 数据库:`content-deconstruction-supply`,表:`demand_find_author`
+
+- 输入字段:`trace_id`, `author_name`, `author_link`, `author_id`, `elderly_ratio`, `elderly_tgi`, `is_good`, `remark`, `content_tags`, `channel`
+- 输出字段:作者资产、画像、标签、入选理由。
+- 边界:作者身份合并逻辑需新版明确。
+
+#### A4. 能力:AIGC plan
+
+- 输入字段:`aweme_id`, `merge_leve2`, `produce_plan_id`, `account_id`, `sort_type`
+- 输出字段:`crawler_plan_id`, `produce_plan_id`, `produce_plan_name`, `publish_plan_id`
+- 边界:写侧副作用,开发测试不能默认触发。
+
+#### A5. 能力:OSS log HTML
+
+- 输入字段:`html_path`, `object_key`, `task_id`
+- 输出字段:`web_html_url`
+- 边界:上传有副作用;旧版只保留能力边界。
+
+#### A6. 缺口:关系资产
+
+- 输入字段:暂无真实表。
+- 输出字段:暂无真实字段。
+- 边界:内容与数据源、query、作者、标签、游走路径关系都需要承载,但当前不伪造 schema。
+
+#### A7. 缺口:搜索线索
+
+- 输入字段:暂无真实表。
+- 输出字段:暂无真实字段。
+- 边界:有效 query、失败 query、标签、话题、相关搜索线索目前只在结果和 trace 中间接保留。
+
+## 7. 策略学习
+
+策略学习阶段回答:下一轮如何根据 trace、表现和概念层事实调整策略。
+
+### 7.1 索引
+
+| 编号 | 接口/表 | 用途 | 状态 |
+|---|---|---|---|
+| L1 | `demand_find_task` | 任务级 trace 和成本 | 已验证 |
+| L2 | `demand_task_oprate` | 调度和预算开关 | 源码定位 |
+| L3 | `demand_find_content_result` | 内容结果复盘 | 已验证 |
+| L4 | `demand_find_author` | 作者资产复盘 | 已验证 |
+| L5 | `open_aigc.global_category` | 长期概念分类 | 已验证 |
+| L6 | `open_aigc.global_element` | 长期概念元素 | 已验证 |
+| L7 | `open_aigc.element_classification_mapping` | 元素归类映射 | 已验证 |
+| L8 | `open_aigc.public.pattern_mining_execution` | Pattern V2 执行快照 | 已验证 |
+| L9 | `open_aigc.public.pattern_mining_element` | Pattern V2 元素快照 | 已验证 |
+| L10 | `open_aigc.public.pattern_itemset` | Pattern V2 itemset | 已验证 |
+| L11 | `open_aigc.public.pattern_itemset_item` | Pattern V2 itemset 成员 | 已验证 |
+| L12 | `loghubods.video_dimension_detail_add_column` | 视频表现回看 | 已验证 |
+
+### 7.2 详情
+
+#### L1. 数据库:`content-deconstruction-supply`,表:`demand_find_task`
+
+- 输入字段:`trace_id`, `demand_content_id`, `status`, `created_at`, `token_coast`
+- 输出字段:执行状态、成本、需求关联。
+- 边界:只能看任务级结果,不能还原全量候选过程。
+
+#### L2. 数据库:`content-deconstruction-supply`,表:`demand_task_oprate`
+
+- 输入字段:`is_open`, `day_limit_coast`, `update_time`
+- 输出字段:是否开启、日成本限制。
+- 边界:策略控制数据,不是学习结论。
+
+#### L3. 数据库:`content-deconstruction-supply`,表:`demand_find_content_result`
+
+- 输入字段:`trace_id`, `query`, `rank_no`, `channel`, `aweme_id`, `process_trace`, `crawler_plan_id`, `produce_plan_id`, `publish_plan_id`, `web_html_url`, `source_evidence`
+- 输出字段:成功路径、平台、计划绑定、过程记录、来源证据。
+- 边界:没有失败候选和淘汰原因结构化表;如果没有 `source_evidence` 或 source edge artifact,策略学习只能看到结果,不能 exact 回溯到 Pattern、Case 和分类树节点。
+
+#### L4. 数据库:`content-deconstruction-supply`,表:`demand_find_author`
+
+- 输入字段:`trace_id`, `author_id`, `author_name`, `content_tags`, `elderly_ratio`, `elderly_tgi`, `is_good`, `channel`
+- 输出字段:作者表现和可复用标签。
+- 边界:后续需要与作品表现关联。
+
+#### L5. 数据库:`open_aigc`,表:`global_category`
+
+- 输入字段:`stable_id`, `name`, `source_type`, `path`, `parent_stable_id`, `category_nature`, `level`
+- 输出字段:分类树和稳定分类 ID。
+- 边界:上游概念层,非旧版实时输入。
+
+#### L6. 数据库:`open_aigc`,表:`global_element`
+
+- 输入字段:`id`, `name`, `source_type`, `belong_category_stable_id`, `element_sub_type`, `occurrence_count`
+- 输出字段:元素词和出现次数。
+- 边界:用于长期策略学习和 Pattern 生成。
+
+#### L7. 数据库:`open_aigc`,表:`element_classification_mapping`
+
+- 输入字段:`source_table`, `source_element_id`, `post_id`, `element_name`, `element_type`, `global_element_id`, `global_category_stable_id`, `classification_path`
+- 输出字段:元素到全局类目的映射。
+- 边界:用于理解上游元素如何进入 Pattern。
+
+#### L8. 数据库:`open_aigc PG`,表:`public.pattern_mining_execution`
+
+- 输入字段:`id`, `snapshot_date`, `is_current`, `status`, `post_count`, `category_count`, `element_count`, `topic_itemset_count`, `cross_itemset_count`, `start_time`, `end_time`, `error_message`
+- 输出字段:Pattern 执行状态和规模。
+- 边界:PG Pattern V2 到 MySQL `topic_pattern_*` 仍是高风险 bridge。
+
+#### L9. 数据库:`open_aigc PG`,表:`public.pattern_mining_element`
+
+- 输入字段:`execution_id`, `post_id`, `source_table`, `source_element_id`, `element_type`, `element_sub_type`, `name`, `category_path`, `point_type`, `point_text`
+- 输出字段:元素快照和类目路径。
+- 边界:可辅助学习,不直接替代 MySQL `topic_pattern_element`。
+
+#### L10. 数据库:`open_aigc PG`,表:`public.pattern_itemset`
+
+- 输入字段:`execution_id`, `combination_type`, `item_count`, `support`, `absolute_support`, `dimensions`, `is_cross_point`, `is_cross_layer`
+- 输出字段:Pattern 组合、支持度、维度。
+- 边界:不能直接写成 DemandAgent / ContentFindAgent 已读。
+
+#### L11. 数据库:`open_aigc PG`,表:`public.pattern_itemset_item`
+
+- 输入字段:`itemset_id`, `layer`, `point_type`, `dimension`, `category_id`, `category_path`, `element_name`, `element_id`, `post_count`
+- 输出字段:itemset 中的元素项。
+- 边界:用于分析 Pattern 词和组合效果。
+
+#### L12. 数据库:`loghubods`,表:`video_dimension_detail_add_column`
+
+- 输入字段:`视频id`, `视频地址`, `标题`, `一级品类`, `二级品类`, `上传渠道`, `dt`
+- 表现字段:`当日分发曝光pv`, `当日分发回流uv`, `rov_t0`, `流量池曝光`, `流量池播放`, `流量池回流`, `推荐曝光`, `推荐播放`, `推荐回流`, `总日回流uv`
+- 输出字段:表现、曝光、播放、回流、标签。
+- 边界:宽表只列策略学习关键字段。
+
+## 8. 缺口清单
+
+本节吸收原环境缺口报告的缺口项。凡是没有真实表、真实 key、真实接口或只读验证证据的能力,都不能在产品或技术文档里写成“已接入”。
+
+### 8.1 P0 必须补齐
+
+#### 新版候选池承载
+
+- 需要补:全量候选池表或 API。
+- 业务用途:保存所有召回的视频、笔记、作者,包括入池、观察、淘汰和最终入选。
+- 当前状态:未发现真实表。
+- 中文注释:旧版只保存最终结果,无法复盘“没选中的候选”。
+
+#### 判断/淘汰日志承载
+
+- 需要补:规则包执行记录、硬门槛、软评分、淘汰原因、停止原因表或 API。
+- 业务用途:记录每个候选为什么继续、入池、观察、停止或淘汰。
+- 当前状态:未发现真实表。
+- 中文注释:`process_trace` 只能粗略复盘,不能替代结构化规则日志。
+
+#### 来源关系承载
+
+- 需要补:数据源、seed、query、平台内容、作者、标签、游走路径关系表或 API;或先用 sidecar/source edge artifact 承载。
+- 业务用途:记录内容从哪里来、经过哪条路径来、由哪个规则放行,并从 `case_id/post_id` 回溯到分类树节点。
+- 当前状态:未发现真实表。
+- 中文注释:没有关系承载,策略学习无法按来源和路径归因;最终只剩 `aweme_id + demand_content_id + process_trace` 时,无法 exact 追到 Pattern 和分类树。
+
+#### 搜索线索承载
+
+- 需要补:有效 query、失败 query、标签、话题、相关搜索线索表或 API。
+- 业务用途:保存下一轮可继续尝试的搜索线索,以及失败线索。
+- 当前状态:未发现真实表。
+- 中文注释:旧版只在结果和 trace 里间接留下 query,不够做系统学习。
+
+#### show 联调后端
+
+- 需要补:`VITE_API_BASE_URL`, `VITE_CONTENTFIND_API_BASE_URL`
+- 业务用途:让 show 从静态沙盘切到真实后端接口。
+- 当前状态:missing
+- 中文注释:当前前端只能展示策略和静态样例,不能跑真实链路。
+
+### 8.2 P1 生产增强
+
+#### TikHub fallback
+
+- 需要补:`TIKHUB_API_KEY`
+- 业务用途:Crawapi 抖音搜索异常、限流或不可用时做备用召回。
+- 当前状态:missing
+- 中文注释:URL 已定位,缺 key;不能写成已接入。
+
+#### OSS 过程链接
+
+- 需要补:`ALIYUN_OSS_ACCESS_KEY_ID`, `ALIYUN_OSS_ACCESS_KEY_SECRET`, `ALIYUN_OSS_BUCKET`, `ALIYUN_OSS_REGION`, `ALIYUN_OSS_PREFIX`, `ALIYUN_OSS_PUBLIC_BASE_URL`
+- 业务用途:保存任务日志、HTML 报告、截图、trace 文件的可访问链接。
+- 当前状态:missing
+- 中文注释:旧版有上传能力线索,但当前缺真实 OSS 配置。
+
+#### 外部调度源
+
+- 需要补:`SCHEDULE_QUERY_API`, `SCHEDULE_QUERY_API_KEY`
+- 业务用途:从外部系统接收任务、定时需求或批量触发策略运行。
+- 当前状态:missing
+- 中文注释:当前没有真实调度入口。
+
+#### AIGC token 治理
+
+- 需要补:将旧版 fallback token 换成密钥平台托管值。
+- 业务用途:避免旧代码硬编码 token,保证后处理计划查询/绑定安全可维护。
+- 当前状态:source-only
+- 中文注释:这是安全治理,不是新增业务能力。
+
+### 8.3 P2 产品待验证
+
+#### 小红书笔记搜索、作者主页、作者笔记、话题标签接口
+
+- 当前状态:无旧版实测接口。
+- 中文注释:产品可以设计小红书路径,但技术文档不能写成已接入。
+
+#### 快手、B站、视频号、票圈平台适配接口
+
+- 当前状态:无旧版实测接口。
+- 中文注释:这些平台暂时不能复用抖音字段口径。
+
+#### 相关搜索、视频 tag、共创作者、相似作者、相似内容接口
+
+- 当前状态:无稳定字段或实测。
+- 中文注释:可以作为游走策略方向,但必须标为待验证。
+
+#### 养号推荐流账号、cookie/session、设备、代理/风控配置
+
+- 当前状态:无真实接入配置。
+- 中文注释:养号是产品意图源,不能伪装成已有旧版 DB/API backing。
+
+#### 策略实验、规则包版本、Prompt 版本、预算回写表/API
+
+- 当前状态:无真实承载。
+- 中文注释:策略学习需要这些承载,但当前没有真实表或 API。
+
+#### Pattern -> Item Set -> 多 Case
+
+- 当前状态:DB 字段存在,旧版未主链路实现。
+- 中文注释:标 `pending / 待接入`;可作为新版重要来源,但不能写成已跑通。

+ 326 - 0
tech_documents/demandagent给下游的迭代需求.md

@@ -0,0 +1,326 @@
+# DemandAgent 给下游的迭代需求
+
+## 1. 结论先行
+
+新版 ContentFindAgent 不能只靠旧版反查机制来恢复 Pattern、Case 和分类树血缘。
+
+旧版能拿到一些候选线索,例如用需求词去查历史 Pattern 元素,再回查 Case 解构点。但这条链路不是精确血缘,也不能稳定闭合到唯一的 Pattern、itemset 或 Case。
+
+为了新版能从需求出发稳定跑通数据源、Query、判断、游走、资产沉淀和策略学习,DemandAgent 或上游证据层需要把生成需求时已经知道的证据一起传给下游。
+
+下游必须新增或扩展的核心字段:
+
+| 字段 | 作用 |
+| --- | --- |
+| `pattern_execution_id` | 标记需求来自哪次 Pattern 挖掘执行 |
+| `source_kind` | 标记需求来源是聚类结果、Pattern 频繁项集、直接 Case,还是其他来源 |
+| `category_bindings` | 绑定分类节点,例如分类 ID、路径、层级、父节点、实质/形式/意图 |
+| `element_bindings` | 绑定具体元素,例如元素名、元素类型、点类型、所属 post |
+| `itemset_ids` | 绑定 Pattern 频繁项集 |
+| `support` / `absolute_support` | 记录 Pattern 支持度,判断它是不是稳定组合 |
+| `matched_post_ids` | 记录支撑 Pattern 的原始帖子或视频 |
+| `video_ids` / `case_ids` | 记录可作为 Case seed 的素材 ID |
+| `seed_terms` | 记录真正给 Query 使用的下层特征或元素词 |
+| `trace_id` / `demand_task_id` / `demand_content_id` | 串起需求生成、内容寻找、判断、沉淀和策略学习 |
+
+V1 不一定马上改复杂 DB schema。可以先在 `demand_content.ext_data` 里增加一个结构化 `evidence_pack`,先把证据传下来。
+
+## 2. 新版下游到底需要什么
+
+新版 ContentFindAgent 不是只拿一个需求词去搜内容。它需要知道这个需求从哪里来、为什么成立、应该怎么生成 Query、判断结果应该绑定回哪棵分类树,以及最终资产入库前是否可追溯。
+
+下游需要的对象主要有五类。
+
+| 对象 | 下游含义 | 为什么需要 |
+| --- | --- | --- |
+| Pattern | 分类或元素的稳定组合 | 用来判断需求是不是有结构支撑,而不是单条素材偶然出现 |
+| Case | 支撑 Pattern 或需求的原始素材 | 用来查看素材原文、解构点、平台调性和后续判断证据 |
+| 分类 / 元素绑定 | 需求对应分类树的哪个父节点或子节点 | 所有游走、判断和入库结果都要能绑定回分类树 |
+| 支撑素材 | `matched_post_ids`、`video_ids`、Case ID 等 | 用来做 Query 输入、Case 回扣、判断规则证据和策略学习 |
+| trace | 一路记录从需求到结果的路径 | 策略学习、失败复盘、入库追溯都需要它 |
+
+三类“有需求”的数据源都应该作为 DemandAgent 下游。它们不能只给一个自然语言需求名,还要给来源证据。不然后续即使找到内容,也说不清这个内容为什么服务于当初那个需求。
+
+所有资产入库前至少要保留:
+
+| 必留信息 | 用途 |
+| --- | --- |
+| 来源路径 | 证明资产从哪个需求、Pattern、Case 或平台线索来 |
+| 判断结果 | 证明为什么继续、入池、观察、淘汰 |
+| 分类或元素绑定 | 证明资产属于分类树中的哪个节点 |
+| 平台调性说明 | 证明资产适合当前平台,不是跨平台硬套 |
+| trace | 支持策略学习和后续复盘 |
+
+## 3. DemandAgent 当前给了什么
+
+### 3.1 DemandItem
+
+DemandAgent 当前生成的 `DemandItem` 字段很窄。
+
+代码证据:
+
+- `/Users/samlee/Documents/works/DemandAgent/examples/demand/demand_build_agent_tools.py:45`
+- `/Users/samlee/Documents/works/DemandAgent/examples/demand/demand_build_agent_tools.py:132`
+
+当前保留字段:
+
+| 字段 | 当前含义 |
+| --- | --- |
+| `element_names` | 需求名称来源,后续会拼成 `demand_content.name` |
+| `reason` | 生成需求的理由 |
+| `desc` | 需求描述,后续写入 `suggestion` |
+| `type` | 来源类型,例如元素、关系、分类 |
+
+这里没有保留 `execution_id`、分类 ID、元素 ID、itemset、support 或 matched posts。
+
+### 3.2 demand_content
+
+`write_demand_items_to_mysql()` 会把 DemandItem 写入 `demand_content`。
+
+代码证据:
+
+- `/Users/samlee/Documents/works/DemandAgent/examples/demand/run.py:242`
+- `/Users/samlee/Documents/works/DemandAgent/examples/demand/run.py:287`
+- `/Users/samlee/Documents/works/DemandAgent/examples/demand/run.py:300`
+- `/Users/samlee/Documents/works/DemandAgent/examples/demand/run.py:304`
+- `/Users/samlee/Documents/works/DemandAgent/examples/demand/run.py:320`
+
+当前写入字段:
+
+| `demand_content` 字段 | 来源 |
+| --- | --- |
+| `merge_leve2` | 当前执行品类 |
+| `name` | `element_names` 逗号拼接 |
+| `reason` | DemandItem.reason |
+| `suggestion` | DemandItem.desc |
+| `score` | 按 `name` 拆分后查权重并求平均 |
+| `ext_data` | JSON,当前包含 `reason`、`desc`、`type`、`video_ids` |
+| `dt` | 当天日期 |
+
+`video_ids` 是通过 `execution_id + name` 反查出来的。代码在 `/Users/samlee/Documents/works/DemandAgent/examples/demand/run.py:184` 和 `/Users/samlee/Documents/works/DemandAgent/examples/demand/db_manager.py:39`。
+
+但写入 `demand_content` 后,`execution_id` 自身没有留下。下游只看到 `video_ids`,看不到这些 video 是从哪个 Pattern execution、哪个分类节点、哪个 itemset 推出来的。
+
+### 3.3 dwd_multi_demand_pool_di
+
+DemandAgent 还会尽力写入 `loghubods.dwd_multi_demand_pool_di`。
+
+代码证据:
+
+- `/Users/samlee/Documents/works/DemandAgent/examples/demand/data_query_tools.py:88`
+- `/Users/samlee/Documents/works/DemandAgent/examples/demand/data_query_tools.py:124`
+- `/Users/samlee/Documents/works/DemandAgent/examples/demand/data_query_tools.py:148`
+
+当前映射字段:
+
+| 字段 | 当前含义 |
+| --- | --- |
+| `strategy` | 策略名 |
+| `demand_id` | 派生 ID |
+| `demand_name` | 需求名 |
+| `weight` | 分数 |
+| `type` | 从 `ext_data.type` 解析 |
+| `video_count` | `video_ids` 数量 |
+| `video_list` | `video_ids` |
+| `extend` | 当前主要保留品类 |
+| `dt` | 分区日期 |
+
+这一层也没有保留 Pattern execution、itemset、分类路径、support 或 matched posts。
+
+## 4. 为什么旧版反查不够
+
+旧版 ContentFindAgent 的 Case 反查工具是 `get_goodcase_topic_point`。
+
+代码证据:
+
+- `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:91`
+- `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:96`
+- `/Users/samlee/Documents/works/ContentFindAgent/examples/content_finder/tools/get_goodcase_topic_point.py:144`
+
+旧链路是:
+
+```text
+features
+-> topic_pattern_element.name = feature
+-> post_id 交集
+-> workflow_decode_task_result.channel_content_id
+-> purpose_points / key_points / inspiration_points
+```
+
+这条链路有三个问题。
+
+| 问题 | 说明 |
+| --- | --- |
+| 不带 `execution_id` | 同一个词可能出现在多个 Pattern execution,不能证明来自当前需求那次执行 |
+| 只查元素名 | DemandAgent 当前有些需求名其实是分类节点,旧 CFA 只查 `topic_pattern_element.name` 会漏掉 |
+| 不查 itemset | 无法恢复 `itemset_id`、support、absolute support、matched posts |
+
+DemandAgent 自己在生成 `video_ids` 时,用的是 `execution_id + name/category`。旧 CFA 事后反查用的是裸 `name`。两条链路不是同一种机制。
+
+所以旧版反查只能作为 fallback,用来找候选 Case;不能作为新版的主血缘。
+
+## 5. 必须补给下游的字段清单
+
+| 字段 | 字段意义 | 下游用途 | 为什么不能靠 CFA 自己补 | 缺失风险 |
+| --- | --- | --- | --- | --- |
+| `pattern_execution_id` | 当前需求来源的 Pattern 执行 ID | 数据源详情、Pattern 回查、策略学习 | CFA 只能按词反查,无法知道当时 execution | 同名词跨 execution 混淆 |
+| `source_kind` | 来源类型:聚类结果、Pattern 频繁项集、直接 Case 等 | 决定后续流程:是否进 Query,是否先游走或先判断 | 只看 `type` 不够,不能区分真实来源路径 | 不同来源走错流程 |
+| `category_bindings` | 分类节点绑定,包含 `category_id/path/level/parent_id/source_type` | 所有结果绑定回分类树父节点 | CFA 按元素名查不到分类节点,且同名分类可能多处存在 | 入库后无法解释属于哪个分类 |
+| `element_bindings` | 元素绑定,包含 `name/element_type/point_type/post_id/category_path` | 作为 Query 输入素材、判断回扣证据 | CFA 可以按词查候选元素,但不知道哪个是 DemandAgent 采用的 | Query seed 可能跑偏 |
+| `itemset_ids` | Pattern 频繁项集 ID | 回查 Pattern 组合和稳定性 | CFA 旧链路不查 itemset | 不能证明 Pattern 是组合,不是散词 |
+| `support` | 相对支持度 | 判断 Pattern 是否稳定 | 需求池当前没有保存 | 无法按稳定性排序或过滤 |
+| `absolute_support` | 绝对支撑素材数 | 判断有多少素材支撑 | 需求池当前没有保存 | 不能区分单例和稳定模式 |
+| `matched_post_ids` | 支撑 itemset 的帖子 ID 列表 | 找 Case、看原始素材、做回扣判断 | `video_ids` 不等价于 itemset 的 matched posts | Case 来源不准,支撑证据不准 |
+| `video_ids` / `case_ids` | 可直接作为 Case seed 的素材 ID | 数据源 Case、Query 输入、判断证据 | CFA 可偶尔从 decode 表命中,但不稳定 | 有时找不到 Case 原文 |
+| `seed_terms` | 真正用于 Query 的下层特征或元素词 | Query 输入素材 | `name` 可能是分类、关系或宽泛表达,不一定适合作 Query | Query 输入不稳定 |
+| `trace_id` | 贯穿下游任务的 trace | 策略学习、失败复盘 | CFA 可创建自己的 trace,但不能补上游生成过程 | 上下游链路断开 |
+| `demand_task_id` | DemandAgent 侧任务 ID | 回查生成日志和任务状态 | CFA 无法凭需求名可靠找到唯一任务 | 无法定位上游运行 |
+| `demand_content_id` | 下游领取的需求 ID | CFA 主任务绑定 | 这个字段 CFA 已能拿到,但应进入统一证据包 | Trace 不统一 |
+
+## 6. 检查过程与证据
+
+### 6.1 DemandAgent 文档审计
+
+`/Users/samlee/Documents/works/DemandAgent工程文档集/demand-agent-analysis.md:432` 已写明 DemandItem 到 `demand_content` 的映射:
+
+| 目标字段 | 来源 |
+| --- | --- |
+| `merge_leve2` | 当前执行品类 |
+| `name` | `element_names` 拼接 |
+| `reason` | DemandItem.reason |
+| `suggestion` | DemandItem.desc |
+| `score` | 权重 JSON 查分求平均 |
+| `ext_data` | `reason/desc/type/video_ids` |
+| `dt` | 当天日期 |
+
+同一份文档也指出:`workflow_decode_task_result` 是辅助回填逻辑,不是主生成 loop 默认步骤;Agent 输出主要靠 Prompt 约束,没有后置校验器强制每条需求都有权重或支持度。
+
+### 6.2 DemandAgent 代码审计
+
+DemandAgent 代码里确实有 Pattern 分类、元素和 itemset 字段。
+
+代码证据:
+
+- `TopicPatternCategory`:`/Users/samlee/Documents/works/DemandAgent/examples/demand/models.py:45`
+- `TopicPatternElement`:`/Users/samlee/Documents/works/DemandAgent/examples/demand/models.py:63`
+- `TopicPatternItemset`:`/Users/samlee/Documents/works/DemandAgent/examples/demand/models.py:93`
+- `TopicPatternItemsetItem`:`/Users/samlee/Documents/works/DemandAgent/examples/demand/models.py:109`
+
+关键字段包括:
+
+| 表 | 关键字段 |
+| --- | --- |
+| `topic_pattern_category` | `execution_id`, `source_type`, `name`, `path`, `level`, `parent_id` |
+| `topic_pattern_element` | `execution_id`, `post_id`, `point_type`, `element_type`, `name`, `category_id`, `category_path` |
+| `topic_pattern_itemset` | `execution_id`, `combination_type`, `item_count`, `support`, `absolute_support`, `dimensions`, `matched_post_ids` |
+| `topic_pattern_itemset_item` | `itemset_id`, `point_type`, `dimension`, `category_id`, `category_path`, `element_name` |
+
+也就是说,DemandAgent 内部能接触到这些字段。问题不是上游完全没有,而是写给下游时没有保留下来。
+
+### 6.3 旧 ContentFindAgent 代码审计
+
+旧 CFA 的 `get_goodcase_topic_point` 只按 `topic_pattern_element.name` 精确匹配。
+
+关键 SQL:
+
+```sql
+SELECT DISTINCT post_id
+FROM topic_pattern_element
+WHERE name = %s
+```
+
+这个 SQL 不带 `execution_id`、`category_id`、`itemset_id`,也不查 `topic_pattern_category`。因此它无法还原 DemandAgent 生成需求时的真实 Pattern 上下文。
+
+### 6.4 真实 DB 只读抽样
+
+本轮只做了只读查询,没有写 DB,也没有输出 secret。
+
+DB 侧验证结论:
+
+| 验证项 | 结果 |
+| --- | --- |
+| `demand_content` 行数 | 29284 |
+| 最近样本 `ext_data` | 有 `reason/desc/type/video_ids` |
+| 最新 10 条需求 | 共 104 个 `video_id` |
+| 这 104 个 `video_id` 命中 `workflow_decode_task_result` | 0 |
+| 这 104 个 `video_id` 命中 `topic_pattern_element.post_id` | 0 |
+| 最近 5000 条非空 `ext_data` | 4920 条有 `video_ids` |
+| distinct `video_id` | 922 |
+| 能命中 `workflow_decode_task_result` 的 `video_id` | 38 |
+
+这说明 `ext_data.video_ids` 可以作为候选 Case seed,但不是稳定闭合通道。
+
+对照样本里,`demand_content.id=48563` 的 12 个 video_id 中有 2 个命中 decode Case;但同一需求词能命中多个 Pattern execution,且没有任何 itemset 的 `matched_post_ids` 与整组 `video_ids` 精确相等。
+
+所以真实 DB 也支持这个结论:当前字段不能精确闭合到唯一 Pattern、itemset 或 Case。
+
+### 6.5 subagent 交叉验证
+
+本轮拆了两个 subagent 交叉验证。
+
+| subagent | 验证范围 | 结论 |
+| --- | --- | --- |
+| DemandAgent 代码与文档侧 | DemandItem、`demand_content`、`dwd_multi_demand_pool_di`、Pattern 模型字段 | DemandAgent 当前下游字段很窄,大量 Pattern lineage 在写需求池时丢失 |
+| DB 只读侧 | `demand_content`、`workflow_decode_task_result`、`topic_pattern_*` 抽样闭合 | 只能做到候选回查,不能唯一闭合 Pattern / itemset / Case |
+
+两个方向结论一致。
+
+## 7. 建议的交付方式
+
+V1 建议先不要大改表结构,先扩展 `demand_content.ext_data`。
+
+推荐结构:
+
+```json
+{
+  "reason": "...",
+  "desc": "...",
+  "type": "...",
+  "video_ids": ["..."],
+  "evidence_pack": {
+    "pattern_execution_id": 123,
+    "source_kind": "pattern_itemset",
+    "category_bindings": [],
+    "element_bindings": [],
+    "itemset_ids": [],
+    "support": null,
+    "absolute_support": null,
+    "matched_post_ids": [],
+    "case_ids": [],
+    "seed_terms": [],
+    "trace_id": "...",
+    "demand_task_id": 123,
+    "demand_content_id": 456
+  }
+}
+```
+
+字段可以分批补齐:
+
+| 阶段 | 建议 |
+| --- | --- |
+| V1 必须 | `pattern_execution_id`, `source_kind`, `category_bindings`, `element_bindings`, `video_ids/case_ids`, `seed_terms`, `trace_id` |
+| V1.1 增强 | `itemset_ids`, `support`, `absolute_support`, `matched_post_ids` |
+| 后续再拆表 | 如果 `evidence_pack` 变大,再考虑独立证据表或关系表 |
+
+注意:如果来源不是 Pattern 频繁项集,而是聚类结果或直接 Case,`itemset_ids/support/matched_post_ids` 可以为空,但必须明确 `source_kind`,不能让下游猜。
+
+## 8. 下游使用方式
+
+| 下游阶段 | 怎么使用这些字段 |
+| --- | --- |
+| 数据源 | 展示 Pattern、Case、分类/元素绑定和原始支撑素材 |
+| Query | 使用 `seed_terms` 作为 Query 输入素材,不直接把宽泛 `name` 当 Query |
+| Platform | 根据来源平台和 seed 生成平台适配 Query |
+| 判断 | 用分类/元素绑定、Case 原文和 support 做回扣判断 |
+| 游走 | 每次游走结果绑定回分类或元素节点,避免无限漂移 |
+| 资产清洗沉淀 | 入库前保留来源路径、判断结果、分类或元素绑定、平台调性说明和 trace |
+| 策略学习 | 用 trace 回看哪类来源、哪类 seed、哪类规则包有效 |
+
+## 9. 最终判断
+
+旧版 ContentFindAgent 可以做补充反查,但不能承担新版主血缘。
+
+如果新版要求精准找到 Pattern、Case、分类或元素绑定,并让所有入库资产可追溯,DemandAgent 必须把生成需求时已经知道的证据一并传给下游。
+
+最小可行做法是:先在 `demand_content.ext_data` 中增加 `evidence_pack`。这样 V1 可以先跑通,不需要马上重构 DB schema,也能避免下游继续靠猜。

+ 284 - 0
tech_documents/前置的坑.md

@@ -0,0 +1,284 @@
+# 前置的坑
+
+本文记录 ContentFindAgent 新版落地前必须先想清楚的边界。假设 DemandAgent 已经按新版要求把 `evidence_pack` 给到下游,部分旧坑可以关闭,但仍有几个边界不能误写成已解决。
+
+状态口径:
+
+| 状态 | 含义 |
+|---|---|
+| 已解决 | 需求和证据字段已定义,按新版实现即可闭合 |
+| 条件解决 | 只有在满足特定字段、校验或沉淀条件时才闭合 |
+| 未解决 | 当前仍不能写成已跑通或 exact |
+| 保留边界 | 能用,但必须保留解释限制 |
+
+## 1. ContentFindAgent 真的实现了 Pattern -> Case 吗?
+
+**问题**:旧 ContentFindAgent 里,我们说有“从 Pattern 出发”和“从 Case 出发”两种路径。那么从 Pattern 找 Case 具体怎么实现?是真的按 Pattern 找多个 Case,还是根本没实现?
+
+**状态**:已解决旧版认知差异;新版仍要按 evidence 走。
+
+**简要答案**:旧 ContentFindAgent 没有实现完整的 `Pattern/id/itemset/execution -> Case` 路径。它实际实现的是一条弱链路:
+
+```text
+下层特征/元素名
+-> topic_pattern_element.name 精确匹配
+-> post_id
+-> workflow_decode_task_result.channel_content_id
+-> case 选题点
+```
+
+也就是说,`get_goodcase_topic_point(features)` 只接收下层特征词,按 `topic_pattern_element.name = feature` 查 `post_id`,再去 `workflow_decode_task_result` 取目的点、关键点、灵感点。它不读 `pattern_id`、`execution_id`、`topic_pattern_itemset`、`matched_post_ids` 或 Pattern 树节点,所以不是严格的 Pattern 本体召回。
+
+新版不能继续把这条弱链路当主血缘。新版主路径应该是:
+
+```text
+demand_content.ext_data.evidence_pack
+-> source_evidence
+-> Pattern / itemset / category / element
+-> Case / post
+```
+
+## 2. `topic_pattern_itemset` 是哪来的?
+
+**问题**:DB 里存在更像完整 Pattern 出发的结构:`topic_pattern_itemset.matched_post_ids -> workflow_decode_task_result`。那 `topic_pattern_itemset` 到底是哪来的?哪个库、哪个表、哪个业务域做出来的?
+
+**状态**:保留边界。
+
+**简要答案**:`topic_pattern_itemset` 物理上是:
+
+```text
+MySQL open_aigc_pattern.topic_pattern_itemset
+```
+
+但它不是狭义 Pattern V2 的主事实表。数据工程文档把狭义 Pattern V2 的主事实库定在 PG/pgvector 口径的 `open_aigc.public`,主事实表更接近 `pattern_itemset / pattern_itemset_item / pattern_itemset_post`。
+
+`topic_pattern_itemset` 属于 MySQL `open_aigc_pattern` 里的旧 `topic_pattern_*` 兼容层 / 8012 Topic/Script Build 工作台数据域。它可以表达某个 itemset 命中了哪些历史帖子,即 `matched_post_ids`,所以更适合拿来做“Pattern/Itemset -> 多个 Case”的下游召回入口。
+
+但当前旧 ContentFindAgent 没有调用这张表。更严谨的边界是:它是 DB 里已经存在的可用证据层,不是旧 ContentFindAgent 已上线实现;而且 `Pattern V2 PG pattern_itemset* -> MySQL topic_pattern_*` 这条桥接关系在数据工程文档里仍应保持风险标注,不能直接写成强验证血缘。
+
+## 3. 只取 `demand_content` 基础字段会有什么坑?
+
+**问题**:如果 DemandAgent 后续把证据写进 `ext_data.evidence_pack`,ContentFindAgent 领取需求时还只取 `id/name/suggestion/score/merge_leve2/dt`,会不会影响新版追溯?
+
+**状态**:条件解决。只有 CFA 解析并继承 `evidence_pack`,最终沉淀 `source_evidence`,才算解决。
+
+**简要答案**:会。这样最终结果仍然只剩弱关联,不能 exact 回溯到 Pattern、itemset 和分类树节点。
+
+旧版结果通常能留下:
+
+```text
+aweme_id
+demand_content_id
+process_trace
+```
+
+这只能说明“某个抖音内容来自某个需求任务”,但不能说明:
+
+- 这个需求来自哪个 `pattern_execution_id`
+- 这个 Case ID / post ID 是哪种 ID 口径
+- 它绑定的是哪个分类节点或元素节点
+- 它来自哪个 itemset
+- 它的支撑素材有哪些 `matched_post_ids`
+- 它的策略种子 `seed_terms` 是什么
+- 这条来源是已验证、候选反查,还是待接入
+
+所以 CFA 后续领取 `demand_content` 时,必须解析并传递:
+
+```text
+ext_data.evidence_pack.source_kind
+ext_data.evidence_pack.pattern_source_system
+ext_data.evidence_pack.case_id_type
+ext_data.evidence_pack.source_post_id
+ext_data.evidence_pack.pattern_execution_id
+ext_data.evidence_pack.mining_config_id
+ext_data.evidence_pack.itemset_ids
+ext_data.evidence_pack.itemset_items[]
+ext_data.evidence_pack.category_bindings
+ext_data.evidence_pack.element_bindings
+ext_data.evidence_pack.matched_post_ids
+ext_data.evidence_pack.seed_terms
+ext_data.evidence_pack.trace_id
+ext_data.evidence_pack.source_certainty
+ext_data.evidence_pack.validation_status
+```
+
+这些字段要一路带进数据源、Query、判断、游走和入库流程。最终写 `demand_find_content_result` 时,也必须保存结构化 `source_evidence`,或者写入 sidecar/source edge artifact。
+
+否则即使上游给了证据,下游也会再次退化成:
+
+```text
+需求 ID -> 最终内容 ID
+```
+
+而不是:
+
+```text
+case_id / post_id
+-> Pattern execution / mining config / itemset
+-> category_bindings / element_bindings
+-> 分类树父节点或元素子节点
+```
+
+这就是新版必须补 `evidence_pack -> source_evidence` 的原因。
+
+## 4. 只有 `case_id/post_id` 能不能反查到唯一 Pattern?
+
+**问题**:如果下游只拿到一个 `case_id` 或 `post_id`,能不能直接查出它属于哪个 Pattern 和哪个分类树节点?
+
+**状态**:未解决;只能 Candidate Mode。
+
+**简要答案**:不能唯一确定。只有 `post_id` 时,只能查到候选 Pattern 集合。
+
+```text
+case_id/post_id
+-> pattern_itemset_post.post_id 或 topic_pattern_itemset.matched_post_ids
+-> 多个候选 itemset
+-> 多组候选分类树节点
+```
+
+原因是同一条帖子通常会支撑多个 Pattern。它可能同时属于“祝福词句 + 分享”,也可能属于“福袋 + 动态捕捉”,还可能属于更宽的“节日祝福 + 配色”组合。
+
+因此只靠 `post_id` 不能说:
+
+```text
+这个 case 唯一来自 Pattern X
+```
+
+只能说:
+
+```text
+这个 case 在 Pattern 库里支持过这些候选 Pattern
+```
+
+要 exact,需要上游 evidence 明确告诉 CFA:
+
+```text
+pattern_execution_id
+mining_config_id
+itemset_ids
+source_post_id
+category_bindings
+element_bindings
+```
+
+详细反查流程见:[Pattern和分类树反查手册.md](./Pattern和分类树反查手册.md)。
+
+## 5. MVP Exact Mode 到底解决到哪一层?
+
+**问题**:如果 DemandAgent 已经写入 DB 校验通过的 `evidence_pack`,CFA 也继承并沉淀 `source_evidence`,是不是就能从某个 case 精确追到分类树?
+
+**状态**:条件解决。
+
+**简要答案**:可以追到 DemandAgent 当前消费的 MySQL Pattern Tree,但不能自动升级成 PG Pattern V2 主事实。
+
+MVP Exact Mode 闭合链路:
+
+```text
+DemandAgent 生成需求
+-> 代码用真实 DB 校验 evidence_refs
+-> demand_content.ext_data.evidence_pack
+-> CFA 领取 demand_content 时解析 evidence_pack
+-> 数据源 / Query / 判断 / 游走 / 入库全程继承 source_evidence
+-> demand_find_content_result.source_evidence 或 sidecar/source edge artifact
+-> source_post_id + pattern_execution_id + mining_config_id + itemset_id
+-> topic_pattern_itemset.matched_post_ids
+-> topic_pattern_itemset_item.category_id
+-> topic_pattern_category.id
+-> 父链、直接子节点、source_type、category_path
+```
+
+这条链路能解决:
+
+- 从需求回到 Pattern execution
+- 从 Pattern 回到 itemset
+- 从 itemset 回到支撑素材
+- 从 itemset item 回到分类树节点
+- 从最终内容回到来源证据
+
+但它只解决到 `pattern_source_system=mysql_topic_pattern` 这一层。如果要追到 PG Pattern V2 主事实和页面,还需要:
+
+```text
+PG 侧 pattern_execution_id / itemset_id / category_id
+或 PG -> MySQL 同步映射证明
+```
+
+没有这些,就只能保守写:
+
+```text
+可追到 DemandAgent 当前消费的 MySQL Pattern Tree
+```
+
+不能写:
+
+```text
+已 exact 追到 PG Pattern V2 主事实
+```
+
+## 6. 新找到的内容能不能自动宣称 exact 命中 Pattern?
+
+**问题**:CFA 用 Pattern demand 搜到了一条新抖音视频,这条新视频能不能直接写成 exact 属于该 Pattern?
+
+**状态**:未解决;必须保留边界。
+
+**简要答案**:不能。CFA 新找到的内容只能继承“需求来源 Pattern”的证据,不能自动宣称它 exact 命中这个 Pattern。
+
+两类帖子要分开:
+
+| 帖子类型 | 能说明什么 | 不能说明什么 |
+|---|---|---|
+| `source_post_id` 或 `matched_post_ids` 中的帖子 | 它是上游 itemset 的支撑素材,可以 exact 回到 `pattern_execution_id + itemset_id + 分类树节点` | 如果启用 merge,仍要看 exact hit ratio |
+| CFA 新找到的 `aweme_id/post_id` | 它是被这个需求引导找到的下游内容,可以继承“需求来源 Pattern” | 不能自动声称这个新 post exact 命中该 Pattern 节点 |
+
+新内容如果要 exact 绑定 Pattern,需要额外完成:
+
+```text
+新 post 解构
+-> 分类或元素绑定
+-> 与 itemset_items / category_bindings 对齐
+-> 计算 exact hit ratio 或通过同口径规则校验
+```
+
+否则只能标:
+
+```text
+derived_from_pattern_demand
+candidate_pattern_related
+```
+
+## 7. matched post 是不是一定完整包含 Pattern 的全部 item?
+
+**问题**:一个帖子出现在 `matched_post_ids` 或 `pattern_itemset_post` 里,是不是就代表它完整命中 Pattern 的所有 item?
+
+**状态**:保留边界。
+
+**简要答案**:不一定。matched post 表示它是 Pattern 的支撑样本,不代表它严格包含全部 Pattern item。
+
+如果挖掘配置启用了 merge 或 soft coverage,一条 matched post 可能只是部分命中。业务表述要分两层:
+
+| 说法 | 是否准确 |
+|---|---|
+| 这个帖子支撑该 Pattern | 准确 |
+| 这个帖子完整包含该 Pattern 的全部 item | 需要 exact hit ratio 证明 |
+
+更稳妥的判断方式:
+
+```text
+pattern_itemset_item
+left join post 自身快照元素
+按 category_id + point_type + dimension/element_type 对齐
+exact_hit_ratio = exact_hits / pattern_item_count
+```
+
+所以通用文档不能写成“每个 matched post 一定严格包含 Pattern 的全部 item”。
+
+## 8. 当前结论:哪些坑算解决,哪些还没解决?
+
+| 坑 | 当前状态 | 说明 |
+|---|---|---|
+| 旧 CFA 没有完整 Pattern -> Case 主链路 | 已解决认知差异 | 已明确旧版只是弱召回,新版不应沿用为主血缘 |
+| 只取 `demand_content` 基础字段 | 条件解决 | 需要 CFA 解析 `evidence_pack` 并沉淀 `source_evidence` |
+| 从 `case_id/post_id` 追到分类树 | 条件解决 | 对上游 `source_post_id/matched_post_ids` 可以;对新搜到的内容不能自动 exact |
+| PG Pattern V2 主事实追溯 | 未解决 | 需要 PG 侧 ID 或 PG -> MySQL 同步映射证明 |
+| matched post 是否完整命中 Pattern | 保留边界 | 需要 exact hit ratio,不能直接等同完整命中 |
+
+因此,“前置的坑”不是全部关闭了。它们从“完全不清楚”变成了“哪些字段能解决、哪些地方必须保守标注”的状态。