#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""大客户部货号丢单/新机会 → 自包含 HTML（含证据与疑问清单）"""
import html as H
import io
import json
import os
import re
import sys

sys.path.insert(0, '/Users/liuxinyuan/Desktop/Hermes输出-工作类/脚本')
import brand_kit as BK  # noqa: E402

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
D = json.load(io.open(W + '/数据/大客户部_货号丢单与新机会.json', encoding='utf-8'))
wan = lambda x: x / 10000.0
m = lambda x, d=1: format(x, ',.%df' % d)
EV = D['evidence']

# 注册申报/量产证据的货号（从活动正文抽取，逐条可溯源）
REG = {'M2161': '客户个性化用药基因检测 3-4 个项目「也需要进行注册报证」，个别项目用我司 M2181-4 / MD2031 / M2161-3（冯贤平 · 2026-07-29）',
       'FE31': '成都普利泰 RI 项目已完成 50℃/60 天稳定性 + 三批验证，「已完成 60 天验证的项目已经完成替换工作用上了」（陈溪子 · 2026-08-10）',
       'M4211-3': '杭州丹威分装工艺确认（干燥剂烘干、引探风干湿度、成品 42-50℃ 3 天石蜡挂壁）→ 量产前工艺定型（2026）',
       'M2181-4': '达安基因：超快速 PCR 体系适配良好，「准备进一步测试」，客户表示会安排采购 FE41（2026-10-15）',
       'MD2091': '细小项目已完成性能测试 + 37℃/7D 热加速，我司匹配 MD2091-092402（2026）',
       'FM5144': '呼吸道检测项目 2：因 FMD5104-014429 冻干前后性能差异大「用回了 FM5144」（刘虹晶 · 2026-09-22）',
       'M0181-4': '客户正在评估替换限制性内切酶（在用愚公生物／菲鹏／翌圣）→ 替换窗口，价格敏感（年用量 10L/酶）',
       'FE40': '粪便类提取多重病原体检测（商机 · 沟通需求确认，宋学伟）'}


def evhtml(sku):
    hs = EV.get(sku) or []
    if sku in REG:
        return '<div class="ev">🔎 ' + H.escape(REG[sku]) + '</div>'
    if hs:
        return '<div class="ev">' + H.escape(hs[0]['seg'][:190]) + '…</div>'
    return '<div class="ev mut">本地活动/商机文本未命中该货号 → 需销售或技术支持确认</div>'


def tab(rows, headers, body):
    head = ''.join('<th%s>%s</th>' % ((' style="text-align:right"' if h.startswith('@') else ''), H.escape(h.lstrip('@'))) for h in headers)
    return '<table><thead><tr>%s</tr></thead><tbody>%s</tbody></table>' % (head, body)


lost = ''.join('<tr><td class="mono b">%s</td><td>%s</td><td class="n">%s</td><td>%s</td></tr>%s'
               % (H.escape(x['sku']), H.escape(x['name'][:34]), m(wan(x['amt25']), 1), H.escape('、'.join(x['custs'][:2])),
                  '<tr class="evrow"><td colspan="4">%s</td></tr>' % evhtml(x['sku'])) for x in D['lost'])
shrink = ''.join('<tr><td class="mono b">%s</td><td>%s</td><td class="n">%s</td><td class="n">%s</td><td class="n bad">%.0f%%</td><td>%s</td><td>%s</td></tr>'
                 % (H.escape(x['sku']), H.escape(x['name'][:30]), m(wan(x['amt25']), 1), m(wan(x['amt26']), 1), x['pct'],
                    x.get('last', ''), H.escape('、'.join(x['custs'][:2]))) for x in D['shrink'])
new = ''.join('<tr><td class="mono b">%s</td><td>%s</td><td class="n">%s</td><td>%s</td><td>%s</td></tr>%s'
              % (H.escape(x['sku']), H.escape(x['name'][:34]), m(wan(x['amt26']), 1), x.get('first', ''),
                 H.escape('、'.join(x['custs'][:2])), '<tr class="evrow"><td colspan="5">%s</td></tr>' % evhtml(x['sku']))
              for x in D['new'])
