video_discovery.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. from __future__ import annotations
  2. from datetime import datetime
  3. from decimal import Decimal
  4. from sqlalchemy import (
  5. BigInteger,
  6. ForeignKey,
  7. Index,
  8. Integer,
  9. Numeric,
  10. String,
  11. Text,
  12. UniqueConstraint,
  13. func,
  14. )
  15. from sqlalchemy.orm import Mapped, mapped_column
  16. from supply_infra.db.base import Base
  17. class VideoDiscoveryRun(Base):
  18. """一次需求找视频运行,保存输入、意图解释和完成状态。"""
  19. __tablename__ = "video_discovery_run"
  20. __table_args__ = (
  21. UniqueConstraint("run_id", name="uk_video_discovery_run_id"),
  22. UniqueConstraint(
  23. "biz_dt",
  24. "demand_grade_id",
  25. name="uk_video_discovery_run_biz_grade",
  26. ),
  27. Index("idx_video_discovery_run_demand", "demand_word"),
  28. Index("idx_video_discovery_run_grade", "demand_grade_id"),
  29. Index("idx_video_discovery_run_biz_dt", "biz_dt"),
  30. Index("idx_video_discovery_run_status", "status"),
  31. Index(
  32. "idx_video_discovery_run_platform_version",
  33. "platform_demand_version_id",
  34. ),
  35. )
  36. id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
  37. run_id: Mapped[str] = mapped_column(String(64), nullable=False, comment="Agent 运行标识")
  38. biz_dt: Mapped[str | None] = mapped_column(
  39. String(32), nullable=True, comment="业务日 YYYYMMDD"
  40. )
  41. demand_grade_id: Mapped[int | None] = mapped_column(
  42. BigInteger, nullable=True, comment="可选 demand_grade.id"
  43. )
  44. demand_package_id: Mapped[str | None] = mapped_column(
  45. String(36), nullable=True, comment="统一每日需求任务包 id"
  46. )
  47. daily_demand_task_id: Mapped[str | None] = mapped_column(
  48. String(36), nullable=True, comment="统一需求任务 id"
  49. )
  50. platform_demand_version_id: Mapped[str | None] = mapped_column(
  51. String(36), nullable=True, comment="平台需求日版本 id"
  52. )
  53. demand_word: Mapped[str] = mapped_column(
  54. String(256), nullable=False, comment="用户给定需求词"
  55. )
  56. seed_video_id: Mapped[str | None] = mapped_column(
  57. String(64), nullable=True, comment="参考视频 id"
  58. )
  59. seed_video_title: Mapped[str | None] = mapped_column(
  60. String(512), nullable=True, comment="参考视频标题"
  61. )
  62. relevant_points_json: Mapped[str] = mapped_column(
  63. Text, nullable=False, comment="与需求相关的视频点位 JSON"
  64. )
  65. intent_summary: Mapped[str | None] = mapped_column(
  66. Text, nullable=True, comment="Agent 对真实内容意图的解释"
  67. )
  68. status: Mapped[str] = mapped_column(
  69. String(24), nullable=False, default="running", comment="running / finished / failed"
  70. )
  71. search_count: Mapped[int] = mapped_column(
  72. Integer, nullable=False, default=0, comment="已保存搜索页数"
  73. )
  74. primary_count: Mapped[int] = mapped_column(
  75. Integer, nullable=False, default=0, comment="主推荐数"
  76. )
  77. stop_reason: Mapped[str | None] = mapped_column(
  78. Text, nullable=True, comment="停止搜索的证据或失败原因"
  79. )
  80. rule_version: Mapped[str | None] = mapped_column(
  81. String(64), nullable=True, comment="本次候选门禁规则版本"
  82. )
  83. rule_config_json: Mapped[str | None] = mapped_column(
  84. Text, nullable=True, comment="运行时规则、当前时间和阈值快照 JSON"
  85. )
  86. create_time: Mapped[datetime] = mapped_column(
  87. nullable=False, server_default=func.now(), comment="创建时间"
  88. )
  89. update_time: Mapped[datetime] = mapped_column(
  90. nullable=False,
  91. server_default=func.now(),
  92. onupdate=func.now(),
  93. comment="更新时间",
  94. )
  95. class VideoDiscoverySearch(Base):
  96. """关键词探索图中的一次具体搜索页。"""
  97. __tablename__ = "video_discovery_search"
  98. __table_args__ = (
  99. Index("idx_video_discovery_search_run", "run_id", "id"),
  100. Index(
  101. "idx_video_discovery_search_parent",
  102. "run_id",
  103. "parent_search_id",
  104. ),
  105. Index("idx_video_discovery_search_keyword", "keyword"),
  106. )
  107. id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
  108. run_id: Mapped[str] = mapped_column(String(64), nullable=False, comment="发现运行 run_id")
  109. search_key: Mapped[str] = mapped_column(
  110. String(64), nullable=False, comment="搜索参数哈希,仅用于追踪,不作为幂等键"
  111. )
  112. keyword: Mapped[str] = mapped_column(
  113. String(256), nullable=False, comment="Agent 自主确定的搜索词"
  114. )
  115. query_reason: Mapped[str] = mapped_column(
  116. Text, nullable=False, comment="为何形成该搜索词、希望验证什么"
  117. )
  118. source_type: Mapped[str] = mapped_column(
  119. String(32),
  120. nullable=False,
  121. comment="demand / seed / point / tag / author / pagination / mixed",
  122. )
  123. source_value: Mapped[str | None] = mapped_column(
  124. Text, nullable=True, comment="来源点位、标签或父关键词"
  125. )
  126. parent_search_id: Mapped[int | None] = mapped_column(
  127. BigInteger, nullable=True, comment="由哪次搜索扩展而来"
  128. )
  129. provider: Mapped[str] = mapped_column(
  130. String(32),
  131. nullable=False,
  132. default="internal_keyword",
  133. comment="internal_keyword / tikhub / internal_blogger",
  134. )
  135. provider_state_json: Mapped[str | None] = mapped_column(
  136. Text, nullable=True, comment="供应方分页状态,如 search_id/backtrace"
  137. )
  138. content_type: Mapped[str] = mapped_column(
  139. String(16), nullable=False, default="视频", comment="搜索内容类型"
  140. )
  141. sort_type: Mapped[str] = mapped_column(
  142. String(32), nullable=False, default="综合排序", comment="搜索排序"
  143. )
  144. publish_time: Mapped[str] = mapped_column(
  145. String(32), nullable=False, default="不限", comment="发布时间筛选"
  146. )
  147. cursor: Mapped[str] = mapped_column(
  148. String(128), nullable=False, default="0", comment="本页游标"
  149. )
  150. page_no: Mapped[int] = mapped_column(
  151. Integer, nullable=False, default=1, comment="该关键词的页码"
  152. )
  153. results_count: Mapped[int] = mapped_column(
  154. Integer, nullable=False, default=0, comment="本页结果数"
  155. )
  156. new_candidate_count: Mapped[int] = mapped_column(
  157. Integer, nullable=False, default=0, comment="本页新增候选数"
  158. )
  159. has_more: Mapped[int] = mapped_column(
  160. Integer, nullable=False, default=0, comment="接口是否有下一页"
  161. )
  162. next_cursor: Mapped[str | None] = mapped_column(
  163. String(128), nullable=True, comment="下一页游标"
  164. )
  165. result_ids_json: Mapped[str | None] = mapped_column(
  166. Text, nullable=True, comment="本页 aweme_id 列表 JSON"
  167. )
  168. status: Mapped[str] = mapped_column(
  169. String(16), nullable=False, default="success", comment="success / failed"
  170. )
  171. error_message: Mapped[str | None] = mapped_column(
  172. Text, nullable=True, comment="搜索失败信息"
  173. )
  174. create_time: Mapped[datetime] = mapped_column(
  175. nullable=False, server_default=func.now(), comment="创建时间"
  176. )
  177. update_time: Mapped[datetime] = mapped_column(
  178. nullable=False,
  179. server_default=func.now(),
  180. onupdate=func.now(),
  181. comment="更新时间",
  182. )
  183. class VideoDiscoveryCandidate(Base):
  184. """本次运行发现的视频及其可审计评估快照。"""
  185. __tablename__ = "video_discovery_candidate"
  186. __table_args__ = (
  187. Index("idx_video_discovery_candidate_search", "search_id", "id"),
  188. Index("idx_video_discovery_candidate_bucket", "run_id", "decision_bucket"),
  189. Index("idx_video_discovery_candidate_author", "author_sec_uid"),
  190. )
  191. id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
  192. run_id: Mapped[str] = mapped_column(String(64), nullable=False, comment="发现运行 run_id")
  193. search_id: Mapped[int | None] = mapped_column(
  194. BigInteger,
  195. ForeignKey(
  196. "video_discovery_search.id",
  197. name="fk_video_discovery_candidate_search",
  198. ondelete="RESTRICT",
  199. ),
  200. nullable=True,
  201. comment="直接关联 video_discovery_search.id;历史数据允许为空",
  202. )
  203. aweme_id: Mapped[str] = mapped_column(String(64), nullable=False, comment="抖音视频 id")
  204. title: Mapped[str | None] = mapped_column(String(512), nullable=True, comment="视频标题")
  205. content_link: Mapped[str | None] = mapped_column(
  206. String(1024), nullable=True, comment="抖音页面链接"
  207. )
  208. author_name: Mapped[str | None] = mapped_column(
  209. String(256), nullable=True, comment="作者名"
  210. )
  211. author_sec_uid: Mapped[str | None] = mapped_column(
  212. String(256), nullable=True, comment="作者 sec_uid"
  213. )
  214. source_keywords_json: Mapped[str | None] = mapped_column(
  215. Text, nullable=True, comment="命中过该视频的搜索词 JSON"
  216. )
  217. source_search_ids_json: Mapped[str | None] = mapped_column(
  218. Text, nullable=True, comment="来源搜索轨迹 id JSON"
  219. )
  220. tags_json: Mapped[str | None] = mapped_column(
  221. Text, nullable=True, comment="视频标签/话题 JSON"
  222. )
  223. publish_at: Mapped[datetime | None] = mapped_column(
  224. nullable=True, comment="视频真实发布时间,按运行时区解释"
  225. )
  226. duration_seconds: Mapped[Decimal | None] = mapped_column(
  227. Numeric(10, 3), nullable=True, comment="视频时长(秒)"
  228. )
  229. play_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  230. like_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  231. comment_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  232. collect_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  233. share_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  234. content_age_evidence_json: Mapped[str | None] = mapped_column(
  235. Text, nullable=True, comment="视频点赞用户年龄证据 JSON"
  236. )
  237. account_age_evidence_json: Mapped[str | None] = mapped_column(
  238. Text, nullable=True, comment="作者粉丝年龄证据 JSON"
  239. )
  240. age_normalization_json: Mapped[str | None] = mapped_column(
  241. Text, nullable=True, comment="双侧年龄画像标准化结果 JSON"
  242. )
  243. content_50_plus_ratio: Mapped[Decimal | None] = mapped_column(
  244. Numeric(8, 6), nullable=True, comment="视频点赞用户中 50+ 占比"
  245. )
  246. content_50_plus_tgi: Mapped[Decimal | None] = mapped_column(
  247. Numeric(10, 4), nullable=True, comment="视频点赞用户 50+ TGI"
  248. )
  249. content_portrait_status: Mapped[str | None] = mapped_column(
  250. String(16), nullable=True, comment="视频画像结论 pass / fail / missing"
  251. )
  252. account_50_plus_ratio: Mapped[Decimal | None] = mapped_column(
  253. Numeric(8, 6), nullable=True, comment="账号粉丝中 50+ 占比"
  254. )
  255. account_50_plus_tgi: Mapped[Decimal | None] = mapped_column(
  256. Numeric(10, 4), nullable=True, comment="账号粉丝 50+ TGI"
  257. )
  258. account_portrait_status: Mapped[str | None] = mapped_column(
  259. String(16), nullable=True, comment="账号画像结论 pass / fail / missing"
  260. )
  261. portrait_conflict: Mapped[int] = mapped_column(
  262. Integer, nullable=False, default=0, comment="视频与账号画像结论是否冲突"
  263. )
  264. temporal_type: Mapped[str | None] = mapped_column(
  265. String(24), nullable=True, comment="daypart / festival / event / seasonal / evergreen"
  266. )
  267. temporal_status: Mapped[str | None] = mapped_column(
  268. String(16), nullable=True, comment="时间有效性 pass / fail / unknown"
  269. )
  270. temporal_evidence_json: Mapped[str | None] = mapped_column(
  271. Text, nullable=True, comment="时间语义、有效窗口和判断理由 JSON"
  272. )
  273. gate_status: Mapped[str | None] = mapped_column(
  274. String(16), nullable=True, comment="硬门槛 pass / fail / pending"
  275. )
  276. gate_results_json: Mapped[str | None] = mapped_column(
  277. Text, nullable=True, comment="逐项硬门槛结果 JSON"
  278. )
  279. reject_reason_code: Mapped[str | None] = mapped_column(
  280. String(64), nullable=True, comment="主要淘汰原因码"
  281. )
  282. rule_version: Mapped[str | None] = mapped_column(
  283. String(64), nullable=True, comment="候选评估使用的规则版本"
  284. )
  285. relevance_score: Mapped[Decimal | None] = mapped_column(
  286. Numeric(8, 6), nullable=True, comment="R,范围 0~1"
  287. )
  288. elder_score: Mapped[Decimal | None] = mapped_column(
  289. Numeric(8, 6), nullable=True, comment="E,范围 0~1"
  290. )
  291. share_score: Mapped[Decimal | None] = mapped_column(
  292. Numeric(8, 6), nullable=True, comment="S,范围 0~1"
  293. )
  294. value_score: Mapped[Decimal | None] = mapped_column(
  295. Numeric(8, 2), nullable=True, comment="联合价值 V,范围 0~1"
  296. )
  297. decision_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
  298. decision_bucket: Mapped[str] = mapped_column(
  299. String(24),
  300. nullable=False,
  301. default="pending_evaluation",
  302. comment="最终为 primary / rejected;pending_evaluation 仅为过程状态",
  303. )
  304. aigc_crawler_plan_id: Mapped[str | None] = mapped_column(
  305. String(64), nullable=True, comment="已创建的 AIGC 爬取计划 id"
  306. )
  307. aigc_produce_plan_id: Mapped[str | None] = mapped_column(
  308. String(64), nullable=True, comment="绑定的 AIGC 生成计划 id"
  309. )
  310. aigc_publish_plan_id: Mapped[str | None] = mapped_column(
  311. String(64), nullable=True, comment="关联的 AIGC 发布计划 id"
  312. )
  313. aigc_plan_label: Mapped[str | None] = mapped_column(
  314. String(64), nullable=True, comment="均匀分发时使用的计划标签"
  315. )
  316. create_time: Mapped[datetime] = mapped_column(
  317. nullable=False, server_default=func.now(), comment="创建时间"
  318. )
  319. update_time: Mapped[datetime] = mapped_column(
  320. nullable=False,
  321. server_default=func.now(),
  322. onupdate=func.now(),
  323. comment="更新时间",
  324. )