#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成《科研促销期 · 档位达成与礼品发放表（按客户机构）》HTML。

口径 = Q2-A：促销期内采购额（价税合计，元）套用【预存返利】7 档 → 返礼品金额。
课题组维度待《待补挂清单》回传后重算（页面预留区块）。
"""
import io
import json
import os
import re
import subprocess
import sys
import tempfile

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

OUTDIR = os.path.join(ROOT, '营销活动', '2026-10实验加速季')
os.makedirs(OUTDIR, exist_ok=True)
OUT = os.path.join(OUTDIR, '科研促销期_档位达成与礼品发放表_按客户.html')

TIERS = [(100000, 10000, 5000), (50000, 5000, 2500), (20000, 2000, 1000),
         (10000, 1000, 500), (5000, 500, 250), (2000, 200, 100), (1000, 100, 50)]

D = json.load(io.open(ROOT + '/销售分析/科研销售_按人与区域_20260820-20260930.json', encoding='utf-8'))
rows = []
for c in D['customers']:
    y = round(c['金额'] * 10000, 2)
    th = gift = reagent = 0
    for t, r, g in TIERS:
        if y >= t:
            th, reagent, gift = t, r, g
            break
    rows.append({'客户': c['客户'], '大区': c['大区'], '主跟': c['主跟销售'], '订单数': c['订单数'],
                 '金额': y, '档位': th, '返试剂': reagent, '返礼品': gift})
rows.sort(key=lambda r: -r['金额'])
tot = round(sum(r['金额'] for r in rows), 2)
gift_tot = sum(r['返礼品'] for r in rows)
reagent_tot = sum(r['返试剂'] for r in rows)
reach = [r for r in rows if r['档位']]
from collections import Counter
dist = Counter(r['档位'] for r in reach)

CSS = """
*{box-sizing:border-box}
body{margin:0;background:#f5f7fa;color:#1f2328;font:14px/1.65 -apple-system,"PingFang SC","Microsoft YaHei",sans-serif}
.wrap{max-width:1320px;margin:0 auto;padding:18px}
h1{font-size:23px;font-weight:900;color:#0F3A8A;margin:8px 0 4px}
.sub{color:#5f6368;font-size:13px;margin-bottom:14px}
.kpi{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px}
.k{background:#fff;border-radius:10px;padding:12px 16px;min-width:140px;border-left:4px solid #1852B6;box-shadow:0 1px 3px rgba(16,24,40,.08)}
.k .t{font-size:12px;color:#5f6368}.k .v{font-size:22px;font-weight:900;color:#1852B6;line-height:1.25}
.k .u{font-size:11px;color:#98A2B3}
.k.gold{border-left-color:#E9A800}.k.gold .v{color:#B45309}
.sec{background:#fff;border-radius:12px;padding:15px 17px;margin-bottom:14px;box-shadow:0 1px 3px rgba(16,24,40,.08)}
.sec h2{font-size:16px;font-weight:900;color:#0F3A8A;margin:0 0 11px;border-left:4px solid #1852B6;padding-left:10px}
.bar{display:flex;gap:9px;align-items:center;flex-wrap:wrap;margin-bottom:10px}
input[type=text]{border:1px solid #D0D5DD;border-radius:8px;padding:6px 10px;font-size:13px;min-width:210px}
.btn{border:1px solid #D0D5DD;background:#fff;border-radius:8px;padding:6px 12px;font-size:13px;cursor:pointer;color:#344054}
.btn:hover{background:#F2F6FD;border-color:#1852B6;color:#1852B6}
.tw{overflow-x:auto}
table{width:100%;border-collapse:collapse;font-size:13px}
th,td{padding:7px 9px;border-bottom:1px solid #EEF1F5}
th{background:#F0F4FB;color:#0F3A8A;font-weight:700;text-align:left;position:sticky;top:0}
th.r,td.r{text-align:right}
th.c,td.c{text-align:center}
tbody tr:hover{background:#F7FAFF}
tr.hit{background:#FFFBF0}
.tg{display:inline-block;border-radius:6px;padding:1px 8px;font-size:12px;font-weight:800;background:#E8F0FE;color:#1852B6}
.tg.no{background:#F1F3F6;color:#98A2B3;font-weight:600}
.tg.g{background:#FFF4D6;color:#B45309}
.warn{background:#FFF8E6;border-left:4px solid #E9A800;border-radius:9px;padding:11px 14px;font-size:13px;margin-bottom:13px}
.warn b{color:#8A6100}
.foot{background:#fff;border-radius:12px;padding:15px 17px;font-size:12.5px;color:#475467;line-height:1.9}
.foot h3{font-size:15px;color:#0F3A8A;margin:0 0 7px}
.bars{display:flex;gap:8px;align-items:flex-end;height:120px;margin:8px 0}
.bars .b{flex:1;text-align:center}
.bars .b i{display:block;background:linear-gradient(180deg,#4E9BFF,#1852B6);border-radius:5px 5px 0 0}
.bars .b span{font-size:11px;color:#5f6368;display:block;margin-top:4px}
.bars .b em{font-size:12px;font-weight:800;color:#1852B6;font-style:normal;display:block}
"""

mx = max(r['金额'] for r in rows) or 1
bars = ''.join('<div class="b"><em>%d</em><i style="height:%.0fpx"></i><span>%s</span></div>'
               % (dist.get(t, 0), max(3, dist.get(t, 0) / max(dist.values()) * 88), ('%d元' % t)) for t in [1000, 2000, 5000, 10000, 20000, 50000, 100000])

body = ''
for i, r in enumerate(rows, 1):
    hit = ' class="hit"' if r['档位'] else ''
    tg = ('<span class="tg g">%d 元档</span>' % r['档位']) if r['档位'] else '<span class="tg no">未达 1,000</span>'
    body += ('<tr%s><td class="c">%d</td><td>%s</td><td class="c"><span class="tg">%s</span></td><td>%s</td>'
             '<td class="c">%d</td><td class="r">%s</td><td class="c">%s</td>'
             '<td class="r">%s</td><td class="r"><b>%s</b></td></tr>'
             % (hit, i, r['客户'], r['大区'], r['主跟'], r['订单数'], f"{r['金额']:,.0f}", tg,
                f"{r['返试剂']:,}" if r['返试剂'] else '—',
                f"{r['返礼品']:,}" if r['返礼品'] else '—'))

html = f"""<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>科研促销期 · 档位达成与礼品发放表</title><style>{CSS}</style></head><body><div class="wrap">

<h1>科研促销期 · 档位达成与礼品发放表</h1>
<div class="sub">口径：<b>按促销期内采购额套用「预存返利」档位</b>（Q2-A）｜ 区间 <b>{D['start']} ~ {D['end']}</b>
 ｜ 生命科学销售部 ｜ 数据源 {D['src']} ｜ 内部资料</div>

<div class="warn"><b>⚠️ 两点必须知道：</b>
① 方案原文的档位依据是<b>预存款</b>，本地无预存款数据，本表按<b>实际采购额</b>套用同一组档位 —— <b>两者口径不同</b>，
若最终要按预存发礼品，需先拿到预存款导出。<br>
② 本表按 <b>客户（开票机构）</b> 归集。你要的<b>课题组</b>维度需先补挂订单的「关联一级课题组」
（促销期 78 张科研单只有 6 张已挂）—— 用《科研促销期订单_课题组待补挂清单》补完后我重算本表。</div>

<div class="kpi">
  <div class="k"><div class="t">采购额合计</div><div class="v">{tot:,.0f}</div><div class="u">元（含税）</div></div>
  <div class="k"><div class="t">客户数</div><div class="v">{len(rows)}</div><div class="u">去重</div></div>
  <div class="k gold"><div class="t">达档客户</div><div class="v">{len(reach)}</div><div class="u">≥1,000 元</div></div>
  <div class="k gold"><div class="t">返礼品合计</div><div class="v">{gift_tot:,}</div><div class="u">元</div></div>
  <div class="k"><div class="t">返试剂合计</div><div class="v">{reagent_tot:,}</div><div class="u">元</div></div>
</div>

<div class="sec"><h2>档位分布（达档 {len(reach)} 家）</h2><div class="bars">{bars}</div>
<div class="sub" style="margin:0">各档家数标注在柱顶；峰值在 <b>2,000 元档</b>（{dist.get(2000, 0)} 家）。</div></div>

<div class="sec"><h2>明细（按采购额降序）</h2>
<div class="bar"><input type="text" id="q" placeholder="筛选：客户 / 销售 / 只输「达档」…">
  <button class="btn" onclick="csv()">导出 CSV</button><span class="sub" id="n" style="margin:0"></span></div>
<div class="tw"><table id="t"><thead><tr>
<th class="c" style="width:34px">#</th><th>客户（开票机构）</th><th class="c" style="width:76px">大区</th>
<th style="width:74px">主跟销售</th><th class="c" style="width:62px">订单</th><th class="r" style="width:104px">采购额(元)</th>
<th class="c" style="width:106px">达标档位</th><th class="r" style="width:96px">返试剂(元)</th><th class="r" style="width:96px">返礼品(元)</th>
</tr></thead><tbody>{body}</tbody></table></div></div>

<div class="sec"><h2>档位对照表（方案原文 · 预存返利）</h2>
<table><thead><tr><th>预存款(元)</th>{"".join('<th class="r">%s</th>' % f'{t:,}' for t, _, _ in TIERS[::-1])}</tr></thead>
<tbody><tr><td>返试剂(元)</td>{"".join('<td class="r">%s</td>' % f'{r:,}' for _, r, _ in TIERS[::-1])}</tr>
<tr class="hit"><td><b>返礼品(元)</b></td>{"".join('<td class="r"><b>%s</b></td>' % f'{g:,}' for _, _, g in TIERS[::-1])}</tr>
</tbody></table>
<div class="sub" style="margin:9px 0 0">取「达标的最高档」计一份礼品（方案要求：一笔预付款、一份赠品、一张签收单）。</div></div>

<div class="foot"><h3>口径与待办</h3>
① <b>金额</b>：{D['caliber']}；单位=元（含税），区间按合同日期。<br>
② <b>档位</b>：取自《2026 秋季促销活动方案 V1.2》三、预存返利规则；返礼品 = 对应档位返试剂价值的 50%。<br>
③ <b>勾稽</b>：Σ客户采购额 = {tot:,.0f} 元 = 科研部门合计 9.9599 万元 ✅。<br>
④ <b>未达档</b>：{len(rows) - len(reach)} 家采购额 &lt; 1,000 元，不计礼品。<br>
⑤ <b>待办</b>：补挂课题组后重算 —— 同一机构会拆到多个课题组（如浙大分属「喻景权课题组」「马忠华课题组」），
届时按课题组出礼品的家数与金额会与按机构不同。<br>
⑥ 本表含真实客户名与金额，<b>仅内部使用，不得对外发送</b>。
</div></div>
<script>
function rows(){{return [...document.querySelectorAll('#t tbody tr')];}}
var comp=false;
document.getElementById('q').addEventListener('compositionstart',function(){{comp=true;}});
document.getElementById('q').addEventListener('compositionend',function(){{comp=false;flt();}});
document.getElementById('q').addEventListener('input',function(){{if(!comp)flt();}});
function flt(){{
  var q=(document.getElementById('q').value||'').trim().toLowerCase();
  var v=0;
  rows().forEach(function(tr){{var ok=!q||tr.innerText.toLowerCase().indexOf(q)>=0;tr.style.display=ok?'':'none';if(ok)v++;}});
  document.getElementById('n').textContent='显示 '+v+' / '+rows().length+' 行';
}}
function csv(){{
  var t=document.getElementById('t');
  var hs=[...t.querySelectorAll('thead th')].map(function(x){{return x.innerText.trim();}});
  var ls=[hs.join(',')];
  rows().forEach(function(tr){{if(tr.style.display==='none')return;
    ls.push([...tr.children].map(function(td){{return '"'+td.innerText.trim().replace(/"/g,'""')+'"';}}).join(','));}});
  var b=new Blob(['\\ufeff'+ls.join('\\n')],{{type:'text/csv;charset=utf-8'}});
  var a=document.createElement('a');a.href=URL.createObjectURL(b);a.download='科研促销期_档位礼品_按客户.csv';a.click();
}}
flt();
</script>
</body></html>
"""

html = BK.inject(html, theme='light', tag='营销中心 · 销售管理',
                 footer_extra='科研促销期档位达成与礼品发放表（按客户）｜ 2026-08-20~09-30')
io.open(OUT, 'w', encoding='utf-8').write(html)

print('=== 质检 ===')
vr = BK.verify(html)
bad = [k for k, v in vr.items() if not v]
print(('✅' if not bad else '❌'), '品牌套件', '全 True' if not bad else bad)
print(('✅' if not re.findall(r'(?:src|href)\s*=\s*["\'](?:https?:)?//', html) else '❌'), '外链数 0')
blocks = re.findall(r'<script[^>]*>(.*?)</script>', html, re.S)
f = 0
for b in blocks:
    fd, p = tempfile.mkstemp(suffix='.js'); os.write(fd, b.encode()); os.close(fd)
    r = subprocess.run(['node', '--check', p], capture_output=True, text=True)
    if r.returncode: f += 1; print('   ❌', r.stderr.strip().splitlines()[-1][:100])
    os.unlink(p)
print(('✅' if not f else '❌'), 'node --check：%d 块 / %d 报错' % (len(blocks), f))
print('  客户 %d ｜ 采购额 %s 元 ｜ 达档 %d ｜ 返礼品 %s 元 ｜ 返试剂 %s 元'
      % (len(rows), f'{tot:,.0f}', len(reach), f'{gift_tot:,}', f'{reagent_tot:,}'))
print('  档位分布:', dict(sorted(dist.items())))
print('  输出 %s（%.0f KB）' % (OUT, os.path.getsize(OUT) / 1024.0))
