""" 审核相关的 Pydantic 模型(API 契约定义) 所有测试和实现必须遵循此契约 """ from typing import Optional from datetime import datetime from pydantic import BaseModel, Field, HttpUrl from enum import Enum # ==================== 枚举定义 ==================== class Platform(str, Enum): """支持的投放平台""" DOUYIN = "douyin" XIAOHONGSHU = "xiaohongshu" BILIBILI = "bilibili" KUAISHOU = "kuaishou" class TaskStatus(str, Enum): """任务状态""" PENDING = "pending" PROCESSING = "processing" COMPLETED = "completed" FAILED = "failed" APPROVED = "approved" REJECTED = "rejected" class RiskLevel(str, Enum): """风险等级""" HIGH = "high" # 法律违规(广告法极限词) MEDIUM = "medium" # 平台规则违规 LOW = "low" # 品牌规范违规 class ViolationType(str, Enum): """违规类型""" FORBIDDEN_WORD = "forbidden_word" # 违禁词 EFFICACY_CLAIM = "efficacy_claim" # 功效宣称 COMPETITOR_LOGO = "competitor_logo" # 竞品露出 DURATION_SHORT = "duration_short" # 时长不足 MENTION_MISSING = "mention_missing" # 品牌提及不足 BRAND_SAFETY = "brand_safety" # 品牌安全风险 class ViolationSource(str, Enum): """违规来源""" TEXT = "text" # 文本/脚本 SPEECH = "speech" # 语音(ASR) SUBTITLE = "subtitle" # 字幕(OCR) VISUAL = "visual" # 画面(CV) class SoftRiskAction(str, Enum): """软性风控动作""" CONFIRM = "confirm" # 需要二次确认 NOTE = "note" # 需要填写备注 class SoftRiskWarning(BaseModel): """软性风控提示(Warn-only)""" code: str = Field(..., description="提示类型代码") message: str = Field(..., description="提示内容") action_required: SoftRiskAction = Field(..., description="要求动作") blocking: bool = Field(default=False, description="是否阻断(默认不阻断)") context: Optional[dict] = Field(None, description="附加上下文") class SoftRiskContext(BaseModel): """软性风控输入上下文""" violation_rate: Optional[float] = Field(None, ge=0, le=1, description="违规率") violation_threshold: Optional[float] = Field(None, ge=0, le=1, description="违规率阈值") asr_confidence: Optional[float] = Field(None, ge=0, le=1, description="ASR 置信度") ocr_confidence: Optional[float] = Field(None, ge=0, le=1, description="OCR 置信度") has_history_violation: Optional[bool] = Field(None, description="是否有历史类似违规") # ==================== 通用模型 ==================== class Position(BaseModel): """文本位置""" start: int = Field(..., description="起始位置") end: int = Field(..., description="结束位置") class Violation(BaseModel): """违规项(统一结构)""" type: ViolationType = Field(..., description="违规类型") content: str = Field(..., description="违规内容") severity: RiskLevel = Field(..., description="严重程度") suggestion: str = Field(..., description="修改建议") # 文本审核字段 position: Optional[Position] = Field(None, description="文本位置(脚本审核)") # 视频审核字段 timestamp: Optional[float] = Field(None, description="开始时间戳(秒)") timestamp_end: Optional[float] = Field(None, description="结束时间戳(秒)") source: Optional[ViolationSource] = Field(None, description="违规来源(视频审核)") # ==================== 脚本预审 ==================== class ScriptReviewRequest(BaseModel): """脚本预审请求""" content: str = Field(..., min_length=1, description="脚本内容") platform: Platform = Field(..., description="投放平台") brand_id: str = Field(..., description="品牌 ID") required_points: Optional[list[str]] = Field(None, description="必要卖点列表") soft_risk_context: Optional[SoftRiskContext] = Field(None, description="软性风控上下文") class ScriptReviewResponse(BaseModel): """ 脚本预审响应 结构: - score: 合规分数 0-100 - summary: 整体摘要 - violations: 违规项列表,每项包含 suggestion - missing_points: 遗漏的卖点(可选) """ score: int = Field(..., ge=0, le=100, description="合规分数") summary: str = Field(..., description="审核摘要") violations: list[Violation] = Field(default_factory=list, description="违规项列表") missing_points: Optional[list[str]] = Field(None, description="遗漏的卖点") soft_warnings: list[SoftRiskWarning] = Field(default_factory=list, description="软性风控提示") # ==================== 视频审核 ==================== class VideoReviewRequest(BaseModel): """视频审核请求""" video_url: HttpUrl = Field(..., description="视频 URL") platform: Platform = Field(..., description="投放平台") brand_id: str = Field(..., description="品牌 ID") creator_id: str = Field(..., description="达人 ID") competitors: Optional[list[str]] = Field(None, description="竞品列表") requirements: Optional[dict] = Field(None, description="审核要求(时长、频次等)") class VideoReviewSubmitResponse(BaseModel): """视频审核提交响应(202 Accepted)""" review_id: str = Field(..., description="审核任务 ID") status: TaskStatus = Field(default=TaskStatus.PENDING, description="任务状态") class VideoReviewProgressResponse(BaseModel): """视频审核进度响应""" review_id: str = Field(..., description="审核任务 ID") status: TaskStatus = Field(..., description="任务状态") progress: int = Field(..., ge=0, le=100, description="进度百分比") current_step: str = Field(..., description="当前处理步骤") class VideoReviewResultResponse(BaseModel): """ 视频审核结果响应(200 OK) 结构与脚本审核一致: - score: 合规分数 - summary: 整体摘要 - violations: 违规项列表,每项包含 timestamp 和 suggestion """ review_id: str = Field(..., description="审核任务 ID") status: TaskStatus = Field(default=TaskStatus.COMPLETED, description="任务状态") score: int = Field(..., ge=0, le=100, description="合规分数") summary: str = Field(..., description="审核摘要") violations: list[Violation] = Field(default_factory=list, description="违规项列表") soft_warnings: list[SoftRiskWarning] = Field(default_factory=list, description="软性风控提示")