grow = ''.join('<tr><td class="mono b">%s</td><td>%s</td><td class="n">%s</td><td class="n">%s</td><td class="n good">+%.0f%%</td><td>%s</td></tr>%s'
               % (H.escape(x['sku']), H.escape(x['name'][:30]), m(wan(x['amt25']), 1), m(wan(x['amt26']), 1), x['pct'],
                  H.escape('、'.join(x['custs'][:2])), '<tr class="evrow"><td colspan="6">%s</td></tr>' % evhtml(x['sku']))
               for x in D['grow'])

SUML = sum(x['amt25'] for x in D['lost'])
SUMNEW = sum(x['amt26'] for x in D['new'])
SUMG = sum(x['amt26'] - x['amt25'] for x in D['grow'])

QUESTIONS = [
 ('丢失的 114 万是真丢单，还是「换号」？',
  '圣湘 <b>MD2104-000804-1</b>（33.33×SensiDirect RTase/UNG）2025 同期 114.0 万 → 2026 = 0，同系列 <b>MD2084-000804-1/-2</b> 也归零；但 2026 新增 <b>MD4104-000801-1</b>（12.5×SensiDirect RTase/UNG，4.75 万）。<b>疑似配方/形态改版换号，不是丢单</b>。需技术支持确认：MD2104/MD2084 → MD4104 是否同一项目族的改版迁移？',
  '技术支持 + 圣湘跟进人（赵云浩）'),
 ('仪器类要不要算丢单？',
  '<b>BTE-8 全自动核酸提取仪</b> 2025 同期 35.7 万 → 2026 = 0（丽珠、郑州纳克）。仪器属<b>一次性装机采购</b>，装机后无复购是常态，与原料丢单性质不同。是否剔除仪器/耗材类重算？',
  '你拍板口径'),
 ('「大客户部」客户边界怎么定？',
  '本次按 ERP 销售员 ∈ {刘子研, 赵云浩, 张立娅, 黄明月, 任鑫, 杜建, 刘新元} 圈定（含离职/代管），得 <b>115 家客户</b>。但同一集团跨部门代采（如圣湘、复星、天隆 在拓展部名下也有单）→ 是否改按<b>集团归属</b>重算，避免同一集团被拆到两个部门？',
  '你拍板口径'),
 ('「上量转化」要有确认动作，谁去确认？',
  '上量 21 个货号里，<b>有明确注册/量产证据</b>的是：<span class="mono">M2161</span>（客户 3-4 个项目要注册报证）、<span class="mono">FE31</span>（已完成 60 天验证并完成替换）、<span class="mono">M4211-3</span>（丹威分装工艺定型）、<span class="mono">M2181-4</span>（达安准备扩测+采购 FE41）。其余 17 个只是<b>金额增长</b>，未证实「已走注册申报」。要逐条确认转化阶段吗？',
  '各客户跟进人（一次性清单）'),
 ('商机侧没接上',
  '本次「注册申报」证据全部来自<b>活动正文</b>；飞书商机字段（<span class="mono">测试需求</span> / <span class="mono">商机关联销售订单</span>）常不回填货号，本地只有 413 条商机摘要。<b>是否要我拉一次飞书商机 MQL</b>（按大客户部客户 + 货号反查）补齐「商机→货号→阶段」链条？',
  '你确认后我执行'),
 ('2026 只到 10-15，可比性',
  '本次一律用 <b>1-9 月同期</b>（2025 vs 2026）保证可比；10 月数据（155.1 万）单独存在、未混入同比。若你要「全年」口径，2025 需用全年、2026 需等年末 —— 现在给不出。',
  '口径说明，无需动作'),
]

qs = ''.join('<div class="q"><div class="qt">%d. %s</div><div class="qb">%s</div><div class="qo">→ 需谁定/做：<b>%s</b></div></div>'
             % (i + 1, q[0], q[1], q[2]) for i, q in enumerate(QUESTIONS))

