#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""统一口径交付物：HTML 一页纸 + Excel"""
import io
import json
import sys

sys.path.insert(0, '/Users/liuxinyuan/Desktop/Hermes输出-工作类/脚本')
import brand_kit as BK  # noqa: E402
from openpyxl import Workbook  # noqa: E402
from openpyxl.styles import Alignment, Font, PatternFill  # noqa: E402
from openpyxl.utils import get_column_letter  # noqa: E402

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
J = W + '/数据/科研经费_统一口径_产品线.json'
OUT_HTML = W + '/销售分析/科研经费与宝锐产品线关联_统一口径_2026-10-17.html'
OUT_XLSX = W + '/销售分析/科研经费_单位级清单_广东浙江_2026.xlsx'
L = ['分子·RT-qPCR', '分子·PCR/克隆', '分子·核酸提取', '分子·LAMP/恒温', '分子·其他', 'WB']
d = json.load(io.open(J, encoding='utf-8'))
P = d['provinces']; S = d['struct']

# 技术路径（蛋白/信号类）：PubMed 提及率（可核）+ 经验使用率 + 宝锐覆盖度
TECH = [
    ('Western blot', '6.8%', '85–90%', '95%', 'WB 一线全线（RIPA/抑制剂/BCA/上样/电泳/转膜/TBST/膜/ECL/marker）', '高'),
    ('qPCR / RT-qPCR', '5.2%', '60–70%', '80%', 'RT-qPCR 预混 + 逆转录 + 核酸提取', '高'),
    ('免疫沉淀 IP/Co-IP', '4.5%', '20–30%', '65%', 'WB 线（裂解液、蛋白酶抑制剂）+ 蛋白纯化缺口', '中高'),
    ('免疫荧光 IF', '4.5%', '50–60%', '20%', '仅覆盖封闭/抗体稀释；抗体本身非我司', '低'),
    ('免疫组化 IHC', '3.1%', '20–30%', '15%', '同上，且石蜡切片流程耗材非我司', '低'),
    ('流式细胞术', '3.5%', '20–25%', '5%', '几乎无对应货号', '极低'),
    ('ELISA', '2.7%', '15–20%', '15%', '仅缓冲液/封闭；板与抗体非我司', '低'),
    ('RNA-seq', '7.9%', '30–40%', '25%', '仅核酸提取（柱式/磁珠）', '低'),
    ('质谱/蛋白组', '7.5%', '15–20%', '10%', '仅裂解/提取，样本前处理为主', '极低'),
    ('CRISPR/基因编辑', '1.7%', '10–15%', '75%', '无缝克隆/连接/感受态/质粒提取', '高'),
]

CSS = """*{box-sizing:border-box}body{margin:0;background:#f4f6fa;font-family:-apple-system,"PingFang SC",sans-serif;color:#1b2430;font-size:13px;line-height:1.62}
.wrap{max-width:1180px;margin:0 auto;padding:0 18px 40px}h1{font-size:21px;margin:10px 0 4px}
.meta{color:#5b6675;font-size:12.3px;margin-bottom:12px}
.card{background:#fff;border:1px solid #e3e8f1;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 #1F4E79}
.card h2 span{font-size:10.6px;color:#8a94a3;font-weight:600;margin-left:6px}
h3{font-size:13px;color:#1F4E79;margin:14px 0 6px}
table{width:100%;border-collapse:collapse;font-size:11.7px}
th{background:#1F4E79;color:#fff;padding:7px 8px;text-align:left;font-weight:600}
th.n{text-align:right}
td{padding:6px 8px;border-bottom:1px solid #eef1f6;vertical-align:top}
td.n{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:10px 0 14px}
.kpi{background:#fff;border:1px solid #e3e8f1;border-top:3px solid #1F4E79;border-radius:9px;padding:9px 11px}
.kl{font-size:11px;color:#6b7684}.kv{font-size:19px;font-weight:800;color:#1F4E79;margin:2px 0}
.ks{font-size:10.5px;color:#8a94a3}
.tag{display:inline-block;font-size:10px;font-weight:800;padding:1px 6px;border-radius:8px}
.ok{background:#e8f6ee;color:#1c7c54}.est{background:#fff4e0;color:#8a5a12}.gap{background:#fde8e6;color:#b3261e}
.box{background:#f7f9fd;border:1px solid #dfe6f2;border-radius:8px;padding:10px 12px}
.box.warn{background:#fffaf0;border-color:#f3e2bf}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.fn{color:#6b7684;font-size:11.4px;margin-top:7px}
ul{margin:4px 0;padding-left:18px}"""


