20260731_08_add_find_agent_p0_gates.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. """add find_agent P0 gate fields
  2. Revision ID: 20260731_08
  3. Revises: 20260730_07
  4. Create Date: 2026-07-31
  5. """
  6. from __future__ import annotations
  7. from collections.abc import Sequence
  8. import sqlalchemy as sa
  9. from alembic import op
  10. revision: str = "20260731_08"
  11. down_revision: str | None = "20260731_08a"
  12. branch_labels: str | Sequence[str] | None = None
  13. depends_on: str | Sequence[str] | None = None
  14. def upgrade() -> None:
  15. inspector = sa.inspect(op.get_bind())
  16. run_column_names = {
  17. column["name"]
  18. for column in inspector.get_columns("video_discovery_run")
  19. }
  20. run_columns = (
  21. sa.Column(
  22. "rule_version",
  23. sa.String(length=64),
  24. nullable=True,
  25. comment="本次候选门禁规则版本",
  26. ),
  27. sa.Column(
  28. "rule_config_json",
  29. sa.Text(),
  30. nullable=True,
  31. comment="运行时规则、当前时间和阈值快照 JSON",
  32. ),
  33. )
  34. for column in run_columns:
  35. if column.name not in run_column_names:
  36. op.add_column("video_discovery_run", column)
  37. candidate_column_names = {
  38. column["name"]
  39. for column in inspector.get_columns("video_discovery_candidate")
  40. }
  41. columns = (
  42. sa.Column(
  43. "publish_at",
  44. sa.DateTime(),
  45. nullable=True,
  46. comment="视频真实发布时间,按运行时区解释",
  47. ),
  48. sa.Column(
  49. "duration_seconds",
  50. sa.Numeric(precision=10, scale=3),
  51. nullable=True,
  52. comment="视频时长(秒)",
  53. ),
  54. sa.Column(
  55. "content_50_plus_ratio",
  56. sa.Numeric(precision=8, scale=6),
  57. nullable=True,
  58. comment="视频点赞用户中 50+ 占比",
  59. ),
  60. sa.Column(
  61. "content_50_plus_tgi",
  62. sa.Numeric(precision=10, scale=4),
  63. nullable=True,
  64. comment="视频点赞用户 50+ TGI",
  65. ),
  66. sa.Column(
  67. "content_portrait_status",
  68. sa.String(length=16),
  69. nullable=True,
  70. comment="视频画像结论 pass / fail / missing",
  71. ),
  72. sa.Column(
  73. "account_50_plus_ratio",
  74. sa.Numeric(precision=8, scale=6),
  75. nullable=True,
  76. comment="账号粉丝中 50+ 占比",
  77. ),
  78. sa.Column(
  79. "account_50_plus_tgi",
  80. sa.Numeric(precision=10, scale=4),
  81. nullable=True,
  82. comment="账号粉丝 50+ TGI",
  83. ),
  84. sa.Column(
  85. "account_portrait_status",
  86. sa.String(length=16),
  87. nullable=True,
  88. comment="账号画像结论 pass / fail / missing",
  89. ),
  90. sa.Column(
  91. "portrait_conflict",
  92. sa.Integer(),
  93. nullable=False,
  94. server_default="0",
  95. comment="视频与账号画像结论是否冲突",
  96. ),
  97. sa.Column(
  98. "temporal_type",
  99. sa.String(length=24),
  100. nullable=True,
  101. comment="时间内容类型",
  102. ),
  103. sa.Column(
  104. "temporal_status",
  105. sa.String(length=16),
  106. nullable=True,
  107. comment="时间有效性 pass / fail / unknown",
  108. ),
  109. sa.Column(
  110. "temporal_evidence_json",
  111. sa.Text(),
  112. nullable=True,
  113. comment="时间语义、有效窗口和判断理由 JSON",
  114. ),
  115. sa.Column(
  116. "gate_status",
  117. sa.String(length=16),
  118. nullable=True,
  119. comment="硬门槛 pass / fail / pending",
  120. ),
  121. sa.Column(
  122. "gate_results_json",
  123. sa.Text(),
  124. nullable=True,
  125. comment="逐项硬门槛结果 JSON",
  126. ),
  127. sa.Column(
  128. "reject_reason_code",
  129. sa.String(length=64),
  130. nullable=True,
  131. comment="主要淘汰原因码",
  132. ),
  133. sa.Column(
  134. "rule_version",
  135. sa.String(length=64),
  136. nullable=True,
  137. comment="候选评估使用的规则版本",
  138. ),
  139. )
  140. for column in columns:
  141. if column.name not in candidate_column_names:
  142. op.add_column("video_discovery_candidate", column)
  143. def downgrade() -> None:
  144. inspector = sa.inspect(op.get_bind())
  145. candidate_column_names = {
  146. column["name"]
  147. for column in inspector.get_columns("video_discovery_candidate")
  148. }
  149. candidate_columns = (
  150. "rule_version",
  151. "reject_reason_code",
  152. "gate_results_json",
  153. "gate_status",
  154. "temporal_evidence_json",
  155. "temporal_status",
  156. "temporal_type",
  157. "portrait_conflict",
  158. "account_portrait_status",
  159. "account_50_plus_tgi",
  160. "account_50_plus_ratio",
  161. "content_portrait_status",
  162. "content_50_plus_tgi",
  163. "content_50_plus_ratio",
  164. "duration_seconds",
  165. "publish_at",
  166. )
  167. for column in candidate_columns:
  168. if column in candidate_column_names:
  169. op.drop_column("video_discovery_candidate", column)
  170. run_column_names = {
  171. column["name"]
  172. for column in inspector.get_columns("video_discovery_run")
  173. }
  174. if "rule_config_json" in run_column_names:
  175. op.drop_column("video_discovery_run", "rule_config_json")
  176. if "rule_version" in run_column_names:
  177. op.drop_column("video_discovery_run", "rule_version")