feat: 规则冲突检测增强 — 后端接入 DB 规则 + 前端集成检查按钮

后端 validate_rules 端点改为 async,合并 DB active 平台规则与硬编码兜底规则,
新增 selling_points 字段支持和时长冲突检测。前端品牌方/代理商 Brief 页面
添加"检查规则冲突"按钮,支持选择平台后展示冲突详情弹窗。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-02-10 14:12:49 +08:00
co-authored by Claude Opus 4.6
parent c17c64cd11
commit 2f24dcfd34
4 changed files with 580 additions and 37 deletions
+53 -16
View File
@@ -455,30 +455,67 @@ async def get_platform_rules(platform: str) -> PlatformRuleResponse:
# ==================== 规则冲突检测 ====================
@router.post("/validate", response_model=RuleValidateResponse)
async def validate_rules(request: RuleValidateRequest) -> RuleValidateResponse:
"""检测 Brief 与平台规则冲突"""
async def validate_rules(
request: RuleValidateRequest,
x_tenant_id: str = Header(..., alias="X-Tenant-ID"),
db: AsyncSession = Depends(get_db),
) -> RuleValidateResponse:
"""检测 Brief 与平台规则冲突(合并 DB 规则 + 硬编码兜底)"""
conflicts = []
platform_rule = _platform_rules.get(request.platform)
if not platform_rule:
return RuleValidateResponse(conflicts=[])
# 1. 收集违禁词:DB active 规则优先,硬编码兜底
db_rules = await get_active_platform_rules(
x_tenant_id, request.brand_id, request.platform, db
)
forbidden_words: set[str] = set()
min_seconds: Optional[int] = None
max_seconds: Optional[int] = None
# 检查 required_phrases 是否包含违禁词
required_phrases = request.brief_rules.get("required_phrases", [])
platform_forbidden = []
for rule in platform_rule.get("rules", []):
if db_rules:
forbidden_words.update(db_rules.get("forbidden_words", []))
duration = db_rules.get("duration") or {}
min_seconds = duration.get("min_seconds")
max_seconds = duration.get("max_seconds")
# 硬编码兜底
hardcoded = _platform_rules.get(request.platform, {})
for rule in hardcoded.get("rules", []):
if rule.get("type") == "forbidden_word":
platform_forbidden.extend(rule.get("words", []))
forbidden_words.update(rule.get("words", []))
elif rule.get("type") == "duration" and min_seconds is None:
if rule.get("min_seconds") is not None:
min_seconds = rule["min_seconds"]
if rule.get("max_seconds") is not None and max_seconds is None:
max_seconds = rule["max_seconds"]
for phrase in required_phrases:
for word in platform_forbidden:
if word in phrase:
# 2. 检查卖点/必选短语与违禁词冲突
phrases = list(request.brief_rules.get("required_phrases", []))
phrases += list(request.brief_rules.get("selling_points", []))
for phrase in phrases:
for word in forbidden_words:
if word in str(phrase):
conflicts.append(RuleConflict(
brief_rule=f"要求使用{phrase}",
platform_rule=f"平台禁止{word}",
suggestion=f"Brief 要求的 '{phrase}' 包含平台违禁词 '{word}',建议修改",
brief_rule=f"卖点包含{phrase}",
platform_rule=f"{request.platform} 禁止使用{word}",
suggestion=f"卖点 '{phrase}' 包含违禁词 '{word}',建议修改表述",
))
# 3. 检查时长冲突
brief_min = request.brief_rules.get("min_duration")
brief_max = request.brief_rules.get("max_duration")
if min_seconds and brief_max and brief_max < min_seconds:
conflicts.append(RuleConflict(
brief_rule=f"Brief 最长时长:{brief_max}",
platform_rule=f"{request.platform} 最短要求:{min_seconds}",
suggestion=f"Brief 最长 {brief_max}s 低于平台最短要求 {min_seconds}s,视频可能不达标",
))
if max_seconds and brief_min and brief_min > max_seconds:
conflicts.append(RuleConflict(
brief_rule=f"Brief 最短时长:{brief_min}",
platform_rule=f"{request.platform} 最长限制:{max_seconds}",
suggestion=f"Brief 最短 {brief_min}s 超过平台最长限制 {max_seconds}s,建议调整",
))
return RuleValidateResponse(conflicts=conflicts)