def fmt(v):
    return f'{float(v):,.0f}'


def _plain(x):
    import re as _re
    return _re.sub(r'<[^>]+>', '', str(x)).replace(',', '').replace('%', '').strip()


def rows(data):
    out = []
    for r in data:
        cells = []
        for i, c in enumerate(r):
            plain = _plain(c)
            num = bool(plain) and plain.replace('.', '').isdigit()
            cells.append('<td%s>%s</td>' % (' class="n"' if i and num else '', c))
        out.append('<tr>' + ''.join(cells) + '</tr>')
    return '\n'.join(out)


zj, gd = P['浙江'], P['广东']
zl, gl = zj['by_line'], gd['by_line']
zs, gs = sum(zl.values()), sum(gl.values())

line_rows = [[l, fmt(zl[l]), '%.1f%%' % (zl[l] / zs * 100), fmt(gl[l]), '%.1f%%' % (gl[l] / gs * 100),
              fmt(zl[l] + gl[l]), '%.1f%%' % ((zl[l] + gl[l]) / (zs + gs) * 100)] for l in L]
line_rows.append(['<b>合计</b>', '<b>%s</b>' % fmt(zs), '100%', '<b>%s</b>' % fmt(gs), '100%', '<b>%s</b>' % fmt(zs + gs), '100%'])

topic_rows = []
for t in sorted(set(zj['by_topic']) | set(gd['by_topic'])):
    a = zj['by_topic'].get(t, {'n': 0, 'amt': 0, 'addr': 0})
    b = gd['by_topic'].get(t, {'n': 0, 'amt': 0, 'addr': 0})
    rate = S[t]['rate'] * 100
    main = max(S[t]['mix'], key=lambda k: S[t]['mix'][k])
    topic_rows.append([t, fmt(a['n']), fmt(a['amt']), fmt(a['addr']), fmt(b['n']), fmt(b['amt']),
                       fmt(b['addr']), '%.0f%%' % rate, '%s（%.0f%%）' % (main, S[t]['mix'][main])])
topic_rows.sort(key=lambda r: -(float(str(r[3]).replace(',', '')) + float(str(r[6]).replace(',', ''))))

tech_rows = [[a, b, c, dd, e, f] for a, b, c, dd, e, f in TECH]


def unit_rows(prov, n=12):
    out = []
    for u in P[prov]['units'][:n]:
        main = max(L, key=lambda l: u['lines'][l])
        out.append([u['unit'], fmt(u['n']), fmt(u['amt']), fmt(u['addr']),
                    '%.0f%%' % (u['addr'] / u['amt'] * 100 if u['amt'] else 0), main])
    return out


