后端: - 审核结果拆分为 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>
68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
"""
|
||
Brief 模型
|
||
"""
|
||
from typing import TYPE_CHECKING, Optional
|
||
from sqlalchemy import String, Text, ForeignKey
|
||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||
|
||
from app.models.base import Base, TimestampMixin
|
||
from app.models.types import JSONType
|
||
|
||
if TYPE_CHECKING:
|
||
from app.models.project import Project
|
||
|
||
|
||
class Brief(Base, TimestampMixin):
|
||
"""Brief 文档表"""
|
||
__tablename__ = "briefs"
|
||
|
||
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||
project_id: Mapped[str] = mapped_column(
|
||
String(64),
|
||
ForeignKey("projects.id", ondelete="CASCADE"),
|
||
unique=True,
|
||
nullable=False,
|
||
index=True,
|
||
)
|
||
|
||
# 原始文件
|
||
file_url: Mapped[Optional[str]] = mapped_column(String(2048), nullable=True)
|
||
file_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||
|
||
# 解析后的结构化内容
|
||
# 卖点要求: [{"content": "SPF50+", "priority": "core"}, ...]
|
||
selling_points: Mapped[Optional[list]] = mapped_column(JSONType, nullable=True)
|
||
|
||
# 代理商要求至少体现的卖点条数(0 或 None 表示不限制)
|
||
min_selling_points: Mapped[Optional[int]] = mapped_column(nullable=True)
|
||
|
||
# 违禁词: [{"word": "最好", "reason": "绝对化用语"}, ...]
|
||
blacklist_words: Mapped[Optional[list]] = mapped_column(JSONType, nullable=True)
|
||
|
||
# 竞品: ["竞品A", "竞品B", ...]
|
||
competitors: Mapped[Optional[list]] = mapped_column(JSONType, nullable=True)
|
||
|
||
# 品牌调性要求
|
||
brand_tone: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||
|
||
# 时长要求(秒)
|
||
min_duration: Mapped[Optional[int]] = mapped_column(nullable=True)
|
||
max_duration: Mapped[Optional[int]] = mapped_column(nullable=True)
|
||
|
||
# 其他要求(自由文本)
|
||
other_requirements: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||
|
||
# 附件文档(品牌方上传的参考资料)
|
||
# [{"id": "af1", "name": "达人拍摄指南.pdf", "url": "...", "size": "1.5MB"}, ...]
|
||
attachments: Mapped[Optional[list]] = mapped_column(JSONType, nullable=True)
|
||
|
||
# 代理商附件(代理商上传的补充资料,与品牌方 attachments 分开存储)
|
||
# [{"id": "af1", "name": "达人拍摄指南.pdf", "url": "...", "size": "1.5MB"}, ...]
|
||
agency_attachments: Mapped[Optional[list]] = mapped_column(JSONType, nullable=True)
|
||
|
||
# 关联
|
||
project: Mapped["Project"] = relationship("Project", back_populates="brief")
|
||
|
||
def __repr__(self) -> str:
|
||
return f"<Brief(id={self.id}, project_id={self.project_id})>"
|