HTML = """<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>大客户部 · 货号级 2025 vs 2026 丢单与新机会</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#f4f6fa;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;color:#20242c;font-size:13px}
.wrap{max-width:1280px;margin:0 auto;padding:16px 18px 44px}
h1{font-size:23px;margin:14px 0 5px}.meta{color:#6b7280;font-size:12.5px;line-height:1.85;margin-bottom:14px}
.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:11px;margin:12px 0 16px}
.kpi{background:#fff;border:1px solid #e6eaf2;border-top:3px solid #1a5276;border-radius:9px;padding:11px 13px}
.kpi .l{font-size:11.5px;color:#6b7280;font-weight:700}.kpi .v{font-size:22px;font-weight:900}.kpi .s{font-size:11px;color:#9aa3b2}
.card{background:#fff;border:1px solid #e6eaf2;border-radius:10px;padding:14px 16px;margin-bottom:14px}
.card h2{font-size:15px;margin:0 0 10px;padding-left:9px;border-left:3px solid #1a5276}
table{width:100%;border-collapse:collapse;font-size:12.5px}
th{background:#1a5276;color:#fff;font-weight:600;padding:7px 9px;text-align:left;white-space:nowrap}
td{padding:7px 9px;border-bottom:1px solid #eef1f6;vertical-align:top}
td.n{text-align:right;font-variant-numeric:tabular-nums}.b{font-weight:700}
.mono{font-family:ui-monospace,Menlo,monospace}
.bad{color:#b91c1c;font-weight:700}.good{color:#15803d;font-weight:700}.mut{color:#9aa3b2}
tr.evrow td{background:#fbfcfe;font-size:11.5px;color:#4b5563;line-height:1.7;border-bottom:1px solid #eef1f6}
.ev{padding-left:2px}
.q{background:#fff;border:1px solid #e6eaf2;border-left:4px solid #e8a300;border-radius:9px;padding:12px 14px;margin-bottom:10px}
.qt{font-weight:800;font-size:13.5px;margin-bottom:5px}
.qb{font-size:12.5px;line-height:1.8;color:#374151}.qo{margin-top:6px;font-size:12px;color:#6b7280}
.note{background:#fbfcfe;border:1px dashed #cbd5e1;border-radius:8px;padding:11px 13px;font-size:12px;color:#4b5563;line-height:1.9}
@media(max-width:1000px){.kpis{grid-template-columns:repeat(2,1fr)}}
</style></head><body><div class="wrap">
<h1>大客户部 · 货号级 2 年对比：丢单 与 新机会 / 上量</h1>
<div class="meta">
口径：<b>2026 年 1-9 月 vs 2025 年 1-9 月</b>（同期可比）｜ 标准销售订单 + 非赠品 + 剔除宝锐系 ｜ 归属＝ERP 销售员 ∈ 大客户部名单（含离职/代管），共 <b>{{ncust}} 家客户</b><br>
数据源：ERP 2026 主档（25,966 行）＋ 2025 全年订单 Sheet1（30,630 行）＋ 活动正文（daily_activities_mcp / my_activities）＋ 商机（opps_by_customer / customer_360）｜ 生成 {{gen}}
</div>
<div class="kpis">
 <div class="kpi"><div class="l">丢单货号</div><div class="v" style="color:#b91c1c">{{nlost}}</div><div class="s">2025 同期基数 {{sumlost}} 万元</div></div>
 <div class="kpi"><div class="l">大幅萎缩（≥70%）</div><div class="v" style="color:#c2410c">{{nshrink}}</div><div class="s">仍在成交但量跌</div></div>
 <div class="kpi"><div class="l">2026 新增货号</div><div class="v" style="color:#15803d">{{nnew}}</div><div class="s">合计 {{sumnew}} 万元</div></div>
 <div class="kpi"><div class="l">上量（≥2×）</div><div class="v" style="color:#15803d">{{ngrow}}</div><div class="s">增量 {{sumg}} 万元</div></div>
</div>

<div class="card"><h2>① 丢单货号（2025 同期 ≥3 万 → 2026 归零）{{nlost}} 个 ｜ 基数 {{sumlost}} 万元</h2>
{{tbLost}}<div class="note" style="margin-top:10px">判定阈值＝2025 同期金额 ≥3 万且 2026 同期为 0；下方灰底为<b>活动/商机证据</b>，未命中的货号需人工确认（不得据此认定丢单原因）。</div></div>

<div class="card"><h2>② 大幅萎缩（跌幅 ≥70%）{{nshrink}} 个</h2>{{tbShrink}}</div>

<div class="card"><h2>③ 新机会 / 新增货号（2026 同期 ≥3 万、2025 无）{{nnew}} 个 ｜ 合计 {{sumnew}} 万元</h2>
{{tbNew}}<div class="note" style="margin-top:10px">「首单月」＝该货号 2026 首次成交月份；<b>9 月集中出现多个新增</b>（丹威、明德、基灵西安、圣湘），多数处于<b>测试转小批</b>阶段，尚不能算转化完成。</div></div>

<div class="card"><h2>④ 上量（2026 ≥2× 于 2025）{{ngrow}} 个 ｜ 增量 {{sumg}} 万元</h2>{{tbGrow}}
<div class="note" style="margin-top:10px">其中<b>已有注册/量产证据</b>的：<span class="mono">M2161</span>（客户 3-4 个项目将注册报证）、<span class="mono">FE31</span>（已完成 60 天验证并完成替换）、<span class="mono">M4211-3</span>（分装工艺定型）、<span class="mono">M2181-4</span>（达安准备扩测并采购 FE41）。</div></div>

<div class="card"><h2>⑤ 待你拍板 / 待确认的疑问（{{nq}} 条）</h2>{{qs}}</div>

<div class="card"><h2>⑥ 口径与缺口</h2><div class="note">
① 期间：一律 <b>1-9 月同期</b>；2026 年 10 月（155.1 万）为在途数据，未混入同比；<br>
② 金额：标准销售订单 + 已审核 + 非赠品 + 剔除宝锐系（与工作台 ERP 口径一致），单位万元；<br>
③ 归属：按 ERP <b>销售员</b> 圈定大客户部（含离职/代管人员），客户跨部门代采部分会同时出现在两个部门，未做集团去重（疑问 3）；<br>
④ 货号粒度：`货号#` 列原样，未按母体系列合并 —— 定制尾段（`-1/-2/-TP/-NA`）差异可能是<b>同族不同形态</b>，读表时勿合并；<br>
⑤ 证据只覆盖本地活动正文与商机摘要（<b>10 个货号</b>命中），飞书商机字段未回填货号，未做 MQL 全量拉取（疑问 5）。
</div></div>
</div><script>/* 静态自包含 */</script></body></html>"""

