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