#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""分子诊断市场与原料 2025-2027 · 动态 HTML 工作台（5 TAB + 筛选）"""
import json
import os
import sys

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

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
D = os.path.join(W, '数据/diag_market_2025_2027')
OUT = os.path.join(W, '销售分析/分子诊断市场与原料_2025-2027_工作台.html')

market = json.load(open(os.path.join(D, 'market.json'), encoding='utf-8'))
companies = json.load(open(os.path.join(D, 'companies.json'), encoding='utf-8'))
materials = json.load(open(os.path.join(D, 'materials.json'), encoding='utf-8'))
academic = json.load(open(os.path.join(D, 'academic.json'), encoding='utf-8'))
cust = json.load(open(os.path.join(D, 'customers_liu.json'), encoding='utf-8'))

DATA = json.dumps({'market': market, 'companies': companies,
                   'materials': materials, 'academic': academic,
                   'customers': cust}, ensure_ascii=False)

page = """<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>分子诊断市场与原料 2025–2027 · 工作台</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#f4f6fa;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;color:#1b2430;font-size:13px;line-height:1.6}
.wrap{max-width:1240px;margin:0 auto;padding:0 16px 40px}
h1{font-size:20px;margin:14px 0 4px}
.sub{color:#5f6b7a;font-size:12.3px;margin-bottom:12px}
.tabs{display:flex;gap:6px;flex-wrap:wrap;margin:10px 0 14px}
.tab{padding:8px 14px;border:1px solid #cfd8e6;background:#fff;border-radius:8px;cursor:pointer;font-size:13px}
.tab.on{background:#1F4E79;color:#fff;border-color:#1F4E79}
.panel{display:none}.panel.on{display:block}
.card{background:#fff;border:1px solid #e3e8f1;border-radius:10px;padding:13px 15px;margin-bottom:13px}
.card h2{font-size:14.5px;margin:0 0 10px;padding-left:9px;border-left:3px solid #1F4E79}
.filters{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}
select,input[type=search]{padding:6px 9px;border:1px solid #cfd8e6;border-radius:7px;font-size:12.5px;background:#fff}
input[type=search]{min-width:220px}
.kpi{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin-bottom:12px}
.kpi .k{background:#fff;border:1px solid #e3e8f1;border-radius:9px;padding:10px 12px}
.kpi b{display:block;font-size:20px;color:#1852B6}.kpi span{font-size:11.2px;color:#6b7684}
table{width:100%;border-collapse:collapse;font-size:12px}
th{background:#1F4E79;color:#fff;padding:6px 7px;text-align:left;position:sticky;top:0;z-index:1}
td{padding:5px 7px;border-bottom:1px solid #eef1f6;vertical-align:top}
tr:nth-child(even) td{background:#fafbfe}
td.n{text-align:right;font-variant-numeric:tabular-nums}
.scroll{max-height:520px;overflow:auto;border:1px solid #eef1f6;border-radius:8px}
.tag{display:inline-block;background:#E8F0FE;color:#1852B6;border-radius:4px;padding:1px 6px;font-size:10.6px;white-space:nowrap}
.tag.g{background:#E3F4EA;color:#1B7A43}.tag.w{background:#FFF3DC;color:#8A5A12}.tag.r{background:#FDE7E5;color:#B3261E}
.note{background:#FFF6E5;border:1px solid #F3D9A6;border-radius:8px;padding:9px 11px;font-size:12.2px;margin:8px 0}
.chart{width:100%;height:auto}
a{color:#1852B6;text-decoration:none}a:hover{text-decoration:underline}
@media(max-width:900px){.kpi{grid-template-columns:1fr 1fr}.bior-tagwrap{display:none!important}}
</style></head><body><div class="wrap">
<h1>分子诊断市场与原料 2025–2027 · 动态工作台</h1>
<div class="sub">数据截至 2026-10-18 ｜ 全球与中国分表、预测标 E、缺口不插值 ｜ 源数据可回溯（见「口径与缺口」）</div>

<div class="kpi" id="kpi"></div>

<div class="tabs">
  <div class="tab on" data-t="mkt">① 市场容量</div>
  <div class="tab" data-t="co">② 企业要闻与产品</div>
  <div class="tab" data-t="mat">③ 原料市场</div>
  <div class="tab" data-t="ac">④ 学术与专利</div>
  <div class="tab" data-t="gap">⑤ 口径与缺口</div>
  <div class="tab" data-t="cu">⑥ 客户专题</div>
</div>

<div class="panel on" id="p-mkt">
  <div class="card"><h2>筛选</h2>
    <div class="filters">
      <select id="m-scope"><option value="">全部口径</option><option>全球</option><option>中国</option></select>
      <select id="m-year"><option value="">全部年份</option></select>
      <input type="search" id="m-q" placeholder="搜分项 / 来源">
    </div>
    <div class="scroll"><table id="m-table"></table></div>
    <div id="m-chart" style="margin-top:12px"></div>
  </div>
  <div class="card"><h2>细分与趋势 <span id="m-segcount"></span></h2><div class="scroll"><table id="m-seg"></table></div></div>
</div>

<div class="panel" id="p-co">
  <div class="card"><h2>要闻（全量）</h2>
    <div class="filters">
      <select id="c-co"><option value="">全部公司</option></select>
      <select id="c-type"><option value="">全部类型</option></select>
      <select id="c-year"><option value="">全部年份</option></select>
      <input type="search" id="c-q" placeholder="搜事件">
    </div>
    <div class="scroll"><table id="c-table"></table></div>
  </div>
  <div class="card"><h2>已落地产品</h2><div class="scroll"><table id="c-prod"></table></div></div>
</div>

<div class="panel" id="p-mat">
  <div class="card"><h2>原料与耗材</h2>
    <div class="filters">
      <select id="x-year"><option value="">全部年份</option></select>
      <input type="search" id="x-q" placeholder="搜细分 / 来源">
    </div>
    <div class="scroll"><table id="x-table"></table></div>
    <div class="note">只有「有值」的行进入表格；无公开口径的细分见「口径与缺口」。耗材单列，不计入原料合计。</div>
  </div>
  <div class="card"><h2>国产化与价格</h2>
    <div class="scroll"><table id="x-loc"></table></div>
    <div class="scroll" style="margin-top:10px"><table id="x-price"></table></div>
  </div>
</div>

<div class="panel" id="p-ac">
  <div class="card"><h2>论文（46 篇，PMID/DOI 已验真）</h2>
    <div class="filters">
      <select id="a-dir"><option value="">全部方向</option><option value="A">A 全预混/冻干</option><option value="B">B 耐抑制</option><option value="C">C LAMP/等温</option><option value="D">D 跨学科</option></select>
      <select id="a-year"><option value="">全部年份</option></select>
      <input type="search" id="a-q" placeholder="搜标题 / 结论">
    </div>
    <div class="scroll"><table id="a-table"></table></div>
  </div>
  <div class="card"><h2>专利（18 件）</h2><div class="scroll"><table id="a-pat"></table></div></div>
</div>

<div class="panel" id="p-cu">
  <div class="card"><h2>刘子研名下 7 家集团客户 · 要闻</h2>
    <div class="filters">
      <select id="u-co"><option value="">全部客户</option></select>
      <select id="u-type"><option value="">全部类型</option></select>
      <input type="search" id="u-q" placeholder="搜事件 / 关键词">
    </div>
    <div class="scroll"><table id="u-table"></table></div>
  </div>
  <div class="card"><h2>已获证/在售产品</h2><div class="scroll"><table id="u-prod"></table></div></div>
  <div class="card"><h2>原料机会点 <span>事实 / 推测已标注</span></h2><div class="scroll"><table id="u-rel"></table></div></div>
</div>

<div class="panel" id="p-gap">
  <div class="card"><h2>缺口清单 <span>不插值、不猜</span></h2><div id="g-list"></div></div>
  <div class="card"><h2>口径说明</h2><div id="g-cal"></div></div>
</div>
__FOOTER__
</div>
<script>
var D = __DATA__;

function esc(s){return String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
function el(id){return document.getElementById(id)}
function opts(sel, arr){var s=el(sel); arr.forEach(function(v){var o=document.createElement('option');o.value=v;o.textContent=v;s.appendChild(o)})}

/* KPI */
(function(){
  var g2025=0, cn=0, ev=0, pa=0;
  D.market.global.forEach(function(x){if(x.year===2025&&x.segment.indexOf('分子诊断（总盘）')===0)g2025=x.value});
  D.market.china.forEach(function(x){if(x.value!=null)cn++});
  D.companies.companies.forEach(function(c){ev+=(c.events||[]).length});
  pa=D.academic.papers.length;
  el('kpi').innerHTML =
    '<div class="k"><b>'+(g2025?g2025+' 亿美元':'—')+'</b><span>全球分子诊断 2025</span></div>'+
    '<div class="k"><b>'+cn+'</b><span>中国可核分项锚点</span></div>'+
    '<div class="k"><b>'+ev+'</b><span>九家企业要闻</span></div>'+
    '<div class="k"><b>'+pa+'</b><span>论文（已验真）</span></div>';
})();

/* Tabs */
document.querySelectorAll('.tab').forEach(function(t){
  t.onclick=function(){
    document.querySelectorAll('.tab').forEach(function(x){x.classList.remove('on')});
    document.querySelectorAll('.panel').forEach(function(x){x.classList.remove('on')});
    t.classList.add('on'); el('p-'+t.dataset.t).classList.add('on');
  };
});

/* 市场 */
function renderMarket(){
  var scope=el('m-scope').value, year=el('m-year').value, q=el('m-q').value.trim();
  var rows=[];
  D.market.global.forEach(function(x){rows.push(Object.assign({scope:'全球'},x))});
  D.market.china.forEach(function(x){rows.push(Object.assign({scope:'中国'},x))});
  rows=rows.filter(function(x){
    if(scope && x.scope!==scope) return false;
    if(year && String(x.year)!==year) return false;
    if(q && (x.segment+x.source_name).indexOf(q)<0) return false;
    return true;
  });
  var h='<thead><tr><th style="width:16%">口径</th><th style="width:8%">年份</th><th style="width:24%">分项</th><th style="width:12%">规模</th><th>来源</th></tr></thead><tbody>';
  rows.forEach(function(x){
    var est=(x.source_name||'').indexOf('推算')>=0?' <span class="tag w">推算</span>':'';
    h+='<tr><td><span class="tag '+(x.scope==='全球'?'':'g')+'">'+x.scope+'</span></td><td>'+x.year+'</td><td>'+esc(x.segment)+est+'</td><td class="n">'+(x.value==null?'<span class="tag w">缺口</span>':x.value+' '+esc(x.unit))+'</td><td>'+esc(x.source_name)+'</td></tr>';
  });
  el('m-table').innerHTML=h+'</tbody>';

  // 柱状图：全球 2025 vs 2026E
  var pairs={};
  D.market.global.forEach(function(x){
    if(x.segment.indexOf('分子诊断')===0) return;
    if((x.source_name||'').indexOf('Kalorama')>=0) return;
    var k=x.segment.split('（')[0]; (pairs[k]=pairs[k]||{})[x.year]=x.value;
  });
  var labels=Object.keys(pairs).filter(function(k){return pairs[k][2025]});
  var max=0; labels.forEach(function(k){max=Math.max(max,pairs[k][2025]||0,pairs[k][2026]||0)});
  var W=980,H=260,bw=Math.min(70,(W-80)/labels.length/2.6);
  var svg='<svg viewBox="0 0 '+W+' '+H+'" class="chart">';
  labels.forEach(function(k,i){
    var x0=60+i*((W-80)/labels.length);
    [2025,2026].forEach(function(y,j){
      var v=pairs[k][y]||0, hh=(H-60)*v/(max||1), x=x0+j*bw*1.2, yy=H-40-hh;
      svg+='<rect x="'+x+'" y="'+yy+'" width="'+bw+'" height="'+hh+'" fill="'+(j?'#6FA8DC':'#1F4E79')+'" rx="2"/>';
      svg+='<text x="'+(x+bw/2)+'" y="'+(yy-4)+'" font-size="10" text-anchor="middle" fill="#43505f">'+v+'</text>';
    });
    svg+='<text x="'+(x0+bw*0.9)+'" y="'+(H-22)+'" font-size="11" text-anchor="middle" fill="#43505f">'+esc(k)+'</text>';
  });
  svg+='<text x="60" y="16" font-size="11" fill="#6b7684">■ 2025　■ 2026E（亿美元；NGS 口径宽于临床，仅作趋势）</text></svg>';
  el('m-chart').innerHTML=svg;

  var seg='<thead><tr><th style="width:18%">细分</th><th style="width:26%">2025 份额</th><th style="width:16%">CAGR</th><th>趋势 / 证据</th></tr></thead><tbody>';
  D.market.segments.forEach(function(s){
    var cls=s.trend.indexOf('扩张')>=0?'g':(s.trend.indexOf('收缩')>=0?'r':'');
    seg+='<tr><td>'+esc(s.name)+'</td><td>'+esc(s.share_2025)+'</td><td>'+esc(s.cagr)+'</td><td><span class="tag '+cls+'">'+esc(s.trend)+'</span> '+esc(s.evidence).slice(0,90)+'</td></tr>';
  });
  el('m-seg').innerHTML=seg+'</tbody>';
  el('m-segcount').textContent=D.market.segments.length+' 条';
}

/* 企业 */
function renderCo(){
  var co=el('c-co').value, ty=el('c-type').value, yr=el('c-year').value, q=el('c-q').value.trim();
  var rows=[];
  D.companies.companies.forEach(function(c){
    if(co && c.name!==co) return;
    (c.events||[]).forEach(function(e){
      if(ty && e.type!==ty) return;
      if(yr && String(e.date).slice(0,4)!==yr) return;
      if(q && (e.fact||'').indexOf(q)<0) return;
      rows.push([c.name,e]);
    });
  });
  var h='<thead><tr><th style="width:12%">公司</th><th style="width:10%">日期</th><th style="width:8%">类型</th><th>事件</th></tr></thead><tbody>';
  rows.forEach(function(r){
    var cls=/获证|并购|合作|战略/.test(r[1].type)?'g':(/监管|亏损/.test(r[1].type)?'r':'');
    h+='<tr><td>'+esc(r[0])+'</td><td>'+esc(r[1].date)+'</td><td><span class="tag '+cls+'">'+esc(r[1].type)+'</span></td><td>'+esc(r[1].fact)+'</td></tr>';
  });
  el('c-table').innerHTML=h+'</tbody>';

  var ph='<thead><tr><th style="width:12%">公司</th><th style="width:30%">产品</th><th style="width:10%">平台</th><th style="width:12%">获证/上市</th><th>注册证号</th></tr></thead><tbody>';
  D.companies.companies.forEach(function(c){
    (c.products||[]).forEach(function(p){
      ph+='<tr><td>'+esc(c.name)+'</td><td>'+esc(p.name)+'</td><td>'+esc(p.platform)+'</td><td>'+esc(p.approval_date)+'</td><td>'+esc(p.reg_no||'—')+'</td></tr>';
    });
  });
  el('c-prod').innerHTML=ph+'</tbody>';
}

/* 原料 */
function renderMat(){
  var yr=el('x-year').value, q=el('x-q').value.trim();
  var h='<thead><tr><th style="width:32%">细分</th><th style="width:8%">年份</th><th style="width:12%">规模</th><th>来源</th></tr></thead><tbody>';
  D.materials.market.forEach(function(x){
    if(x.value==null) return;
    if(yr && String(x.year)!==yr) return;
    if(q && (x.segment+(x.source||x.source_name||'')).indexOf(q)<0) return;
    h+='<tr><td>'+esc(x.segment)+'</td><td>'+x.year+'</td><td class="n">'+x.value+' '+esc(x.unit)+'</td><td>'+esc(x.source||x.source_name||'')+'</td></tr>';
  });
  el('x-table').innerHTML=h+'</tbody>';
  var lh='<thead><tr><th style="width:24%">品类</th><th style="width:14%">国产化率</th><th>依据</th></tr></thead><tbody>';
  (D.materials.localization||[]).forEach(function(x){lh+='<tr><td>'+esc(x.category)+'</td><td>'+esc(x.rate)+'</td><td>'+esc(x.evidence)+'</td></tr>'});
  el('x-loc').innerHTML=lh+'</tbody>';
  var ph='<thead><tr><th style="width:20%">品类</th><th style="width:36%">变化</th><th>依据</th></tr></thead><tbody>';
  (D.materials.price_trend||[]).forEach(function(x){ph+='<tr><td>'+esc(x.item)+'</td><td>'+esc(x.change)+'</td><td>'+esc(x.evidence)+'</td></tr>'});
  el('x-price').innerHTML=ph+'</tbody>';
}

/* 学术 */
function renderAc(){
  var dir=el('a-dir').value, yr=el('a-year').value, q=el('a-q').value.trim();
  var h='<thead><tr><th style="width:6%">方向</th><th style="width:7%">年份</th><th style="width:26%">标题</th><th>关键结论</th><th style="width:12%">痛点</th><th style="width:8%">证据</th></tr></thead><tbody>';
  D.academic.papers.forEach(function(p){
    if(dir && p.direction!==dir) return;
    if(yr && String(p.year)!==yr) return;
    if(q && (p.title+p.finding).indexOf(q)<0) return;
    h+='<tr><td>'+p.direction+'</td><td>'+p.year+'</td><td>'+esc(p.title).slice(0,70)+'</td><td>'+esc(p.finding).slice(0,110)+'</td><td>'+esc(p.pain_point).slice(0,40)+'</td><td><span class="tag">'+esc(p.evidence_level)+'</span></td></tr>';
  });
  el('a-table').innerHTML=h+'</tbody>';
  var ph='<thead><tr><th style="width:14%">专利号</th><th style="width:28%">名称</th><th style="width:16%">申请人</th><th style="width:6%">年份</th><th>状态</th></tr></thead><tbody>';
  D.academic.patents.forEach(function(p){
    ph+='<tr><td>'+esc(p.no)+'</td><td>'+esc(p.title)+'</td><td>'+esc(p.applicant)+'</td><td>'+p.year+'</td><td>'+esc(p.status)+'</td></tr>';
  });
  el('a-pat').innerHTML=ph+'</tbody>';
}

/* 客户专题 */
function renderCu(){
  var co=el('u-co').value, ty=el('u-type').value, q=el('u-q').value.trim();
  var h='<thead><tr><th style="width:16%">客户</th><th style="width:9%">日期</th><th style="width:8%">类型</th><th>事件</th></tr></thead><tbody>';
  (D.customers.customers||[]).forEach(function(c){
    if(co && c.name!==co) return;
    (c.events||[]).forEach(function(e){
      if(ty && e.type!==ty) return;
      if(q && (e.fact||'').indexOf(q)<0) return;
      h+='<tr><td>'+esc(c.name).slice(0,22)+'</td><td>'+esc(e.date)+'</td><td><span class="tag">'+esc(e.type)+'</span></td><td>'+esc(e.fact)+'</td></tr>';
    });
  });
  el('u-table').innerHTML=h+'</tbody>';
  var ph='<thead><tr><th style="width:16%">客户</th><th style="width:30%">产品</th><th style="width:9%">平台</th><th style="width:18%">靶标</th><th>获证/上市</th></tr></thead><tbody>';
  var rh='<thead><tr><th style="width:16%">客户</th><th>判断</th><th style="width:8%">性质</th></tr></thead><tbody>';
  (D.customers.customers||[]).forEach(function(c){
    if(co && c.name!==co) return;
    (c.products||[]).forEach(function(p){
      ph+='<tr><td>'+esc(c.name).slice(0,22)+'</td><td>'+esc(p.name)+'</td><td>'+esc(p.platform)+'</td><td>'+esc(p.target)+'</td><td>'+esc(p.approval_date)+'</td></tr>';
    });
    (c.raw_material_relevance||[]).forEach(function(r){
      var cls=(r.basis==='推测')?'w':'g';
      rh+='<tr><td>'+esc(c.name).slice(0,22)+'</td><td>'+esc(r.point)+'</td><td><span class="tag '+cls+'">'+esc(r.basis||'—')+'</span></td></tr>';
    });
  });
  el('u-prod').innerHTML=ph+'</tbody>';
  el('u-rel').innerHTML=rh+'</tbody>';
}

/* 缺口 */
(function(){
  var all=[];
  [['市场',D.market],['企业',D.companies],['原料',D.materials],['学术',D.academic]].forEach(function(x){
    (x[1].gaps||[]).forEach(function(g){all.push([x[0],g])});
  });
  var h='<table><thead><tr><th style="width:8%">板块</th><th>缺口 / 限制</th></tr></thead><tbody>';
  all.forEach(function(r){h+='<tr><td>'+r[0]+'</td><td>'+esc(r[1])+'</td></tr>'});
  el('g-list').innerHTML=h+'</tbody></table>';
  var c='<table><thead><tr><th style="width:22%">口径</th><th>说明</th></tr></thead><tbody>';
  (D.market.caliber_notes||[]).forEach(function(x){
    c+='<tr><td>'+esc(x.caliber||x.name||'')+'</td><td>'+esc(x.note||x.desc||JSON.stringify(x))+'</td></tr>';
  });
  el('g-cal').innerHTML=c+'</tbody></table>';
})();

/* 初始化 */
var years={};
D.market.global.concat(D.market.china).forEach(function(x){years[x.year]=1});
opts('m-year',Object.keys(years).sort());
var cyears={};
D.companies.companies.forEach(function(c){(c.events||[]).forEach(function(e){cyears[String(e.date).slice(0,4)]=1})});
opts('c-year',Object.keys(cyears).filter(Boolean).sort());
var types={};
D.companies.companies.forEach(function(c){(c.events||[]).forEach(function(e){types[e.type]=1})});
opts('c-type',Object.keys(types));
var names=D.companies.companies.map(function(c){return c.name});
opts('c-co',names);
opts('x-year',['2025','2026','2027']);
var ayear={}; D.academic.papers.forEach(function(p){ayear[p.year]=1}); opts('a-year',Object.keys(ayear).sort());
['m-scope','m-year','m-q','c-co','c-type','c-year','c-q','x-year','x-q','a-dir','a-year','a-q'].forEach(function(id){
  el(id).addEventListener('input',function(){
    if(id[0]==='m') renderMarket(); else if(id[0]==='c') renderCo(); else if(id[0]==='x') renderMat(); else renderAc();
  });
  el(id).addEventListener('change',function(){
    if(id[0]==='m') renderMarket(); else if(id[0]==='c') renderCo(); else if(id[0]==='x') renderMat(); else renderAc();
  });
});
var unames=(D.customers.customers||[]).map(function(c){return c.name});
opts('u-co',unames);
var utypes={};(D.customers.customers||[]).forEach(function(c){(c.events||[]).forEach(function(e){utypes[e.type]=1})});
opts('u-type',Object.keys(utypes));
['u-co','u-type','u-q'].forEach(function(id){
  el(id).addEventListener('input',renderCu); el(id).addEventListener('change',renderCu);
});
renderMarket(); renderCo(); renderMat(); renderAc(); renderCu();
</script></body></html>"""

page = page.replace('__DATA__', DATA)
page = BK.inject(page, theme='dark', tag='分子诊断市场与原料 2025–2027',
                 footer_extra='内部资料 · 不对外 ｜ 数字均带口径，缺口不插值')
page = page.replace('__FOOTER__', '')
open(OUT, 'w', encoding='utf-8').write(page)
print('✅', OUT, '%.0f KB' % (len(page.encode()) / 1024))
