compliance_scan.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 美业护肤科普口播 · 敏感词 / 合规机器初筛
  5. 整合自 content-reviewer 与 medical-content-compliance-auditor 两技能,按美业场景裁剪。
  6. 输出 markdown 审查报告(风险等级 + 法规依据 + 合规替换 + 结论三选一)。
  7. 用法:
  8. python compliance_scan.py --text "口播全文"
  9. python compliance_scan.py --file path/to/script.md
  10. python compliance_scan.py --file path/to/script.md --output report.md
  11. """
  12. import argparse
  13. import sys
  14. from pathlib import Path
  15. # 词库: (词, 类别, 风险符号, 法规依据, 合规替换)
  16. WORD_LIST = [
  17. # 绝对化用语
  18. ("最好", "绝对化用语", "🟠", "《广告法》第9条", "适合 / 值得考虑"),
  19. ("最佳", "绝对化用语", "🟠", "《广告法》第9条", "合适"),
  20. ("第一", "绝对化用语", "🟠", "《广告法》第9条", "代表性 / 较早"),
  21. ("顶级", "绝对化用语", "🟠", "《广告法》第9条", "优质"),
  22. ("唯一", "绝对化用语", "🟠", "《广告法》第9条", "特色"),
  23. ("国家级", "绝对化用语", "🟠", "《广告法》第9条", "(删除)"),
  24. ("最安全", "绝对化用语", "🟠", "《广告法》第9条", "较安全"),
  25. ("最有效", "绝对化用语", "🟠", "《广告法》第9条", "较有效"),
  26. ("全球领先", "绝对化用语", "🟠", "《广告法》第9条", "行业先进"),
  27. # 治愈承诺
  28. ("根治", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "改善"),
  29. ("治愈", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "改善"),
  30. ("根除", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "减少"),
  31. ("彻底消除", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "明显缓解"),
  32. ("永不复发", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "减少反复"),
  33. ("包治", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "辅助改善"),
  34. ("治好", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "改善"),
  35. # 保证性承诺
  36. ("保证", "保证性承诺", "🟠", "《广告法》第9、25条", "有助于"),
  37. ("承诺", "保证性承诺", "🟠", "《广告法》第9、25条", "预期(疗效类承诺删除)"),
  38. ("100%", "保证性承诺", "🟠", "《广告法》第9、25条", "多数情况下"),
  39. ("无效退款", "保证性承诺", "🟠", "《广告法》第25条", "(删除)"),
  40. ("签约治疗", "保证性承诺", "🟠", "《广告法》第25条", "(删除)"),
  41. # 功效夸大·即时
  42. ("一夜回春", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "逐步改善"),
  43. ("秒变", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "逐渐"),
  44. ("瞬间", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "逐渐"),
  45. ("奇效", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "效果"),
  46. ("神效", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "效果"),
  47. ("立竿见影", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "循序渐进、需要坚持"),
  48. ("立马见效", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "逐渐见效"),
  49. # 医疗功效越界(护肤品禁宣称)
  50. ("治疗", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "护理 / 改善"),
  51. ("药用", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "(删除)"),
  52. ("消炎", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "舒缓"),
  53. ("杀菌", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "洁净"),
  54. ("抗炎", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "舒缓"),
  55. ("抗菌", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "清洁"),
  56. ("医用", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "(删除)"),
  57. ("药效", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "(删除)"),
  58. # 患者证言·对比
  59. ("术前术后", "患者证言·对比", "🔴", "《医疗广告管理办法》", "(删除,改用示意图)"),
  60. ("对比图", "患者证言·对比", "🔴", "《医疗广告管理办法》", "(删除,改用示意图)"),
  61. ("疗效见证", "患者证言·对比", "🔴", "《医疗广告管理办法》", "(删除)"),
  62. ("患者案例", "患者证言·对比", "🔴", "《医疗广告管理办法》", "(删除 / 脱敏)"),
  63. # 平台诱导风险
  64. ("加微信", "平台诱导风险", "🟡", "抖音/小红书审核规则", "进店了解"),
  65. ("私信我", "平台诱导风险", "🟡", "抖音/小红书审核规则", "(删除)"),
  66. ("点击下方链接", "平台诱导风险", "🟡", "抖音/小红书审核规则", "(删除)"),
  67. ("限时优惠", "平台诱导风险", "🟡", "抖音/小红书审核规则", "活动期间优惠"),
  68. ("最低价", "平台诱导风险", "🟡", "抖音/小红书审核规则", "优惠价"),
  69. ("免费领", "平台诱导风险", "🟡", "抖音/小红书审核规则", "(删除)"),
  70. ("扫码", "平台诱导风险", "🟡", "抖音/小红书审核规则", "(删除)"),
  71. ]
  72. RISK_RANK = {"🔴": 4, "🟠": 3, "🟡": 2, "🟢": 1}
  73. CONCLUSION = {
  74. 4: "不予通过(存在极高违规,必须删除/替换后重检)",
  75. 3: "修改后通过(高)—— 须替换 🟠 项方可进入生成",
  76. 2: "修改后通过(中)—— 建议替换 🟡 项",
  77. 1: "修改后通过(低)—— 建议酌情优化",
  78. 0: "通过(未检出违禁词)",
  79. }
  80. def scan(text: str):
  81. hits = []
  82. seen = set()
  83. for word, cat, risk, law, repl in WORD_LIST:
  84. if word in text and word not in seen:
  85. seen.add(word)
  86. hits.append({
  87. "word": word, "cat": cat, "risk": risk,
  88. "law": law, "repl": repl,
  89. "count": text.count(word),
  90. })
  91. hits.sort(key=lambda h: -RISK_RANK[h["risk"]])
  92. return hits
  93. def build_report(text: str, hits: list) -> str:
  94. lines = []
  95. lines.append("## 敏感词 / 合规检测报告\n")
  96. lines.append("### 内容类型")
  97. lines.append("抖音短视频口播文案(美业护肤科普)\n")
  98. lines.append("### 检测条目")
  99. if not hits:
  100. lines.append("\n> ✅ 未检出高危违禁词。\n")
  101. else:
  102. lines.append("")
  103. lines.append("| 违规点 | 原表述 | 风险等级 | 法规依据 | 合规替换 |")
  104. lines.append("|--------|--------|----------|----------|----------|")
  105. for h in hits:
  106. cnt = f"(×{h['count']})" if h["count"] > 1 else ""
  107. lines.append(
  108. f"| {h['cat']} | 「{h['word']}」{cnt} | {h['risk']} {('极高' if h['risk']=='🔴' else '高' if h['risk']=='🟠' else '中' if h['risk']=='🟡' else '低')} | {h['law']} | {h['repl']} |"
  109. )
  110. lines.append("")
  111. max_rank = max((RISK_RANK[h["risk"]] for h in hits), default=0)
  112. lines.append("### 审查结论")
  113. lines.append(CONCLUSION[max_rank] + "\n")
  114. if hits:
  115. lines.append("### 建议")
  116. for i, h in enumerate(hits, 1):
  117. lines.append(f"{i}. 「{h['word']}」({h['cat']})→ 替换为「{h['repl']}」,规避对应风险。")
  118. lines.append(f"{len(hits)+1}. 修改后建议再跑一次本脚本确认无新增违禁词。")
  119. return "\n".join(lines)
  120. def main():
  121. ap = argparse.ArgumentParser(description="美业口播敏感词/合规机器初筛")
  122. g = ap.add_mutually_exclusive_group(required=True)
  123. g.add_argument("--text", help="直接传入口播文本")
  124. g.add_argument("--file", help="传入文本/脚本文件路径(.txt/.md)")
  125. ap.add_argument("--output", help="将报告写入该文件(可选)")
  126. args = ap.parse_args()
  127. if args.text:
  128. text = args.text
  129. else:
  130. p = Path(args.file)
  131. if not p.exists():
  132. print(f"[错误] 文件不存在: {args.file}", file=sys.stderr)
  133. sys.exit(2)
  134. text = p.read_text(encoding="utf-8")
  135. hits = scan(text)
  136. report = build_report(text, hits)
  137. print(report)
  138. if args.output:
  139. Path(args.output).write_text(report, encoding="utf-8")
  140. print(f"\n[已写入报告] {args.output}", file=sys.stderr)
  141. # 退出码:极高/高违规 → 非0,便于流程判断
  142. max_rank = max((RISK_RANK[h["risk"]] for h in hits), default=0)
  143. sys.exit(0 if max_rank <= 2 else 1)
  144. if __name__ == "__main__":
  145. main()