schemas.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. from __future__ import annotations
  2. from typing import Any, Literal
  3. from pydantic import BaseModel, ConfigDict, Field, model_validator
  4. from content_agent.constants import DEFAULT_STRATEGY_VERSION
  5. class RunStartRequest(BaseModel):
  6. model_config = ConfigDict(extra="forbid")
  7. run_id: str | None = Field(
  8. default=None,
  9. description="Optional fixed run_id (V3-M5: deterministic replay for concurrency-consistency tests).",
  10. )
  11. source: str | None = Field(
  12. default=None,
  13. description="Optional source_context.json or DemandAgent demand_content.json path.",
  14. )
  15. demand_content_id: int | None = Field(
  16. default=None,
  17. description="Optional content-deconstruction-supply.demand_content id.",
  18. )
  19. run_label: str | None = Field(
  20. default=None,
  21. description="Optional ext_data.run_label selector in demand_content.",
  22. )
  23. platform: str = Field(default="douyin", description="Requested platform.")
  24. platform_mode: Literal["mock", "real"] = Field(
  25. default="real",
  26. description="mock uses deterministic fixtures; real calls the configured platform API.",
  27. )
  28. strategy_version: str = Field(default=DEFAULT_STRATEGY_VERSION)
  29. @model_validator(mode="after")
  30. def validate_source_selector(self) -> "RunStartRequest":
  31. selectors = [
  32. bool(self.source),
  33. self.demand_content_id is not None,
  34. bool(self.run_label),
  35. ]
  36. if sum(selectors) > 1:
  37. raise ValueError("source, demand_content_id, and run_label are mutually exclusive")
  38. return self
  39. class RunStartResponse(BaseModel):
  40. run_id: str
  41. policy_run_id: str
  42. status: str
  43. policy_bundle_id: str
  44. strategy_version: str
  45. platform: str
  46. platform_mode: str
  47. output_dir: str
  48. class RunSummaryResponse(BaseModel):
  49. run_id: str
  50. policy_run_id: str | None = None
  51. status: str
  52. current_step: str
  53. output_dir: str
  54. files: dict[str, bool]
  55. validation_status: str
  56. errors: list[str] = Field(default_factory=list)
  57. class RecordsResponse(BaseModel):
  58. run_id: str
  59. records: list[dict[str, Any]]
  60. class JsonFileResponse(BaseModel):
  61. run_id: str
  62. data: dict[str, Any]
  63. class ValidationResponse(BaseModel):
  64. run_id: str
  65. status: str
  66. findings: list[dict[str, Any]]
  67. class RunListItem(BaseModel):
  68. run_id: str
  69. policy_run_id: str | None = None
  70. status: str | None = None
  71. current_step: str | None = None
  72. platform: str | None = None
  73. platform_mode: str | None = None
  74. content_format: str | None = None
  75. strategy_version: str | None = None
  76. demand_content_id: int | None = None
  77. demand_name: str | None = None
  78. demand_desc: str | None = None
  79. validation_status: str | None = None
  80. error_code: str | None = None
  81. started_at: str | None = None
  82. completed_at: str | None = None
  83. class RunListResponse(BaseModel):
  84. items: list[RunListItem]
  85. page: int
  86. page_size: int
  87. total: int
  88. data_origin: str
  89. class DashboardResponse(BaseModel):
  90. run_id: str
  91. summary: dict[str, Any]
  92. counts: dict[str, int]
  93. files: dict[str, bool]
  94. runtime_files: list[dict[str, Any]]
  95. validation: dict[str, Any]
  96. final_output_summary: dict[str, Any]
  97. strategy_review_status: str
  98. business_summary: dict[str, Any]
  99. stage_conclusions: list[dict[str, Any]]
  100. rule_application_summary: list[dict[str, Any]]
  101. walk_graph: dict[str, Any]
  102. primary_failure_reason: dict[str, Any] | None = None
  103. technical_refs: dict[str, Any]
  104. data_origin: str
  105. links: dict[str, str]
  106. class RuntimeFilesResponse(BaseModel):
  107. run_id: str
  108. files: list[dict[str, Any]]
  109. data_origin: str
  110. class RuntimeFileResponse(BaseModel):
  111. run_id: str
  112. filename: str
  113. data_origin: str
  114. data: dict[str, Any] | None = None
  115. records: list[dict[str, Any]] | None = None
  116. offset: int | None = None
  117. limit: int | None = None
  118. total: int | None = None
  119. class QueryListResponse(BaseModel):
  120. run_id: str
  121. items: list[dict[str, Any]]
  122. total: int
  123. data_origin: str
  124. class ConfigFileResponse(BaseModel):
  125. source_file: str
  126. data: dict[str, Any]
  127. class PlatformDescriptor(BaseModel):
  128. # 平台展示元数据(从 platform_profiles 派生,前端按此渲染,不再写死抖音)
  129. platform: str
  130. label: str
  131. status: str | None = None
  132. # V3 历史展示字段:来自 profile.heat.signals,保留兼容,不等同于 V4 平台表现分。
  133. heat_fields: list[str] = []
  134. # V4 可观测字段:来自 profile.observable_fields,用于解释平台可观测表现。
  135. observable_fields: list[str] = []
  136. class PlatformCatalogResponse(BaseModel):
  137. platforms: dict[str, PlatformDescriptor]
  138. class TimelineResponse(BaseModel):
  139. run_id: str
  140. items: list[dict[str, Any]]
  141. total: int
  142. data_origin: str
  143. summary: dict[str, Any]
  144. class ContentItemsResponse(BaseModel):
  145. run_id: str
  146. items: list[dict[str, Any]]
  147. total: int
  148. data_origin: str
  149. class FlowLedgerResponse(BaseModel):
  150. run_id: str
  151. data_origin: str
  152. data_origin_label: str
  153. summary: dict[str, Any]
  154. demand_summary: dict[str, Any] | None = None
  155. rows: list[dict[str, Any]]
  156. extension_queries: list[dict[str, Any]]
  157. debug: dict[str, Any] | None = None
  158. class FlowLedgerVideosResponse(BaseModel):
  159. run_id: str
  160. query: dict[str, Any] | None = None
  161. videos: list[dict[str, Any]]
  162. summary: dict[str, Any]
  163. data_origin: str
  164. data_origin_label: str
  165. class FlowLedgerWalkResponse(BaseModel):
  166. run_id: str
  167. query: dict[str, Any] | None = None
  168. actions: list[dict[str, Any]]
  169. lanes: dict[str, list[dict[str, Any]]]
  170. extension_queries: list[dict[str, Any]]
  171. summary: dict[str, Any]
  172. data_origin: str
  173. data_origin_label: str
  174. class FlowLedgerVideoResponse(BaseModel):
  175. run_id: str
  176. video: dict[str, Any] | None = None
  177. source_paths: list[dict[str, Any]] = Field(default_factory=list)
  178. decision: dict[str, Any] | None = None
  179. data_origin: str
  180. data_origin_label: str
  181. class FlowLedgerVideoWalkResponse(BaseModel):
  182. run_id: str
  183. root_video: dict[str, Any] | None = None
  184. actions: list[dict[str, Any]]
  185. summary: dict[str, Any]
  186. data_origin: str
  187. data_origin_label: str
  188. class AigcPushRecordListResponse(BaseModel):
  189. items: list[dict[str, Any]]
  190. total: int
  191. data_origin: str