| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- # -*- coding: utf-8 -*-
- """
- 考核指标默认值 + 门店健康诊断规则(v2.1)
- 设计依据:项目记录/规则全景图_可编辑.html(2026-11-01 用户确认版)
- - 按漏斗分 4 层:流量 → 转化 → 履约 → 口碑
- - 5 档:R 违规红线 / B 考核未达标 / T 运营提升 / N 样本不足
- - 核销链内部去重:零成交 > 成交未核销 > 核销率低(只报最上游一条)
- - 核销金额独立判定(用户 2026-11-01 定)
- - 1 棵诊断树选「主因」,问题全列,每个问题都给对应动作
- - 全项目唯一的规则实现,前端只负责渲染
- """
- import re
- # 官方默认基准线(可在工作台【指标配置】修改,修改后存库)
- DEFAULT_THRESHOLDS = {
- # —— 飞书《门店考核指标》官方基准 ——
- "score_base": 4.3,
- "score_target": 4.5,
- "hx_rate_base": 0.4,
- "hx_rate_target": 0.6,
- "hx_amt_base": 10000,
- "tuDan": [0.0127, 0.0318, 0.0635], # 商责退单率 预警/一般严重/非常严重
- "touSu": [0.0011, 0.0028, 0.0056], # 投诉率
- "chaPing": [0.0066, 0.0166, 0.0331], # 差评率
- "reply_base": 50, # 评价回复率(%) 百分数
- # —— v2 新增:判定口径与门槛 ——
- "visit_low": 100, # 访问低于此次数 → 访问过低
- "cvr_low": 10, # 成交转化率(%) 低于此 → 转化低(2026-11-01 品牌口径)
- "pend_high": 10, # 未核销券积压达到此次数 → 积压提醒
- "hx_min_deal": 5, # 核销率判定要求的最小意向成交券数
- "amt_min_coupons": 5, # 「客单价偏低」判定要求的最少核销券数
- "cp_min_new": 10, # 差评率:新增口径最小样本
- "cp_min_total": 20, # 差评率:累计口径最小样本
- "reply_min_new": 1, # 回复率判定要求的最小新增评价数
- "video_low": 10, # 新发布视频低于此数 → 视频量少
- "lit_low": 10, # 门店点亮低于此数 → 点亮次数偏低
- "new_review_low": 10, # 新增评价低于此数 → 新增评价少
- }
- LEVEL_WEIGHT = {"R3": 3, "R2": 2, "R1": 1, "B": 0.6, "T": 0.3, "N": 0}
- LEVEL_NAME = {"R1": "预警线", "R2": "一般严重线", "R3": "非常严重线"}
- LAYER_NAME = {"traffic": "流量", "convert": "转化", "fulfill": "履约", "repute": "口碑"}
- # 建议动作库
- ACT_TD = "调整门店接待流程避免商责退单进一步加重,主动补20单核销好评,降低商责退单占比"
- ACT_TS = "调整门店接待流程避免客诉进一步加重"
- ACT_CP = "逐条处理近30天差评,主攻优质深度好评(避免无效评价)"
- ACT_SCORE = "优先引导带图深度好评,需先将门店评分提升至{base}以上"
- ACT_SCORE0 = "优先引导带图深度好评,达到开分门槛后评分才会生成"
- ACT_HX = "每天使用客资中心邀约未核销客户,设置到店核销激励"
- ACT_ZERO = "触达全部已成交客户确认到店意愿,核查门店核销流程是否卡单"
- ACT_AMT = "每月至少完成1.2w大单上翻,保持金牌门店,获取平台流量激励"
- ACT_REPLY = "安排时间定时回复新增评价,差评24小时内及时处理"
- ACT_VISIT = "1. 检查门店是否有违规限流\n2.点亮门店+更新AIGC素材,增加门店投稿,提升门店曝光"
- ACT_VISIT0 = "核查门店是否营业/被限流,确认团购在售,完成门店点亮"
- ACT_VIDEO = "每周用后台AIGC工具产出1条图文或视频"
- ACT_LIT = "店内需放置优惠码物料,积极引导到店客户点亮门店"
- ACT_CVR = "提高门店基建数据,增加进店转化"
- ACT_DEAL0 = "检查团购是否在售、门店是否有违规限流"
- ACT_PEND = "每天使用客资中心及时邀约新增客资"
- ACT_RVOL = "每月保持不低于15条新增优质评价"
- ACT_RANK0 = "连续7天交易上翻 + 主攻好评冲本地榜单;上榜可改善平台限流"
- ACT_RANK_IN = "在已入围榜单上持续做好评与交易增量,把名次冲上去"
- # ---------------------------------------------------------------------------
- # 文案模板(全部可在前端「文案配置」面板自定义,改后存库、全店重算)
- # 可用占位符见各条注释;未知占位符原样保留,不会报错
- # ---------------------------------------------------------------------------
- DEFAULT_TEXTS = {
- # —— 反馈文案结构 ——
- "fb_sec_overview": "✅经营数据概览:",
- "fb_line_overview": "近30天:客资{客资} 核销{核销} 退单{退单}",
- "fb_sec_risk": "✅风险或限流因素:",
- "fb_item_risk": "{i}、{问题}",
- "fb_line_pending": "✅后台待核销订单{待核销}单(估算未核销)",
- "fb_sec_loss": "✅顾客流失分析(近30天):",
- "fb_item_visit": "{i}、门店页有效人数 {访问} 人({访问环比})",
- "fb_item_deal": "{i}、成交 {成交} 人(占比 {成交占比} )",
- "fb_item_lost": "{i}、流失 {流失} 人(占比 {流失占比} )",
- "fb_sec_insight": "✅分析洞察:",
- "fb_item_insight": "{i}、{洞察}",
- "fb_insight_metric": "指标层面:核销率仅 {核销率},距离 {核销目标} 基准线差距明显,是门店第一大限流指标,平台自然流量权重持续承压,推荐量会持续收紧。",
- "fb_insight_score": "口碑分:团购评分 {评分} 低于平台准入基准 {评分基准},评分权重直接影响自然流量推荐与转化。",
- "fb_insight_flow": "流量转化:门店页访客 {访问} 人,成交转化率仅 {成交占比},{流失占比} 访客流失,用户浏览后更容易被隔壁同类门店抢走。提升门店基建数据,增加进店转化。",
- "fb_insight_order": "订单风险:后台积压 {待核销} 单待核销订单,存量体量较大,如不主动跟进邀约,后期极易集中产生退单、负面评价,进一步拉低评分,形成恶性循环。",
- "fb_insight_reply": "口碑管理:评价回复率仅 {回复率},低于 {回复基准} 考核要求,差评无人回应会进一步压低门店权重。",
- "fb_insight_priority": "整改优先级:优先电话 / 私信触达待核销客户,加速到店核销;服务环节引导优质评价拉升团购分。",
- "fb_sec_action": "📌 门店调整动作",
- "fb_no_issue": "本期无明显异常",
- # —— 一句话结论(诊断树选出的主因)——
- "fb_sec_headline": "✅本期结论:",
- "fb_item_headline": "{结论}",
- "hd_R": "有违规在限流:{问题} —— 平台已在处置或限流,先处理违规项再谈增长",
- "hd_traffic": "卡在「流量」:{问题} —— 没有足够流量进店,后面几步都无从谈起",
- "hd_convert": "卡在「转化」:{问题} —— 有流量但没下单,转化环节断掉了",
- "hd_fulfill": "卡在「履约」:{问题} —— 成交了但没核销,钱卡在未履约这一环",
- "hd_repute": "卡在「口碑」:{问题} —— 口碑在拖累自然流量与转化",
- "hd_AMT": "核销金额没达标:{问题} —— 要靠大单上翻把金额做起来,才能保住金牌门店和流量激励",
- "hd_SCORE0": "卡在「口碑」:{问题} —— 还没开分,先把有效评价量做起来",
- "hd_OK": "本期各项指标均未触发问题",
- # —— 整改动作条目(来自诊断树,最多 3 条)——
- "fb_action_item": "✅ {动作}",
- # —— 严重档位名称 ——
- "lv_R1": "预警线",
- "lv_R2": "一般严重线",
- "lv_R3": "非常严重线",
- # —— 问题文案:{v}=实际值 {base}=基准值 {口径}=新增/累计 {档}=档位名 ——
- "t_TD": "商责退单率{v}%超{档}",
- "t_TS": "投诉率{v}%超{档}",
- "t_CP": "差评率({口径}口径){v}%超{档}",
- "t_SCORE": "评分{v}(基准线{base})-低于平台准入",
- "t_SCORE0": "未开分({原因}),暂无有效评分",
- "t_HX": "核销率{v}%(基准线{base}%)",
- "t_ZERO": "成交{n}张券一张未核销",
- "t_AMT": "当月核销{金额}元,未做大单上翻",
- "t_REPLY": "新增{新增}条评价,回复率{v}%偏低",
- "t_VISIT": "门店曝光访问偏低",
- "t_VIDEO": "门店发布视频数量少",
- "t_LIT": "门店点亮次数偏低",
- "t_CVR": "成交转化率仅{v}%",
- "t_DEAL0": "有{v}次访问却零成交",
- "t_PEND": "积压{p}张未核销券",
- "t_RANK0": "未上榜(好评榜/人气榜均未进入)",
- "t_RANK_IN": "仅入围未进榜:{榜}",
- "t_RVOL": "近30天新增评价少",
- # —— 整改建议(评分建议可用 {base} {target})——
- "a_TD": ACT_TD,
- "a_TS": ACT_TS,
- "a_CP": ACT_CP,
- "a_SCORE": ACT_SCORE,
- "a_SCORE0": ACT_SCORE0,
- "a_HX": ACT_HX,
- "a_ZERO": ACT_ZERO,
- "a_AMT": ACT_AMT,
- "a_REPLY": ACT_REPLY,
- "a_VISIT": ACT_VISIT,
- "a_VIDEO": ACT_VIDEO,
- "a_LIT": ACT_LIT,
- "a_CVR": ACT_CVR,
- "a_DEAL0": ACT_DEAL0,
- "a_PEND": ACT_PEND,
- "a_RVOL": ACT_RVOL,
- "a_RANK0": ACT_RANK0,
- "a_RANK_IN": ACT_RANK_IN,
- }
- # v1 的默认文案快照:用于迁移。
- # 库里存的是整份文案(用户点过「应用」就全量落库),改默认值后旧存档会盖住新默认。
- # 规则:存档值 == v1 默认值 → 说明用户没改过,直接采用新默认;否则视为用户自定义,保留。
- V1_TEXTS = {
- "fb_sec_overview": "✅经营数据概览:",
- "fb_line_overview": "近30天:客资{客资} 核销{核销} 退单{退单}",
- "fb_sec_risk": "✅风险或限流因素:",
- "fb_item_risk": "{i}、{问题}",
- "fb_line_pending": "✅后台待核销订单{待核销}单(真实未核销)",
- "fb_sec_loss": "✅顾客流失分析(近30天):",
- "fb_item_visit": "{i}、门店页有效人数 {访问} 人({访问环比})",
- "fb_item_deal": "{i}、成交 {成交} 人(占比 {成交占比} )",
- "fb_item_lost": "{i}、流失 {流失} 人(占比 {流失占比} )",
- "fb_sec_insight": "✅分析洞察:",
- "fb_item_insight": "{i}、{洞察}",
- "fb_insight_metric": "指标层面:核销率仅 {核销率},距离 {核销目标} 基准线差距明显,是门店第一大限流指标,平台自然流量权重持续承压,推荐量会持续收紧。",
- "fb_insight_score": "口碑分:团购评分 {评分} 低于平台准入基准 {评分基准},评分权重直接影响自然流量推荐与转化。",
- "fb_insight_flow": "流量转化:门店页访客 {访问} 人,成交转化率仅 {成交占比},{流失占比} 访客流失,用户浏览后更容易被隔壁同类门店抢走,门店团购套餐、主页吸引力不足。",
- "fb_insight_order": "订单风险:后台积压 {待核销} 单待核销订单,存量体量较大,如不主动跟进邀约,后期极易集中产生退单、负面评价,进一步拉低评分,形成恶性循环。",
- "fb_insight_reply": "口碑管理:评价回复率仅 {回复率},低于 {回复基准} 考核要求,差评无人回应会进一步压低门店权重。",
- "fb_insight_priority": "整改优先级:优先电话 / 私信触达待核销客户,加速到店核销;服务环节引导优质评价拉升团购分。",
- "fb_sec_action": "📌 门店调整动作",
- "fb_actions": "✅ 做评价:主攻优质深度好评\n避免短期,避免无效评价,长期做,稳定做,多渠道来客的都友好引导写评价,避免强硬逼迫写评价!\n✅ 传素材:后台 AIGC 内容定期更\n定期使用后台的AIGC素材工具,上传并更新高质量的图片与视频内容。这是吸引用户点击、提升页面转化率的基础。\n✅ 亮门店:每日记得点亮门店\n需长期执行「门店点亮」动作,通过在店内设置引导物料和准备小额礼品,主动激励客户完成打卡、评价等行为,将线下流量有效反哺线上。\n✅ 冲榜单:全力抢占本地榜单席位\n榜单能增加投放转化能力(有商责退单的如果上榜单可改善平台限流情况)\n✅ 每月连续7天交易上翻,总核销金额不低于{核销金额基准}元",
- "fb_no_issue": "本期无明显异常",
- # —— 一句话结论(诊断树选出的主因)——
- "fb_sec_headline": "✅本期结论:",
- "fb_item_headline": "{结论}",
- "hd_R": "有违规在限流:{问题} —— 平台已在处置或限流,先处理违规项再谈增长",
- "hd_traffic": "卡在「流量」:{问题} —— 没有足够流量进店,后面几步都无从谈起",
- "hd_convert": "卡在「转化」:{问题} —— 有流量但没下单,转化环节断掉了",
- "hd_fulfill": "卡在「履约」:{问题} —— 成交了但没核销,钱卡在未履约这一环",
- "hd_repute": "卡在「口碑」:{问题} —— 口碑在拖累自然流量与转化",
- "hd_AMT": "核销金额没达标:{问题} —— 要靠大单上翻把金额做起来,才能保住金牌门店和流量激励",
- "hd_SCORE0": "卡在「口碑」:{问题} —— 还没开分,先把有效评价量做起来",
- "hd_OK": "本期各项指标均未触发问题",
- # —— 整改动作条目(来自诊断树,最多 3 条)——
- "fb_action_item": "✅ {动作}",
- "lv_R1": "预警线", "lv_R2": "一般严重线", "lv_R3": "非常严重线",
- "t_TD": "商责退单率{v}%超{档}",
- "t_TS": "投诉率{v}%超线",
- "t_CP": "差评率({口径}口径){v}%超线",
- "t_SCORE": "评分{v}(基准线{base})-低于平台准入",
- "t_HX": "核销率{v}%(基准线{base}%)",
- "t_ZERO": "本期零核销",
- "t_AMT": "核销金额{v}元低于基准{base}",
- "t_REPLY": "评价回复率{v}%偏低",
- "t_VISIT": "访问仅{v}次",
- "t_VIDEO": "本期零视频",
- "a_TD": "催办到店核销、主动处理退单商责, 避免平台处置",
- "a_TS": "24小时内响应投诉工单, 复盘服务环节",
- "a_CP": "处理近30天差评, 主攻优质深度好评",
- "a_SCORE": "引导好评+回复差评, 目标先回{base}再冲{target}",
- "a_HX": "电话/私信触达待核销客户, 加速到店",
- "a_ZERO": "检查团购在售状态与价格, 重新起量",
- "a_AMT": "补齐在售品结构, 冲单量",
- "a_REPLY": "安排专人回复全部评价",
- "a_VISIT": "检查POI完整度, 点亮门店+更新AIGC素材",
- "a_VIDEO": "后台AIGC工具定期产出图片/视频",
- }
- # 规则 key → 问题类型(筛选项/图表分类,固定不变,不受文案修改影响)
- RULE_CAT = {
- "TD": "退单率超线", "TS": "投诉率超线", "CP": "差评率超线",
- "SCORE": "评分不达标",
- "REPLY": "回复率偏低", "RVOL": "新增评价少",
- "DEAL0": "零成交", "CVR": "转化率低",
- "ZERO": "成交未核销", "HX": "核销率不达标", "AMT": "客单价偏低", "PEND": "未核销积压",
- "VISIT": "访问过低", "VIDEO": "视频量少", "LIT": "点亮偏低",
- "RANK0": "未上榜", "RANK_IN": "仅入围",
- }
- # 规则元信息:(key, tier, layer)
- RULES = [
- ("TD", "R", "repute"), ("TS", "R", "repute"), ("CP", "R", "repute"),
- ("SCORE", "B", "repute"), ("REPLY", "B", "repute"), ("RVOL", "T", "repute"),
- ("DEAL0", "B", "convert"),
- ("ZERO", "B", "fulfill"), ("HX", "B", "fulfill"), ("AMT", "B", "fulfill"),
- ("VISIT", "T", "traffic"),
- ("VIDEO", "T", "traffic"), ("LIT", "T", "traffic"),
- ("RANK0", "T", "traffic"), ("RANK_IN", "T", "traffic"),
- ("CVR", "T", "convert"), ("PEND", "T", "fulfill"),
- ]
- # 诊断树:漏斗顺序,取第一个命中作「主因」
- # "R" 表示"任一 R 级问题",命中时取违规级别最高的那条
- DIAGNOSIS_ORDER = ["R", "VISIT", "DEAL0", "CVR",
- "ZERO", "HX", "AMT", "SCORE", "REPLY", "RVOL",
- "VIDEO", "LIT", "PEND", "RANK0", "RANK_IN"]
- HEADLINE = {
- "R": "平台已在处置或限流,先处理违规项再谈增长",
- "traffic": "没有足够流量进店,后面几步都无从谈起",
- "convert": "有流量但没下单,转化环节断掉了",
- "fulfill": "成交了但没核销,钱卡在未履约这一环",
- "repute": "口碑在拖累自然流量与转化",
- "OK": "本期各项指标均未触发问题",
- }
- class _SafeDict(dict):
- def __missing__(self, key):
- return "{" + key + "}"
- def fmt_tpl(tpl, **vars):
- """模板填充:未知占位符原样保留"""
- try:
- return str(tpl).format_map(_SafeDict(**vars))
- except Exception:
- return str(tpl)
- def _clean_action(a):
- """动作条目去掉行首的 ✅/✓ 与空白,避免和「✅ {动作}」模板叠加"""
- return re.sub(r"^[\s\u2705\u2714\u2611\ufe0f]+", "", str(a or "")).strip()
- def _money(v):
- try:
- return "{:,.0f}".format(float(v or 0))
- except (TypeError, ValueError):
- return "0"
- def band(v, lines):
- """违规三级:返回值 None / R1 / R2 / R3"""
- if v is None:
- return None
- if v > lines[2]:
- return "R3"
- if v > lines[1]:
- return "R2"
- if v > lines[0]:
- return "R1"
- return None
- def diff_rate(used_coupons, intend_deal):
- """核销率 = 核销券数 ÷ 意向成交券数"""
- if intend_deal and intend_deal > 0:
- return used_coupons / intend_deal
- return None
- def bad_rate(reviews_new, bad_new, reviews_total, bad_total):
- """
- 差评率口径:
- 新增评价 ≥ cp_min_new → 新增口径,样本充足,可判三级
- 累计评价 ≥ cp_min_total → 累计口径,样本充足,可判三级
- 累计评价 > 0 → 累计口径,小样本,最高只判「预警线」
- 新增评价 > 0 → 新增口径,小样本,最高只判「预警线」
- 完全无评价 → 不判定
- 返回 (ratio, 口径, 是否小样本)
- """
- rn = reviews_new or 0
- bn = bad_new or 0
- rt = reviews_total or 0
- bt = bad_total or 0
- if rn >= 10:
- return bn / rn, "新增", False
- if rt >= 20:
- return bt / rt, "累计", False
- if rt > 0:
- return bt / rt, "累计", True
- if rn > 0:
- return bn / rn, "新增", True
- return None, "", False
- def diagnose(m, T, X=None):
- """
- 单店诊断。m 为聚合后的指标 dict,T 为阈值,X 为文案模板。
- 返回 dict:
- problems 全部命中的问题;每条含 tpl/atpl/vars,前端可直接用改后的模板重渲染
- primary 主因(诊断树第一个命中),没有则 None
- secondary 次要问题(R/B 级,最多 2 条)
- actions 整改动作(去重后最多 3 条)
- sev 严重度(0~3)
- tier 最高档位 R/B/T/OK
- headline 一句话结论
- """
- X = X or DEFAULT_TEXTS
- P = []
- def add(key, tier, layer, level, tpl, atpl, **vars):
- P.append({
- "key": key, "tier": tier, "layer": layer, "level": level,
- "cat": RULE_CAT.get(key, "其他"),
- "tpl": tpl, "atpl": atpl, "vars": vars,
- "text": fmt_tpl(X.get(tpl, ""), **vars) if X.get(tpl) else "",
- "action": fmt_tpl(X.get(atpl, ""), **vars) if X.get(atpl) else "",
- })
- visit = m.get("visit") or 0
- # 口径(2026-11-01):核销率分母、待核销、零成交、转化率分子统一用「门店页成交券数」
- intend = m.get("deal") or 0
- used = m.get("used_coupons") or 0
- amt = m.get("used_amount") or 0
- score = m.get("score")
- rn = m.get("reviews_new") or 0
- rt = m.get("reviews_total") or 0
- videos = m.get("videos") or 0
- lit = m.get("lit") or 0
- pend = max(intend - used, 0)
- hx = diff_rate(used, intend)
- cvr = (intend / visit) if visit > 0 else None
- # ================= 口碑层 =================
- b = band(m.get("return_rate"), T["tuDan"])
- if b:
- add("TD", "R", "repute", b, "t_TD", "a_TD",
- v="%.2f" % (m["return_rate"] * 100), 档=X["lv_" + b])
- b = band(m.get("complaint_rate"), T["touSu"])
- if b:
- add("TS", "R", "repute", b, "t_TS", "a_TS",
- v="%.2f" % (m["complaint_rate"] * 100), 档=X["lv_" + b])
- cp, cp_src, cp_small = bad_rate(rn, m.get("bad_new"), rt, m.get("bad_total"))
- b = band(cp, T["chaPing"])
- if b:
- if cp_small and b != "R1":
- b = "R1" # 小样本最高只判预警线
- add("CP", "R", "repute", b, "t_CP", "a_CP",
- 口径=cp_src, v="%.2f" % (cp * 100), 档=X["lv_" + b])
- # 评分:0 分是「未开分」,仍算不达标,但文案与动作分开(用户 2026-11-01 定)
- if score is not None and score < T["score_base"]:
- if score == 0:
- add("SCORE", "B", "repute", "B", "t_SCORE0", "a_SCORE0",
- 原因=("暂无评价" if rt == 0 else "评价量不足"))
- else:
- add("SCORE", "B", "repute", "B", "t_SCORE", "a_SCORE",
- v="%.1f" % score, base=T["score_base"], target=T["score_target"])
- reply = m.get("reply_rate")
- if reply is not None and rn >= T["reply_min_new"] and reply < T["reply_base"] / 100.0:
- add("REPLY", "B", "repute", "B", "t_REPLY", "a_REPLY",
- v="%.0f" % (reply * 100), 新增=rn)
- # 新增评价量:低于门槛 → 评价量不足,属运营提升项
- if rn < T["new_review_low"]:
- add("RVOL", "T", "repute", "T", "t_RVOL", "a_RVOL", n=rn)
- # ================= 转化层 =================
- # 四层是并行关系:转化层的问题不会压制履约层,各层各自报自己的
- if visit > 0 and intend == 0:
- add("DEAL0", "B", "convert", "B", "t_DEAL0", "a_DEAL0", v=visit)
- # ================= 履约层(层内按根因去重:成交未核销 > 核销率低)=================
- if intend > 0 and used == 0:
- add("ZERO", "B", "fulfill", "B", "t_ZERO", "a_ZERO", n=intend)
- elif intend >= T["hx_min_deal"] and hx is not None and hx < T["hx_rate_base"]:
- add("HX", "B", "fulfill", "B", "t_HX", "a_HX",
- v="%.0f" % (hx * 100), base="%.0f" % (T["hx_rate_base"] * 100))
- # 核销金额:独立判定(用户 2026-11-01 定),不再受核销率约束
- if amt < T["hx_amt_base"]:
- add("AMT", "B", "fulfill", "B", "t_AMT", "a_AMT",
- n=used, 金额=_money(amt), 客单价=_money(amt / used if used else 0))
- # ================= 流量层(T 级提升项)=================
- if visit < T["visit_low"]:
- add("VISIT", "T", "traffic", "T", "t_VISIT", "a_VISIT", v=visit)
- if videos < T["video_low"]:
- add("VIDEO", "T", "traffic", "T", "t_VIDEO", "a_VIDEO", v=videos)
- if lit < T["lit_low"] and visit > 0:
- add("LIT", "T", "traffic", "T", "t_LIT", "a_LIT", v=lit)
- # 上榜:有内容才算上榜;只有「入围」没有名次单独提示
- rank = (m.get("rank_note") or "").strip()
- ranked = rank not in ("", "-", "无")
- if not ranked:
- add("RANK0", "T", "traffic", "T", "t_RANK0", "a_RANK0")
- elif not re.search(r"第\s*\d+\s*名", rank):
- add("RANK_IN", "T", "traffic", "T", "t_RANK_IN", "a_RANK_IN",
- 榜=rank.split("、")[0])
- # ================= 转化效率 / 积压 =================
- if visit >= 100 and intend > 0 and cvr is not None and cvr < T["cvr_low"] / 100.0:
- add("CVR", "T", "convert", "T", "t_CVR", "a_CVR", v="%.1f" % (cvr * 100))
- if pend >= T["pend_high"]:
- add("PEND", "T", "fulfill", "T", "t_PEND", "a_PEND", p=pend)
- # ================= 诊断树:选主因 =================
- by_key = {}
- for p in P:
- by_key.setdefault(p["key"], p)
- primary = None
- for step in DIAGNOSIS_ORDER:
- if step == "R":
- rs = [p for p in P if p["tier"] == "R"]
- if rs:
- primary = max(rs, key=lambda x: LEVEL_WEIGHT[x["level"]])
- break
- elif step in by_key:
- primary = by_key[step]
- break
- rest = [p for p in P if p is not primary]
- secondary = sorted([p for p in rest if p["tier"] in ("R", "B")],
- key=lambda x: -LEVEL_WEIGHT[x["level"]])[:2]
- # 动作:每个问题都给对应动作(用户 2026-11-01 定:存在什么问题就给对应动作),不再限 3 条
- actions = []
- for p in ([primary] if primary else []) + [x for x in P if x is not primary]:
- act = _clean_action(p["action"])
- if act and act not in actions:
- actions.append(act)
- sev = max((LEVEL_WEIGHT[p["level"]] for p in P), default=0)
- if any(p["tier"] == "R" for p in P):
- tier = "R"
- elif any(p["tier"] == "B" for p in P):
- tier = "B"
- elif P:
- tier = "T"
- else:
- tier = "OK"
- if primary is None:
- hd_key = "hd_OK"
- headline = X.get(hd_key) or HEADLINE["OK"]
- else:
- # 结论句回退链:规则专属 → 模板专属(区分「未开分」)→ 该层通用 → OK
- cands = ["hd_" + primary["key"]]
- tpl = primary.get("tpl") or ""
- if tpl.startswith("t_"):
- cands.append("hd_" + tpl[2:])
- cands.append("hd_R" if primary["tier"] == "R" else "hd_" + primary["layer"])
- cands.append("hd_OK")
- hd_key = next((c for c in cands if c in X), "hd_OK")
- headline = fmt_tpl(X[hd_key], 问题=primary["text"])
- return {
- "problems": P,
- "primary": primary,
- "secondary": secondary,
- "actions": actions,
- "sev": sev,
- "tier": tier,
- "headline": headline,
- "hd_key": hd_key,
- }
- def detect(m, T, X=None):
- """兼容旧接口:返回 (flags, sev),flags 为 [level, 文案, 建议动作, 规则key]"""
- d = diagnose(m, T, X)
- flags = [[p["level"], p["text"], p["action"], p["key"]] for p in d["problems"]]
- return flags, d["sev"]
- def cat_of(label):
- """按文案反查问题类型(文案被改过时不可靠,筛选用 RULE_CAT[key])"""
- for prefix, key in (("商责退单率", "TD"), ("投诉率", "TS"), ("差评率", "CP"),
- ("评分", "SCORE"), ("未开分", "SCORE0"), ("核销率", "HX"),
- ("成交", "ZERO"), ("核销", "AMT"), ("回复率", "REPLY"),
- ("访问", "VISIT"), ("视频", "VIDEO"), ("点亮", "LIT"),
- ("转化", "CVR"), ("积压", "PEND")):
- if label.startswith(prefix):
- return RULE_CAT[key]
- return "其他"
- def validate(T):
- """校验阈值合法性,返回错误列表(空=通过)"""
- errs = []
- try:
- for k in ("score_base", "score_target", "hx_rate_base", "hx_rate_target",
- "hx_amt_base", "reply_base", "visit_low", "cvr_low", "pend_high",
- "hx_min_deal", "amt_min_coupons", "cp_min_new", "cp_min_total",
- "reply_min_new"):
- if k in T:
- T[k] = float(T[k])
- for k in ("tuDan", "touSu", "chaPing"):
- T[k] = [float(x) for x in T[k]]
- a = T[k]
- if not (a[0] > 0 and a[1] > a[0] and a[2] > a[1]):
- errs.append("三级阈值需递增")
- if T["score_base"] >= T["score_target"]:
- errs.append("评分目标值应高于考核基准")
- if T["hx_rate_base"] >= T["hx_rate_target"]:
- errs.append("核销率目标值应高于考核基准")
- if T["hx_amt_base"] < 0 or T["visit_low"] < 0:
- errs.append("金额/访问线不能为负")
- if T.get("cvr_low", 0) < 0 or T.get("pend_high", 0) < 0:
- errs.append("转化率/积压线不能为负")
- if T.get("cp_min_new", 0) <= 0 or T.get("cp_min_total", 0) <= 0:
- errs.append("差评率最小样本量需大于 0")
- if T.get("hx_min_deal", 0) < 1 or T.get("amt_min_coupons", 0) < 1:
- errs.append("核销率最小成交券数需 ≥ 1")
- except (TypeError, ValueError, KeyError):
- errs.append("阈值格式不正确")
- return errs
|