schemas.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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_name: str | None = None
  77. demand_desc: str | None = None
  78. validation_status: str | None = None
  79. error_code: str | None = None
  80. started_at: str | None = None
  81. completed_at: str | None = None
  82. class RunListResponse(BaseModel):
  83. items: list[RunListItem]
  84. page: int
  85. page_size: int
  86. total: int
  87. data_origin: str
  88. class DashboardResponse(BaseModel):
  89. run_id: str
  90. summary: dict[str, Any]
  91. counts: dict[str, int]
  92. files: dict[str, bool]
  93. runtime_files: list[dict[str, Any]]
  94. validation: dict[str, Any]
  95. final_output_summary: dict[str, Any]
  96. strategy_review_status: str
  97. business_summary: dict[str, Any]
  98. stage_conclusions: list[dict[str, Any]]
  99. rule_application_summary: list[dict[str, Any]]
  100. walk_graph: dict[str, Any]
  101. primary_failure_reason: dict[str, Any] | None = None
  102. technical_refs: dict[str, Any]
  103. data_origin: str
  104. links: dict[str, str]
  105. class RuntimeFilesResponse(BaseModel):
  106. run_id: str
  107. files: list[dict[str, Any]]
  108. data_origin: str
  109. class RuntimeFileResponse(BaseModel):
  110. run_id: str
  111. filename: str
  112. data_origin: str
  113. data: dict[str, Any] | None = None
  114. records: list[dict[str, Any]] | None = None
  115. offset: int | None = None
  116. limit: int | None = None
  117. total: int | None = None
  118. class QueryListResponse(BaseModel):
  119. run_id: str
  120. items: list[dict[str, Any]]
  121. total: int
  122. data_origin: str
  123. class ConfigFileResponse(BaseModel):
  124. source_file: str
  125. data: dict[str, Any]
  126. class PlatformDescriptor(BaseModel):
  127. # 平台展示元数据(从 platform_profiles 派生,前端按此渲染,不再写死抖音)
  128. platform: str
  129. label: str
  130. status: str | None = None
  131. # 该平台真实可得的互动指标(= profile.heat.signals 的 field,按重要度排序;统一键名)
  132. heat_fields: list[str] = []
  133. class PlatformCatalogResponse(BaseModel):
  134. platforms: dict[str, PlatformDescriptor]
  135. class TimelineResponse(BaseModel):
  136. run_id: str
  137. items: list[dict[str, Any]]
  138. total: int
  139. data_origin: str
  140. summary: dict[str, Any]
  141. class ContentItemsResponse(BaseModel):
  142. run_id: str
  143. items: list[dict[str, Any]]
  144. total: int
  145. data_origin: str