Your Name e4959d584f feat: 完善代理商端业务逻辑与前后端框架
主要更新:
- 更新代理商端文档,明确项目由品牌方分配流程
- 新增Brief配置详情页(已配置)设计稿
- 完善工作台紧急待办中品牌新任务功能
- 整理Pencil设计文件中代理商端页面顺序
- 新增后端FastAPI框架及核心API
- 新增前端Next.js页面和组件库
- 添加.gitignore排除构建和缓存文件

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 19:27:31 +08:00

313 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
审核相关的 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="软性风控提示")
# ==================== 一致性指标 ====================
class ConsistencyWindow(str, Enum):
"""一致性指标计算周期"""
ROLLING_30D = "rolling_30d"
SNAPSHOT_WEEK = "snapshot_week"
SNAPSHOT_MONTH = "snapshot_month"
class RuleConsistencyMetric(BaseModel):
"""按规则类型的指标"""
rule_type: ViolationType = Field(..., description="规则类型")
total_reviews: int = Field(..., ge=0, description="总审核数")
violation_count: int = Field(..., ge=0, description="违规数")
violation_rate: float = Field(..., ge=0, le=1, description="违规率(0-1)")
class ConsistencyMetricsResponse(BaseModel):
"""一致性指标响应"""
influencer_id: str = Field(..., description="达人 ID")
window: ConsistencyWindow = Field(..., description="计算周期")
period_start: datetime = Field(..., description="周期起始时间")
period_end: datetime = Field(..., description="周期结束时间")
metrics: list[RuleConsistencyMetric] = Field(default_factory=list)
# ==================== 特例审批(风控豁免) ====================
class RiskTargetType(str, Enum):
"""特例目标类型"""
INFLUENCER = "influencer"
ORDER = "order"
CONTENT = "content"
class RiskExceptionStatus(str, Enum):
"""特例审批状态"""
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"
EXPIRED = "expired"
REVOKED = "revoked"
class RiskExceptionCreateRequest(BaseModel):
"""创建特例请求"""
applicant_id: str = Field(..., description="申请人")
target_type: RiskTargetType = Field(..., description="目标类型")
target_id: str = Field(..., description="目标 ID")
risk_rule_id: str = Field(..., description="豁免规则 ID")
reason_category: str = Field(..., description="原因分类")
justification: str = Field(..., min_length=1, description="详细理由")
attachment_url: Optional[str] = Field(None, description="附件链接")
current_approver_id: str = Field(..., description="当前审批人")
valid_start_time: datetime = Field(..., description="生效开始时间")
valid_end_time: datetime = Field(..., description="生效结束时间")
class RiskExceptionRecord(BaseModel):
"""特例记录"""
record_id: str = Field(..., description="记录 ID")
applicant_id: str = Field(..., description="申请人")
apply_time: datetime = Field(..., description="申请时间")
target_type: RiskTargetType = Field(..., description="目标类型")
target_id: str = Field(..., description="目标 ID")
risk_rule_id: str = Field(..., description="豁免规则 ID")
status: RiskExceptionStatus = Field(..., description="状态")
valid_start_time: datetime = Field(..., description="生效开始时间")
valid_end_time: datetime = Field(..., description="生效结束时间")
reason_category: str = Field(..., description="原因分类")
justification: str = Field(..., description="详细理由")
attachment_url: Optional[str] = Field(None, description="附件链接")
current_approver_id: Optional[str] = Field(None, description="当前审批人")
approval_chain_log: list[dict] = Field(default_factory=list, description="审批流转日志")
auto_rejected: bool = Field(default=False, description="是否超时自动拒绝")
rejection_reason: Optional[str] = Field(None, description="驳回原因")
last_status_at: Optional[datetime] = Field(None, description="最近状态变更时间")
class RiskExceptionDecisionRequest(BaseModel):
"""特例审批决策请求"""
approver_id: str = Field(..., description="审批人")
comment: Optional[str] = Field(None, description="审批备注")
# ==================== 审核任务 ====================
class TaskCreateRequest(BaseModel):
"""创建任务请求"""
platform: Platform = Field(..., description="投放平台")
creator_id: str = Field(..., description="达人 ID")
video_url: Optional[HttpUrl] = Field(None, description="视频 URL")
script_content: Optional[str] = Field(None, min_length=1, description="脚本内容")
script_file_url: Optional[HttpUrl] = Field(None, description="脚本文档 URL")
class TaskScriptUploadRequest(BaseModel):
"""上传脚本请求"""
script_content: Optional[str] = Field(None, min_length=1, description="脚本内容")
script_file_url: Optional[HttpUrl] = Field(None, description="脚本文档 URL")
class TaskVideoUploadRequest(BaseModel):
"""上传视频请求"""
video_url: HttpUrl = Field(..., description="视频 URL")
class TaskResponse(BaseModel):
"""任务响应"""
task_id: str = Field(..., description="任务 ID")
video_url: Optional[str] = Field(None, description="视频 URL")
script_content: Optional[str] = Field(None, description="脚本内容")
script_file_url: Optional[str] = Field(None, description="脚本文档 URL")
has_script: bool = Field(..., description="是否已上传脚本")
has_video: bool = Field(..., description="是否已上传视频")
platform: Platform = Field(..., description="投放平台")
creator_id: str = Field(..., description="达人 ID")
status: TaskStatus = Field(..., description="任务状态")
created_at: str = Field(..., description="创建时间")
class TaskListResponse(BaseModel):
"""任务列表响应"""
items: list[TaskResponse] = Field(default_factory=list)
total: int = Field(..., description="总数")
page: int = Field(..., description="当前页")
page_size: int = Field(..., description="每页数量")
class TaskApproveRequest(BaseModel):
"""通过任务请求"""
comment: Optional[str] = Field(None, description="备注")
class TaskRejectRequest(BaseModel):
"""驳回任务请求"""
reason: str = Field(..., min_length=1, description="驳回原因")
violations: list[str] = Field(default_factory=list, description="违规类型列表")