video_discovery.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. from __future__ import annotations
  2. from datetime import datetime
  3. from decimal import Decimal
  4. from sqlalchemy import (
  5. BigInteger,
  6. Index,
  7. Integer,
  8. Numeric,
  9. String,
  10. Text,
  11. UniqueConstraint,
  12. func,
  13. )
  14. from sqlalchemy.orm import Mapped, mapped_column
  15. from supply_infra.db.base import Base
  16. class VideoDiscoveryRun(Base):
  17. """一次需求找片运行,保存输入、意图解释和完成状态。"""
  18. __tablename__ = "video_discovery_run"
  19. __table_args__ = (
  20. UniqueConstraint("run_id", name="uk_video_discovery_run_id"),
  21. UniqueConstraint(
  22. "biz_dt",
  23. "demand_grade_id",
  24. name="uk_video_discovery_run_biz_grade",
  25. ),
  26. Index("idx_video_discovery_run_demand", "demand_word"),
  27. Index("idx_video_discovery_run_grade", "demand_grade_id"),
  28. Index("idx_video_discovery_run_biz_dt", "biz_dt"),
  29. Index("idx_video_discovery_run_status", "status"),
  30. )
  31. id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
  32. run_id: Mapped[str] = mapped_column(String(64), nullable=False, comment="Agent 运行标识")
  33. biz_dt: Mapped[str | None] = mapped_column(
  34. String(32), nullable=True, comment="业务日 YYYYMMDD"
  35. )
  36. demand_grade_id: Mapped[int | None] = mapped_column(
  37. BigInteger, nullable=True, comment="可选 demand_grade.id"
  38. )
  39. demand_word: Mapped[str] = mapped_column(
  40. String(256), nullable=False, comment="用户给定需求词"
  41. )
  42. seed_video_id: Mapped[str | None] = mapped_column(
  43. String(64), nullable=True, comment="参考视频 id"
  44. )
  45. seed_video_title: Mapped[str | None] = mapped_column(
  46. String(512), nullable=True, comment="参考视频标题"
  47. )
  48. relevant_points_json: Mapped[str] = mapped_column(
  49. Text, nullable=False, comment="与需求相关的视频点位 JSON"
  50. )
  51. intent_summary: Mapped[str | None] = mapped_column(
  52. Text, nullable=True, comment="Agent 对真实内容意图的解释"
  53. )
  54. status: Mapped[str] = mapped_column(
  55. String(24), nullable=False, default="running", comment="running / finished / failed"
  56. )
  57. search_count: Mapped[int] = mapped_column(
  58. Integer, nullable=False, default=0, comment="已保存搜索页数"
  59. )
  60. primary_count: Mapped[int] = mapped_column(
  61. Integer, nullable=False, default=0, comment="主推荐数"
  62. )
  63. stop_reason: Mapped[str | None] = mapped_column(
  64. Text, nullable=True, comment="停止搜索的证据或失败原因"
  65. )
  66. create_time: Mapped[datetime] = mapped_column(
  67. nullable=False, server_default=func.now(), comment="创建时间"
  68. )
  69. update_time: Mapped[datetime] = mapped_column(
  70. nullable=False,
  71. server_default=func.now(),
  72. onupdate=func.now(),
  73. comment="更新时间",
  74. )
  75. class VideoDiscoverySearch(Base):
  76. """关键词探索图中的一次具体搜索页。"""
  77. __tablename__ = "video_discovery_search"
  78. __table_args__ = (
  79. UniqueConstraint("run_id", "search_key", name="uk_video_discovery_search_key"),
  80. Index("idx_video_discovery_search_run", "run_id", "id"),
  81. Index(
  82. "idx_video_discovery_search_parent",
  83. "run_id",
  84. "parent_search_id",
  85. ),
  86. Index("idx_video_discovery_search_keyword", "keyword"),
  87. )
  88. id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
  89. run_id: Mapped[str] = mapped_column(String(64), nullable=False, comment="发现运行 run_id")
  90. search_key: Mapped[str] = mapped_column(
  91. String(64), nullable=False, comment="关键词/筛选/游标组合哈希"
  92. )
  93. keyword: Mapped[str] = mapped_column(
  94. String(256), nullable=False, comment="Agent 自主确定的搜索词"
  95. )
  96. query_reason: Mapped[str] = mapped_column(
  97. Text, nullable=False, comment="为何形成该搜索词、希望验证什么"
  98. )
  99. source_type: Mapped[str] = mapped_column(
  100. String(32),
  101. nullable=False,
  102. comment="demand / seed / point / tag / author / pagination / mixed",
  103. )
  104. source_value: Mapped[str | None] = mapped_column(
  105. Text, nullable=True, comment="来源点位、标签或父关键词"
  106. )
  107. parent_search_id: Mapped[int | None] = mapped_column(
  108. BigInteger, nullable=True, comment="由哪次搜索扩展而来"
  109. )
  110. provider: Mapped[str] = mapped_column(
  111. String(32),
  112. nullable=False,
  113. default="internal_keyword",
  114. comment="internal_keyword / tikhub / internal_blogger",
  115. )
  116. provider_state_json: Mapped[str | None] = mapped_column(
  117. Text, nullable=True, comment="供应方分页状态,如 search_id/backtrace"
  118. )
  119. content_type: Mapped[str] = mapped_column(
  120. String(16), nullable=False, default="视频", comment="搜索内容类型"
  121. )
  122. sort_type: Mapped[str] = mapped_column(
  123. String(32), nullable=False, default="综合排序", comment="搜索排序"
  124. )
  125. publish_time: Mapped[str] = mapped_column(
  126. String(32), nullable=False, default="不限", comment="发布时间筛选"
  127. )
  128. cursor: Mapped[str] = mapped_column(
  129. String(128), nullable=False, default="0", comment="本页游标"
  130. )
  131. page_no: Mapped[int] = mapped_column(
  132. Integer, nullable=False, default=1, comment="该关键词的页码"
  133. )
  134. results_count: Mapped[int] = mapped_column(
  135. Integer, nullable=False, default=0, comment="本页结果数"
  136. )
  137. new_candidate_count: Mapped[int] = mapped_column(
  138. Integer, nullable=False, default=0, comment="本页新增候选数"
  139. )
  140. has_more: Mapped[int] = mapped_column(
  141. Integer, nullable=False, default=0, comment="接口是否有下一页"
  142. )
  143. next_cursor: Mapped[str | None] = mapped_column(
  144. String(128), nullable=True, comment="下一页游标"
  145. )
  146. result_ids_json: Mapped[str | None] = mapped_column(
  147. Text, nullable=True, comment="本页 aweme_id 列表 JSON"
  148. )
  149. status: Mapped[str] = mapped_column(
  150. String(16), nullable=False, default="success", comment="success / failed"
  151. )
  152. error_message: Mapped[str | None] = mapped_column(
  153. Text, nullable=True, comment="搜索失败信息"
  154. )
  155. create_time: Mapped[datetime] = mapped_column(
  156. nullable=False, server_default=func.now(), comment="创建时间"
  157. )
  158. update_time: Mapped[datetime] = mapped_column(
  159. nullable=False,
  160. server_default=func.now(),
  161. onupdate=func.now(),
  162. comment="更新时间",
  163. )
  164. class VideoDiscoveryCandidate(Base):
  165. """本次运行发现的视频及其可审计评估快照。"""
  166. __tablename__ = "video_discovery_candidate"
  167. __table_args__ = (
  168. UniqueConstraint(
  169. "run_id", "aweme_id", name="uk_video_discovery_candidate_run_aweme"
  170. ),
  171. Index("idx_video_discovery_candidate_bucket", "run_id", "decision_bucket"),
  172. Index("idx_video_discovery_candidate_author", "author_sec_uid"),
  173. )
  174. id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
  175. run_id: Mapped[str] = mapped_column(String(64), nullable=False, comment="发现运行 run_id")
  176. aweme_id: Mapped[str] = mapped_column(String(64), nullable=False, comment="抖音视频 id")
  177. title: Mapped[str | None] = mapped_column(String(512), nullable=True, comment="视频标题")
  178. content_link: Mapped[str | None] = mapped_column(
  179. String(1024), nullable=True, comment="抖音页面链接"
  180. )
  181. author_name: Mapped[str | None] = mapped_column(
  182. String(256), nullable=True, comment="作者名"
  183. )
  184. author_sec_uid: Mapped[str | None] = mapped_column(
  185. String(256), nullable=True, comment="作者 sec_uid"
  186. )
  187. source_keywords_json: Mapped[str | None] = mapped_column(
  188. Text, nullable=True, comment="命中过该视频的搜索词 JSON"
  189. )
  190. source_search_ids_json: Mapped[str | None] = mapped_column(
  191. Text, nullable=True, comment="来源搜索轨迹 id JSON"
  192. )
  193. tags_json: Mapped[str | None] = mapped_column(
  194. Text, nullable=True, comment="视频标签/话题 JSON"
  195. )
  196. hit_points_json: Mapped[str | None] = mapped_column(
  197. Text, nullable=True, comment="命中的需求相关点 JSON"
  198. )
  199. play_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  200. like_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  201. comment_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  202. collect_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  203. share_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  204. publish_timestamp: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  205. content_age_evidence_json: Mapped[str | None] = mapped_column(
  206. Text, nullable=True, comment="视频点赞用户年龄证据 JSON"
  207. )
  208. account_age_evidence_json: Mapped[str | None] = mapped_column(
  209. Text, nullable=True, comment="作者粉丝年龄证据 JSON"
  210. )
  211. age_normalization_json: Mapped[str | None] = mapped_column(
  212. Text, nullable=True, comment="双侧年龄画像标准化结果 JSON"
  213. )
  214. detail_verified: Mapped[int] = mapped_column(
  215. Integer, nullable=False, default=0, comment="是否已核验视频详情"
  216. )
  217. content_portrait_attempted: Mapped[int] = mapped_column(
  218. Integer, nullable=False, default=0, comment="是否已尝试视频点赞画像"
  219. )
  220. account_portrait_attempted: Mapped[int] = mapped_column(
  221. Integer, nullable=False, default=0, comment="是否已尝试作者粉丝画像"
  222. )
  223. age_portraits_normalized: Mapped[int] = mapped_column(
  224. Integer, nullable=False, default=0, comment="是否已执行年龄画像标准化"
  225. )
  226. expansion_worthy_tags_json: Mapped[str | None] = mapped_column(
  227. Text, nullable=True, comment="值得继续搜索的标签 JSON"
  228. )
  229. relevance_score: Mapped[Decimal | None] = mapped_column(
  230. Numeric(8, 6), nullable=True, comment="R,范围 0~1"
  231. )
  232. elder_score: Mapped[Decimal | None] = mapped_column(
  233. Numeric(8, 6), nullable=True, comment="E,范围 0~1"
  234. )
  235. share_score: Mapped[Decimal | None] = mapped_column(
  236. Numeric(8, 6), nullable=True, comment="S,范围 0~1"
  237. )
  238. value_score: Mapped[Decimal | None] = mapped_column(
  239. Numeric(8, 2), nullable=True, comment="联合价值 V,范围 0~100"
  240. )
  241. confidence: Mapped[str | None] = mapped_column(
  242. String(16), nullable=True, comment="high / medium / low"
  243. )
  244. relevance_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
  245. elder_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
  246. share_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
  247. decision_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
  248. decision_bucket: Mapped[str] = mapped_column(
  249. String(24),
  250. nullable=False,
  251. default="pending_evaluation",
  252. comment="最终为 primary / rejected;pending_evaluation 仅为过程状态",
  253. )
  254. aigc_crawler_plan_id: Mapped[str | None] = mapped_column(
  255. String(64), nullable=True, comment="已创建的 AIGC 爬取计划 id"
  256. )
  257. aigc_produce_plan_id: Mapped[str | None] = mapped_column(
  258. String(64), nullable=True, comment="绑定的 AIGC 生成计划 id"
  259. )
  260. aigc_publish_plan_id: Mapped[str | None] = mapped_column(
  261. String(64), nullable=True, comment="关联的 AIGC 发布计划 id"
  262. )
  263. aigc_plan_label: Mapped[str | None] = mapped_column(
  264. String(64), nullable=True, comment="均匀分发时使用的计划标签"
  265. )
  266. create_time: Mapped[datetime] = mapped_column(
  267. nullable=False, server_default=func.now(), comment="创建时间"
  268. )
  269. update_time: Mapped[datetime] = mapped_column(
  270. nullable=False,
  271. server_default=func.now(),
  272. onupdate=func.now(),
  273. comment="更新时间",
  274. )