|
|
@@ -0,0 +1,1621 @@
|
|
|
+import json
|
|
|
+import re
|
|
|
+import zipfile
|
|
|
+import xml.etree.ElementTree as ET
|
|
|
+from collections import Counter, defaultdict
|
|
|
+from datetime import datetime
|
|
|
+from pathlib import Path
|
|
|
+
|
|
|
+
|
|
|
+BASE = Path("outputs/centrum_douyin_audit_2026-06-08")
|
|
|
+LIVE_DIR = BASE / "live-full"
|
|
|
+TRANSCRIPT_DIR = BASE / "transcript-full"
|
|
|
+LIVE_DATA = LIVE_DIR / "live-analysis-data.json"
|
|
|
+RUN_LOG = TRANSCRIPT_DIR / "transcription-run-log.json"
|
|
|
+RULE_PACK_DOC = Path(r"E:\新建文件夹\WXWork\1688855615758934\Cache\File\2026-06\QW1_Rule_Pack_ExamplesV0.1.docx")
|
|
|
+ACCOUNT_LIST_XLSX = Path(r"E:\新建文件夹\WXWork\1688855615758934\Cache\File\2026-06\店铺与账号.xlsx")
|
|
|
+SUPPLEMENTAL_STRATEGY_MD = Path(r"E:\新建文件夹\WXWork\1688855615758934\Cache\File\2026-06\抖音保健品过审黄金公式与边界策略.md")
|
|
|
+RUNNER_SUPPLEMENT_JSON = BASE / "runner-supplement-check" / "daily-report.json"
|
|
|
+RUNNER_SUPPLEMENT_RAW = BASE / "runner-supplement-check" / "runner-raw-input.json"
|
|
|
+PUBLIC_PROFILE_ALL_VIDEOS = BASE / "recollection-2026-06-09" / "attempt-10-public-profile-extract" / "public-profile-videos.json"
|
|
|
+PUBLIC_PROFILE_NEW_VIDEOS = BASE / "recollection-2026-06-09" / "attempt-10-public-profile-extract" / "public-profile-new-videos.json"
|
|
|
+OUT_JSON = TRANSCRIPT_DIR / "transcript-analysis-data.json"
|
|
|
+OUT_MD = TRANSCRIPT_DIR / "善存抖音逐字稿话术审核分析.md"
|
|
|
+
|
|
|
+
|
|
|
+def clean(value):
|
|
|
+ return re.sub(r"\s+", " ", str(value or "")).strip()
|
|
|
+
|
|
|
+
|
|
|
+def load_json(path):
|
|
|
+ return json.loads(path.read_text(encoding="utf-8"))
|
|
|
+
|
|
|
+
|
|
|
+def chinese_count(text):
|
|
|
+ return len(re.findall(r"[\u4e00-\u9fff]", text or ""))
|
|
|
+
|
|
|
+
|
|
|
+def snippet(text, length=120):
|
|
|
+ text = clean(text)
|
|
|
+ return text if len(text) <= length else text[: length - 1] + "…"
|
|
|
+
|
|
|
+
|
|
|
+NORMALIZE_REPLACEMENTS = [
|
|
|
+ ("阴阳包", "营养包"),
|
|
|
+ ("30家", "30+"),
|
|
|
+ ("40家", "40+"),
|
|
|
+ ("女40", "女40+"),
|
|
|
+ ("男40", "男40+"),
|
|
|
+ ("富维", "复维"),
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+def normalize_asr(text):
|
|
|
+ text = clean(text)
|
|
|
+ for old, new in NORMALIZE_REPLACEMENTS:
|
|
|
+ text = text.replace(old, new)
|
|
|
+ return text
|
|
|
+
|
|
|
+
|
|
|
+RULE_PATTERNS = [
|
|
|
+ ("RP01", "绝对化/一粒补全/闭眼入", r"闭眼入|就够了|一包全部|一粒.*(就够|补齐|补充到|满足)|全营养|全部.*营养|需要的.*都有|都补充"),
|
|
|
+ ("RP02", "疾病/治疗/特殊健康时期", r"甲流|感冒|治疗|治好|预防|医生|肿瘤|病"),
|
|
|
+ ("RP03", "未经确认功效/状态承诺", r"精力|脑力|大脑|气血|代谢|吃瘦|瘦|焕活|状态|活力|疲惫|亏空|底子|皮肤状态|气色|睡眠|抗炎"),
|
|
|
+ ("RP04", "数字成分推导功效", r"\d+\s*(种|倍|个月|粒|大|天|年)|26种|22种|15倍|40多年|120粒|240粒"),
|
|
|
+ ("RP05", "海外/跨境/版本边界", r"海外|美国|澳洲|跨境|进口|本土版|Costco|版本|国内"),
|
|
|
+ ("RP06", "正品/效期/包装信任", r"正品|喷码|批号|效期|包装|保障|大品牌|国际品牌|官方"),
|
|
|
+ ("RP07", "用法用量/特殊人群", r"每天|一粒|一包|孕妇|儿童|孩子|老人|长辈|爸妈|男士|女士|30\+|40\+|50\+|适合|怎么吃"),
|
|
|
+ ("RP11", "竞品比较/排名/红黑榜", r"排行榜|排名|只认|不服|红黑榜|智商税|最差|不要买|别乱买|不如|单一的维生素"),
|
|
|
+ ("RP13", "表达强度过高", r"满格|全开|必须|强烈推荐|真的绝|离不开|快冲|不行|太懂|稳稳当当|直接爱上"),
|
|
|
+ ("RP14", "证据/专业身份背书不足", r"营养师|医生|研究|实测|专业|干货|建议|国际大品牌|40多年"),
|
|
|
+ ("RP18", "促销/价格/活动", r"直播间|下单|活动|大促|买一送一|买1送1|拍一发|送|价格|降价|满赠|福利|红包|冲"),
|
|
|
+ ("RP19", "明星/达人/身份背书", r"秋瓷炫|王艳|百克力|李维嘉|颖儿|明星|达人|他们都在吃|在吃"),
|
|
|
+ ("RP21", "购买引导/CTA", r"快来|赶紧|下单|直播间|拍|冲|购买|领券|到手价|链接"),
|
|
|
+ ("RP22", "情绪/趋势钩子", r"焦虑|白月光|趋势|梗王|小妙招|别乱吃|不花冤枉钱|热点|精致女人"),
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+PUBLIC_TITLE_RISK_PATTERNS = [
|
|
|
+ ("RP01", "一粒/一瓶解决感", r"一瓶顶多瓶|每天一粒|每日1粒|每日一片|从\s*1\s*粒开始|一粒就够|每天一片|就够了"),
|
|
|
+ ("RP03", "状态/功效感标题", r"精力|状态|活力|疲惫|少疲惫|亚健康|调理|自救|熬夜|脆皮|补足|营养缺口|内在保养|状态密码|拉满活力"),
|
|
|
+ ("RP04", "数字成分推导", r"\d+\s*(种|粒|片|岁)|24种|18-50|一瓶|多瓶"),
|
|
|
+ ("RP05", "海外/跨境版本边界", r"海外|VCHOICE|跨境|进口|版本"),
|
|
|
+ ("RP06", "正品/渠道信任", r"正品|直发|官方|旗舰店|专卖店|店铺"),
|
|
|
+ ("RP07", "人群/用法用量", r"男士|男性|老公|男友|打工人|健身党|职场|中年|年轻人|每日|每天|随餐吃"),
|
|
|
+ ("RP13", "表达强度过高", r"超炸|拉满|硬扛|精准补|实力派|必入|必看|错过再等"),
|
|
|
+ ("RP18", "促销/价格/活动", r"618|福利|活动|打折|抢跑|羊毛|直播间|链接|赠|大促"),
|
|
|
+ ("RP21", "强购买 CTA", r"必看|必入|看过来|速来|抓紧|错过再等|薅羊毛"),
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+MODULE_PATTERNS = [
|
|
|
+ ("明星/达人背书", r"秋瓷炫|王艳|百克力|李维嘉|颖儿|他们都在吃|达人|明星"),
|
|
|
+ ("年龄/人群场景", r"30\+|40\+|50\+|女生|女性|男士|中老年|孕妇|儿童|长辈|爸妈|上班族|当妈"),
|
|
|
+ ("功效/状态表达", r"精力|脑力|大脑|气血|代谢|吃瘦|焕活|状态|活力|气色|疲惫|亏空|底子|抗炎"),
|
|
|
+ ("成分/剂型说明", r"维生素|矿物质|维矿|叶酸|烟酰胺|生物素|钙|铁|锌|硒|黄芪|大枣|儿茶素|胶原蛋白|谷胱甘肽|颗|粒|包"),
|
|
|
+ ("一包/一粒解决感", r"一包|一粒|就够|都有|全部|补齐|满足|不费心|不用搭配|瓶瓶罐罐"),
|
|
|
+ ("版本/渠道信任", r"海外|跨境|美国|澳洲|进口|本土版|官方|正品|版本|大品牌|国际"),
|
|
|
+ ("促销/购买引导", r"直播间|下单|活动|买|送|拍|价格|福利|满赠|快冲|领券|到手价"),
|
|
|
+ ("比较/榜单/避坑", r"排行榜|排名|只认|红黑榜|智商税|不要买|别乱买|不花冤枉钱|最差"),
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+REWRITE_TEMPLATES = {
|
|
|
+ "明星/达人背书": "达人分享仅代表个人体验;产品信息以包装、标签和官方说明为准。",
|
|
|
+ "年龄/人群场景": "不同年龄/人群适用产品不同,请按具体 SKU、包装标签和自身需求选择。",
|
|
|
+ "功效/状态表达": "改为日常营养支持、均衡饮食和规律作息场景,不承诺精力、气血、代谢、焕活等结果。",
|
|
|
+ "成分/剂型说明": "可保留成分数量和剂型信息,但避免推导为全部所需、明显改善或特定健康结果。",
|
|
|
+ "一包/一粒解决感": "改为“按标签建议作为日常营养补充”,避免“一包解决/一粒补全”。",
|
|
|
+ "版本/渠道信任": "不同版本包装、成分和适用说明可能不同,请以购买页、实物标签和客服说明为准。",
|
|
|
+ "促销/购买引导": "活动、价格、赠品和库存以当前页面为准,CTA 不与功效承诺绑定。",
|
|
|
+ "比较/榜单/避坑": "避免排名、红黑榜和竞品贬损,改为中性选购维度:成分、剂型、标签、预算、适用人群。",
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+FULL_RULE_NAMES = {
|
|
|
+ "RP01": "绝对化 / 保证性 / 最高级表达",
|
|
|
+ "RP02": "疾病治疗 / 治愈 / 诊断暗示",
|
|
|
+ "RP03": "未经确认功效 / 状态承诺",
|
|
|
+ "RP04": "效果数据 / 成分数字推导 / 前后对比",
|
|
|
+ "RP05": "产品法规属性 / 跨境版本边界",
|
|
|
+ "RP06": "标签一致性 / 用法用量 / 包装信息",
|
|
|
+ "RP07": "特殊人群 / 用法用量 / 适用范围",
|
|
|
+ "RP08": "政府/监管/官方背书暗示",
|
|
|
+ "RP09": "恐惧营销 / 歧视弱势人群 / 制造焦虑",
|
|
|
+ "RP10": "第三方素材 / 肖像 / IP 未授权",
|
|
|
+ "RP11": "竞品贬损 / 排名 / 红黑榜",
|
|
|
+ "RP12": "隐私泄露 / 订单与评论截图",
|
|
|
+ "RP13": "表达强度过高 / 结果确定性",
|
|
|
+ "RP14": "研究证据 / 专业身份背书不足",
|
|
|
+ "RP15": "孕妇儿童老人等特殊人群强推荐",
|
|
|
+ "RP16": "平台禁用词 / 谐音规避",
|
|
|
+ "RP17": "口播字幕画面不一致 / A 产品 B 卖点",
|
|
|
+ "RP18": "促销 / 价格 / 活动 / 库存表达",
|
|
|
+ "RP19": "明星达人专家身份背书",
|
|
|
+ "RP20": "低俗擦边 / 品牌安全 / 公序良俗",
|
|
|
+ "RP21": "购买压力 / 强 CTA / 情绪化催单",
|
|
|
+ "RP22": "公共事件 / 灾难疾病恐慌 / 极端钩子",
|
|
|
+ "RP23": "平台反馈原因不明 / 候选风险",
|
|
|
+ "RP24": "待确认监管新规 / 候选风险",
|
|
|
+ "RP25": "历史投诉高发模式 / 候选风险",
|
|
|
+ "RP26": "AI 合成肖像 / 声音 / 商业使用",
|
|
|
+ "RP27": "新兴平台风险 / DP 反馈待确认",
|
|
|
+ "RP28": "Out of Scope / 自动审批发布等非本轮范围",
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+LEGAL_REFERENCES = [
|
|
|
+ {
|
|
|
+ "name": "《中华人民共和国广告法》",
|
|
|
+ "url": "https://www.gov.cn/guoqing/2021-10/29/content_5647620.htm",
|
|
|
+ "use": "广告真实合法、绝对化用语、疾病治疗功能、保健食品广告限制、广告代言与审查底线。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "name": "《中华人民共和国食品安全法》",
|
|
|
+ "url": "https://www.nhc.gov.cn/fzs/c100048/201808/31b2202291464a5fb9e1f64bebc4d877.shtml",
|
|
|
+ "use": "保健食品标签说明书、适宜/不适宜人群、功效成分、广告审查批准和“不能代替药物”。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "name": "《药品、医疗器械、保健食品、特殊医学用途配方食品广告审查管理暂行办法》",
|
|
|
+ "url": "https://www.gov.cn/gongbao/content/2020/content_5488918.htm",
|
|
|
+ "use": "三品一械广告发布前审查、保健食品广告内容以注册证书/备案凭证和说明书为准、审查通过内容一致性。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "name": "市场监管总局 2026 年三品一械广告审查管理办法征求意见稿",
|
|
|
+ "url": "https://www.samr.gov.cn/hd/zjdc/art/2026/art_2a99498546434468b1fada4203fdc7ce.html",
|
|
|
+ "use": "提示三品一械广告审查口径仍在更新,正式定稿需以最新监管文件和法务确认版本为准。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "name": "《互联网广告管理办法》",
|
|
|
+ "url": "https://www.gov.cn/zhengce/202305/content_6858084.htm",
|
|
|
+ "use": "用于互联网广告可识别性、变相广告、依法需审查广告发布前审查和达人电商场景的边界判断。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "name": "GB 28050《预包装食品营养标签通则》(2011/2025 衔接口径)",
|
|
|
+ "url": "https://www.nhc.gov.cn/sps/c100087/202509/470fa4ff5de14dd38619223cce9da4e7.shtml",
|
|
|
+ "use": "营养标签、营养声称和营养成分功能/作用声称需匹配标准用语;2025 版实施前后需由法务确认适用版本。",
|
|
|
+ },
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+SPEECH_SYSTEM_ROWS = [
|
|
|
+ [
|
|
|
+ "功效描述",
|
|
|
+ "精力满格、气血变好、代谢提升、吃了变瘦、皮肤状态变好",
|
|
|
+ "作为日常营养补充;配合均衡饮食和规律作息;具体感受因人而异",
|
|
|
+ "《广告法》第十八条禁止保健食品广告作功效、安全性断言或保证;超出批文/备案范围的功能 claim 需截停",
|
|
|
+ "营养包样本里“状态/代谢/气血”多为边界表达,达人 brief 应弱化为生活场景。",
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ "营养成分功能声称",
|
|
|
+ "某成分促进/修复/激活身体状态,或把国标语改成更强同义词",
|
|
|
+ "仅在符合标签与标准条件时使用 GB 28050 对应标准用语原文",
|
|
|
+ "GB 28050 对营养声称、营养成分功能/作用声称有标准用语和条件要求;不得自行放大",
|
|
|
+ "“26 种/22 种”可作为标签事实,不能接成“一粒补全/全部所需”。",
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ "数字推导",
|
|
|
+ "一粒等于很多瓶、一次补齐、一天营养都够、含量翻倍吸收",
|
|
|
+ "每份/每粒/每包含有若干营养素;以商品页、包装标签和检测/备案资料为准",
|
|
|
+ "《广告法》第十一条要求广告中数据真实、准确并表明出处;数字不得推导不可证功效",
|
|
|
+ "多维 SKU 保留成分数量,但删除“满足全部/闭眼入/就够了”。",
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ "适用人群",
|
|
|
+ "大人小孩都能吃、所有女生都需要、爸妈必须囤、孕妇老人也适合",
|
|
|
+ "适用人群、用法用量以具体 SKU 标签、说明书和客服说明为准",
|
|
|
+ "保健食品广告内容不得超出注册证书/备案凭证、说明书和标签范围",
|
|
|
+ "30+/40+/50+ 是选购入口,不是全人群适用承诺。",
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ "疾病/特殊健康时期",
|
|
|
+ "甲流、感冒、肿瘤、术后、治疗、预防、改善症状",
|
|
|
+ "一般营养补充信息;特殊健康状况请咨询专业人士",
|
|
|
+ "《广告法》第十七条、第十八条禁止食品/保健食品涉及疾病预防、治疗功能",
|
|
|
+ "已发布疾病词只作为撞审边界记录,不进入可复制话术库。",
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ "专业/明星背书",
|
|
|
+ "医生推荐、营养师强推、明星都在吃所以适合你",
|
|
|
+ "达人/明星分享仅代表个人体验;产品信息以包装标签、商品页和官方说明为准",
|
|
|
+ "保健食品广告不得利用广告代言人作推荐、证明,医务人员身份风险更高",
|
|
|
+ "秋瓷炫、颖儿、李维嘉等样本可看传播结构,不直接复制背书逻辑。",
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ "促销 CTA",
|
|
|
+ "赶紧冲、手慢无、再不买就亏、下单解决状态问题",
|
|
|
+ "活动、赠品、价格和库存以当前页面为准;按需选择",
|
|
|
+ "互联网广告与平台虚假宣传规则要求价格、库存、优惠真实,不制造虚假紧迫感",
|
|
|
+ "官方店铺可承接活动规则,达人强 CTA 标为边界样本。",
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ "跨境/版本",
|
|
|
+ "海外版更有效、国内外通用功效、进口所以更适合",
|
|
|
+ "不同版本包装、成分、标签、售后和适用说明可能不同,请以商品页和实物标签为准",
|
|
|
+ "跨境商品不能自动套用国内蓝帽、保健食品批文或国内标签功能口径",
|
|
|
+ "善存海外/Costco/澳洲版样本重点做版本信任和客服承接。",
|
|
|
+ ],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+CORE_JUDGMENT_MATRIX_ROWS = [
|
|
|
+ ["备案/批文内保健功能", "默认红灯,资料齐全后可转绿", "可说,需蓝帽/备案/广审/提示语一致", "不可代言推荐", "产品批文、说明书、广告审查内容、商品页和口播一致", "需批文/广审核验后复制"],
|
|
|
+ ["营养成分功能声称", "黄灯", "可有限使用标准用语", "只能做中性科普,避免品牌推荐", "匹配 GB 28050 与产品标签;不得同义放大", "需标签/法务核验"],
|
|
|
+ ["成分/规格事实", "绿灯基础层", "可说", "可说", "数字、规格、剂型可在包装或商品页核验", "高可复制"],
|
|
|
+ ["个人食用体验", "黄灯灰区", "可说非功效体验", "严格限制", "仅限口感、包装、携带、便利性;不得涉及身体变化", "达人需弱化"],
|
|
|
+ ["状态/功效感", "红灯或高边界", "仅在批文/广审支持时可控表达", "不可直接说", "精力、气血、代谢、睡眠、皮肤等需逐条核验", "边界样本"],
|
|
|
+ ["疾病预防/治疗", "红灯截停", "不可说", "不可说", "含疾病、诊断、治疗、预防、症状改善即截停", "不可复制"],
|
|
|
+ ["促销/购买引导", "黄灯", "可说真实活动", "弱 CTA", "活动信息与页面一致,且不绑定功效承诺", "官方可复制,达人慎用"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+COMPLIANCE_PRINCIPLE_ROWS = [
|
|
|
+ {
|
|
|
+ "title": "原则1|强制提示语原则",
|
|
|
+ "content": "凡涉及保健食品广告/推广,应显著提示“保健食品不是药物,不能代替药物治疗疾病”。提示语不能抵消违法功效、疾病治疗或达人代言推荐风险。",
|
|
|
+ "check": "发布前核验标题、口播、字幕、画面、商品卡、落地页和评论区是否都没有把保健食品当药品表达。",
|
|
|
+ "evidence": "当前善存稿需客户补齐具体 SKU 的蓝帽/备案范围、广审批件与页面提示语位置。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "title": "原则2|标签一致原则",
|
|
|
+ "content": "所有成分数量、适用人群、用法用量、版本差异、营养声称和功能声称都必须回到具体 SKU 标签、说明书、商品页和批文。",
|
|
|
+ "check": "同一视频不得出现口播 A SKU、画面 B SKU、商品卡 C SKU、评论区 D 功效的混配。",
|
|
|
+ "evidence": "善存营养包、多维、海外版、50+、小佳维等不能互相套用人群和功能口径。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "title": "原则3|资质匹配原则",
|
|
|
+ "content": "同一句话在官方/店铺账号、达人账号、医生/营养师账号、分销/跨境账号下的审核上限不同;达人不能继承品牌官方的广告审查能力。",
|
|
|
+ "check": "先判断账号身份,再判断产品属性,再判断 claim 层级;没有账号与产品资质证据时,不把已发布样本写成绿灯。",
|
|
|
+ "evidence": "本轮用 `店铺与账号.xlsx` 匹配官方/店铺账号,未命中清单的店铺型账号仍标为待核验。",
|
|
|
+ },
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+ACCOUNT_CLAIM_STRATEGY_ROWS = [
|
|
|
+ ["官方/店铺账号", "Level 0-1 生活/成分事实", "直接可用", "用标签、商品页、客服和页面活动承接"],
|
|
|
+ ["官方/店铺账号", "Level 2 营养成分功能声称", "按标准用语谨慎使用", "需 GB 28050、标签和法务口径逐字校验"],
|
|
|
+ ["官方/店铺账号", "Level 3 备案内保健功能", "资料齐全后可用", "需蓝帽/备案/广审/提示语/页面一致"],
|
|
|
+ ["官方/店铺账号", "Level 4-5 备案外功能/疾病", "截停或改写", "改为日常营养支持、标签事实或专业咨询"],
|
|
|
+ ["达人/内容账号", "Level 0-0.5 生活/体验", "可用但弱化", "只说个人习惯、便利性、口感、场景,不说身体变化"],
|
|
|
+ ["达人/内容账号", "Level 1 成分事实", "可做中性选购", "不推荐具体功效,不强 CTA,不替代官方说明"],
|
|
|
+ ["达人/内容账号", "Level 2-3 功能声称", "高风险", "一般不建议说品牌功能;转为标签阅读或客服入口"],
|
|
|
+ ["医生/营养师账号", "任何品牌推荐", "高风险", "科普与产品推广分离,不用专业身份证明具体 SKU"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+PRODUCT_PERSON_STRATEGY_ROWS = [
|
|
|
+ ["善存营养包/PRO", "30+/忙碌/熬夜/身材管理人群", "省心搭配、一包便利、日常补充", "代谢、吃瘦、气血、皮肤变好", "弱化为生活场景和按标签补充"],
|
|
|
+ ["善存复合维生素/多维", "成人男女版", "成分数量、剂型、规格、每日习惯", "一粒补全、满足全部所需", "成分事实 + 标签回流"],
|
|
|
+ ["善存女性/男士/50+", "分龄分性别人群", "标签适用范围、版本差异", "所有女生/男士都适合、爸妈必须吃", "按具体 SKU 人群说明,不跨 SKU 套用"],
|
|
|
+ ["海外/跨境版本", "海淘/正品/版本关注人群", "包装、批号、效期、渠道、客服", "海外更有效、国内外通用功能", "版本事实 + 售后承接"],
|
|
|
+ ["小佳维/儿童相关", "儿童/家长", "儿童 SKU 标签和用量说明", "儿童都能吃、长高变聪明", "特殊人群只走标签与专业咨询"],
|
|
|
+ ["银善存/大脑类素材", "40+/50+/长辈", "画面隐喻、生活任务、标签信息", "改善记忆、提升脑力、治疗认知问题", "备案内才可直接说,其他转隐喻/场景"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+SELLING_POINT_REWRITE_ROWS = [
|
|
|
+ ["状态/精力/气血", "精力满格、气血满格、状态全开", "日常营养支持;配合规律作息;保持好状态的生活习惯之一", "《广告法》第十八条", "达人、官方都需慎用"],
|
|
|
+ ["代谢/身材", "代谢包、吃瘦、身材管理靠它", "作为日常营养补充选择之一,不替代饮食管理和运动", "不得暗示减肥/治疗/确定改善", "达人强风险"],
|
|
|
+ ["成分数量", "一粒补充 26 种,全部都够", "含多种维生素和矿物质,具体成分以包装标签为准", "广告数据真实准确;GB 28050 标签口径", "全账号可用,但不得推导"],
|
|
|
+ ["一包省心", "一包解决所有营养、懒人闭眼入", "独立小包/组合搭配,便于按标签建议补充", "绝对化/保证性表达风险", "营养包核心可复制卖点"],
|
|
|
+ ["明星/达人", "明星都在吃,你也快冲", "达人分享仅代表个人体验,产品信息以官方说明为准", "保健食品广告代言限制", "达人/明星样本需授权复核"],
|
|
|
+ ["跨境/正品", "海外版更好,进口更有效", "不同版本包装、成分和售后政策可能不同,以商品页和实物标签为准", "版本与资质不得混用", "跨境 SKU 必备"],
|
|
|
+ ["促销活动", "手慢无、再不买就没了", "当前活动以页面展示为准,按需选择", "虚假紧迫感/价格库存真实性", "官方可用,达人弱化"],
|
|
|
+ ["疾病词", "甲流/感冒/肿瘤/治疗/预防", "特殊健康状况请咨询专业人士;本内容仅作一般营养信息", "《广告法》第十七条、第十八条", "不可复制"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+AI_FORMULA_GROUPS = [
|
|
|
+ {
|
|
|
+ "title": "公式组A:话术改写类(可直接编码为规则引擎)",
|
|
|
+ "items": [
|
|
|
+ "A1 功效词替换:IF 文本含精力/气血/代谢/皮肤状态/睡眠改善 THEN 改为日常营养支持 + 个体差异 + 标签依据。",
|
|
|
+ "A2 疾病词截停:IF 文本含疾病名/治疗/预防/诊断/症状改善 THEN 标为红灯,不进入可复制话术。",
|
|
|
+ "A3 数字推导降级:IF 数字后接补全/翻倍/满足全部/相当于 THEN 保留标签数字,删除效果推导。",
|
|
|
+ "A4 人群泛化回流:IF 文本含全家/所有人/爸妈都适合/儿童孕妇 THEN 回到具体 SKU 标签和专业咨询。",
|
|
|
+ "A5 GB 28050 校验:IF 文本含营养成分功能/作用声称 THEN 校验是否与适用标准用语和标签条件一致。",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "title": "公式组B:账号差异化规则",
|
|
|
+ "items": [
|
|
|
+ "B1 官方/店铺账号 = 产品资质 + 广审范围 + 商品页一致 + 强制提示语;缺任一项则降为待核验。",
|
|
|
+ "B2 达人账号 = 个人体验 + 生活场景 + 成分事实 + 弱 CTA;不得承接官方备案功能或代言证明。",
|
|
|
+ "B3 医生/营养师账号 = 科普与具体 SKU 分离;不得用专业身份推荐善存某 SKU。",
|
|
|
+ "B4 跨境/分销账号 = 版本事实 + 渠道承接;不得套用国内蓝帽或官方旗舰店能力。",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "title": "公式组C:内容架构规则",
|
|
|
+ "items": [
|
|
|
+ "C1 稳妥脚本结构 = 生活场景 Hook + 成分/规格事实 + 标签/商品页承接 + 弱购买入口。",
|
|
|
+ "C2 边界脚本结构 = 强情绪 Hook + 状态隐喻 + 明星/达人场景 + 促销承接;必须逐条审核。",
|
|
|
+ "C3 评论承接结构 = 置顶 FAQ + 功效追问统一回复 + 版本/正品/用量分流客服。",
|
|
|
+ "C4 多模态一致性 = 标题、口播、字幕、画面、商品卡、评论区不得互相补违规 claim。",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "title": "公式组D:宣称层次自动判定",
|
|
|
+ "items": [
|
|
|
+ "D0 生活场景:只含日常习惯/携带/包装/口感,默认绿灯基础层。",
|
|
|
+ "D1 成分规格:含成分名、数量、剂型、包装规格,需标签可核验。",
|
|
|
+ "D2 标准声称:含营养成分功能/作用声称,需 GB 28050 与标签条件校验。",
|
|
|
+ "D3 备案内保健功能:仅官方/店铺在批文和广审一致时可表达。",
|
|
|
+ "D4 备案外功效感:精力、气血、代谢、脑力、睡眠、抗炎等默认红灯或边界。",
|
|
|
+ "D5 疾病治疗:疾病、治疗、预防、诊断、症状改善,一律截停。",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "title": "公式组E:三维决策矩阵(完整版)",
|
|
|
+ "items": [
|
|
|
+ "E1 账号维度:官方/店铺、达人、医生/营养师、分销/跨境分别判定上限。",
|
|
|
+ "E2 产品维度:保健食品、普通食品、跨境版本、OTC/药品先分流。",
|
|
|
+ "E3 Claim 维度:生活场景、成分事实、标准声称、备案内功能、备案外功能、疾病治疗分层。",
|
|
|
+ "E4 承载维度:口播、字幕、画面、商品卡、评论区、直播间任一处补违规 claim 都要回流审核。",
|
|
|
+ "E5 复用结论:高可复制、需资料核验、边界样本、不可复制四类输出到黄金数据集和 Excel。",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+SELF_CHECK_ROWS = [
|
|
|
+ ["1", "是否出现疾病名、症状、治疗、预防、诊断暗示?", "出现即红灯,改为一般营养信息或专业咨询。"],
|
|
|
+ ["2", "是否把精力、气血、代谢、脑力、睡眠、皮肤状态说成确定结果?", "降级为生活场景和日常营养支持。"],
|
|
|
+ ["3", "是否有“一粒/一包就够、全部补齐、闭眼入”等绝对化表达?", "保留规格事实,删除补全和保证性结论。"],
|
|
|
+ ["4", "营养成分功能/作用声称是否匹配 GB 28050 与产品标签条件?", "不匹配则改为成分事实或交法务确认。"],
|
|
|
+ ["5", "适用人群是否回到具体 SKU 标签?", "删除全家都能吃、所有女生/男士都适合等泛化话术。"],
|
|
|
+ ["6", "账号是否具备对应发布资质?", "官方/店铺、达人、医生/营养师、分销/跨境分开判定。"],
|
|
|
+ ["7", "官方功能 claim 是否具备蓝帽/备案/广审/强制提示语/页面一致?", "缺资料则标为需批文/广审核验后复制。"],
|
|
|
+ ["8", "达人内容是否在推荐、证明产品功效?", "改成个人生活习惯和非功效体验,不强导购。"],
|
|
|
+ ["9", "明星、医生、营养师、研究证据是否可核验并适用于该 SKU?", "无法补证时删除背书,只保留一般信息。"],
|
|
|
+ ["10", "海外/跨境/进口版本是否套用了国内蓝帽或其他 SKU 口径?", "改为版本、包装、效期、售后和商品页说明。"],
|
|
|
+ ["11", "价格、赠品、库存、限时活动是否与页面实时一致?", "不确定则写“以当前页面为准”,不制造紧迫感。"],
|
|
|
+ ["12", "标题、口播、字幕、画面、商品卡和评论区是否一致?", "发现 A 产品 B 卖点 C 功效时回炉修改。"],
|
|
|
+ ["13", "评论区是否追加了口播里没说的功效承诺?", "统一用 FAQ/客服分流,不在评论区补火。"],
|
|
|
+ ["14", "是否有红黑榜、智商税、竞品贬损或排名压制?", "改为中性选购维度和自身标签事实。"],
|
|
|
+ ["15", "是否已给每条样本标注复用结论?", "输出高可复制、需资料核验、边界样本、不可复制。"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+PRODUCT_TYPE_BOUNDARY_ROWS = [
|
|
|
+ ["保健食品/蓝帽", "国食健字/国食健注、备案或注册凭证", "仅限批准或备案范围内;需强制提示语和广审一致性", "官方/店铺账号可在资料齐全时表达;达人不可代言推荐。"],
|
|
|
+ ["普通食品", "SC 生产许可、普通食品标签", "不能使用保健功能、疾病改善、治疗预防等表达", "只能讲配料、口味、食用场景、包装和一般生活方式。"],
|
|
|
+ ["OTC/药品", "国药准字", "不纳入普通达人带货话术;需按药品广告与平台规则单独审查", "本报告仅作为排除项,不把药品话术迁移到善存营养包。"],
|
|
|
+ ["跨境/海外版", "海外标签、跨境商品信息、无国内蓝帽时需标版本差异", "不能套用国内蓝帽和国内保健功能口径", "只能讲版本、包装、渠道、标签差异和客服承接。"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+ACCOUNT_TRACK_ROWS = [
|
|
|
+ ["蓝V/官方/店铺账号", "企业认证、店铺归属清晰、产品资质和广审资料可核验", "商品事实、备案内保健功能、价格活动、客服承接", "超出备案范围、跨 SKU 套用、绝对化、疾病治疗、虚假紧迫感"],
|
|
|
+ ["蓝V海外/跨境店铺", "跨境资质、版本标签、售后和商品页清晰", "海外标签事实、版本差异、包装规格、购买渠道", "把海外版等同国内蓝帽;暗示更有效或国内外通用功效"],
|
|
|
+ ["达人/KOL账号", "不承接保健食品广告发布主体资格", "生活场景、成分事实、中性选购、口感/包装/便利性体验", "功效推荐、身体变化、强购买引导、代言式证明"],
|
|
|
+ ["医生/营养师/专家达人", "身份会放大医疗建议和权威背书风险", "一般科普、标签阅读方法、非品牌化知识", "推荐具体品牌/SKU、医疗机构背书、跨专业建议、诊疗暗示"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+CLAIM_LEVEL_ROWS = [
|
|
|
+ ["Level 0", "生活场景", "我日常会按标签补充;出门携带方便", "绿灯基础层", "一旦延伸到身体变化就升级为功效风险。"],
|
|
|
+ ["Level 0.5", "食用/使用体验", "颗粒大小、口感、包装、吞咽便利", "达人灰区", "只能讲口感和便利性,不讲精力、气色、睡眠、疼痛等身体变化。"],
|
|
|
+ ["Level 1", "成分/规格事实", "含多种维生素矿物质;每包含 X 类营养素", "可复制", "数字可说为标签事实,不能推导为补全、改善或替代饮食。"],
|
|
|
+ ["Level 2", "营养成分功能声称", "某营养素有助于某生理功能", "需国标/标签原文", "必须以适用国标、标签和法务确认口径为准,不自行改写放大。"],
|
|
|
+ ["Level 3", "备案内保健功能", "已备案或广审范围内的保健功能", "仅官方/店铺资料齐全时可说", "达人不能以个人身份推荐或证明。"],
|
|
|
+ ["Level 4", "备案外保健功能", "改善记忆力、焕活、代谢提升等超范围 claim", "红灯", "任何账号都不应直接说;只可观察隐喻边界。"],
|
|
|
+ ["Level 5", "疾病预防/治疗", "甲流、感冒、治疗、预防、诊断、疼痛改善", "红灯截停", "即使已发布也只记录为撞审样本,不作为可复制经验。"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+BOUNDARY_TECHNIQUE_ROWS = [
|
|
|
+ ["概念替换", "疾病/医疗词 -> 营养学/健康维护词", "甲流/感冒 -> 换季健康管理;治疗/改善 -> 日常营养支持", "中低", "善存样本中疾病词较少,后续需补采专题验证。"],
|
|
|
+ ["比喻暗示", "功效不写入口播,转为画面或角色隐喻", "银善存大脑球场、守门员/前锋", "中", "只能作为边界样本,不能把隐喻翻译成直接功效。"],
|
|
|
+ ["权威归因拆解", "荣誉归科学发现,不归产品效果", "国际品牌/40多年历史只能承载品牌信任,不能替代功效证据", "中", "善存样本多用“大品牌/国际品牌/40多年”,需要补证或回到品牌事实。"],
|
|
|
+ ["成分事实", "只说标签可核验内容,不说产品做到什么", "26种/22种/一包/一粒作为规格事实", "低", "善存多维和营养包样本命中最多,可复制价值高。"],
|
|
|
+ ["专利/技术事实", "可以说有技术或配方事实,不能说技术带来效果倍数", "升级配方、重点复配、男女版配比只讲事实", "中", "“配比/升级”不能接成“更有效/更适合所有人”。"],
|
|
|
+ ["个人体验叙事", "第三人称功效声明 -> 第一人称生活习惯 + 免责", "我日常会备/按标签补充/方便坚持", "中", "达人可以用,但不能讲身体变化或引导购买。"],
|
|
|
+ ["标签回流", "人群/用量/版本一律回到标签、商品页、客服", "30+/40+/50+、男女版、海外版", "低", "适合做置顶评论和客服 FAQ。"],
|
|
|
+ ["身份脱敏", "专家/医生/明星不作为功效证明", "达人分享仅代表个人体验", "中", "医生专家达人仍属高风险,建议品牌化内容与科普内容分离。"],
|
|
|
+ ["短脚本冲量", "短促销脚本保留强 Hook", "快冲、手慢无、直播间下单", "高", "可以解释已发布样本,但不建议沉淀为稳定话术。"],
|
|
|
+ ["隐性比较", "不点名竞品,只讲自身维度", "不同产品各有特点,按成分/剂型/预算选择", "中", "避免红黑榜、智商税、别买某品牌。"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+SYSTEM_FUNCTION_ROWS = [
|
|
|
+ ["资料接入", "读取 Rule Pack、客户理想稿、官方账号清单、产品批文/标签/广审资料", "形成项目规则底座和账号白名单,不靠模型空猜。"],
|
|
|
+ ["抖音采集", "按关键词、账号、SKU、claim 缺口采集视频和评论", "区分官方/达人/分销账号,保留已发布成功案例证据。"],
|
|
|
+ ["逐字稿转写", "优先转高风险、高互动、官方账号、边界 claim 样本", "把标题/标签风险推进到真实口播、字幕和评论诱因分析。"],
|
|
|
+ ["规则命中", "逐条命中 RP、宣称层次、账号类型、产品属性、复用判断", "输出黄金边界数据集,而不是单条改写建议。"],
|
|
|
+ ["脚本建议", "按账号和 claim 生成可复制、需核验、边界样本、不可复制四类动作", "同一卖点在蓝V和达人账号下自动给不同上限。"],
|
|
|
+ ["评论承接", "识别功效追问、版本信任、购买路径、用法人群、体验副作用", "同步生成置顶评论、客服 FAQ 和禁回复口径。"],
|
|
|
+ ["复盘闭环", "记录采集失败、转写失败、未命中 RP 和补采方向", "每轮报告都能说明样本覆盖与下一轮数据缺口。"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+STRATEGY_FORMULA_ROWS = [
|
|
|
+ {
|
|
|
+ "formula": "官方/店铺账号 × 善存营养包/PRO × 省心搭配 × 30+/忙碌人群 × 日常营养支持 × 商品页/标签承接 × 弱 CTA",
|
|
|
+ "condition": "账号归属清晰,产品标签、商品页、字幕和口播一致。",
|
|
|
+ "experience": "可把“一包/多种营养/方便坚持”作为便利性卖点,但不要推导成“全部都补齐、状态一定变好”。",
|
|
|
+ "reuse": "高可复制;适合官方 brief 和店铺短视频。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "达人账号 × 善存营养包/PRO × 一包省心 × 护肤/身材/工作状态 × 状态感 claim × 个人体验 × 无强 CTA",
|
|
|
+ "condition": "达人只做生活化分享,不承接官方广告备案能力。",
|
|
|
+ "experience": "把卖点落在“我日常会备、懒人省心、按标签补充”,避免“吃瘦、气血好、皮肤变好、快冲下单”。",
|
|
|
+ "reuse": "可复制但需弱化;适合达人 brief。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "官方/店铺账号 × 善存复合维生素/多维 × 26/22 种成分 × 成人男女版 × 泛营养 claim × 成分事实 × 商品页承接",
|
|
|
+ "condition": "成分数量、规格、适用说明能在包装或商品页核验。",
|
|
|
+ "experience": "数字可以说成“含有”,不要说成“一粒满足全部所需、一次补全、闭眼入”。",
|
|
|
+ "reuse": "高可复制;适合形成标准商品信息话术。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "达人账号 × 善存复合维生素/多维 × 每天一粒 × 成人男女版 × 便利性 claim × 个人习惯 × 低购买压力",
|
|
|
+ "condition": "达人表达为个人选择,不做全人群适用判断。",
|
|
|
+ "experience": "可说“我会看复配、品牌、颗粒大小、性价比”,再回到“按标签选择”;不要说所有女生/男生都需要。",
|
|
|
+ "reuse": "可复制;注意删除绝对化和强导购。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "官方/店铺账号 × 海外/跨境 SKU × 正品/版本 × 海淘用户 × 信任 claim × 包装/批号/效期 × 客服承接",
|
|
|
+ "condition": "跨境版本、商品资质、售后入口明确。",
|
|
|
+ "experience": "海外/进口/本土版只承载渠道和版本事实,不承载“更有效、更适合国内人群”。",
|
|
|
+ "reuse": "高可复制;适合置顶评论和商品卡 FAQ。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "达人/分销账号 × 海外/Costco/澳洲版 × 版本比较 × 成人/50+ × 功效差异 claim × 选购经验 × 中性比较",
|
|
|
+ "condition": "账号非官方或授权未核验时,版本信息只能做经验观察。",
|
|
|
+ "experience": "可说包装、规格、购买场景和个人偏好;不要把不同版本差异说成功效优劣或国内外通用保健功能。",
|
|
|
+ "reuse": "边界经验;需要人工复核。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "官方/店铺账号 × 银善存/50+ × 大脑/脑力素材 × 中老年/爸妈 × 备案内功能 claim × 官方素材 × 审查一致",
|
|
|
+ "condition": "必须核验产品批文、说明书、广告审查批件和画面素材。",
|
|
|
+ "experience": "若 claim 在备案和广审范围内,可相对直接;若不在范围内,只能转为营养支持、生活场景或画面隐喻。",
|
|
|
+ "reuse": "需批文/广审核验后复制。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "达人账号 × 银善存/大脑类 × 守门员/前锋/球场隐喻 × 40+/脑力场景 × 大脑表现 claim × 画面比喻 × 无医学承诺",
|
|
|
+ "condition": "直接说“改善记忆/提升脑力/治疗认知问题”风险高。",
|
|
|
+ "experience": "已发布样本的边界价值在于用角色、运动、任务感承载“状态感”,不把隐喻翻译成医学或功能承诺。",
|
|
|
+ "reuse": "高边界;逐条审核后才可复用。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "达人/明星账号 × 善存营养包 × 明星在吃 × 粉丝/女性 × 效果背书 claim × 场景分享 × 非功效背书",
|
|
|
+ "condition": "明星达人内容容易被用户理解为功效或适用性背书。",
|
|
|
+ "experience": "可保留生活场景和个人体验,不把“他们都在吃”写成“所以你也适合/一定有效”。",
|
|
|
+ "reuse": "边界样本;必须补授权和背书合规判断。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "官方/店铺账号 × 明星素材 × 多 SKU × 品牌信任 × 背书 claim × 官方授权素材 × 商品页一致",
|
|
|
+ "condition": "素材授权、代言关系、广告审查内容和商品链接需要一致。",
|
|
|
+ "experience": "官方账号可以承接品牌信任,但明星素材仍不能替代产品功效证据。",
|
|
|
+ "reuse": "需素材授权和法务确认。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "任意账号 × 代谢包/身材管理 × 代谢/吃瘦 × 女性/管理期 × 减重结果 claim × 生活方式组合 × 无结果承诺",
|
|
|
+ "condition": "代谢、吃瘦、身材管理容易被理解为减肥或治疗效果。",
|
|
|
+ "experience": "把表达转成日常营养补充、均衡饮食和运动作息的一部分,不说“代谢变快、吃了瘦”。",
|
|
|
+ "reuse": "不可直接复制;需大幅弱化。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "官方/店铺账号 × 直播间活动 × 价格/赠品 × 已关注人群 × 购买信息 claim × 活动规则 × 页面承接",
|
|
|
+ "condition": "活动信息必须与页面实时一致。",
|
|
|
+ "experience": "可说活动、赠品、库存和到手价,但不能绑定功效承诺,也不能制造虚假紧迫感。",
|
|
|
+ "reuse": "可复制;需运营实时校验。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "达人账号 × 橱窗/分销 × 快冲/下单 × 泛人群 × 强购买 CTA × 视频口播 × 评论承接",
|
|
|
+ "condition": "达人账号的强导购审核边界明显更紧。",
|
|
|
+ "experience": "即便已发布,也只记录为边界样本;更稳的做法是把购买动作放到商品卡自然承接。",
|
|
|
+ "reuse": "高风险;不作为标准话术。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "医生/营养师/专业账号 × 任意 SKU × 科普建议 × 特殊健康人群 × 专业背书 claim × 可核验资质 × 不诊断不治疗",
|
|
|
+ "condition": "专业身份会放大用户对医疗建议的理解。",
|
|
|
+ "experience": "可做一般营养科普和标签阅读方法,不说诊断、治疗、预防,不把个人身份变成产品功效证明。",
|
|
|
+ "reuse": "需资质、素材和平台规则复核。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "任意账号 × 孕妇/儿童/老人/慢病人群 × 适用人群 × 特殊人群 × 用量/适合 claim × 标签说明 × 专业咨询",
|
|
|
+ "condition": "特殊人群不能被一概而论。",
|
|
|
+ "experience": "把回答固定到具体 SKU 标签、说明书、客服和专业人士;不要说“爸妈都能吃、小孩也适合”。",
|
|
|
+ "reuse": "慎用;适合 FAQ 而非强口播。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "任意账号 × 多 SKU/竞品比较 × 红黑榜/排名 × 选购人群 × 最好/只认 claim × 中性维度 × 无贬损",
|
|
|
+ "condition": "红黑榜、排名、智商税和竞品贬损容易触发平台和广告法风险。",
|
|
|
+ "experience": "改成“看成分、剂型、规格、标签、预算和适用场景”,只讲自身可核验信息。",
|
|
|
+ "reuse": "可复制中性框架;不可复制贬损表达。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "官方/店铺账号 × 评论区承接 × 功效追问 × 已购买/观望人群 × 效果如何 claim × 标准 FAQ × 客服分流",
|
|
|
+ "condition": "评论区回复也是广告表达的一部分。",
|
|
|
+ "experience": "对“效果明显吗”统一回到日常营养补充、均衡饮食、个体差异和专业咨询,不在评论区追加功效承诺。",
|
|
|
+ "reuse": "高可复制;适合置顶评论模板。",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "formula": "任意账号 × 疾病/甲流/感冒 × 治疗/预防 × 特殊健康时期 × 医疗 claim × 删除或转咨询 × 无导购",
|
|
|
+ "condition": "出现疾病、治疗、预防、诊断暗示时,不因已发布就认定可复制。",
|
|
|
+ "experience": "只能作为撞审边界记录;稳定话术应回到一般营养信息和专业人士咨询。",
|
|
|
+ "reuse": "不可复制。",
|
|
|
+ },
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+UNOBSERVED_RULE_SAMPLING_GUIDE = {
|
|
|
+ "RP08": "补采含“国家认证、监管推荐、官方背书、平台认证截图、蓝帽等同万能资质”等表述的视频,区分真实资质展示和背书滥用。",
|
|
|
+ "RP09": "补采制造焦虑或歧视性表达,例如“30+不补就垮、爸妈不吃就危险、熬夜党必须囤”等,观察恐惧钩子能否发布。",
|
|
|
+ "RP10": "补采明星综艺截屏、影视/IP 素材、音乐音频、他人评论截图和未经授权图文素材样本,建立素材授权边界。",
|
|
|
+ "RP12": "补采订单、物流、聊天记录、用户评论截图和售后截图类视频,判断隐私遮挡、授权和截图来源要求。",
|
|
|
+ "RP15": "补采孕妇、儿童、老人、术后、慢病、备孕等特殊人群样本,和 RP07 的用法用量规则联动复盘。",
|
|
|
+ "RP16": "补采谐音避审、错别字规避、字幕替换禁词、口播不说字幕暗示等样本,判断平台是否按语义识别。",
|
|
|
+ "RP17": "补采口播 A 产品、画面 B 产品、标题 C 卖点、商品卡 D SKU 的混配样本,验证素材一致性风险。",
|
|
|
+ "RP20": "补采擦边玩梗、低俗隐喻、过度身材焦虑、攻击性表达等样本,判断品牌安全边界。",
|
|
|
+ "RP22": "补采公共事件、疾病流行、考试季焦虑、节日恐慌和趋势钩子样本,判断热点借势与恐惧营销边界。",
|
|
|
+ "RP23": "收集平台驳回、限流、申诉、人工审核反馈和发布时间差异样本,用于反推 DP/平台候选风险。",
|
|
|
+ "RP24": "跟踪三品一械广告审查新规、平台健康品规则更新和品牌法务口径变化,形成监管候选规则池。",
|
|
|
+ "RP25": "补采副作用、不适、吞咽困难、投诉、售后争议和夸大宣传质疑评论,沉淀历史投诉高发模式。",
|
|
|
+ "RP26": "补采 AI 合成明星脸、AI 声音、数字人带货、仿真人设口播样本,核验授权与平台标识要求。",
|
|
|
+ "RP27": "补采 DP 反馈、平台新增禁投词、新兴达人玩法和特殊账号类型样本,作为新增规则候选。",
|
|
|
+ "RP28": "该类多为流程或范围外事项,暂不主动补采;仅在平台审核流程、自动审批或发布机制影响结论时记录。",
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+RULE_REWRITE_TEMPLATES = {
|
|
|
+ "RP01": "删除“唯一、最好、全网第一、就够、补齐、全部所需”等绝对化或保证性表达;改为“含多种维生素/矿物质,可按标签建议作为日常营养补充”。",
|
|
|
+ "RP02": "删除甲流、感冒、治疗、预防、医生诊断等疾病/医疗暗示;改为“一般营养补充信息,特殊健康状况请咨询专业人士”。",
|
|
|
+ "RP03": "删除精力满格、气血改善、代谢变好、焕活、抗炎等结果承诺;改为“配合均衡饮食和规律作息,支持日常营养补充”。",
|
|
|
+ "RP04": "保留可核验的成分数量、规格和剂型,但不要把“26种/22种/120粒”等数字推导成效果、补全或改善结果。",
|
|
|
+ "RP05": "跨境/海外/不同版本只讲包装、标签、适用说明和购买渠道差异;不要默认等同国内蓝帽、保健功能或统一适用人群。",
|
|
|
+ "RP06": "正品、批号、喷码、效期、包装等信息以商品页、实物标签和客服确认为准,避免用“大品牌”替代合规凭证。",
|
|
|
+ "RP07": "按具体 SKU 标签建议说明用法用量和适用人群;孕妇、儿童、老人、30+/40+/50+等人群不要一概而论。",
|
|
|
+ "RP11": "删除红黑榜、智商税、别买某品牌、排名碾压等竞品贬损;改为中性选购维度和自身产品标签信息。",
|
|
|
+ "RP13": "弱化“必须、离不开、快冲、稳稳、真的有效”等强语气;改为“可作为日常补充选择之一,按需选择”。",
|
|
|
+ "RP14": "医生、营养师、专业建议或研究背书需要可核验证据;无法补证时改为个人经验/一般选购建议,并回到标签依据。",
|
|
|
+ "RP18": "活动、价格、赠品、库存和到手价以当前页面为准,不虚构限量或福利,也不把促销 CTA 与功效承诺绑定。",
|
|
|
+ "RP19": "明星/达人/专家内容仅作为分享场景,不作为功效或适用性背书;补充“产品信息以包装标签和官方说明为准”。",
|
|
|
+ "RP21": "删除“马上下单就解决、快冲改善状态”等强 CTA;改为“如需了解规格/适用说明,可查看商品页或咨询客服”。",
|
|
|
+ "RP22": "避免借焦虑、趋势、恐慌、白月光或梗化表达放大购买压力;改为平实生活场景和可核验产品信息。",
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+RULE_MODULE_MAP = {
|
|
|
+ "RP01": "一包/一粒解决感",
|
|
|
+ "RP02": "功效/状态表达",
|
|
|
+ "RP03": "功效/状态表达",
|
|
|
+ "RP04": "成分/剂型说明",
|
|
|
+ "RP05": "版本/渠道信任",
|
|
|
+ "RP06": "版本/渠道信任",
|
|
|
+ "RP07": "年龄/人群场景",
|
|
|
+ "RP11": "比较/榜单/避坑",
|
|
|
+ "RP13": "功效/状态表达",
|
|
|
+ "RP14": "版本/渠道信任",
|
|
|
+ "RP18": "促销/购买引导",
|
|
|
+ "RP19": "明星/达人背书",
|
|
|
+ "RP21": "促销/购买引导",
|
|
|
+ "RP22": "促销/购买引导",
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+COMMENT_REPLY_TEMPLATES = {
|
|
|
+ "泛互动/情绪反馈": "感谢喜欢,产品信息可查看商品页成分表、包装标签和客服说明。",
|
|
|
+ "功效/健康诉求": "营养补充品不承诺特定效果,建议结合均衡饮食和规律作息;特殊健康状况请咨询专业人士。",
|
|
|
+ "正品/渠道/版本信任": "不同版本包装、成分和标签说明可能不同,正品、批号、效期和售后以商品页及实物标签为准。",
|
|
|
+ "购买路径/价格": "活动、赠品、库存和价格以当前页面为准;如需规格或适用说明,可咨询客服。",
|
|
|
+ "质疑/比较/性价比": "可从成分、剂型、规格、标签说明、预算和适用人群做中性比较,不建议用红黑榜或贬损表达。",
|
|
|
+ "内容互动/达人明星": "达人分享仅代表个人体验,产品信息以包装标签、商品页和官方说明为准。",
|
|
|
+ "低质互动/刷金币": "需人工复核评论上下文后再互动,避免用模板回复强化无效互动。",
|
|
|
+ "用法用量/适用人群": "不同 SKU 适用人群和建议用量不同,请按包装标签、商品页和客服说明选择。",
|
|
|
+ "体验/副作用/剂型": "关于服用体验、颗粒大小、吞咽和不适感,可引导查看 FAQ 或咨询客服;不要在评论区给医疗建议。",
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+COMMENT_TRANSCRIPT_TRIGGERS = {
|
|
|
+ "泛互动/情绪反馈": "明星/达人场景、强生活化表达和高互动标题会带来大量情绪评论,但信息价值较低。",
|
|
|
+ "功效/健康诉求": "精力、气血、代谢、状态、抗炎、甲流等口播会触发用户追问“效果怎么样”。",
|
|
|
+ "正品/渠道/版本信任": "海外、跨境、官方、大品牌、国内外版本等表达会触发真假、版本、效期和售后疑问。",
|
|
|
+ "购买路径/价格": "直播间、拍一发四、送礼袋、活动数量有限、快冲等口播会把用户注意力拉到价格和购买路径。",
|
|
|
+ "质疑/比较/性价比": "红黑榜、排行榜、只认某款、别乱买等表达会引发比较、质疑和性价比讨论。",
|
|
|
+ "内容互动/达人明星": "明星都在吃、达人推荐、医生/营养师身份会带来背书追问,也会放大合规风险。",
|
|
|
+ "低质互动/刷金币": "高流量视频容易混入刷金币、求赞、无意义互动,需和真实疑问分开处理。",
|
|
|
+ "用法用量/适用人群": "30+/40+/50+、孕妇、儿童、爸妈、长辈等口播会触发适用人群和用量追问。",
|
|
|
+ "体验/副作用/剂型": "一粒/一包/颗粒/吞咽/搭配方式等口播会触发体验和服用方式问题。",
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+def load_account_list():
|
|
|
+ records = []
|
|
|
+ names = set()
|
|
|
+ if not ACCOUNT_LIST_XLSX.exists():
|
|
|
+ return {"records": records, "names": names}
|
|
|
+ try:
|
|
|
+ import openpyxl
|
|
|
+
|
|
|
+ wb = openpyxl.load_workbook(ACCOUNT_LIST_XLSX, data_only=True)
|
|
|
+ ws = wb.active
|
|
|
+ for row in ws.iter_rows(min_row=4, values_only=True):
|
|
|
+ store = clean(row[0] if len(row) > 0 else "")
|
|
|
+ live_account = clean(row[2] if len(row) > 2 else "")
|
|
|
+ douyin_id = clean(row[3] if len(row) > 3 else "")
|
|
|
+ if not store and not live_account:
|
|
|
+ continue
|
|
|
+ record = {
|
|
|
+ "store": store,
|
|
|
+ "liveAccount": live_account,
|
|
|
+ "douyinId": douyin_id,
|
|
|
+ }
|
|
|
+ records.append(record)
|
|
|
+ for name in (store, live_account):
|
|
|
+ if name:
|
|
|
+ names.add(name)
|
|
|
+ except Exception:
|
|
|
+ return {"records": records, "names": names}
|
|
|
+ return {"records": records, "names": names}
|
|
|
+
|
|
|
+
|
|
|
+def classify_account(author, official_names):
|
|
|
+ author = clean(author)
|
|
|
+ if author in official_names:
|
|
|
+ return {
|
|
|
+ "accountType": "官方/店铺账号(清单匹配)",
|
|
|
+ "accountConfidence": "高",
|
|
|
+ "accountRuleLogic": "可承接商品资质、广告审查备案和官方说明;功能宣称是否可说,取决于产品属性、批文/备案范围和素材一致性。",
|
|
|
+ }
|
|
|
+ if re.search(r"善存.*(旗舰店|官方|专卖店|店铺|直播|营养品)|VCHOICE", author, flags=re.I):
|
|
|
+ return {
|
|
|
+ "accountType": "疑似店铺/分销账号(待核验)",
|
|
|
+ "accountConfidence": "中",
|
|
|
+ "accountRuleLogic": "可能具备商品承接能力,但不在本次官方清单内;结论需回查账号认证、店铺归属和广告备案。",
|
|
|
+ }
|
|
|
+ if re.search(r"旗舰店|官方|专卖店|店铺|优选|海外专营店", author, flags=re.I):
|
|
|
+ return {
|
|
|
+ "accountType": "其他店铺/分销账号(非官方清单)",
|
|
|
+ "accountConfidence": "中",
|
|
|
+ "accountRuleLogic": "可做购买承接但不应默认等同品牌官方;需要核验授权关系和商品资质。",
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ "accountType": "达人/内容账号",
|
|
|
+ "accountConfidence": "中",
|
|
|
+ "accountRuleLogic": "应以个人体验、生活场景和中性科普为主;不得替代官方广告备案,不宜强 CTA 或直接导购。",
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def account_rule_notes(account_type):
|
|
|
+ if account_type.startswith("官方/店铺"):
|
|
|
+ return "官方/店铺账号可在资质与广审备案范围内表达功能或商品信息,但要保证产品、画面、口播、字幕和落地页一致。"
|
|
|
+ if account_type.startswith("疑似店铺") or account_type.startswith("其他店铺"):
|
|
|
+ return "店铺/分销账号要先核验授权和资质;在未确认前,不把功能宣称当作绿灯经验,只记录为边界样本。"
|
|
|
+ return "达人账号的成功经验主要是生活化、隐喻化、弱导购和个人体验化;功能/治疗/强购买引导仍按更严边界处理。"
|
|
|
+
|
|
|
+
|
|
|
+def claim_boundary_for_row(row):
|
|
|
+ text = f"{row.get('cleanText', '')} {row.get('title', '')}"
|
|
|
+ account_type = row.get("accountType", "")
|
|
|
+ sku = row.get("skuGroup", "")
|
|
|
+ if re.search(r"大脑|脑力|守门员|前锋", text):
|
|
|
+ claim = "脑力/大脑表现"
|
|
|
+ carrier = "画面隐喻或角色比喻优于直接效果承诺"
|
|
|
+ elif re.search(r"精力|状态|气血|代谢|焕活|抗炎|皮肤", text):
|
|
|
+ claim = "状态/功效感"
|
|
|
+ carrier = "生活场景 + 日常营养支持,避免直接承诺改善结果"
|
|
|
+ elif re.search(r"甲流|感冒|疾病|治疗|预防", text):
|
|
|
+ claim = "疾病/特殊健康时期"
|
|
|
+ carrier = "不作为成功经验复制;只能回到一般营养补充或专业咨询"
|
|
|
+ elif re.search(r"一粒|一包|26种|22种|全部|都在", text):
|
|
|
+ claim = "成分/剂型/便利性"
|
|
|
+ carrier = "可说成分与剂型事实,不把数字推导成全部所需或补全效果"
|
|
|
+ else:
|
|
|
+ claim = "泛营养/生活场景"
|
|
|
+ carrier = "保留场景和产品事实,减少效果与购买压力"
|
|
|
+ if account_type.startswith("官方/店铺"):
|
|
|
+ account_logic = "若属于保健食品且 claim 在批文/广审备案范围内,可作为绿灯表达;超出备案范围仍需弱化或改用隐喻。"
|
|
|
+ else:
|
|
|
+ account_logic = "达人/内容账号不承接官方备案能力,claim 需要个人体验化、场景化、隐喻化,并避免强购买引导。"
|
|
|
+ return {
|
|
|
+ "claimType": claim,
|
|
|
+ "claimCarrier": carrier,
|
|
|
+ "boundaryLogic": f"{sku or '当前SKU'} × {account_type or '未知账号'}:{account_logic}",
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def match_items(patterns, text):
|
|
|
+ hits = []
|
|
|
+ for item in patterns:
|
|
|
+ key, name, pattern = item if len(item) == 3 else (item[0], item[0], item[1])
|
|
|
+ if re.search(pattern, text, flags=re.I):
|
|
|
+ hits.append({"id": key, "name": name})
|
|
|
+ return hits
|
|
|
+
|
|
|
+
|
|
|
+def classify_modules(text):
|
|
|
+ hits = []
|
|
|
+ for label, pattern in MODULE_PATTERNS:
|
|
|
+ if re.search(pattern, text, flags=re.I):
|
|
|
+ hits.append(label)
|
|
|
+ return hits or ["低信息/泛口播"]
|
|
|
+
|
|
|
+
|
|
|
+def is_valid_transcript(text):
|
|
|
+ normalized = normalize_asr(text)
|
|
|
+ if chinese_count(normalized) < 30:
|
|
|
+ return False
|
|
|
+ if re.fullmatch(r"[A-Za-z0-9 ,.'!?-]+", normalized or ""):
|
|
|
+ return False
|
|
|
+ return True
|
|
|
+
|
|
|
+
|
|
|
+def build_rewrite(row):
|
|
|
+ modules = row["modules"]
|
|
|
+ suggestions = [REWRITE_TEMPLATES[m] for m in modules if m in REWRITE_TEMPLATES]
|
|
|
+ if not suggestions:
|
|
|
+ suggestions = ["保留生活场景和成分信息,删除无法证明的结果承诺,回到标签/说明书口径。"]
|
|
|
+ return ";".join(dict.fromkeys(suggestions))
|
|
|
+
|
|
|
+
|
|
|
+RULE_LOOKUP = {item[0]: {"name": item[1], "pattern": item[2]} for item in RULE_PATTERNS}
|
|
|
+
|
|
|
+
|
|
|
+def rule_name(rule_id):
|
|
|
+ return FULL_RULE_NAMES.get(rule_id) or RULE_LOOKUP.get(rule_id, {}).get("name", "")
|
|
|
+
|
|
|
+
|
|
|
+def rule_default_status(rule_id):
|
|
|
+ try:
|
|
|
+ num = int(rule_id.replace("RP", ""))
|
|
|
+ except ValueError:
|
|
|
+ return "Unknown"
|
|
|
+ if 1 <= num <= 12:
|
|
|
+ return "Active-Blocking"
|
|
|
+ if 13 <= num <= 22:
|
|
|
+ return "Advisory"
|
|
|
+ if 23 <= num <= 27:
|
|
|
+ return "Candidate"
|
|
|
+ return "Disabled/Park"
|
|
|
+
|
|
|
+
|
|
|
+def rule_audit_action(rule_id):
|
|
|
+ status = rule_default_status(rule_id)
|
|
|
+ if status == "Active-Blocking":
|
|
|
+ return "红灯截停;删除或改写后再审"
|
|
|
+ if status == "Advisory":
|
|
|
+ return "黄灯提示;弱化、补证或人工确认"
|
|
|
+ if status == "Candidate":
|
|
|
+ return "候选记录;与其他 Active/Advisory 规则联动判断"
|
|
|
+ return "不纳入正式判断"
|
|
|
+
|
|
|
+
|
|
|
+def primary_module_for_rule(row, rule_id):
|
|
|
+ preferred = RULE_MODULE_MAP.get(rule_id)
|
|
|
+ modules = row.get("modules") or []
|
|
|
+ if preferred in modules:
|
|
|
+ return preferred
|
|
|
+ for module in modules:
|
|
|
+ if module != "低信息/泛口播":
|
|
|
+ return module
|
|
|
+ return preferred or "低信息/泛口播"
|
|
|
+
|
|
|
+
|
|
|
+def extract_rule_matches(rule_id, text, max_items=3):
|
|
|
+ lookup = RULE_LOOKUP.get(rule_id)
|
|
|
+ if not lookup:
|
|
|
+ return []
|
|
|
+ values = []
|
|
|
+ for match in re.finditer(lookup["pattern"], text or "", flags=re.I):
|
|
|
+ value = snippet(match.group(0), 48)
|
|
|
+ if value and value not in values:
|
|
|
+ values.append(value)
|
|
|
+ if len(values) >= max_items:
|
|
|
+ break
|
|
|
+ return values
|
|
|
+
|
|
|
+
|
|
|
+def extract_rule_context(rule_id, text, length=180):
|
|
|
+ lookup = RULE_LOOKUP.get(rule_id)
|
|
|
+ if not lookup:
|
|
|
+ return snippet(text, length)
|
|
|
+ match = re.search(lookup["pattern"], text or "", flags=re.I)
|
|
|
+ if not match:
|
|
|
+ return snippet(text, length)
|
|
|
+ start = max(0, match.start() - 55)
|
|
|
+ end = min(len(text), match.end() + 115)
|
|
|
+ return snippet(text[start:end], length)
|
|
|
+
|
|
|
+
|
|
|
+def build_rule_rewrite(row, rule_id):
|
|
|
+ module = primary_module_for_rule(row, rule_id)
|
|
|
+ suggestions = [
|
|
|
+ RULE_REWRITE_TEMPLATES.get(rule_id),
|
|
|
+ REWRITE_TEMPLATES.get(module),
|
|
|
+ ]
|
|
|
+ return ";".join(dict.fromkeys(item for item in suggestions if item))
|
|
|
+
|
|
|
+
|
|
|
+def boundary_reusability(row, rule_id):
|
|
|
+ account_type = row.get("accountType", "")
|
|
|
+ claim_type = row.get("claimType", "")
|
|
|
+ if rule_id in {"RP02"}:
|
|
|
+ return "不可复制高风险"
|
|
|
+ if account_type.startswith("官方/店铺") and claim_type in {"状态/功效感", "脑力/大脑表现"}:
|
|
|
+ return "需批文/广审核验后复制"
|
|
|
+ if account_type.startswith("达人") and rule_id in {"RP18", "RP21"}:
|
|
|
+ return "达人强导购边界样本"
|
|
|
+ if rule_id in {"RP01", "RP03", "RP07"}:
|
|
|
+ return "边界样本,需弱化后复制"
|
|
|
+ return "可作为经验参考"
|
|
|
+
|
|
|
+
|
|
|
+def build_success_experience(row, rule_id):
|
|
|
+ account_type = row.get("accountType", "")
|
|
|
+ claim_type = row.get("claimType", "")
|
|
|
+ carrier = row.get("claimCarrier", "")
|
|
|
+ reusability = boundary_reusability(row, rule_id)
|
|
|
+ if account_type.startswith("官方/店铺"):
|
|
|
+ base = "官方/店铺账号的可迁移经验是:把表达落在商品页、包装标签、批文/备案和广告审查可覆盖的信息上;若是保健食品且功能 claim 在备案与广审范围内,可形成绿灯表达。"
|
|
|
+ elif account_type.startswith("疑似店铺") or account_type.startswith("其他店铺"):
|
|
|
+ base = "店铺/分销账号的经验需要先核验授权与资质;可复用的是商品事实、版本边界和客服承接,不应默认复制官方功能宣称。"
|
|
|
+ else:
|
|
|
+ base = "达人/内容账号的可迁移经验是:个人体验化、生活场景化、隐喻化,不替代官方广告备案,不把内容口播变成强购买引导。"
|
|
|
+ return f"{base} 本条属于“{claim_type}”,承载方式为“{carrier}”;复用判断:{reusability}。"
|
|
|
+
|
|
|
+
|
|
|
+def build_golden_cases(valid_rows):
|
|
|
+ rows_by_rule = defaultdict(list)
|
|
|
+ for row in valid_rows:
|
|
|
+ for rule_id in [item for item in row["ruleHits"].split("、") if item]:
|
|
|
+ rows_by_rule[rule_id].append(row)
|
|
|
+
|
|
|
+ golden = []
|
|
|
+
|
|
|
+ def sort_rule(rule_id):
|
|
|
+ return int(rule_id.replace("RP", "")) if rule_id.startswith("RP") else 999
|
|
|
+
|
|
|
+ for rule_id in sorted(rows_by_rule, key=sort_rule):
|
|
|
+ sorted_rows = sorted(
|
|
|
+ rows_by_rule[rule_id],
|
|
|
+ key=lambda r: (r["likeCount"], r["commentCount"], r["textLength"]),
|
|
|
+ reverse=True,
|
|
|
+ )
|
|
|
+ for index, row in enumerate(sorted_rows, start=1):
|
|
|
+ module = primary_module_for_rule(row, rule_id)
|
|
|
+ name = rule_name(rule_id)
|
|
|
+ matches = extract_rule_matches(rule_id, row["cleanText"])
|
|
|
+ comment_examples = [clean(item) for item in row.get("commentExamples", []) if clean(item)]
|
|
|
+ golden.append({
|
|
|
+ "id": f"TG-{rule_id}-{index:03d}",
|
|
|
+ "caseLayer": "逐字稿 Rule 命中案例",
|
|
|
+ "ruleId": rule_id,
|
|
|
+ "ruleName": name,
|
|
|
+ "defaultStatus": rule_default_status(rule_id),
|
|
|
+ "auditAction": rule_audit_action(rule_id),
|
|
|
+ "module": module,
|
|
|
+ "sourceVideoId": row["videoId"],
|
|
|
+ "sourceTitle": row["title"],
|
|
|
+ "author": row["author"],
|
|
|
+ "skuGroup": row["skuGroup"],
|
|
|
+ "accountType": row.get("accountType", ""),
|
|
|
+ "accountConfidence": row.get("accountConfidence", ""),
|
|
|
+ "claimType": row.get("claimType", ""),
|
|
|
+ "claimCarrier": row.get("claimCarrier", ""),
|
|
|
+ "boundaryLogic": row.get("boundaryLogic", ""),
|
|
|
+ "engagement": f"{row['likeCount']}赞/{row['commentCount']}评",
|
|
|
+ "riskRules": rule_id,
|
|
|
+ "allRiskRules": row["ruleHits"],
|
|
|
+ "triggerText": "、".join(matches),
|
|
|
+ "originalTranscriptSnippet": extract_rule_context(rule_id, row["cleanText"]),
|
|
|
+ "suggestedApprovedWording": build_rule_rewrite(row, rule_id),
|
|
|
+ "successExperience": build_success_experience(row, rule_id),
|
|
|
+ "reusability": boundary_reusability(row, rule_id),
|
|
|
+ "why": f"命中 {rule_id}({name}),默认状态为 {rule_default_status(rule_id)};该条从真实逐字稿中按规则拆行,用于扩充黄金测试集和审核训练样本。",
|
|
|
+ "commentEvidence": "\n".join(comment_examples[:3]),
|
|
|
+ "url": row["url"],
|
|
|
+ })
|
|
|
+ return golden
|
|
|
+
|
|
|
+
|
|
|
+def rule_pack_table_stats():
|
|
|
+ if not RULE_PACK_DOC.exists():
|
|
|
+ return {"tableCount": 0, "tableRows": 0}
|
|
|
+ try:
|
|
|
+ with zipfile.ZipFile(RULE_PACK_DOC) as docx:
|
|
|
+ root = ET.fromstring(docx.read("word/document.xml"))
|
|
|
+ ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
|
|
|
+ tables = root.findall(".//w:tbl", ns)
|
|
|
+ return {
|
|
|
+ "tableCount": len(tables),
|
|
|
+ "tableRows": sum(len(table.findall(".//w:tr", ns)) for table in tables),
|
|
|
+ }
|
|
|
+ except Exception:
|
|
|
+ return {"tableCount": 0, "tableRows": 0}
|
|
|
+
|
|
|
+
|
|
|
+def supplemental_strategy_stats():
|
|
|
+ if not SUPPLEMENTAL_STRATEGY_MD.exists():
|
|
|
+ return {"exists": False, "lineCount": 0, "headingCount": 0}
|
|
|
+ try:
|
|
|
+ text = SUPPLEMENTAL_STRATEGY_MD.read_text(encoding="utf-8", errors="ignore")
|
|
|
+ except Exception:
|
|
|
+ return {"exists": False, "lineCount": 0, "headingCount": 0}
|
|
|
+ return {
|
|
|
+ "exists": True,
|
|
|
+ "lineCount": len(text.splitlines()),
|
|
|
+ "headingCount": len(re.findall(r"^##+\s+", text, flags=re.M)),
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def runner_supplement_stats():
|
|
|
+ if not RUNNER_SUPPLEMENT_JSON.exists():
|
|
|
+ return {"exists": False, "status": "", "videoCount": 0, "commentCount": 0, "errorCount": 0, "qualityGate": ""}
|
|
|
+ try:
|
|
|
+ data = load_json(RUNNER_SUPPLEMENT_JSON)
|
|
|
+ except Exception:
|
|
|
+ return {"exists": False, "status": "", "videoCount": 0, "commentCount": 0, "errorCount": 0, "qualityGate": ""}
|
|
|
+ summary = data.get("summary", {})
|
|
|
+ quality_gate = data.get("qualityGate", {})
|
|
|
+ raw_errors = []
|
|
|
+ if RUNNER_SUPPLEMENT_RAW.exists():
|
|
|
+ try:
|
|
|
+ raw_errors = load_json(RUNNER_SUPPLEMENT_RAW).get("metadata", {}).get("errors", [])
|
|
|
+ except Exception:
|
|
|
+ raw_errors = []
|
|
|
+ return {
|
|
|
+ "exists": True,
|
|
|
+ "status": data.get("status", ""),
|
|
|
+ "videoCount": int(summary.get("videoCount") or 0),
|
|
|
+ "commentCount": int(summary.get("commentCount") or 0),
|
|
|
+ "errorCount": len(raw_errors) or int(summary.get("errorCount") or 0),
|
|
|
+ "qualityGate": quality_gate.get("level", ""),
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def classify_public_title_risk(title):
|
|
|
+ hits = match_items(PUBLIC_TITLE_RISK_PATTERNS, title)
|
|
|
+ if not hits:
|
|
|
+ return {
|
|
|
+ "riskText": "未命中高频标题风险;仍需逐字稿/画面复核",
|
|
|
+ "riskRules": "",
|
|
|
+ "reuse": "标题观察样本,待逐字稿确认",
|
|
|
+ }
|
|
|
+ rules = "、".join(item["id"] for item in hits)
|
|
|
+ names = ";".join(f"{item['id']} {item['name']}" for item in hits)
|
|
|
+ high_risk_rules = {item["id"] for item in hits}
|
|
|
+ if "RP18" in high_risk_rules or "RP21" in high_risk_rules:
|
|
|
+ reuse = "促销/CTA 边界样本;活动可承接,但不要绑定功效承诺"
|
|
|
+ elif {"RP01", "RP03", "RP13"} & high_risk_rules:
|
|
|
+ reuse = "标题边界样本;需弱化为日常营养补充/标签事实后再复制"
|
|
|
+ else:
|
|
|
+ reuse = "标题观察样本,需结合口播、字幕、商品卡确认"
|
|
|
+ return {
|
|
|
+ "riskText": names,
|
|
|
+ "riskRules": rules,
|
|
|
+ "reuse": reuse,
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def load_public_profile_samples(official_names):
|
|
|
+ def load_list(path):
|
|
|
+ if not path.exists():
|
|
|
+ return []
|
|
|
+ try:
|
|
|
+ data = load_json(path)
|
|
|
+ except Exception:
|
|
|
+ return []
|
|
|
+ return data if isinstance(data, list) else []
|
|
|
+
|
|
|
+ all_samples = load_list(PUBLIC_PROFILE_ALL_VIDEOS)
|
|
|
+ new_samples = load_list(PUBLIC_PROFILE_NEW_VIDEOS)
|
|
|
+ seen = set()
|
|
|
+ rows = []
|
|
|
+ for sample in new_samples:
|
|
|
+ video_id = clean(sample.get("videoId"))
|
|
|
+ if not video_id or video_id in seen:
|
|
|
+ continue
|
|
|
+ seen.add(video_id)
|
|
|
+ title = clean(sample.get("title"))
|
|
|
+ author = clean(sample.get("author"))
|
|
|
+ row = {
|
|
|
+ "videoId": video_id,
|
|
|
+ "author": author,
|
|
|
+ "title": title,
|
|
|
+ "url": clean(sample.get("url")),
|
|
|
+ "profileUrl": clean(sample.get("profileUrl")),
|
|
|
+ "source": clean(sample.get("source")) or "public_profile_playwright",
|
|
|
+ "collectedAt": clean(sample.get("collectedAt")),
|
|
|
+ }
|
|
|
+ row.update(classify_account(author, official_names))
|
|
|
+ row.update(claim_boundary_for_row(row))
|
|
|
+ risk = classify_public_title_risk(title)
|
|
|
+ row.update(risk)
|
|
|
+ row["evidenceStatus"] = "公开视频主页标题/账号可见证据;暂无评论、媒体下载和逐字稿,不并入逐字稿黄金案例。"
|
|
|
+ rows.append(row)
|
|
|
+ account_counter = Counter(row.get("accountType", "") for row in rows)
|
|
|
+ return {
|
|
|
+ "exists": PUBLIC_PROFILE_NEW_VIDEOS.exists(),
|
|
|
+ "allCount": len({clean(item.get("videoId")) for item in all_samples if clean(item.get("videoId"))}) or len(rows),
|
|
|
+ "newCount": len(rows),
|
|
|
+ "rows": rows,
|
|
|
+ "accountCounts": [{"accountType": k, "videoCount": v} for k, v in account_counter.most_common()],
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ live = load_json(LIVE_DATA)
|
|
|
+ account_list = load_account_list()
|
|
|
+ official_names = account_list["names"]
|
|
|
+ video_rows = []
|
|
|
+ for row in live["videoRows"]:
|
|
|
+ item = dict(row)
|
|
|
+ item.update(classify_account(item.get("author", ""), official_names))
|
|
|
+ item.update(claim_boundary_for_row(item))
|
|
|
+ item["accountBoundaryNote"] = account_rule_notes(item["accountType"])
|
|
|
+ video_rows.append(item)
|
|
|
+ videos_by_id = {str(row["videoId"]): row for row in video_rows}
|
|
|
+ comments_by_video = defaultdict(list)
|
|
|
+ for row in live["commentRows"]:
|
|
|
+ comments_by_video[str(row.get("videoId"))].append(row)
|
|
|
+
|
|
|
+ transcript_rows = []
|
|
|
+ for transcript_path in sorted((TRANSCRIPT_DIR / "transcripts").glob("*/transcript.json")):
|
|
|
+ data = load_json(transcript_path)
|
|
|
+ video_id = str(data.get("awemeId") or transcript_path.parent.name)
|
|
|
+ video = videos_by_id.get(video_id, {})
|
|
|
+ raw_text = clean(data.get("text"))
|
|
|
+ text = normalize_asr(raw_text)
|
|
|
+ valid = data.get("status") == "ok" and is_valid_transcript(text)
|
|
|
+ modules = classify_modules(text) if valid else ["无效/低信息逐字稿"]
|
|
|
+ rules = match_items(RULE_PATTERNS, text) if valid else []
|
|
|
+ row_obj = {
|
|
|
+ "videoId": video_id,
|
|
|
+ "title": video.get("title", ""),
|
|
|
+ "author": video.get("author", ""),
|
|
|
+ "keyword": video.get("keyword", ""),
|
|
|
+ "skuGroup": video.get("skuGroup", ""),
|
|
|
+ "riskLevel": video.get("riskLevel", ""),
|
|
|
+ "likeCount": video.get("likeCount", 0),
|
|
|
+ "commentCount": video.get("commentCount", 0),
|
|
|
+ "url": video.get("url", ""),
|
|
|
+ "transcriptStatus": data.get("status", ""),
|
|
|
+ "durationMs": data.get("durationMs", 0),
|
|
|
+ "rawText": raw_text,
|
|
|
+ "cleanText": text,
|
|
|
+ "textLength": len(text),
|
|
|
+ "chineseCount": chinese_count(text),
|
|
|
+ "validTranscript": valid,
|
|
|
+ "modules": modules,
|
|
|
+ "moduleText": "、".join(modules),
|
|
|
+ "ruleHits": "、".join(item["id"] for item in rules),
|
|
|
+ "ruleNames": "、".join(item["name"] for item in rules),
|
|
|
+ "rewriteDirection": "",
|
|
|
+ "commentExamples": [c.get("text", "") for c in comments_by_video.get(video_id, [])[:3]],
|
|
|
+ "accountType": video.get("accountType", ""),
|
|
|
+ "accountConfidence": video.get("accountConfidence", ""),
|
|
|
+ "accountRuleLogic": video.get("accountRuleLogic", ""),
|
|
|
+ }
|
|
|
+ row_obj.update(claim_boundary_for_row(row_obj))
|
|
|
+ row_obj["accountBoundaryNote"] = account_rule_notes(row_obj["accountType"])
|
|
|
+ transcript_rows.append(row_obj)
|
|
|
+
|
|
|
+ for row in transcript_rows:
|
|
|
+ row["rewriteDirection"] = build_rewrite(row) if row["validTranscript"] else "不纳入话术结论;仅记录为转写低信息/疑似背景音乐。"
|
|
|
+
|
|
|
+ valid_rows = [row for row in transcript_rows if row["validTranscript"]]
|
|
|
+ invalid_rows = [row for row in transcript_rows if not row["validTranscript"]]
|
|
|
+
|
|
|
+ module_counter = Counter()
|
|
|
+ rule_counter = Counter()
|
|
|
+ account_counter = Counter(row.get("accountType", "") for row in video_rows)
|
|
|
+ valid_account_counter = Counter(row.get("accountType", "") for row in valid_rows)
|
|
|
+ for row in valid_rows:
|
|
|
+ module_counter.update(row["modules"])
|
|
|
+ rule_counter.update([item for item in row["ruleHits"].split("、") if item])
|
|
|
+
|
|
|
+ golden = build_golden_cases(valid_rows)
|
|
|
+
|
|
|
+ run_log = load_json(RUN_LOG) if RUN_LOG.exists() else []
|
|
|
+ run_log_video_ids = {str(row.get("videoId", "")) for row in run_log if row.get("videoId")}
|
|
|
+ transcript_video_ids = {str(row.get("videoId", "")) for row in transcript_rows if row.get("videoId")}
|
|
|
+ attempted_video_ids = run_log_video_ids | transcript_video_ids
|
|
|
+ failed_run_log_ids = {
|
|
|
+ str(row.get("videoId", ""))
|
|
|
+ for row in run_log
|
|
|
+ if row.get("videoId") and row.get("transcriptStatus") != "ok"
|
|
|
+ } - transcript_video_ids
|
|
|
+ rule_pack_stats = rule_pack_table_stats()
|
|
|
+ supplemental_stats = supplemental_strategy_stats()
|
|
|
+ runner_stats = runner_supplement_stats()
|
|
|
+ public_profile_stats = load_public_profile_samples(official_names)
|
|
|
+ summary = {
|
|
|
+ "generatedAt": datetime.now().astimezone().isoformat(),
|
|
|
+ "liveVideoCount": live["summary"]["videoCount"],
|
|
|
+ "liveCommentCount": live["summary"]["commentCount"],
|
|
|
+ "publicProfileVideoCount": public_profile_stats["allCount"],
|
|
|
+ "publicProfileNewVideoCount": public_profile_stats["newCount"],
|
|
|
+ "transcriptAttemptSelectedCount": len(attempted_video_ids),
|
|
|
+ "transcriptFileCount": len(transcript_rows),
|
|
|
+ "validTranscriptCount": len(valid_rows),
|
|
|
+ "invalidTranscriptCount": len(invalid_rows),
|
|
|
+ "runLogOkCount": sum(1 for row in transcript_rows if row.get("transcriptStatus") == "ok"),
|
|
|
+ "runLogFailedCount": len(failed_run_log_ids),
|
|
|
+ "goldenTranscriptCount": len(golden),
|
|
|
+ "rulePackTableCount": rule_pack_stats["tableCount"],
|
|
|
+ "rulePackTableRows": rule_pack_stats["tableRows"],
|
|
|
+ "supplementalStrategyDocExists": supplemental_stats["exists"],
|
|
|
+ "supplementalStrategyDocLines": supplemental_stats["lineCount"],
|
|
|
+ "supplementalStrategyDocHeadings": supplemental_stats["headingCount"],
|
|
|
+ "runnerSupplementExists": runner_stats["exists"],
|
|
|
+ "runnerSupplementStatus": runner_stats["status"],
|
|
|
+ "runnerSupplementVideoCount": runner_stats["videoCount"],
|
|
|
+ "runnerSupplementCommentCount": runner_stats["commentCount"],
|
|
|
+ "runnerSupplementErrorCount": runner_stats["errorCount"],
|
|
|
+ "runnerSupplementQualityGate": runner_stats["qualityGate"],
|
|
|
+ "officialAccountListCount": len(account_list["records"]),
|
|
|
+ "accountCounts": [{"accountType": k, "videoCount": v} for k, v in account_counter.most_common()],
|
|
|
+ "validTranscriptAccountCounts": [{"accountType": k, "validTranscriptCount": v} for k, v in valid_account_counter.most_common()],
|
|
|
+ "publicProfileAccountCounts": public_profile_stats["accountCounts"],
|
|
|
+ "moduleCounts": [{"module": k, "count": v} for k, v in module_counter.most_common()],
|
|
|
+ "ruleCounts": [{"rule": k, "count": v} for k, v in rule_counter.most_common()],
|
|
|
+ }
|
|
|
+
|
|
|
+ report = {
|
|
|
+ "summary": summary,
|
|
|
+ "transcriptRows": transcript_rows,
|
|
|
+ "moduleSummary": summary["moduleCounts"],
|
|
|
+ "ruleSummary": summary["ruleCounts"],
|
|
|
+ "goldenTranscriptRows": golden,
|
|
|
+ "invalidTranscriptRows": invalid_rows,
|
|
|
+ "liveSummary": live["summary"],
|
|
|
+ "commentThemeSummary": live["themeSummary"],
|
|
|
+ "videoRows": video_rows,
|
|
|
+ "commentRows": live["commentRows"],
|
|
|
+ "publicProfileTitleRows": public_profile_stats["rows"],
|
|
|
+ "officialAccountRecords": account_list["records"],
|
|
|
+ }
|
|
|
+ OUT_JSON.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
+ OUT_MD.write_text(build_markdown(report), encoding="utf-8")
|
|
|
+ print(json.dumps({"status": "ok", "output": str(OUT_JSON), "markdown": str(OUT_MD), **summary}, ensure_ascii=False, indent=2))
|
|
|
+
|
|
|
+
|
|
|
+def build_markdown(report):
|
|
|
+ s = report["summary"]
|
|
|
+ golden_rows = report["goldenTranscriptRows"]
|
|
|
+ public_profile_rows = report.get("publicProfileTitleRows", [])
|
|
|
+ golden_by_rule = defaultdict(list)
|
|
|
+ for row in golden_rows:
|
|
|
+ golden_by_rule[row.get("ruleId", row.get("riskRules", ""))].append(row)
|
|
|
+ observed_rule_ids = [row["rule"] for row in report["ruleSummary"]]
|
|
|
+ observed_rule_set = set(observed_rule_ids)
|
|
|
+ all_rule_ids = [f"RP{index:02d}" for index in range(1, 29)]
|
|
|
+ unobserved_rule_ids = [rule_id for rule_id in all_rule_ids if rule_id not in observed_rule_set]
|
|
|
+ account_counts = s.get("accountCounts", [])
|
|
|
+ valid_account_counts = s.get("validTranscriptAccountCounts", [])
|
|
|
+ account_video_map = {row.get("accountType", ""): row.get("videoCount", 0) for row in account_counts}
|
|
|
+ official_video_count = sum(count for account_type, count in account_video_map.items() if account_type.startswith("官方/店铺"))
|
|
|
+ creator_video_count = sum(count for account_type, count in account_video_map.items() if account_type.startswith("达人/内容"))
|
|
|
+ other_store_video_count = sum(
|
|
|
+ count
|
|
|
+ for account_type, count in account_video_map.items()
|
|
|
+ if account_type.startswith("其他店铺") or account_type.startswith("疑似店铺")
|
|
|
+ )
|
|
|
+ lines = [
|
|
|
+ "# 善存抖音逐字稿话术审核分析",
|
|
|
+ "",
|
|
|
+ f"生成时间:{s['generatedAt']}",
|
|
|
+ "",
|
|
|
+ "## 样本与方法",
|
|
|
+ "",
|
|
|
+ f"- 抖音网关实采去重视频:{s['liveVideoCount']} 条;评论:{s['liveCommentCount']} 条。",
|
|
|
+ f"- 公开主页补采:Playwright 已从官方/店铺公开主页抽取 {s.get('publicProfileVideoCount', 0)} 条公开视频列表,其中与既有网关样本去重后新增 {s.get('publicProfileNewVideoCount', 0)} 条标题样本;该批为标题/账号可见证据,未计入评论和逐字稿数量。",
|
|
|
+ f"- 本轮优先转写高互动/高风险/营养包及多 SKU 样本,已生成逐字稿文件:{s['transcriptFileCount']} 条,其中有效口播逐字稿:{s['validTranscriptCount']} 条,低信息/疑似背景音乐:{s['invalidTranscriptCount']} 条。",
|
|
|
+ f"- 参考资料文档包含 {s.get('rulePackTableCount', 0)} 个表格、{s.get('rulePackTableRows', 0)} 行表格样例;本轮黄金边界数据集已从摘要式 12 行扩充为 {s.get('goldenTranscriptCount', 0)} 行,按真实逐字稿的 Rule Pack 命中逐条拆分。",
|
|
|
+ f"- 补充参考稿 `抖音保健品过审黄金公式与边界策略.md` 已纳入结构对齐:已读取,行数={s.get('supplementalStrategyDocLines', 0)},标题数={s.get('supplementalStrategyDocHeadings', 0)}。",
|
|
|
+ f"- 本轮补跑说明:没有在网关权限失败后停止;已继续尝试关键词、官方账号、已知 sec_user_id、单视频详情、搜索页和公开主页。实时网关仍显示权限不足或搜索失败(runner 状态={s.get('runnerSupplementStatus', '') or '未生成'},错误数={s.get('runnerSupplementErrorCount', 0)}),但公开主页路径新增 {s.get('publicProfileNewVideoCount', 0)} 条官方/店铺标题样本;新增有效逐字稿仍为 0,逐字稿结论仍以现有 {s['transcriptFileCount']} 个逐字稿文件为证据。",
|
|
|
+ "- 媒体链路:从抖音搜索原始返回恢复视频播放地址,使用 ffmpeg/媒体直传处理后调用 iflytek-gateway 转写;逐字稿用于话术级审核分析。",
|
|
|
+ "- 限制说明:ASR 存在同音误识别,例如“营养包”可能识别成“阴阳包”;本报告已做轻量清洗,但关键结论仍建议结合视频画面和品牌原稿复核。",
|
|
|
+ "",
|
|
|
+ "## 采集执行审计(回应“是否按要求采集”)",
|
|
|
+ "",
|
|
|
+ "结论:本轮有按要求做抖音采集、补采、转写和评论复核,不是只基于资料文档脑补。当前可交付证据来自抖音网关已实采样本;追加扩量已执行多条路径,但实时网关权限/搜索链路未返回新增有效视频,所以本版把“已执行动作”和“新增数据受阻”分开呈现,避免把采集失败误写成未采集。",
|
|
|
+ "",
|
|
|
+ "| 采集/分析要求 | 已执行动作 | 当前证据 | 审视判断 | 后续补齐条件 |",
|
|
|
+ "|---|---|---|---|---|",
|
|
|
+ f"| 善存主品牌、营养包及其他 SKU | 以善存、善存 PRO/每日营养包、多维、男士/女士/50+、海外/跨境、小佳维等作为样本池入口。 | 网关实采去重视频 {s['liveVideoCount']} 条、评论 {s['liveCommentCount']} 条。 | 已形成本版证据底座。 | 后续继续补银善存/大脑、官方店铺专题和特殊人群专题。 |",
|
|
|
+ f"| 官方/店铺账号与达人账号分层 | 读取 `店铺与账号.xlsx`,将视频作者按官方/店铺、达人/内容、其他店铺/分销拆分。 | 官方/店铺清单 {s.get('officialAccountListCount', 0)} 条;样本中官方/店铺 {official_video_count} 条、达人/内容 {creator_video_count} 条、其他店铺/分销 {other_store_video_count} 条。 | 已满足老板强调的账号身份区分。 | 仍需后台蓝V认证、授权关系和具体产品广审资料确认。 |",
|
|
|
+ f"| 高风险话术逐字稿分析 | 优先选择高互动、高风险、营养包、多 SKU 和边界 claim 样本做 ASR 转写。 | 已选 {s.get('transcriptAttemptSelectedCount', 0)} 条;生成逐字稿 {s['transcriptFileCount']} 条,有效口播 {s['validTranscriptCount']} 条,低信息 {s['invalidTranscriptCount']} 条。 | 已从标题/标签推进到真实口播。 | 8 条旧样本媒体回查未找到,需新链接或可访问媒体源。 |",
|
|
|
+ f"| 黄金数据集与样本案例 | 按 Rule Pack 命中将有效逐字稿拆成案例行,而不是抽少量展示。 | 黄金边界案例 {s.get('goldenTranscriptCount', 0)} 行,覆盖当前有效逐字稿命中的 {len(observed_rule_ids)}/28 个 RP。 | 已满足“黄金数据集和样本案例”的 MD 版本要求。 | Excel 等 MD 口径确认后再按同字段重建。 |",
|
|
|
+ f"| 评论与改进空间 | 对已投放视频评论做主题复核,回看口播诱因和评论区承接。 | 评论 {s['liveCommentCount']} 条;已输出评论主题、典型评论、口播诱因、客服/置顶回复建议。 | 已覆盖用户反馈和改进空间。 | 下一轮可按高赞、负面体验、功效追问做更深分层。 |",
|
|
|
+ f"| 定向补采:官方、银善存/大脑、强 CTA、特殊人群 | 已尝试项目采集脚本、通用 runner、已知账号/关键词、单视频详情、搜索页和公开主页路径。 | runner 状态 `{s.get('runnerSupplementStatus', '') or '未生成'}`,错误数 {s.get('runnerSupplementErrorCount', 0)};公开主页新增标题样本 {s.get('publicProfileNewVideoCount', 0)} 条;新增有效逐字稿 0 条。 | 已执行但受权限/搜索链路阻塞,不能写成全网穷尽覆盖。 | 恢复抖音网关权限,或由客户提供可访问视频链接/文件。 |",
|
|
|
+ "",
|
|
|
+ "对老板的可交付判断:方向和结构已经按要求来,证据也用足了当前可访问数据;剩余缺口主要不是 MD 结构,而是新增抖音实时数据权限、银善存/大脑专题样本量,以及客户侧产品批文/蓝帽/广审/商品页资料。",
|
|
|
+ "",
|
|
|
+ "## 逐字稿结论",
|
|
|
+ "",
|
|
|
+ "本轮口径已调整:抖音采集到的是“已经发出来的成功案例”,不是待审核稿件。因此分析重点不是告诉业务“这条怎么改更好过审”,而是反推这些已发布稿件为什么能踩在边界上发出来:它用了什么账号身份、产品资质、卖点承载、隐喻画面、人群标签、弱化措辞或商品页承接,形成了怎样的成功经验。",
|
|
|
+ "",
|
|
|
+ "真实口播比标题/标签暴露出更强的边界动作:不少视频不只在标题里出现“精力满格、气血满格、代谢、甲流、一粒补充”,口播中还会进一步强化“全部都调配好了、每天一粒/一包就够、身体底子/皮肤状态慢慢养稳、明星都在吃、直播间快冲”等表达。这里要区分两类结论:一类是可迁移经验,例如“成分事实 + 标签建议 + 官方/客服承接”;另一类是撞审/边界样本,例如达人强 CTA、疾病词、未备案范围功效隐喻,只能作为风险边界观察,不能直接复制。",
|
|
|
+ "",
|
|
|
+ "本轮补齐后,MD 不再把黄金边界数据集压缩成少量展示样本,而是把每条有效逐字稿按 Rule Pack 命中拆成成功边界案例。这样做的好处是:同一视频里“每天一粒就够”“26 种营养”“直播间快冲”“30+女性”“海外版/正品”等风险点会各自成行,方便回流到规则测试、达人 brief、审核标注和后续 Excel 明细。",
|
|
|
+ "",
|
|
|
+ "## 目标口径校正",
|
|
|
+ "",
|
|
|
+ "| 旧口径 | 新口径 | 报告处理方式 |",
|
|
|
+ "|---|---|---|",
|
|
|
+ "| 怎么改这条稿件更好过审 | 为什么这条已发布稿件能发出来 | 每个黄金案例增加“账号类型、claim 类型、承载方式、边界逻辑、复用判断”。 |",
|
|
|
+ "| 把红灯都写成删改建议 | 区分可复制经验和撞审边界 | 官方/店铺账号若有保健食品批文与广审备案,部分功能 claim 可绿灯;达人账号不能借官方备案直接说功能或强导购。 |",
|
|
|
+ "| 按规则命中给风险 | 按账号×产品×卖点×人群×claim 组合找边界 | 增加策略公式清单,帮助后续批量跑样本时归纳成功路径。 |",
|
|
|
+ "| 不区分账号身份 | 明确蓝V/官方店铺、疑似店铺、达人/内容账号不同边界 | 使用 `店铺与账号.xlsx` 做官方/店铺账号清单匹配;未在清单但名称像店铺的账号标为待核验。 |",
|
|
|
+ "",
|
|
|
+ "## 老板要求逐条对照结论",
|
|
|
+ "",
|
|
|
+ "结论:当前 MD 已按老板补充方向来做,核心定位是“从抖音已发布成功案例反推审核边界和可复制经验”,不是单纯写敏感词删改。需要对外说明的是:当前样本是已采集可用数据,不等于全网最终覆盖;新增补采受抖音网关权限/搜索失败影响,后续恢复权限后可继续扩容。上方“采集执行审计”已单独回答是否按要求采集。",
|
|
|
+ "",
|
|
|
+ "| 老板/客户要求 | 当前文档落点 | 完成度 | 还需要补什么 |",
|
|
|
+ "|---|---|---|---|",
|
|
|
+ f"| 目标品牌善存,覆盖营养包及其他 SKU | 样本覆盖善存 PRO/每日营养包、复合维生素/多维、男士/女士/50+、海外/跨境版本、小佳维等;总计 {s['liveVideoCount']} 条去重视频。 | 已覆盖当前可用样本 | 后续按缺口补采银善存/大脑、官方店铺、特殊人群和强 CTA 专题。 |",
|
|
|
+ f"| 抖音渠道已发布稿件如何通过审核 | `逐字稿结论`、`目标口径校正`、`过审边界策略公式`、`Rule Pack 逐条复盘`均按“已发布成功案例反推边界”组织。 | 已落实 | 需要品牌提供原始待审稿/修改前稿时,才能进一步做“修改前后差异”。 |",
|
|
|
+ f"| 需要黄金数据集和样本案例 | 已把有效逐字稿按 Rule Pack 命中拆成 {s.get('goldenTranscriptCount', 0)} 行黄金边界案例,并保留重点逐字稿样本、视频 ID、触发词、账号类型、claim 类型和复用判断。 | 已落实 | Excel 待 MD 方向确认后再补齐筛选字段。 |",
|
|
|
+ f"| 是否要转逐字稿并分析话术 | 已生成逐字稿 {s['transcriptFileCount']} 条,其中有效口播 {s['validTranscriptCount']} 条;分析从标题/标签推进到真实口播。 | 已落实 | ASR 同音误识别处建议结合视频画面和品牌原稿复核。 |",
|
|
|
+ f"| 已投放稿件用户评论如何,有无改进空间 | 已基于 {s['liveCommentCount']} 条评论做主题复核,拆出泛互动、正品/渠道、购买路径、达人明星、低质互动、体验/副作用等,并给置顶评论/客服回复建议。 | 已落实 | 可在下一轮按“高赞评论、负面体验、功效追问”继续深挖。 |",
|
|
|
+ f"| 结合资料、规则和客户理想稿 | 已读取 Rule Pack 文档 {s.get('rulePackTableCount', 0)} 个表格/{s.get('rulePackTableRows', 0)} 行样例、补充参考稿 {s.get('supplementalStrategyDocLines', 0)} 行/{s.get('supplementalStrategyDocHeadings', 0)} 个标题,并接入官方/店铺账号清单 {len(report.get('officialAccountRecords', []))} 条。 | 已落实 | 仍需客户补产品批文、蓝帽/备案范围、广审批件、商品页标签资料。 |",
|
|
|
+ f"| 尽可能多的视频样本 | 当前已有 {s['liveVideoCount']} 条视频、{s['liveCommentCount']} 条评论;已做关键词、账号、单视频详情、搜索页、公开主页等补采尝试。 | 已执行,新增受阻 | 本轮补采因 runner 状态 `{s.get('runnerSupplementStatus', '') or '未生成'}`、错误数 {s.get('runnerSupplementErrorCount', 0)} 未新增有效样本,需恢复抖音网关权限或提供视频链接。 |",
|
|
|
+ "| 先补 MD,Excel 后确认 | 本轮只优先补 MD,并把后续 Excel 字段放进复用判断和下一步建议。 | 已按用户要求执行 | 等你确认 MD 口径后再生成/补齐 Excel。 |",
|
|
|
+ "",
|
|
|
+ "## 补充需求对齐:客户理想稿结构",
|
|
|
+ "",
|
|
|
+ "新增参考稿的核心不是单一品牌复盘,而是要沉淀一套“合规审核 + 脚本修改建议”的技能框架。本版将善存报告从样本复盘升级为三层结构:先给法规和账号/产品/claim 的通用判断框架,再给善存已发布案例的边界证据,最后给可系统化实现的功能模块。",
|
|
|
+ "",
|
|
|
+ "| 客户理想稿模块 | 本版善存报告承接方式 |",
|
|
|
+ "|---|---|",
|
|
|
+ "| 前置合规基础 | 增加强制提示语、产品身份区分、账号资质要求,明确不能把跨境/普通食品/蓝帽保健食品混用。 |",
|
|
|
+ "| 蓝V vs 达人双轨制 | 使用官方账号清单区分官方/店铺、疑似店铺、达人/内容账号,给出不同 claim 上限。 |",
|
|
|
+ "| 功效宣称红绿灯 | 将 claim 拆成 Level 0 到 Level 5,分别判断生活场景、食用体验、成分事实、备案内功能、备案外功能和疾病治疗。 |",
|
|
|
+ "| 撞审边界技巧 | 将概念替换、比喻暗示、成分事实、标签回流、身份脱敏、短脚本冲量等提炼为技巧库。 |",
|
|
|
+ "| 系统功能需求 | 追加“合规审核与脚本建议系统功能模块”,方便后续做成技能、工作流或产品模块。 |",
|
|
|
+ "",
|
|
|
+ "### 话语体系对照(含法规依据)",
|
|
|
+ "",
|
|
|
+ "参考稿的标准不是简单列敏感词,而是把“高危话术 -> 安全话术 -> 法规/平台依据 -> 善存样本落点”做成可复用替换库。以下按善存营养包、多维、海外版和达人/官方账号的真实样本改写。",
|
|
|
+ "",
|
|
|
+ "| 维度 | 高危话术 | 安全话术 | 法规/规则依据 | 善存样本处理 |",
|
|
|
+ "|---|---|---|---|---|",
|
|
|
+ ]
|
|
|
+ for row in SPEECH_SYSTEM_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} | {row[4]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "### 前置合规基础",
|
|
|
+ "",
|
|
|
+ "保健食品广告/推广内容需显著标明:**保健食品不是药物,不能代替药物治疗疾病**。这条不是“降低风险的建议”,而是保健食品广告内容里的硬约束;但它也不是万能护身符,达人账号即使加提示语,也不能因此获得保健食品广告发布或代言推荐资格。实际发布仍需以品牌法务、RA、广审批件、商品页和平台规则确认。",
|
|
|
+ "",
|
|
|
+ "| 产品类型 | 识别依据 | claim 上限 | 善存报告处理方式 |",
|
|
|
+ "|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in PRODUCT_TYPE_BOUNDARY_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "### 账号类型双轨制",
|
|
|
+ "",
|
|
|
+ "| 账号类型 | 资质/身份前提 | 可承接内容 | 不可承接内容 |",
|
|
|
+ "|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in ACCOUNT_TRACK_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "### 功效宣称五级层次",
|
|
|
+ "",
|
|
|
+ "| 层级 | 类型 | 示例 | 默认判断 | 关键边界 |",
|
|
|
+ "|---|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in CLAIM_LEVEL_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} | {row[4]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "善存现有逐字稿里最常见的是 Level 1 成分事实、Level 0/0.5 生活体验和 Level 4 边界功效感混在一起。例如“26 种营养”本身是 Level 1,但一旦接成“每天一粒就够、全部补齐、状态变好”,就上升到 Level 4 风险;“明星都在吃”若变成适用性或效果证明,则进入达人/代言红线。",
|
|
|
+ "",
|
|
|
+ "### 核心判断矩阵(含账号×渠道双维度)",
|
|
|
+ "",
|
|
|
+ "同一句话不能只看词,还要同时看账号身份、产品属性、claim 层级和发布承接。以下矩阵用于把已发布善存样本拆成“可复制、需核验、边界样本、不可复制”。",
|
|
|
+ "",
|
|
|
+ "| 宣称类型 | 默认状态 | 官方/店铺账号 | 达人/内容账号 | 前提条件 | 本报告复用结论 |",
|
|
|
+ "|---|---|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in CORE_JUDGMENT_MATRIX_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} | {row[4]} | {row[5]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "### 10 大技巧中的善存适配版",
|
|
|
+ "",
|
|
|
+ "| 技巧 | 公式 | 善存适配示例 | 风险 | 样本观察 |",
|
|
|
+ "|---|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in BOUNDARY_TECHNIQUE_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} | {row[4]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "### 合规审核与脚本建议系统功能模块",
|
|
|
+ "",
|
|
|
+ "| 模块 | 输入/动作 | 输出价值 |",
|
|
|
+ "|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in SYSTEM_FUNCTION_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "## 三个核心合规原则(确保所有策略落地)",
|
|
|
+ "",
|
|
|
+ "参考稿里最值得保留的是三个原则:不是为了写得像法规摘要,而是为了让脚本、评论、商品卡、达人 brief 和审核系统都能共用同一个判断顺序。",
|
|
|
+ "",
|
|
|
+ ])
|
|
|
+ for principle in COMPLIANCE_PRINCIPLE_ROWS:
|
|
|
+ lines.extend([
|
|
|
+ f"### {principle['title']}",
|
|
|
+ "",
|
|
|
+ f"- 原则说明:{principle['content']}",
|
|
|
+ f"- 发布前核验:{principle['check']}",
|
|
|
+ f"- 善存当前证据:{principle['evidence']}",
|
|
|
+ "",
|
|
|
+ ])
|
|
|
+ lines.extend([
|
|
|
+ "## 规则依据底座",
|
|
|
+ "",
|
|
|
+ "以下规则只作为边界判断底座,不能替代品牌法务、产品注册/备案资料、广告审查批件和平台最新规则。尤其是“功能宣称”:一般情况下属于红灯高风险;但如果产品是保健食品,且该功能在注册证书/备案凭证、说明书和广审备案范围内,并由官方/店铺账号按审查内容一致发布,则可以形成绿灯表达。",
|
|
|
+ "",
|
|
|
+ "| 依据 | 用在本报告中的作用 | 链接 |",
|
|
|
+ "|---|---|---|",
|
|
|
+ ])
|
|
|
+ for ref in LEGAL_REFERENCES:
|
|
|
+ lines.append(f"| {ref['name']} | {ref['use']} | {ref['url']} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "## 账号身份分层",
|
|
|
+ "",
|
|
|
+ f"本轮读取 `店铺与账号.xlsx` 后,识别到 {s.get('officialAccountListCount', 0)} 条官方/店铺账号记录。报告将清单命中的作者暂按“官方/店铺账号(清单匹配)”处理;是否蓝V、是否具备对应广告备案能力,仍需以账号后台认证和产品广审材料复核。",
|
|
|
+ "",
|
|
|
+ "| 账号类型 | 视频样本数 | 有效逐字稿数 | 可说边界 | 不宜复制边界 |",
|
|
|
+ "|---|---:|---:|---|---|",
|
|
|
+ ])
|
|
|
+ valid_account_map = {row["accountType"]: row.get("validTranscriptCount", 0) for row in valid_account_counts}
|
|
|
+ for row in account_counts:
|
|
|
+ account_type = row["accountType"]
|
|
|
+ if account_type.startswith("官方/店铺"):
|
|
|
+ allow = "可承接商品资质、标签说明、批文/备案范围内的功能 claim、活动规则和客服说明。"
|
|
|
+ avoid = "不能超出备案/广审范围;不能口播字幕画面与商品页不一致。"
|
|
|
+ elif account_type.startswith("疑似店铺") or account_type.startswith("其他店铺"):
|
|
|
+ allow = "可观察为店铺型边界样本,但要先核验授权、账号认证、商品资质和广告审查范围。"
|
|
|
+ avoid = "不能默认套用官方账号能力;不把未核验功能宣称写成绿灯经验。"
|
|
|
+ else:
|
|
|
+ allow = "可说个人体验、生活场景、成分事实、中性科普和非强导购互动。"
|
|
|
+ avoid = "不能借官方备案说功能;不宜强 CTA、价格催单、治疗/预防、保证效果。"
|
|
|
+ lines.append(f"| {account_type} | {row['videoCount']} | {valid_account_map.get(account_type, 0)} | {allow} | {avoid} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "## 过审边界策略公式",
|
|
|
+ "",
|
|
|
+ "可把后续样本按以下公式打标签:`账号身份 × 产品属性 × 卖点类型 × 人群标签 × 功效 claim × 承载方式 × 购买引导强度 = 发布边界`。",
|
|
|
+ "",
|
|
|
+ "### 账号类型 × 宣称层次 → 过审策略",
|
|
|
+ "",
|
|
|
+ "| 账号类型 | 宣称层次 | 过审策略 | 执行动作 |",
|
|
|
+ "|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in ACCOUNT_CLAIM_STRATEGY_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "### 产品属性 × 目标人群 → 过审策略",
|
|
|
+ "",
|
|
|
+ "| 产品属性 | 目标人群 | 可承接卖点 | 高危延伸 | 过审策略 |",
|
|
|
+ "|---|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in PRODUCT_PERSON_STRATEGY_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} | {row[4]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "### 卖点维度 × 话术替换公式(含法规依据)",
|
|
|
+ "",
|
|
|
+ "| 卖点维度 | 高危话术 | 替换公式 | 规则依据 | 适用账号 |",
|
|
|
+ "|---|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in SELLING_POINT_REWRITE_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} | {row[4]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "### 善存已发布策略公式库",
|
|
|
+ "",
|
|
|
+ "| 公式 | 适用条件 | 已发布成功经验 | 复用判断 |",
|
|
|
+ "|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in STRATEGY_FORMULA_ROWS:
|
|
|
+ lines.append(f"| {row['formula']} | {row['condition']} | {row['experience']} | {row['reuse']} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "公式使用说明:同一个视频可以命中多个公式,例如“官方店铺账号 + 海外版 + 26 种成分 + 直播活动”应拆成成分事实、跨境版本、价格活动三条边界分别判断;达人账号出现同样内容时,不能自动继承官方/店铺账号的功能宣称能力。",
|
|
|
+ "",
|
|
|
+ "对老板关心的“撞审/边界通过”,本报告不把它写成稳定绿灯,而是用复用判断分为四类:`高可复制`、`需资料核验后复制`、`边界样本需逐条审核`、`不可复制`。后续 Excel 可把这四类做成筛选字段。",
|
|
|
+ "",
|
|
|
+ "## AI可用的过审策略公式清单",
|
|
|
+ "",
|
|
|
+ "这一节把前面的人工判断压缩成可编码规则。后续做成系统时,可以先用规则引擎打底,再让模型只负责解释、归类和生成改写建议,避免模型自由发挥法规结论。",
|
|
|
+ "",
|
|
|
+ ])
|
|
|
+ for group in AI_FORMULA_GROUPS:
|
|
|
+ lines.extend([
|
|
|
+ f"### {group['title']}",
|
|
|
+ "",
|
|
|
+ "| 规则 |",
|
|
|
+ "|---|",
|
|
|
+ ])
|
|
|
+ for item in group["items"]:
|
|
|
+ lines.append(f"| {item} |")
|
|
|
+ lines.append("")
|
|
|
+ lines.extend([
|
|
|
+ "## 与资料文档对齐",
|
|
|
+ "",
|
|
|
+ f"- 资料文件 `QW1_Rule_Pack_ExamplesV0.1.docx` 当前识别到 {s.get('rulePackTableCount', 0)} 个表格、{s.get('rulePackTableRows', 0)} 行表格内容。该文档的结构是“Rule Pack 总览 + 每个 RP 的红灯反例/黄灯样例/绿灯参考/识别线索/推荐改法”。",
|
|
|
+ f"- 当前抖音逐字稿样本命中 {len(observed_rule_ids)}/28 个 RP,扩充后的黄金边界数据集为 {s.get('goldenTranscriptCount', 0)} 行。它不是复制资料文档的规则行,而是把善存抖音真实口播补成可训练案例。",
|
|
|
+ "- 行数仍低于资料文档总行数,是因为本轮只基于已采集且有效转写的善存抖音样本;未出现的 RP 不应强行虚构案例,而应标记为“待补采/待人工构造测试语料”。",
|
|
|
+ "- 因此本版 MD 的置信度提升点在于:每个黄金案例都有真实视频、真实逐字稿上下文、触发词、Rule ID、账号类型、claim 类型、复用判断和边界经验,不再只有摘要结论。",
|
|
|
+ "",
|
|
|
+ "## 审核口径总览",
|
|
|
+ "",
|
|
|
+ "| Rule | 默认状态 | 命中逐字稿数 | 核心触发 | 成功边界经验 |",
|
|
|
+ "|---|---|---:|---|---|",
|
|
|
+ ])
|
|
|
+ for row in report["ruleSummary"]:
|
|
|
+ rule_id = row["rule"]
|
|
|
+ rule_cases = golden_by_rule.get(rule_id, [])
|
|
|
+ trigger_samples = []
|
|
|
+ for case in rule_cases:
|
|
|
+ for item in (case.get("triggerText") or "").split("、"):
|
|
|
+ item = clean(item)
|
|
|
+ if item and item not in trigger_samples:
|
|
|
+ trigger_samples.append(item)
|
|
|
+ if len(trigger_samples) >= 5:
|
|
|
+ break
|
|
|
+ if len(trigger_samples) >= 5:
|
|
|
+ break
|
|
|
+ trigger_text = "、".join(trigger_samples) or "见逐字稿上下文"
|
|
|
+ lines.append(
|
|
|
+ f"| {rule_id} {rule_name(rule_id)} | {rule_default_status(rule_id)} | {row['count']} | {snippet(trigger_text, 80)} | {RULE_REWRITE_TEMPLATES.get(rule_id, '回到标签、包装、客服和官方说明口径。')} |"
|
|
|
+ )
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "## 未命中规则与补采建议",
|
|
|
+ "",
|
|
|
+ "以下 RP 在本轮有效逐字稿里没有稳定命中,不代表品牌没有风险,而是当前样本没有足够证据。为了避免“看起来覆盖很全但其实是编造”,本版不为这些 RP 虚构黄金案例。",
|
|
|
+ "",
|
|
|
+ "| 未命中 RP | 默认状态 | 建议补采方向 |",
|
|
|
+ "|---|---|---|",
|
|
|
+ ])
|
|
|
+ for rule_id in unobserved_rule_ids:
|
|
|
+ guide = UNOBSERVED_RULE_SAMPLING_GUIDE.get(
|
|
|
+ rule_id,
|
|
|
+ "继续采集对应风险场景的视频;如业务需要,可用资料文档红黄绿样例人工构造离线测试集。",
|
|
|
+ )
|
|
|
+ lines.append(
|
|
|
+ f"| {rule_id} {rule_name(rule_id)} | {rule_default_status(rule_id)} | {guide} |"
|
|
|
+ )
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "## 品牌级成功经验原则",
|
|
|
+ "",
|
|
|
+ "| 场景 | 已发布稿件常见边界 | 成功经验 | 评论区承接 |",
|
|
|
+ "|---|---|---|---|",
|
|
|
+ "| 善存营养包/PRO 营养包 | “一包就够、全部调配好了、明星都在吃、状态变好”会同时触发一包解决感、达人背书和功效承诺。 | 成功稿件通常把它包装成“省心/方便/日常补充/标签建议”,不直接写成治疗或确定改善。 | 置顶说明适用人群、每日建议、版本差异和客服入口。 |",
|
|
|
+ "| 善存复合维生素/多维 | “一粒补充 26 种、每天一粒就够、冬天也要爱自己”容易把成分数量推成补全或效果。 | 成功稿件保留“26种/22种/一粒”等事实,但把结果承诺让位给商品页、标签和日常营养补充语境。 | 对“效果怎么样”统一回复为不承诺特定效果,建议结合饮食和作息。 |",
|
|
|
+ "| 30+/40+/50+ 人群 | “30+女生、40+精力、老人/爸妈/长辈都适合”容易一概而论。 | 成功稿件把人群作为标签和选购入口,不把人群标签推导为所有人适合或一定有效。 | 用 FAQ 区分 30+/40+/50+、男女版、海外版与国内版。 |",
|
|
|
+ "| 海外/跨境/正品 | “海外版更好、国内外通用、大品牌有保障”容易模糊版本、资质和渠道。 | 成功稿件把海外/正品表达放到渠道、包装、喷码、效期和客服承接,不把版本差异说成效果差异。 | 补充真伪查询、效期、版本差异、售后入口。 |",
|
|
|
+ "| 直播间促销 | “快冲、拍一发四、数量有限、下单就解决状态”容易把促销和功效绑定。 | 官方/店铺号可承接活动规则;达人号若出现强 CTA,应标记为边界样本,不能作为稳定经验直接复制。 | 置顶购买路径,不在评论区反复承诺功效或价格。 |",
|
|
|
+ "",
|
|
|
+ "## 话术模块分布",
|
|
|
+ "",
|
|
|
+ "| 模块 | 有效逐字稿数 |",
|
|
|
+ "|---|---:|",
|
|
|
+ ])
|
|
|
+ for row in report["moduleSummary"]:
|
|
|
+ lines.append(f"| {row['module']} | {row['count']} |")
|
|
|
+ lines.extend(["", "## Rule Pack 命中分布", "", "| 规则 | 命中逐字稿数 |", "|---|---:|"])
|
|
|
+ for row in report["ruleSummary"]:
|
|
|
+ lines.append(f"| {row['rule']} | {row['count']} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "## Rule Pack 逐条复盘",
|
|
|
+ "",
|
|
|
+ "这一节把资料文档里的红黄绿思路翻译成善存抖音口播的真实场景。每个 RP 先给判断口径,再列出本轮逐字稿里最有代表性的 3 条触发样本,方便后续和审核、法务或达人团队逐条校准。",
|
|
|
+ "",
|
|
|
+ ])
|
|
|
+ for row in report["ruleSummary"]:
|
|
|
+ rule_id = row["rule"]
|
|
|
+ rule_cases = golden_by_rule.get(rule_id, [])
|
|
|
+ lines.extend([
|
|
|
+ f"### {rule_id}|{rule_name(rule_id)}",
|
|
|
+ "",
|
|
|
+ f"- 默认动作:{rule_default_status(rule_id)},{rule_audit_action(rule_id)}。",
|
|
|
+ f"- 本轮命中:{row['count']} 条有效逐字稿;高频模块:{RULE_MODULE_MAP.get(rule_id, '按上下文判断')}。",
|
|
|
+ f"- 边界经验:{RULE_REWRITE_TEMPLATES.get(rule_id, '回到标签、包装、客服和官方说明口径。')}",
|
|
|
+ "",
|
|
|
+ "| 案例ID | 账号类型 | 来源 | 触发片段 | 可迁移经验 |",
|
|
|
+ "|---|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for case in rule_cases[:3]:
|
|
|
+ lines.append(
|
|
|
+ f"| {case['id']} | {case.get('accountType', '')} | {case['author']}:{snippet(case['sourceTitle'], 36)} | {snippet(case.get('triggerText') or case['originalTranscriptSnippet'], 90)} | {snippet(case.get('successExperience', ''), 140)} |"
|
|
|
+ )
|
|
|
+ lines.append("")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ f"## 逐字稿成功边界案例集({s.get('goldenTranscriptCount', 0)} 行)",
|
|
|
+ "",
|
|
|
+ "说明:以下不是按视频抽 12 条,也不是给待审稿写“建议通过版”;它按“有效逐字稿 × Rule Pack 命中”拆行,用已发布内容反推成功边界。同一条视频同时命中多个 RP 时,会沉淀为多条可训练、可复核的边界案例。",
|
|
|
+ "",
|
|
|
+ "| ID | Rule | 账号类型 | claim类型 | 复用判断 | 来源 | 触发片段 | 边界逻辑/成功经验 |",
|
|
|
+ "|---|---|---|---|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in report["goldenTranscriptRows"]:
|
|
|
+ lines.append(
|
|
|
+ f"| {row['id']} | {row.get('ruleId', row['riskRules'])} {row.get('ruleName', '')} | {row.get('accountType', '')} | {row.get('claimType', '')} | {row.get('reusability', '')} | {row['author']}:{snippet(row['sourceTitle'], 32)} | {snippet(row.get('triggerText', ''), 60)} | {snippet(row.get('successExperience', ''), 180)} |"
|
|
|
+ )
|
|
|
+ lines.extend(["", "## 重点逐字稿样本", ""])
|
|
|
+ for row in sorted([r for r in report["transcriptRows"] if r["validTranscript"]], key=lambda r: (r["likeCount"], r["commentCount"]), reverse=True)[:18]:
|
|
|
+ lines.extend([
|
|
|
+ f"### {row['author']}|{snippet(row['title'], 50)}",
|
|
|
+ "",
|
|
|
+ f"- 视频 ID:{row['videoId']};互动:{row['likeCount']} 赞 / {row['commentCount']} 评论;SKU:{row['skuGroup']}",
|
|
|
+ f"- 账号类型:{row.get('accountType', '')};claim 类型:{row.get('claimType', '')};边界逻辑:{row.get('boundaryLogic', '')}",
|
|
|
+ f"- 话术模块:{row['moduleText']}",
|
|
|
+ f"- 命中规则:{row['ruleHits']}({row['ruleNames']})",
|
|
|
+ f"- 逐字稿摘录:{snippet(row['cleanText'], 260)}",
|
|
|
+ f"- 成功经验观察:{account_rule_notes(row.get('accountType', ''))} {row.get('claimCarrier', '')}",
|
|
|
+ "",
|
|
|
+ ])
|
|
|
+ lines.extend([
|
|
|
+ "## 评论改进空间复核",
|
|
|
+ "",
|
|
|
+ "| 评论主题 | 评论数 | 典型评论 | 口播诱因 | 改进动作 | 置顶/客服回复建议 |",
|
|
|
+ "|---|---:|---|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in report["commentThemeSummary"]:
|
|
|
+ sample_comments = " / ".join(snippet(item, 32) for item in (row.get("sampleComments") or [])[:3])
|
|
|
+ lines.append(
|
|
|
+ f"| {row['theme']} | {row['commentCount']} | {sample_comments} | {COMMENT_TRANSCRIPT_TRIGGERS.get(row['theme'], '由口播、标题、标签和商品页共同触发,需结合视频上下文复核。')} | {row['opportunity']} | {COMMENT_REPLY_TEMPLATES.get(row['theme'], '先确认用户真实问题,再回到商品页、包装标签和客服说明。')} |"
|
|
|
+ )
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "逐字稿确认用户评论里的疑问并不是孤立售后问题,而是被口播触发:当口播强化“就这一包/一粒就够/明星都在吃/状态变好/直播间快冲”时,评论区自然追问功效、正品、版本、适用人群和购买路径。后续投放建议同步改三处:口播降风险、置顶评论答疑、商品卡/客服承接版本和标签信息。",
|
|
|
+ "",
|
|
|
+ "评论区不建议用“亲测有效、放心吃、适合所有人、闭眼入、快冲就完事”这类短回复补火。更稳的做法是把评论分成三类:真实购买疑问交给商品页/客服,功效健康诉求回到日常营养补充和专业咨询,低质互动只做轻回应或不回应。",
|
|
|
+ "",
|
|
|
+ "## 官方/店铺账号清单对照",
|
|
|
+ "",
|
|
|
+ "以下来自 `店铺与账号.xlsx`,用于本轮账号类型判定。清单命中代表“可按官方/店铺账号逻辑分析”,但不自动等同于所有功能 claim 都能说;仍需匹配具体产品批文、广审备案和平台发布身份。",
|
|
|
+ "",
|
|
|
+ "| 店铺 | 直播账号 | 抖音号ID |",
|
|
|
+ "|---|---|---|",
|
|
|
+ ])
|
|
|
+ for row in report.get("officialAccountRecords", []):
|
|
|
+ lines.append(f"| {row.get('store', '')} | {row.get('liveAccount', '')} | {row.get('douyinId', '')} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "## 自检清单(发布前逐条核验)",
|
|
|
+ "",
|
|
|
+ "这份清单用于把黄金数据集变成审核 SOP:每条脚本、标题、字幕、商品卡和评论区回复发布前都按同一顺序扫一遍。命中红灯项时,不建议靠谐音、错别字或画面暗示绕审;应回到产品资料、标签、广审和账号资质。",
|
|
|
+ "",
|
|
|
+ "| 序号 | 核验问题 | 处理动作 |",
|
|
|
+ "|---:|---|---|",
|
|
|
+ ])
|
|
|
+ for row in SELF_CHECK_ROWS:
|
|
|
+ lines.append(f"| {row[0]} | {row[1]} | {row[2]} |")
|
|
|
+ lines.extend([
|
|
|
+ "",
|
|
|
+ "## 下一步建议",
|
|
|
+ "",
|
|
|
+ "1. 继续多跑样本时优先按“官方/店铺账号、疑似店铺账号、达人账号”分层采集,避免把不同账号身份的成功经验混在一起。",
|
|
|
+ "2. 对每条新增样本都打 `账号身份 × 产品属性 × 卖点类型 × 人群标签 × 功效 claim × 承载方式 × 购买引导强度` 标签,累计成可筛选的策略公式库。",
|
|
|
+ "3. 对官方/店铺账号样本补齐产品批文、保健食品功能、广审备案范围和商品页信息,区分“备案内绿灯表达”和“超范围隐喻/撞审表达”。",
|
|
|
+ "4. 对达人样本重点观察隐喻、生活场景、弱化词、个人体验和非强导购路径,形成达人 brief 里的可复制经验与禁用边界。",
|
|
|
+ "5. 对银善存/大脑/脑力类 claim 另建专题:区分直接功能宣称、画面隐喻、角色比喻和备案范围,复核“守门员/前锋”等画面话术为什么能发。",
|
|
|
+ ])
|
|
|
+ return "\n".join(lines)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ main()
|