后端: - 审核结果拆分为 4 个独立维度 (法规合规/平台规则/品牌安全/Brief匹配度) - 卖点优先级从 required:bool 改为三级 (core/recommended/reference) - AI 语义匹配卖点覆盖 + AI 整体 Brief 匹配度分析 - BriefMatchDetail 评分详情 (覆盖率+亮点+问题点) - min_selling_points 代理商可配置最少卖点数 + Alembic 迁移 - AI 语境复核过滤误报 - Brief AI 解析 + 规则 AI 解析 - AI 未配置/异常时通知品牌方 - 种子数据更新 (新格式审核结果+brief_match_detail) 前端: - 三端审核页面展示四维度评分卡片 - 卖点编辑改为三级优先级选择器 - BriefMatchDetail 展示 (覆盖率进度条+亮点+问题) - min_selling_points 配置 UI - AI 配置页未配置时静默处理 - 文件预览/下载/签名 URL 优化 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
224 lines
9.3 KiB
Python
224 lines
9.3 KiB
Python
"""
|
||
审核相关的 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="修改建议")
|
||
dimension: Optional[str] = Field(None, description="所属维度: legal/platform/brand_safety/brief_match")
|
||
|
||
# 文本审核字段
|
||
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 ReviewDimension(BaseModel):
|
||
"""审核维度评分"""
|
||
score: int = Field(..., ge=0, le=100)
|
||
passed: bool
|
||
issue_count: int = 0
|
||
|
||
|
||
class ReviewDimensions(BaseModel):
|
||
"""四维度审核结果"""
|
||
legal: ReviewDimension # 法规合规(违禁词、功效词、Brief黑名单词)
|
||
platform: ReviewDimension # 平台规则
|
||
brand_safety: ReviewDimension # 品牌安全(竞品、其他品牌词)
|
||
brief_match: ReviewDimension # Brief 匹配度(卖点覆盖)
|
||
|
||
|
||
class SellingPointMatch(BaseModel):
|
||
"""卖点匹配结果"""
|
||
content: str
|
||
priority: str # "core" | "recommended" | "reference"
|
||
matched: bool
|
||
evidence: Optional[str] = None # AI 给出的匹配依据
|
||
|
||
|
||
class BriefMatchDetail(BaseModel):
|
||
"""Brief 匹配度评分详情"""
|
||
# 卖点覆盖
|
||
total_points: int = Field(0, description="需要检查的卖点总数(core + recommended)")
|
||
matched_points: int = Field(0, description="实际匹配的卖点数")
|
||
required_points: int = Field(0, description="代理商要求至少体现的卖点条数(min_selling_points)")
|
||
coverage_score: int = Field(0, ge=0, le=100, description="卖点覆盖率得分")
|
||
# AI 整体匹配分析
|
||
overall_score: int = Field(0, ge=0, le=100, description="整体 Brief 匹配度得分")
|
||
highlights: list[str] = Field(default_factory=list, description="内容亮点(AI 分析)")
|
||
issues: list[str] = Field(default_factory=list, description="问题点(AI 分析)")
|
||
explanation: str = Field("", description="评分说明(一句话总结)")
|
||
|
||
|
||
# ==================== 脚本预审 ====================
|
||
|
||
class ScriptReviewRequest(BaseModel):
|
||
"""脚本预审请求"""
|
||
content: str = Field(..., min_length=1, description="脚本内容")
|
||
platform: Platform = Field(..., description="投放平台")
|
||
brand_id: str = Field(..., description="品牌 ID")
|
||
selling_points: Optional[list[dict]] = Field(None, description="卖点列表 [{content, priority}]")
|
||
min_selling_points: Optional[int] = Field(None, ge=0, description="代理商要求至少体现的卖点条数")
|
||
blacklist_words: Optional[list[dict]] = Field(None, description="Brief 黑名单词 [{word, reason}]")
|
||
soft_risk_context: Optional[SoftRiskContext] = Field(None, description="软性风控上下文")
|
||
file_url: Optional[str] = Field(None, description="脚本文件 URL(用于自动解析文本和提取图片)")
|
||
file_name: Optional[str] = Field(None, description="原始文件名(用于判断格式)")
|
||
|
||
|
||
class ScriptReviewResponse(BaseModel):
|
||
"""
|
||
脚本预审响应
|
||
|
||
结构:
|
||
- score: 加权总分(向后兼容)
|
||
- summary: 整体摘要
|
||
- dimensions: 四维度评分(法规/平台/品牌安全/Brief匹配)
|
||
- selling_point_matches: 卖点匹配详情
|
||
- violations: 违规项列表,每项带 dimension 标签
|
||
- missing_points: 遗漏的核心卖点(向后兼容)
|
||
"""
|
||
score: int = Field(..., ge=0, le=100, description="加权总分")
|
||
summary: str = Field(..., description="审核摘要")
|
||
dimensions: ReviewDimensions = Field(..., description="四维度评分")
|
||
selling_point_matches: list[SellingPointMatch] = Field(default_factory=list, description="卖点匹配详情")
|
||
brief_match_detail: Optional[BriefMatchDetail] = Field(None, description="Brief 匹配度评分详情")
|
||
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="软性风控提示")
|
||
ai_available: bool = Field(True, description="AI 服务是否可用(False 表示降级为纯关键词检测)")
|
||
|
||
|
||
# ==================== 视频审核 ====================
|
||
|
||
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="软性风控提示")
|
||
|