2026-07-31 15:20:37 +08:00
|
|
|
"""海报文案合规规则与字符定位。"""
|
|
|
|
|
import hashlib
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RULES = (
|
|
|
|
|
{
|
|
|
|
|
"ruleId": "RETURN_GUARANTEE",
|
|
|
|
|
"terms": ("保证", "稳赚", "保底"),
|
|
|
|
|
"severity": "block",
|
|
|
|
|
"message": "不得使用确定性收益承诺",
|
|
|
|
|
"suggestion": "改为“利益以正式计划书为准”",
|
2026-07-31 15:41:58 +08:00
|
|
|
"replacement": "相关利益",
|
2026-07-31 15:20:37 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"ruleId": "RISK_FREE_CLAIM",
|
|
|
|
|
"terms": ("无风险", "零风险"),
|
|
|
|
|
"severity": "block",
|
|
|
|
|
"message": "不得宣称保险或投资安排不存在风险",
|
|
|
|
|
"suggestion": "删除绝对化表述,并补充必要的风险提示",
|
2026-07-31 15:41:58 +08:00
|
|
|
"replacement": "存在一定风险",
|
2026-07-31 15:20:37 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"ruleId": "EXAGGERATED_RETURN",
|
|
|
|
|
"terms": ("最高收益", "回报率"),
|
|
|
|
|
"severity": "block",
|
|
|
|
|
"message": "收益表述必须有正式计划书依据且不得夸大",
|
|
|
|
|
"suggestion": "引用已确认的计划书数据并注明非保证利益",
|
2026-07-31 15:41:58 +08:00
|
|
|
"replacement": "演示利益",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"ruleId": "UNSUPPORTED_SUPERLATIVE",
|
|
|
|
|
"terms": ("行业领先", "最佳"),
|
|
|
|
|
"severity": "warn",
|
|
|
|
|
"message": "比较性或最高级表述需要可核验依据",
|
|
|
|
|
"suggestion": "改为客观描述产品特点,或补充权威依据",
|
|
|
|
|
"replacement": "具有特色",
|
2026-07-31 15:20:37 +08:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
FIELDS = ("headline", "body", "call_to_action")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def copy_revision(copy_content: dict) -> str:
|
|
|
|
|
"""生成与字段顺序无关的文案版本摘要。"""
|
|
|
|
|
normalized = {
|
|
|
|
|
field: str((copy_content or {}).get(field) or "")
|
|
|
|
|
for field in FIELDS
|
|
|
|
|
}
|
|
|
|
|
payload = json.dumps(normalized, ensure_ascii=False, sort_keys=True)
|
|
|
|
|
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_copy_compliance(copy_content: dict) -> dict:
|
|
|
|
|
"""返回合规状态、文案版本和可定位的问题列表。"""
|
|
|
|
|
issues = []
|
|
|
|
|
for field in FIELDS:
|
|
|
|
|
text = str((copy_content or {}).get(field) or "")
|
|
|
|
|
for rule in RULES:
|
|
|
|
|
for term in rule["terms"]:
|
|
|
|
|
start = 0
|
|
|
|
|
while True:
|
|
|
|
|
index = text.find(term, start)
|
|
|
|
|
if index < 0:
|
|
|
|
|
break
|
|
|
|
|
issues.append({
|
|
|
|
|
"ruleId": rule["ruleId"],
|
|
|
|
|
"severity": rule["severity"],
|
|
|
|
|
"field": field,
|
|
|
|
|
"start": index,
|
|
|
|
|
"end": index + len(term),
|
|
|
|
|
"text": term,
|
|
|
|
|
"message": rule["message"],
|
|
|
|
|
"suggestion": rule["suggestion"],
|
2026-07-31 15:41:58 +08:00
|
|
|
"replacement": rule["replacement"],
|
2026-07-31 15:20:37 +08:00
|
|
|
})
|
|
|
|
|
start = index + len(term)
|
|
|
|
|
|
|
|
|
|
severity_order = {"pass": 0, "warn": 1, "block": 2}
|
|
|
|
|
status = "pass"
|
|
|
|
|
for issue in issues:
|
|
|
|
|
if severity_order[issue["severity"]] > severity_order[status]:
|
|
|
|
|
status = issue["severity"]
|
|
|
|
|
return {
|
|
|
|
|
"status": status,
|
|
|
|
|
"revision": copy_revision(copy_content),
|
|
|
|
|
"issues": issues,
|
|
|
|
|
}
|