Files
beige/backend/app/services/ai_engine/banned_word_checker.py
yangqianqian 6a2632da70 baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:30:22 +08:00

126 lines
5.2 KiB
Python
Raw Permalink 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.
"""
违禁词三级处理(扒 copy.js sanitizePlanningText 扩展为三级)
🟢 auto_fix = 自动改写replacement 字段给出替换词)
🟡 soft_warn = 软提示(返回建议词,不阻塞)
🔴 hard_block= 硬拦截(直接返回 None拦住发布
词库来自数据库 banned_words 表level + replacement 字段),
DB 未配时用本模块内置默认词库作冷启动。
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from typing import Literal
BannedLevel = Literal["auto_fix", "soft_warn", "hard_block"]
@dataclass
class BannedWordEntry:
word: str
level: BannedLevel
replacement: str | None = None # auto_fix 时提供替换词
# ── 默认词库(北哥回填解读与落点 §4.3,数据库未配时使用)─
DEFAULT_BANNED_WORDS: list[BannedWordEntry] = [
# 功效违禁auto_fix改写成合规表达对应北哥"提亮肤色感/改善暗沉观感"
BannedWordEntry("美白", "auto_fix", "提亮肤色感"),
BannedWordEntry("祛斑", "auto_fix", "改善暗沉观感"),
# 功效违禁hard_block无法合规改写直接拦截
BannedWordEntry("速效", "hard_block"),
BannedWordEntry("医用", "hard_block"),
BannedWordEntry("药妆", "hard_block"),
BannedWordEntry("强效焕白", "hard_block"),
# 保证性词soft_warn
BannedWordEntry("绝对", "soft_warn"),
BannedWordEntry("第一名", "soft_warn"),
BannedWordEntry("再也不", "soft_warn"),
# 夸张词soft_warn
BannedWordEntry("杀疯了", "soft_warn"),
BannedWordEntry("秒杀", "soft_warn"),
BannedWordEntry("震撼", "soft_warn"),
# AI 味词auto_fix置换为口语表达同时在 _NEGATIVE_WORDS prompt负向约束里已禁止AI写进正文
BannedWordEntry("神器", "auto_fix", "好用的"),
BannedWordEntry("福音", "auto_fix", "适合的"),
BannedWordEntry("救急单品", "auto_fix", "随手备用的"),
BannedWordEntry("遮羞布", "auto_fix", "底妆感"), # 北哥原文补录
BannedWordEntry("不仅而且", "auto_fix", ",另外"),
BannedWordEntry("焕发", "auto_fix", "呈现"),
BannedWordEntry("守护", "auto_fix", ""),
BannedWordEntry("尽享", "auto_fix", "使用"),
BannedWordEntry("日常维稳", "auto_fix", "日常保养"),
BannedWordEntry("精简底妆", "auto_fix", "轻便底妆"),
# 视觉违禁hard_block文案含这些词不许过
BannedWordEntry("前后对比", "hard_block"),
BannedWordEntry("使用前后", "hard_block"),
BannedWordEntry("变白", "auto_fix", "自然光泽感"),
BannedWordEntry("瑕疵消失", "auto_fix", "妆感更服帖"),
]
@dataclass
class CheckResult:
text: str # 原文soft_warn/hard_block 场景下保持原文)
fixed_text: str | None # auto_fix 后的文本;其他级别为 None
status: Literal["pass", "auto_fixed", "soft_warn", "hard_block"]
found: list[dict] = field(default_factory=list)
# found 每项: {"word": str, "level": BannedLevel, "replacement": str|None}
def check_and_fix(
text: str,
entries: list[BannedWordEntry] | None = None,
) -> CheckResult:
"""
对一段文本做三级违禁词扫描。
entries优先用 DB 词条,为 None 时用默认词库。
"""
word_list = entries if entries is not None else DEFAULT_BANNED_WORDS
found: list[dict] = []
working = text
# 先扫描所有命中
for entry in word_list:
if entry.word.lower() in working.lower():
found.append({
"word": entry.word,
"level": entry.level,
"replacement": entry.replacement,
})
if not found:
return CheckResult(text=text, fixed_text=None, status="pass", found=[])
# 有 hard_block → 直接拦截
if any(f["level"] == "hard_block" for f in found):
return CheckResult(text=text, fixed_text=None, status="hard_block", found=found)
# 只有 soft_warn → 软提示,不改文字
if any(f["level"] == "soft_warn" for f in found) and \
all(f["level"] in ("soft_warn", "auto_fix") for f in found):
# 仍执行 auto_fix 改写,但结果状态是 soft_warn优先级高
for f in found:
if f["level"] == "auto_fix" and f["replacement"] is not None:
working = re.sub(re.escape(f["word"]), f["replacement"], working, flags=re.IGNORECASE)
return CheckResult(text=text, fixed_text=working, status="soft_warn", found=found)
# 只有 auto_fix → 自动改写,返回 fixed_text
for f in found:
if f["level"] == "auto_fix" and f["replacement"] is not None:
working = re.sub(re.escape(f["word"]), f["replacement"], working, flags=re.IGNORECASE)
return CheckResult(text=text, fixed_text=working, status="auto_fixed", found=found)
def build_entries_from_db(rows: list[dict]) -> list[BannedWordEntry]:
"""把 DB banned_words 行转成 BannedWordEntry 列表"""
return [
BannedWordEntry(
word=r["word"],
level=r["level"],
replacement=r.get("replacement"),
)
for r in rows
if r.get("word") and r.get("level") in ("auto_fix", "soft_warn", "hard_block")
]