baseline: Clover 独立仓库首次基线提交

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-16 11:30:22 +08:00
commit 6a2632da70
253 changed files with 27467 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
"""
违禁词三级处理(扒 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")
]