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:
co-authored by
Claude Opus 4.6
parent
a2f6f82e15
commit
fed361b9b3
@@ -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",
|
||||
# 消息
|
||||
|
||||
@@ -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})>"
|
||||
|
||||
@@ -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})>"
|
||||
|
||||
Reference in New Issue
Block a user