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()