page = """<!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>科研经费与宝锐产品线关联：广东 / 浙江 2026 省基金</h1>
<div class="meta">口径：<b>两省统一</b>——项目名称 → 研究方向 → 主技术路径 → 宝锐产品线。金额来自官方立项清单逐项（广东逐项公布；浙江按类别标准额测算）。研究方向结构与可及率为<b>经验值</b>，技术路径占比用 PubMed 2024–2025 条件提及率做排序锚点。截至 2026-10-17。</div>

<div class="kpis">
 <div class="kpi"><div class="kl">浙江 省基金总盘</div><div class="kv">4.99 亿</div><div class="ks">2,667 项</div></div>
 <div class="kpi"><div class="kl">浙江 宝锐可及</div><div class="kv">1.52 亿</div><div class="ks">生医 1,496 项 / 2.79 亿 × 55%</div></div>
 <div class="kpi"><div class="kl">广东 省基金总盘</div><div class="kv">4.21 亿</div><div class="ks">3,171 项</div></div>
 <div class="kpi"><div class="kl">广东 宝锐可及</div><div class="kv">1.31 亿</div><div class="ks">生医 1,887 项 / 2.40 亿 × 54%</div></div>
</div>

<div class="card"><h2>一、两省统一口径：六条产品线关联经费 <span>万元 / 年（2026 立项）</span></h2>
<table><thead><tr><th>产品线</th><th>浙江</th><th>占比</th><th>广东</th><th>占比</th><th>两省合计</th><th>合计占比</th></tr></thead>
<tbody>__LINES__</tbody></table>
<div class="fn">两省结构高度一致（WB 35%、RT-qPCR 21.6%、提取 14.9%），说明口径可跨省复用。<b>WB 是第一大关联品类</b>，与我们 WB 产品线偏弱形成直接矛盾。</div></div>

<div class="card"><h2>二、研究方向 → 主技术路径 → 产品线 <span>这是对“权重被关键词带偏”的修正</span></h2>
<table><thead><tr><th>研究方向</th><th>浙江项数</th><th>浙江经费</th><th>浙江可及</th><th>广东项数</th><th>广东经费</th><th>广东可及</th><th>可及率</th><th>最大产品线</th></tr></thead>
<tbody>__TOPICS__</tbody></table>
<div class="fn">可及率 = 该方向经费中会花在宝锐六大品类上的比例（经验值）。例如蛋白/信号类可及率 62%，因为抗体、流式、测序占了剩下 38%。</div></div>

<div class="card"><h2>三、蛋白 / 信号 / 通路：主流技术路径与宝锐关联度 <span>直接回答“占比多少、关联度怎样”</span></h2>
<table><thead><tr><th>技术路径</th><th>PubMed 提及率<br>（2024–25，可核）</th><th>实验中使用率<br>（经验值）</th><th>宝锐覆盖度</th><th>对应产品线 / 缺口</th><th>关联强度</th></tr></thead>
<tbody>__TECH__</tbody></table>
<div class="grid2" style="margin-top:10px">
<div class="box warn"><h4 style="margin:0 0 4px">两个数为什么差这么多</h4>
<p style="margin:2px 0">PubMed 6.8% 是“摘要里提到 western blot”的比例，<b>不是实验使用率</b>——绝大多数论文摘要不写方法。实验中使用率（85–90%）来自实验室惯例与试剂消耗结构，属经验值。<b>两个数一起看：排序可信，绝对值不可直接当份额。</b></p></div>
<div class="box"><h4 style="margin:0 0 4px">结论：蛋白方向的钱，一半在 WB</h4>
<p style="margin:2px 0">蛋白/信号类项目里，宝锐可及支出结构约 <b>WB 50%、RT-qPCR 13%、克隆 12%、提取 10%、其他分子 14%、LAMP 1%</b>。所以这个方向的核心不是 LAMP，而是 <b>WB 全线 + qPCR 验证</b>。</p></div>
</div></div>

<div class="card"><h2>四、单位层级：宝锐可及经费 Top 12</h2>
<h3>广东</h3>
<table><thead><tr><th>单位</th><th>省基金项数</th><th>总经费(万元)</th><th>宝锐可及(万元)</th><th>可及占比</th><th>最大产品线</th></tr></thead><tbody>__UGD__</tbody></table>
<h3>浙江</h3>
<table><thead><tr><th>单位</th><th>省基金项数</th><th>总经费(万元)</th><th>宝锐可及(万元)</th><th>可及占比</th><th>最大产品线</th></tr></thead><tbody>__UZJ__</tbody></table>
<div class="fn">全量单位清单（广东 255 家 / 浙江 231 家）见同名 Excel，含每家六条产品线拆分与项目明细。</div></div>

<div class="card"><h2>五、口径与不确定点 <span>请你判断</span></h2>
<div class="grid2">
<div class="box"><h4 style="margin:0 0 4px">统一口径做了什么</h4><ul>
<li>两省<b>都按项目名称</b>归类研究方向，不再“浙江用学科代码、广东用关键词”。</li>
<li>浙江学科代码只做校验：代码 C/H 与名称判定一致率 <b>35.1%</b>——因为大量生医研究立项在工材/化学部（生物材料、生物传感、生物化工），名称口径更贴近我们的客户。</li>
<li>生物材料/传感器已纳入，但要求“材料词 + 生物锚点”同时命中，避免把电池材料、涂层算进来（浙江 48 项 / 780 万，广东 52 项 / 610 万）。</li>
</ul></div>
<div class="box warn"><h4 style="margin:0 0 4px">我不确定的 3 个点</h4><ul>
<li><b>可及率（55%/54%）是经验值</b>：需用 3–5 家客户的实际支出结构校准。</li>
<li><b>“实验中使用率”没有权威统计</b>：85–90%（WB）来自经验；若要硬数据，只能做客户访谈或采购结构反推。</li>
<li><b>浙江金额是类别标准额测算</b>（重大 100/杰青 80/重点 30/探索 10/青基 10 万），不是财政逐项下达数。</li>
</ul></div>
</div></div>
__FOOTER__
</div></body></html>"""