VALS = {
    'gen': '2026-10-16', 'ncust': str(D['meta']['dk_cust']),
    'nlost': str(len(D['lost'])), 'sumlost': m(wan(SUML), 1),
    'nshrink': str(len(D['shrink'])), 'nnew': str(len(D['new'])), 'sumnew': m(wan(SUMNEW), 1),
    'ngrow': str(len(D['grow'])), 'sumg': m(wan(SUMG), 1),
    'tbLost': tab(None, ['货号', '品名', '@2025 同期(万)', '涉及客户'], lost),
    'tbShrink': tab(None, ['货号', '品名', '@2025(万)', '@2026(万)', '@同比', '最后成交月', '涉及客户'], shrink),
    'tbNew': tab(None, ['货号', '品名', '@2026(万)', '首单月', '客户'], new),
    'tbGrow': tab(None, ['货号', '品名', '@2025(万)', '@2026(万)', '@增幅', '涉及客户'], grow),
    'nq': str(len(QUESTIONS)), 'qs': qs,
}
page = HTML
for k, v in VALS.items():
    page = page.replace('{{%s}}' % k, str(v))
assert not re.findall(r'\{\{\w+\}\}', page), set(re.findall(r'\{\{\w+\}\}', page))
page = BK.inject(page, theme='light', tag='营销中心 · 大客户部', footer_extra='数据源：ERP 2026 主档 / 2025 全年订单 / 活动正文 / 商机摘要')
v = BK.verify(page)
assert all(v.values()), [k for k, ok in v.items() if not ok]
out = W + '/销售分析/大客户部_货号丢单与新机会_2025vs2026.html'
io.open(out, 'w', encoding='utf-8').write(page)
print('✅ HTML:', out, '%.0f KB' % (os.path.getsize(out) / 1024))
print('   丢单 %d 个（%s 万）｜ 萎缩 %d ｜ 新增 %d 个（%s 万）｜ 上量 %d 个（+%s 万）｜ 疑问 %d 条'
      % (len(D['lost']), m(wan(SUML), 1), len(D['shrink']), len(D['new']), m(wan(SUMNEW), 1), len(D['grow']), m(wan(SUMG), 1), len(QUESTIONS)))
