#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""构建「宝锐分子诊断原料知识汇总 / 查询工具」——自包含单文件 HTML + 说明书 PDF 打包。

输入：数据/diag_knowledge.json（由 build_diag_knowledge.py 生成）
输出：产品与市场/宝锐分子诊断原料知识库/
        index.html          自包含查询工具（数据内嵌、零外链）
        说明书/*.pdf        核心货号说明书（相对路径可点开）
        使用说明.md
"""
import json
import os
import re
import shutil
import sys

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

SRC = os.path.join(ROOT, '数据/diag_knowledge.json')
OUTDIR = os.path.join(ROOT, '产品与市场/宝锐分子诊断原料知识库')
NOW = __import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M')


def esc(s):
    return (str(s or '').replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;'))


def table_html(rows):
    if not rows:
        return ''
    head = ''.join('<th>%s</th>' % esc(c) for c in rows[0])
    body = ''.join('<tr>%s</tr>' % ''.join('<td>%s</td>' % esc(c) for c in r) for r in rows[1:])
    return '<table class="pt"><thead><tr>%s</tr></thead><tbody>%s</tbody></table>' % (head, body)


def build_html(data):
    items = data['items']
    stat = data['统计']
    scen_names = sorted({s['项目类型'] for r in items for s in r['应用场景'] if s['项目类型']})
    cats = ['DNA', 'RNA', '单酶', '预混液', 'LAMP', '辅料']
    cat_counts = {c: sum(1 for r in items if c in r['分类标签']) for c in cats}

    comp_path = os.path.join(ROOT, '数据/competitor_benchmark.json')
    comp = json.load(open(comp_path, encoding='utf-8')) if os.path.exists(comp_path) else {'items': []}
    payload = json.dumps({'items': items, 'stat': stat, '口径': data['口径'],
                          '生成时间': data['生成时间'], 'comp': comp}, ensure_ascii=False).replace('</', '<\\/')

    kpi = ''.join(
        '<div class="kpi"><div class="kpi-n">%s</div><div class="kpi-l">%s</div></div>' % (v, k) for k, v in
        [('核心货号', stat['核心货号数']), ('含说明书', stat['有说明书']),
         ('体系配制表', stat['有体系配制表']), ('反应程序', stat['有反应程序']),
         ('应用场景', stat['有应用场景']), ('客户案例', stat['有案例反馈']),
         ('竞品证据', stat['有竞品证据'])])

    chips_cat = ''.join(
        '<button class="chip" data-cat="%s">%s <b>%d</b></button>' % (c, c, cat_counts[c]) for c in cats)
    chips_scen = ''.join('<button class="chip" data-scen="%s">%s</button>' % (esc(s), esc(s)) for s in scen_names)

    html = """<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>宝锐分子诊断原料知识汇总 / 查询工具</title>
<style>
:root{--blue:#1a5fb4;--blue2:#2b7fd4;--bg:#f0f2f5;--card:#fff;--line:#e3e8ef;--muted:#5f6b7a;--ok:#1a7f37;--warn:#b45309;--bad:#b91c1c}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:#1c2430;font:14px/1.65 -apple-system,"PingFang SC","Microsoft YaHei",sans-serif;display:flex;flex-direction:column;min-height:100vh}
main{flex:1 0 auto}
.wrap{max-width:1440px;margin:0 auto;padding:18px 20px 40px}
nav.kb-nav{position:sticky;top:0;z-index:30;background:linear-gradient(135deg,#0b3d91,#1a5fb4);color:#fff;padding:10px 20px;display:flex;gap:14px;align-items:center;flex-wrap:wrap}
nav.kb-nav b{font-size:15px}
nav.kb-nav span{opacity:.85;font-size:12.5px}
h1{font-size:21px;margin:16px 0 6px}
.sub{color:var(--muted);font-size:13px;margin-bottom:14px}
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin:14px 0 18px}
.kpi{background:var(--card);border:1px solid var(--line);border-left:4px solid var(--blue2);border-radius:10px;padding:10px 12px}
.kpi-n{font-size:22px;font-weight:700;color:var(--blue)}
.kpi-l{font-size:12px;color:var(--muted)}
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px 16px;margin-bottom:14px}
.frow{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0 0 10px}
.frow .lbl{font-size:12.5px;color:var(--muted);min-width:62px}
input.search{flex:1 1 260px;min-width:200px;padding:9px 12px;border:1px solid var(--line);border-radius:9px;font-size:13.5px}
.chip{border:1px solid var(--line);background:#fff;color:#33415c;border-radius:999px;padding:5px 12px;font-size:12.5px;cursor:pointer}
.chip b{color:var(--blue);font-weight:600}
.chip.on{background:var(--blue);border-color:var(--blue);color:#fff}
.chip.on b{color:#fff}
.tools{display:flex;gap:10px;align-items:center;flex-wrap:wrap;font-size:12.5px;color:var(--muted)}
.btn{border:1px solid var(--line);background:#fff;border-radius:8px;padding:6px 12px;font-size:12.5px;cursor:pointer}
.btn:hover{background:#f7f9fc}
.tblwrap{overflow-x:auto;border:1px solid var(--line);border-radius:10px;background:#fff}
table.grid{width:100%;border-collapse:collapse;font-size:13px}
table.grid th{position:sticky;top:0;background:#f5f8fc;text-align:left;padding:9px 10px;border-bottom:2px solid var(--line);white-space:nowrap;cursor:pointer;font-weight:600}
table.grid td{padding:9px 10px;border-bottom:1px solid #eef2f7;vertical-align:top;white-space:normal;word-break:break-word}
table.grid tr:hover{background:#f8fbff;cursor:pointer}
table.grid tr.sel{background:#eaf3ff}
.tag{display:inline-block;padding:1px 7px;border-radius:6px;font-size:11.5px;margin:1px 3px 1px 0;border:1px solid transparent;white-space:nowrap}
.t-DNA{background:#eaf3ff;color:#1a5fb4;border-color:#cfe3ff}
.t-RNA{background:#f0ebff;color:#5b21b6;border-color:#e0d7ff}
.t-单酶{background:#fff5e6;color:#b45309;border-color:#ffe3b8}
.t-预混液{background:#e8f7ee;color:#1a7f37;border-color:#c9eeda}
.t-LAMP{background:#ffeef1;color:#b91c1c;border-color:#ffd6dd}
.t-辅料{background:#f1f3f6;color:#4b5563;border-color:#e2e6ec}
.t-冻干{background:#e6f7ff;color:#0369a1;border-color:#c9ecff}
.yes{color:var(--ok);font-weight:600}.no{color:#9aa4b2}
table.pt{border-collapse:collapse;font-size:12.5px;margin:6px 0 10px;max-width:100%}
table.pt th,table.pt td{border:1px solid var(--line);padding:6px 9px;text-align:left;white-space:normal}
table.pt th{background:#f5f8fc;font-weight:600}
.drawer{position:fixed;top:0;right:0;height:100%;width:min(880px,96vw);background:#fff;box-shadow:-8px 0 32px rgba(16,32,64,.18);transform:translateX(102%);transition:transform .22s ease;overflow-y:auto;z-index:60;padding:0 0 40px}
.drawer.open{transform:none}
.dhead{position:sticky;top:0;background:linear-gradient(135deg,#0b3d91,#1a5fb4);color:#fff;padding:14px 18px;display:flex;justify-content:space-between;gap:12px;align-items:flex-start}
.dhead h2{margin:0;font-size:17px}
.dhead .code{font-size:13px;opacity:.9}
.dbody{padding:14px 18px}
.sec{border-top:1px solid var(--line);padding:12px 0}
.sec:first-child{border-top:0}
.sec h3{margin:0 0 8px;font-size:14px;color:var(--blue)}
.kv{display:grid;grid-template-columns:104px 1fr;gap:4px 10px;font-size:13px}
.kv .k{color:var(--muted)}
.quote{background:#f7f9fc;border-left:3px solid var(--blue2);border-radius:0 8px 8px 0;padding:7px 10px;margin:6px 0;font-size:12.5px;white-space:normal}
.quote .m{color:var(--muted);font-size:11.5px}
.note{font-size:12px;color:var(--muted);background:#fbfcfe;border:1px dashed var(--line);border-radius:8px;padding:8px 10px;margin:6px 0}
.warn{font-size:12px;color:#8a4b00;background:#fff8ec;border:1px solid #ffe3b8;border-radius:8px;padding:8px 10px;margin:6px 0}
.close{border:0;background:rgba(255,255,255,.18);color:#fff;border-radius:8px;padding:6px 10px;cursor:pointer;font-size:12.5px}
@media(max-width:760px){.kv{grid-template-columns:1fr}.wrap{padding:12px}}
.tabs{display:flex;gap:10px;align-items:center;margin:14px 0 4px;flex-wrap:wrap}
.tab{border:1px solid var(--line);background:#fff;color:#33415c;border-radius:9px;padding:8px 16px;font-size:13.5px;cursor:pointer;font-weight:600}
.tab.on{background:var(--blue);border-color:var(--blue);color:#fff}
.tabhint{font-size:12px;color:var(--muted)}
</style></head>
<body>
<nav class="kb-nav"><b>宝锐生物 · 分子诊断原料知识库</b>
<span>货号 × 应用场景 × DNA/RNA/单酶/预混液/LAMP × 说明书体系与程序</span></nav>
<main><div class="wrap">
<div class="tabs"><button class="tab on" id="tab-kb">货号知识库</button><button class="tab" id="tab-comp">竞品对标</button>
<span class="tabhint">第二页：诺唯赞 / 翌圣 / 菲鹏 / 全式金 公开产品与参数对照</span></div>
<div id="view-kb">
<h1>分子诊断原料知识汇总 / 查询工具</h1>
<div class="sub">数据源：官网产品页与说明书 PDF ｜ 产品推荐清单（客户场景）｜ 内部产品清单（规格/组分）｜ 飞书活动记录（客户案例与竞品证据）　·　生成于 __NOW__　·　**内部资料**</div>
<div class="kpis">__KPI__</div>

<div class="card">
  <div class="frow"><span class="lbl">搜索</span>
    <input class="search" id="q" placeholder="输入货号 / 品名 / 客户 / 项目类型 / 竞品品牌，例如 M3291、结核、诺唯赞、LAMP">
  </div>
  <div class="frow"><span class="lbl">分类</span><button class="chip on" data-cat="__ALL__">全部 <b>__TOTAL__</b></button>__CHIPCAT__</div>
  <div class="frow"><span class="lbl">应用场景</span><button class="chip on" data-scen="__ALL__">全部</button>__CHIPSCEN__</div>
  <div class="frow"><span class="lbl">快捷</span>
    <button class="chip" id="fMan">仅看有说明书</button>
    <button class="chip" id="fSys">仅看有体系配制表</button>
    <button class="chip" id="fComp">仅看有竞品证据</button>
    <button class="chip" id="fDry">仅看冻干体系</button>
  </div>
  <div class="tools">
    <span id="cnt">—</span>
    <button class="btn" id="exp">导出当前结果 CSV</button>
    <button class="btn" id="rst">重置筛选</button>
    <span>点击任意行查看货号详情（说明书体系配制表 / 反应程序 / 案例 / 竞品）</span>
  </div>
</div>

<div class="tblwrap"><table class="grid" id="grid">
<thead><tr>
<th data-k="货号">货号</th><th data-k="品名">品名</th><th data-k="主分类">分类</th>
<th data-k="规格">规格</th><th data-k="nug">场景</th><th data-k="man">说明书</th>
<th data-k="sys">体系表</th><th data-k="comp">竞品证据</th>
</tr></thead><tbody id="tb"></tbody></table></div>
</div><!-- /view-kb -->

<div id="view-comp" hidden>
  <h1>竞品对标 · 诺唯赞 / 翌圣 / 菲鹏 / 全式金</h1>
  <div class="sub">数据只取公开来源（各厂官网产品页与解决方案页），逐条可回溯。<b>公开宣称 ≠ 客户实测</b>；与本方货号的差距请结合「货号知识库」里的客户反馈使用。诺唯赞官网采集时被 WAF 拦截，参数待补。</div>
  <div class="kpis" id="comp-kpi"></div>
  <div class="card">
    <div class="frow"><span class="lbl">品牌</span><span id="comp-brands"></span></div>
    <div class="frow"><span class="lbl">品类</span><span id="comp-cats"></span></div>
    <div class="frow"><span class="lbl">搜索</span><input class="search" id="cq" placeholder="输入品牌 / 货号 / 品名关键词，例如 可冻干、Bst、14316ES"></div>
    <div class="tools"><span id="comp-cnt">—</span><button class="btn" id="comp-exp">导出 CSV</button><button class="btn" id="comp-rst">重置</button></div>
  </div>
  <div class="tblwrap"><table class="grid" id="comp-grid">
    <thead><tr><th>品牌</th><th>竞品货号</th><th>品名</th><th>品类</th><th>特性标签</th><th>公开卖点</th><th>来源</th></tr></thead>
    <tbody id="comp-tb"></tbody></table></div>
  <div class="card" style="margin-top:14px"><div class="sec"><h3>我方可对标货号（按分类）</h3>
    <div id="comp-ours" style="font-size:12.5px;line-height:1.9"></div>
    <div class="note" style="margin-top:8px">对标以「同品类」为准，不做逐型号硬对；型号级差异须以实验室数据或客户实测为准。</div></div></div>
</div>
</div></main>

<div class="drawer" id="dr"><div class="dhead"><div><h2 id="dt">—</h2><div class="code" id="dc"></div></div>
<button class="close" id="dclose">关闭 ✕</button></div><div class="dbody" id="db"></div></div>

<script>
const D = __PAYLOAD__;
const ITEMS = (D.items||[]).map(function(r,i){r._i=i;return r;});
const S = {cat:(D.stat&&D.stat['分类分布'])||{}, cats:['DNA','RNA','单酶','预混液','LAMP','辅料'], q:'', scen:'__ALL__', cat:'__ALL__', man:0, sys:0, comp:0, dry:0, sortK:'', sortD:1};
const ALL='__ALL__';
function has(r,k){return (r['分类标签']||[]).indexOf(k)>=0;}
function sysCount(r){return (r['体系配制']||[]).reduce(function(a,x){return a+((x['表格']||[]).length)},0);}
function rowCtx(r){
  return [r['货号'],r['品名'],(r['分类标签']||[]).join(''),r['规格'],r['官网分类'],r['销售分组'],
    (r['应用场景']||[]).map(function(s){return s['项目类型']+' '+s['技术方法']+' '+(s['核酸类型']||'')}).join(' '),
    (r['场景特征']||[]).map(function(s){return s['特征']}).join(' '),
    (r['案例与反馈']||[]).map(function(s){return s['客户']+' '+s['原文']}).join(' '),
    (r['竞品证据']||[]).map(function(s){return (s['竞品']||[]).join('')+' '+s['原文']}).join(' '),
    Object.keys(r['竞品品牌']||{}).join(' ')].join(' ').toLowerCase();
}
function rows(){
  var q=S.q.trim().toLowerCase();
  var out=ITEMS.filter(function(r){
    if(S.cat!==ALL && !has(r,S.cat))return false;
    if(S.scen!==ALL && !(r['应用场景']||[]).some(function(s){return s['项目类型']===S.scen}))return false;
    if(S.man && !(r['说明书文件名']||r['说明书_产品资料文件']||r['说明书URL']))return false;
    if(S.sys && !sysCount(r))return false;
    if(S.comp && !(r['竞品证据']||[]).length)return false;
    if(S.dry && !r['冻干体系'])return false;
    if(q && rowCtx(r).indexOf(q)<0)return false;
    return true;
  });
  if(S.sortK){
    out.sort(function(a,b){
      function val(r){
        switch(S.sortK){
          case 'nug':return (r['应用场景']||[]).length;
          case 'man':return (r['说明书文件名']||r['说明书URL'])?1:0;
          case 'sys':return sysCount(r);
          case 'comp':return (r['竞品证据']||[]).length;
          default:return (r[S.sortK]===undefined?'':r[S.sortK]);
        }
      }
      var x=val(a),y=val(b);
      if(typeof x==='number'&&typeof y==='number')return (x-y)*S.sortD;
      return String(x).localeCompare(String(y),'zh')*S.sortD;
    });
  }
  return out;
}
function esc(s){return String(s===undefined||s===null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
function tblToHtml(rows){
  if(!rows||!rows.length)return '';
  var h='<table class="pt"><thead><tr>'+rows[0].map(function(c){return '<th>'+esc(c)+'</th>'}).join('')+'</tr></thead><tbody>';
  for(var i=1;i<rows.length;i++){h+='<tr>'+rows[i].map(function(c){return '<td>'+esc(c)+'</td>'}).join('')+'</tr>'}
  return h+'</tbody></table>';
}
function tags(r){
  return (r['分类标签']||[]).map(function(c){return '<span class="tag t-'+c+'">'+c+'</span>'}).join('')
    + (r['冻干体系']?'<span class="tag t-冻干">冻干</span>':'');
}
function render(){
  var rs=rows(), tb=document.getElementById('tb');
  document.getElementById('cnt').textContent='匹配 '+rs.length+' / '+ITEMS.length+' 个货号';
  tb.innerHTML=rs.map(function(r){
    var sc=sysCount(r);
    return '<tr data-i="'+r._i+'"><td><b>'+r['货号']+'</b></td><td>'+(r['品名']||'<span class="no">待补</span>')+'</td>'
      +'<td>'+tags(r)+'</td><td>'+(r['规格']||'<span class="no">—</span>')+'</td>'
      +'<td>'+((r['应用场景']||[]).length||'<span class="no">0</span>')+'</td>'
      +'<td class="'+((r['说明书文件名']||r['说明书_产品资料文件'])?'yes':'no')+'">'+((r['说明书文件名']||r['说明书_产品资料文件'])?'✓':'—')+'</td>'
      +'<td class="'+(sc?'yes':'no')+'">'+(sc||'—')+'</td>'
      +'<td class="'+((r['竞品证据']||[]).length?'yes':'no')+'">'+((r['竞品证据']||[]).length||'—')+'</td></tr>';
  }).join('')||'<tr><td colspan="8" style="text-align:center;color:#9aa4b2;padding:22px">无匹配货号</td></tr>';
  Array.prototype.forEach.call(tb.querySelectorAll('tr[data-i]'),function(tr){
    tr.onclick=function(){open(parseInt(tr.getAttribute('data-i'),10))};
  });
}
var cur=-1;
function open(i){
  var r=ITEMS[i]; cur=i;
  Array.prototype.forEach.call(document.querySelectorAll('tr.sel'),function(x){x.classList.remove('sel')});
  var tr=document.querySelector('tr[data-i="'+i+'"]'); if(tr)tr.classList.add('sel');
  document.getElementById('dt').textContent=r['品名']||r['货号'];
  document.getElementById('dc').textContent='货号 '+r['货号']+(r['官网分类']?' ｜ 官网分类：'+r['官网分类']:'')+(r['规格']?' ｜ 规格：'+r['规格']:'');
  var h='';
  h+='<div class="sec"><h3>基本信息</h3><div class="kv">'
    +'<div class="k">货号</div><div><b>'+r['货号']+'</b></div>'
    +'<div class="k">品名</div><div>'+(r['品名']||'待补')+'<span class="no">（来源：'+(r['品名来源']||'—')+'）</span></div>'
    +(r['同系列说明']?'<div class="k">同系列</div><div>'+r['同系列说明']+'</div>':'')
    +'<div class="k">分类</div><div>'+tags(r)+'</div>'
    +'<div class="k">官网分类</div><div>'+(r['官网分类']||'—')+'</div>'
    +'<div class="k">内部货号</div><div>'+(r['内部货号']||'—')+'</div>'
    +'<div class="k">规格</div><div>'+(r['规格']||'—')+'</div>'
    +'<div class="k">销售分组</div><div>'+(r['销售分组']||'—')+'</div>'
    +'<div class="k">BOM分组</div><div>'+(r['BOM分组']||'—')+'</div>'
    +'</div>';
  if((r['组分']||[]).length){
    h+='<div style="margin-top:8px"><b style="font-size:12.5px">套装组分</b><ul style="margin:6px 0 0 18px;font-size:12.5px">'
      +r['组分'].map(function(c){return '<li>'+c['组分']+(c['组分货号']?' ('+c['组分货号']+')':'')+(c['用量']?' × '+c['用量']:'')+'</li>'}).join('')+'</ul></div>';
  }
  h+='</div>';
  // 说明书
  h+='<div class="sec"><h3>说明书</h3>';
  var mname=r['说明书文件名']||r['说明书_产品资料文件']||'';
  if(mname){
    var pages=(r['体系配制']||[]).concat(r['反应程序']||[]).map(function(x){return x['页']});
    var pg=pages.length?Math.min.apply(null,pages):1;
    var lf=r['说明书_产品资料文件']||r['说明书本地文件']||'';
    h+='<div class="kv"><div class="k">文件</div><div>'+mname+'</div>'
      +'<div class="k">来源</div><div>'+(r['说明书来源']||'—')+'</div>'
      +(lf?'<div class="k">本地下载</div><div><a href="说明书/'+encodeURIComponent(lf)+'#page='+pg+'" target="_blank">打开 PDF（跳到第 '+pg+' 页）</a></div>'
          :'<div class="k">本地下载</div><div class="no">本包未附该 PDF，请用右侧官网链接</div>')
      +(r['说明书URL']?'<div class="k">官网链接</div><div><a href="'+r['说明书URL']+'" target="_blank" rel="noopener">官网 PDF</a></div>':'')
      +'</div>';
  } else {
    h+='<div class="note">该货号官网未发布说明书 PDF。可参考同场景体系货号的说明书：'
      +((r['同场景关联货号']||[]).map(function(p){return '<b>'+p['货号']+'</b>'}).join('、')||'—')+'</div>';
  }
  h+='</div>';
  // 体系配制表
  h+='<div class="sec"><h3>标准体系配制表（说明书原文）</h3>';
  if((r['体系配制']||[]).length){
    (r['体系配制']||[]).forEach(function(x){
      h+='<div class="note">出处：说明书第 '+x['页']+' 页</div>';
      (x['表格']||[]).forEach(function(t){h+=__TBL__(t)});
      if(!(x['表格']||[]).length){
        h+='<div class="note">该页未识别到规范表格，原文如下：</div>';
        (x['文本块']||[]).forEach(function(b){h+='<div class="quote">'+b.replace(/\\n/g,'<br>')+'</div>'});
      }
    });
  } else h+='<div class="note">待补：官网未发布该货号说明书。</div>';
  h+='</div>';
  // 反应程序
  h+='<div class="sec"><h3>推荐反应程序（说明书原文）</h3>';
  if((r['反应程序']||[]).length){
    (r['反应程序']||[]).forEach(function(x){
      h+='<div class="note">出处：说明书第 '+x['页']+' 页</div>';
      (x['表格']||[]).forEach(function(t){h+=__TBL__(t)});
      if(!(x['表格']||[]).length){
        (x['文本块']||[]).forEach(function(b){h+='<div class="quote">'+b.replace(/\\n/g,'<br>')+'</div>'});
      }
    });
  } else h+='<div class="note">待补：官网未发布该货号说明书。</div>';
  h+='</div>';
  // 场景
  h+='<div class="sec"><h3>推荐应用场景（客户热点）</h3>';
  if((r['应用场景']||[]).length){
    var seen={};
    (r['应用场景']||[]).forEach(function(s){
      var key=s['项目类型']+'|'+s['技术方法'];
      var feats=(r['场景特征']||[]).filter(function(f){return f['项目类型']===s['项目类型']&&f['技术方法']===s['技术方法']&&f['特征']});
      h+='<div class="quote"><b>'+s['项目类型']+'</b>'
        +(s['细分场景']?' ／ '+s['细分场景']:'')
        +' ｜ 技术方法：<b>'+s['技术方法']+'</b>'
        +(s['核酸类型']?' ｜ 核酸：'+s['核酸类型']:'')
        +(s['样本类型']?' ｜ 样本：'+s['样本类型']:'')
        +'<div class="m">出处：'+s['来源']+'</div>'
        +feats.map(function(f){return '<div style="margin-top:4px">· '+f['特征']+'</div>'}).join('')
        +'</div>';
    });
  } else h+='<div class="note">待补：该货号未进入产品推荐清单。</div>';
  h+='</div>';
  // 案例
  h+='<div class="sec"><h3>客户案例与使用反馈（活动记录原文）</h3>';
  if((r['案例与反馈']||[]).length){
    (r['案例与反馈']||[]).forEach(function(c){
      h+='<div class="quote">'+c['原文']+'<div class="m">'+c['日期']+' ｜ '+c['客户']+' ｜ 跟进：'+(c['销售']||'—')+'</div></div>';
    });
  } else h+='<div class="note">活动记录中未检索到该货号的直接反馈。</div>';
  h+='</div>';
  // 竞品
  h+='<div class="sec"><h3>对标竞品与差异</h3><div class="warn">诊断原料线<b>没有</b>货号级竞品对标表；以下为活动记录中真实提及（客户 / 日期 / 原文可溯源），属「客户在用哪家」的证据，非产品参数级对比。</div>';
  if(Object.keys(r['竞品品牌']||{}).length){
    h+='<div style="margin:6px 0">提及品牌热度：'+Object.keys(r['竞品品牌']).map(function(k){return '<span class="tag t-辅料">'+k+' '+r['竞品品牌'][k]+'</span>'}).join('')+'</div>';
  }
  if((r['竞品证据']||[]).length){
    (r['竞品证据']||[]).forEach(function(c){
      h+='<div class="quote">'+c['原文']+'<div class="m">'+c['日期']+' ｜ '+c['客户']+' ｜ 竞品：'+(c['竞品']||[]).join('、')+'</div></div>';
    });
  } else h+='<div class="note">活动记录中未检索到该货号与竞品的同句提及。</div>';
  h+='</div>';
  // 出处
  h+='<div class="sec"><h3>数据出处</h3><div class="note">来源标记：'+(r['来源']||[]).join(' ／ ')+
     '<br>· 官网产品页与说明书 PDF（宝锐官网）<br>· 产品推荐清单（客户项目 × 技术方法）<br>· 内部产品清单（规格 / 组分）<br>· 飞书活动记录（案例与竞品原文）</div></div>';
  document.getElementById('db').innerHTML=h;
  document.getElementById('dr').classList.add('open');
}
document.getElementById('dclose').onclick=function(){document.getElementById('dr').classList.remove('open')};
document.addEventListener('keydown',function(e){if(e.key==='Escape')document.getElementById('dr').classList.remove('open')});
document.querySelectorAll('.chip[data-cat]').forEach(function(b){b.onclick=function(){
  document.querySelectorAll('.chip[data-cat]').forEach(function(x){x.classList.remove('on')});
  b.classList.add('on'); S.cat=b.getAttribute('data-cat'); render();
}});
document.querySelectorAll('.chip[data-scen]').forEach(function(b){b.onclick=function(){
  document.querySelectorAll('.chip[data-scen]').forEach(function(x){x.classList.remove('on')});
  b.classList.add('on'); S.scen=b.getAttribute('data-scen'); render();
}});
function tog(id,key){var b=document.getElementById(id);b.onclick=function(){S[key]=S[key]?0:1;b.classList.toggle('on');render()}}
tog('fMan','man');tog('fSys','sys');tog('fComp','comp');tog('fDry','dry');
document.getElementById('q').oninput=function(e){S.q=e.target.value;render()};
document.getElementById('rst').onclick=function(){
  S.q='';S.scen=ALL;S.cat=ALL;S.man=S.sys=S.comp=S.dry=0;S.sortK='';S.sortD=1;
  document.getElementById('q').value='';
  document.querySelectorAll('.chip').forEach(function(x){x.classList.remove('on')});
  document.querySelector('.chip[data-cat="'+ALL+'"]').classList.add('on');
  document.querySelector('.chip[data-scen="'+ALL+'"]').classList.add('on');
  render();
};
document.getElementById('exp').onclick=function(){
  var rs=rows();
  var head=['货号','品名','分类','规格','官网分类','销售分组','说明书文件','体系表数','反应程序页','场景数','目标场景','竞品证据数','竞品品牌','冻干体系','品名来源'];
  var lines=[head.join(',')];
  rs.forEach(function(r){
    var cells=[r['货号'],r['品名'],(r['分类标签']||[]).join('+'),r['规格'],r['官网分类'],r['销售分组'],
      r['说明书文件名'],sysCount(r),(r['反应程序']||[]).length,(r['应用场景']||[]).length,
      (r['应用场景']||[]).map(function(s){return s['项目类型']}).join('/'),
      (r['竞品证据']||[]).length,Object.keys(r['竞品品牌']||{}).join('/'),r['冻干体系']?'是':'',r['品名来源']||''];
    lines.push(cells.map(function(c){return '"'+String(c===undefined?'':c).replace(/"/g,'""')+'"'}).join(','));
  });
  var blob=new Blob(['\\ufeff'+lines.join('\\r\\n')],{type:'text/csv;charset=utf-8'});
  var a=document.createElement('a');a.href=URL.createObjectURL(blob);
  a.download='宝锐分子诊断原料知识库_筛选结果.csv';a.click();
};
document.querySelectorAll('table.grid th[data-k]').forEach(function(th){th.onclick=function(){
  var k=th.getAttribute('data-k');
  if(S.sortK===k){S.sortD=-S.sortD}else{S.sortK=k;S.sortD=1}
  render();
}});

/* ────── 竞品对标视图 ────── */
const C=(D.comp&&D.comp.items)||[];
const CINFO=(D.comp&&D.comp['口径'])||'';
const CS={brand:'__ALL__',cat:'__ALL__',q:''};
function compRows(){
  var q=CS.q.trim().toLowerCase();
  return C.filter(function(e){
    if(CS.brand!=='__ALL__'&&e['品牌']!==CS.brand)return false;
    if(CS.cat!=='__ALL__'&&e['品类']!==CS.cat)return false;
    if(q&&((e['货号']+' '+e['品名']+' '+e['品类']+' '+(e['卖点']||[]).join(' ')+' '+e['品牌']).toLowerCase().indexOf(q)<0))return false;
    return true;
  });
}
function countBy(key){
  var m={};C.forEach(function(e){ if(e[key])m[e[key]]=(m[e[key]]||0)+1; });return m;
}
function renderComp(){
  var bc=countBy('品牌'), cc=countBy('品类');
  document.getElementById('comp-kpi').innerHTML=
    [['竞品条目',C.length],['覆盖品牌',Object.keys(bc).length],['覆盖品类',Object.keys(cc).length],
     ['可冻干/无甘油',C.filter(function(e){return (e['标签']||[]).length}).length]]
    .map(function(kv){return '<div class="kpi"><div class="kpi-n">'+kv[1]+'</div><div class="kpi-l">'+kv[0]+'</div></div>'}).join('');
  document.getElementById('comp-brands').innerHTML='<button class="chip '+(CS.brand==='__ALL__'?'on':'')+'" data-b="__ALL__">全部 <b>'+C.length+'</b></button>'
    +Object.keys(bc).map(function(b){return '<button class="chip '+(CS.brand===b?'on':'')+'" data-b="'+b+'">'+b+' <b>'+bc[b]+'</b></button>'}).join('');
  document.getElementById('comp-cats').innerHTML='<button class="chip '+(CS.cat==='__ALL__'?'on':'')+'" data-c="__ALL__">全部</button>'
    +Object.keys(cc).map(function(c){return '<button class="chip '+(CS.cat===c?'on':'')+'" data-c="'+c+'">'+c+' <b>'+cc[c]+'</b></button>'}).join('');
  document.querySelectorAll('#comp-brands .chip').forEach(function(b){b.onclick=function(){CS.brand=b.getAttribute('data-b');renderComp()}});
  document.querySelectorAll('#comp-cats .chip').forEach(function(b){b.onclick=function(){CS.cat=b.getAttribute('data-c');renderComp()}});
  var rs=compRows();
  document.getElementById('comp-cnt').textContent='匹配 '+rs.length+' / '+C.length+' 条 ｜ '+CINFO;
  document.getElementById('comp-tb').innerHTML=rs.map(function(e){
    var tags=(e['标签']||[]).map(function(t){return '<span class="tag t-冻干">'+t+'</span>'}).join('');
    var pts=(e['卖点']||[]).map(function(p){return '<div>· '+p+'</div>'}).join('');
    var src=e['来源']?'<a href="'+e['来源']+'" target="_blank" rel="noopener">'+e['数据类型']+'</a>':'—';
    return '<tr><td><b>'+e['品牌']+'</b></td><td>'+e['货号']+'</td><td>'+(e['品名']||'')+'</td><td>'+e['品类']+'</td>'
      +'<td>'+tags+'</td><td style="font-size:12.5px">'+pts+'</td><td style="font-size:12px">'+src+'</td></tr>';
  }).join('')||'<tr><td colspan="7" style="text-align:center;color:#9aa4b2;padding:20px">无匹配条目</td></tr>';
  var ours=(D.comp&&D.comp['我方可对标货号(按分类)'])||{};
  document.getElementById('comp-ours').innerHTML=Object.keys(ours).map(function(k){
    return '<div><b>'+k+'</b>：'+ours[k].map(function(s){return '<span class="tag t-预混液">'+s+'</span>'}).join('')+'</div>';
  }).join('');
}
document.getElementById('cq').oninput=function(e){CS.q=e.target.value;renderComp()};
document.getElementById('comp-rst').onclick=function(){CS.brand='__ALL__';CS.cat='__ALL__';CS.q='';document.getElementById('cq').value='';renderComp()};
document.getElementById('comp-exp').onclick=function(){
  var head=['品牌','竞品货号','品名','品类','标签','公开卖点','来源','数据类型'];
  var lines=[head.join(',')];
  compRows().forEach(function(e){
    lines.push([e['品牌'],e['货号'],e['品名'],e['品类'],(e['标签']||[]).join('+'),(e['卖点']||[]).join(' / '),e['来源'],e['数据类型']]
      .map(function(c){return '"'+String(c===undefined?'':c).replace(/"/g,'""')+'"'}).join(','));
  });
  var blob=new Blob([String.fromCharCode(0xFEFF)+lines.join(String.fromCharCode(13,10))],{type:'text/csv;charset=utf-8'});
  var a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='竞品对标_诺唯赞翌圣菲鹏全式金.csv';a.click();
};
document.getElementById('tab-kb').onclick=function(){
  document.getElementById('tab-kb').classList.add('on');document.getElementById('tab-comp').classList.remove('on');
  document.getElementById('view-kb').hidden=false;document.getElementById('view-comp').hidden=true;window.scrollTo(0,0);
};
document.getElementById('tab-comp').onclick=function(){
  document.getElementById('tab-comp').classList.add('on');document.getElementById('tab-kb').classList.remove('on');
  document.getElementById('view-comp').hidden=false;document.getElementById('view-kb').hidden=true;window.scrollTo(0,0);renderComp();
};
render();
renderComp();
</script>
</body></html>"""
    html = (html.replace('__NOW__', NOW).replace('__KPI__', kpi)
            .replace('__CHIPCAT__', chips_cat).replace('__CHIPSCEN__', chips_scen)
            .replace('__TOTAL__', str(stat['核心货号数']))
            .replace('__ALL__', 'ALL').replace('__PAYLOAD__', payload)
            .replace('__TBL__', 'tblToHtml'))
    return html


def main():
    data = json.load(open(SRC, encoding='utf-8'))
    os.makedirs(OUTDIR, exist_ok=True)
    pdf_dir = os.path.join(OUTDIR, '说明书')
    os.makedirs(pdf_dir, exist_ok=True)
    # 拷贝核心货号说明书
    src_pdf = os.path.join(ROOT, '资料/说明书')
    copied = 0
    asset_pdf = os.path.join(ROOT, '资料/说明书_产品资料')
    want = {}
    for r in data['items']:
        f = r.get('说明书_产品资料文件') or r.get('说明书本地文件') or r['说明书文件名']
        if f:
            want[f] = asset_pdf if r.get('说明书_产品资料文件') else src_pdf
    missing = []
    for f, base in want.items():
        s = os.path.join(base, f)
        if os.path.exists(s):
            shutil.copy2(s, os.path.join(pdf_dir, f))
            copied += 1
        else:
            missing.append(f)
    if missing:
        print('  ⚠ 未能拷贝（页面将改指官网链接）:', missing)
    html = build_html(data)
    html = BK.inject(html, theme='light', tag='营销中心 · 产品知识库',
                     footer_extra='内部资料 · 请勿外发 ｜ 生成于 ' + NOW)
    chk = BK.verify(html)
    assert all(chk.values()), chk
    out = os.path.join(OUTDIR, 'index.html')
    open(out, 'w', encoding='utf-8').write(html)
    print('index.html %.0f KB ｜ 说明书拷贝 %d/%d 份' % (len(html) / 1024.0, copied, len(want)))
    print('品牌自检:', BK.verify(html))
    print('→', out)


if __name__ == '__main__':
    main()