def align_numeric_headers(html):
    """把数值列的表头也右对齐，并校验每张表列数一致（防表头/数据错位）"""
    import re as _re
    out = []
    pos = 0
    for m in _re.finditer(r'<table>(.*?)</table>', html, _re.S):
        out.append(html[pos:m.start()])
        tbl = m.group(0)
        ths = _re.findall(r'<th([^>]*)>(.*?)</th>', tbl, _re.S)
        first_tr = _re.search(r'<tbody>\s*<tr>(.*?)</tr>', tbl, _re.S)
        tds = _re.findall(r'<td[^>]*>(.*?)</td>', first_tr.group(1), _re.S) if first_tr else []
        if len(tds) != len(ths):
            raise AssertionError('表头 %d 列 vs 数据 %d 列：%s' % (len(ths), len(tds), tbl[:120]))
        def is_num(x):
            x = _re.sub(r'<[^>]+>', '', x).replace(',', '').replace('%', '').strip()
            return bool(_re.fullmatch(r'-?\d+(\.\d+)?', x))
        new_ths = []
        for i, (attrs, inner) in enumerate(ths):
            if i < len(tds) and is_num(tds[i]) and 'class=' not in attrs:
                attrs += ' class="n"'
            new_ths.append('<th%s>%s</th>' % (attrs, inner))
        # 重建 thead
        thead_m = _re.search(r'<thead>.*?</thead>', tbl, _re.S)
        if thead_m:
            old = thead_m.group(0)
            new = '<thead><tr>' + ''.join(new_ths) + '</tr></thead>'
            tbl = tbl.replace(old, new, 1)
        out.append(tbl)
        pos = m.end()
    out.append(html[pos:])
    return ''.join(out)


page = (page.replace('__CSS__', CSS).replace('__LINES__', rows(line_rows))
        .replace('__TOPICS__', rows(topic_rows)).replace('__TECH__', rows(tech_rows))
        .replace('__UGD__', rows(unit_rows('广东'))).replace('__UZJ__', rows(unit_rows('浙江'))))
page = align_numeric_headers(page)
page = BK.inject(page, theme='dark', tag='科研经费 × 产品线 · 统一口径',
                 footer_extra='内部资料 · 请勿外发 ｜ 源：广东/浙江 2026 省基金立项清单（官方 PDF）、PubMed E-utilities')
page = page.replace('__FOOTER__', '')
io.open(OUT_HTML, 'w', encoding='utf-8').write(page)
print('✅ HTML %.0f KB' % (len(page.encode()) / 1024))

# ── Excel ──
wb = Workbook(); HEAD = PatternFill('solid', fgColor='1F4E79'); HF = Font(color='FFFFFF', bold=True, size=10)


