""" 违禁词三级处理(扒 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") ]