feat: 平台规则从硬编码改为品牌方上传文档 + AI 解析

- 新增 PlatformRule 模型 (draft/active/inactive 状态流转)
- 新增文档解析服务 (PDF/Word/Excel → 纯文本)
- 新增 4 个 API: 解析/确认/查询/删除平台规则
- 脚本审核优先从 DB 读取 active 规则,硬编码兜底
- 视频审核合并平台规则违禁词到检测列表
- Alembic 迁移 006: platform_rules 表

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-02-10 13:23:11 +08:00
co-authored by Claude Opus 4.6
parent a2f6f82e15
commit fed361b9b3
10 changed files with 790 additions and 34 deletions
+3 -1
View File
@@ -10,7 +10,7 @@ from app.models.task import Task, TaskStage, TaskStatus
from app.models.brief import Brief
from app.models.ai_config import AIConfig
from app.models.review import ReviewTask, Platform
from app.models.rule import ForbiddenWord, WhitelistItem, Competitor
from app.models.rule import ForbiddenWord, WhitelistItem, Competitor, PlatformRule, RuleStatus
from app.models.audit_log import AuditLog
from app.models.message import Message
# 保留 Tenant 兼容旧代码,但新代码应使用 Brand
@@ -44,6 +44,8 @@ __all__ = [
"ForbiddenWord",
"WhitelistItem",
"Competitor",
"PlatformRule",
"RuleStatus",
# 审计日志
"AuditLog",
# 消息
+42 -1
View File
@@ -1,7 +1,8 @@
"""
规则模型
违禁词、白名单、竞品
违禁词、白名单、竞品、平台规则
"""
import enum
from typing import TYPE_CHECKING, Optional
from sqlalchemy import String, Text, ForeignKey
from app.models.types import JSONType
@@ -13,6 +14,13 @@ if TYPE_CHECKING:
from app.models.tenant import Tenant
class RuleStatus(str, enum.Enum):
"""平台规则状态"""
DRAFT = "draft" # AI 解析完成,待确认
ACTIVE = "active" # 品牌方已确认,生效中
INACTIVE = "inactive" # 已停用
class ForbiddenWord(Base, TimestampMixin):
"""违禁词表"""
__tablename__ = "forbidden_words"
@@ -83,3 +91,36 @@ class Competitor(Base, TimestampMixin):
def __repr__(self) -> str:
return f"<Competitor(name={self.name}, brand_id={self.brand_id})>"
class PlatformRule(Base, TimestampMixin):
"""平台规则表 — 品牌方上传文档 + AI 解析"""
__tablename__ = "platform_rules"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
tenant_id: Mapped[str] = mapped_column(
String(64),
ForeignKey("tenants.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
brand_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
platform: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
# 文档信息
document_url: Mapped[str] = mapped_column(String(2048), nullable=False)
document_name: Mapped[str] = mapped_column(String(512), nullable=False)
# AI 解析结果(JSON
parsed_rules: Mapped[Optional[dict]] = mapped_column(JSONType, nullable=True)
# 状态
status: Mapped[str] = mapped_column(
String(20), nullable=False, default=RuleStatus.DRAFT.value, index=True,
)
# 关联
tenant: Mapped["Tenant"] = relationship("Tenant", back_populates="platform_rules")
def __repr__(self) -> str:
return f"<PlatformRule(id={self.id}, platform={self.platform}, status={self.status})>"
+7 -1
View File
@@ -10,7 +10,7 @@ from app.models.base import Base, TimestampMixin
if TYPE_CHECKING:
from app.models.ai_config import AIConfig
from app.models.review import ReviewTask
from app.models.rule import ForbiddenWord, WhitelistItem, Competitor
from app.models.rule import ForbiddenWord, WhitelistItem, Competitor, PlatformRule
class Tenant(Base, TimestampMixin):
@@ -48,5 +48,11 @@ class Tenant(Base, TimestampMixin):
back_populates="tenant",
lazy="selectin",
)
platform_rules: Mapped[list["PlatformRule"]] = relationship(
"PlatformRule",
back_populates="tenant",
lazy="selectin",
)
def __repr__(self) -> str:
return f"<Tenant(id={self.id}, name={self.name})>"