def style(ws, widths):
    for c in range(1, len(widths) + 1):
        cell = ws.cell(row=1, column=c)
        cell.fill = HEAD; cell.font = HF; cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
        ws.column_dimensions[get_column_letter(c)].width = widths[c - 1]
    ws.freeze_panes = 'A2'


ws = wb.active; ws.title = '汇总_产品线'
ws.append(['产品线', '浙江(万元)', '浙江占比', '广东(万元)', '广东占比', '两省合计', '合计占比'])
for l in L:
    ws.append([l, round(zl[l], 1), zl[l] / zs, round(gl[l], 1), gl[l] / gs, round(zl[l] + gl[l], 1), (zl[l] + gl[l]) / (zs + gs)])
for r in range(2, ws.max_row + 1):
    for c in (3, 5, 7):
        ws.cell(row=r, column=c).number_format = '0.0%'
style(ws, [22, 13, 10, 13, 10, 13, 10])

ws = wb.create_sheet('研究方向')
ws.append(['研究方向', '浙江项数', '浙江经费', '浙江可及', '广东项数', '广东经费', '广东可及', '可及率', '最大产品线'])
for r in topic_rows:
    ws.append([r[0], int(str(r[1]).replace(',','')), float(str(r[2]).replace(',','')), float(str(r[3]).replace(',','')), int(str(r[4]).replace(',','')), float(str(r[5]).replace(',','')), float(str(r[6]).replace(',','')), r[7], r[8]])
style(ws, [18, 9, 11, 11, 9, 11, 11, 8, 22])

ws = wb.create_sheet('技术路径_蛋白信号')
ws.append(['技术路径', 'PubMed提及率', '实验使用率(经验值)', '宝锐覆盖度', '对应产品线/缺口', '关联强度'])
for r in tech_rows:
    ws.append(list(r))
style(ws, [22, 14, 16, 11, 56, 9])

for prov in ('广东', '浙江'):
    ws = wb.create_sheet('%s_单位级' % prov)
    ws.append(['单位', '项数', '总经费', '宝锐可及', '可及占比'] + [l for l in L])
    for u in P[prov]['units']:
        ws.append([u['unit'], u['n'], round(u['amt'], 1), round(u['addr'], 1),
                   round(u['addr'] / u['amt'], 3) if u['amt'] else 0] + [round(u['lines'][l], 1) for l in L])
    for r in range(2, ws.max_row + 1):
        ws.cell(row=r, column=5).number_format = '0.0%'
    style(ws, [32, 7, 12, 12, 9] + [12] * len(L))

ws = wb.create_sheet('口径与来源')
for r in [['项目', '内容'],
          ['浙江清单', 'zjnsf.kjt.zj.gov.cn 2026年度拟资助项目清单.pdf（92 页，2,667 项，序号 1–2667 完整）'],
          ['广东清单', 'gdstc 公示附件（WAF 拦截，用 hcwgx 转载件；表内“总计 3171 项 42110 万元”与逐项相加一致）'],
          ['统一口径', '两省均按项目名称归类研究方向；浙江学科代码 C/H 仅校验（一致率 35.1%）'],
          ['可及率', '经验值：蛋白/信号 62%、肿瘤 55%、免疫 50%、神经 55%、代谢 55%、遗传 65%、生物材料 40%、细胞 55%、药理 50%、临床 50%、农学 55%'],
          ['产品线结构', '经验值，见「研究方向」表主产品线列；技术路径占比用 PubMed 2024–25 条件提及率做排序锚点'],
          ['金额口径', '广东逐项公布；浙江按类别标准额测算（重大100/杰青80/重点30/探索10/青基10/青稞10/地区10）'],
          ['国自然', '单位级全量不公布，仅有样本（深大465项/2.05亿、浙工大197项/0.95亿、浙农林110项/4344万）']]:
    ws.append(r)
style(ws, [16, 110])

wb.save(OUT_XLSX)
print('✅ Excel', OUT_XLSX)
