#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""蛋白结构与性能关联工作台（4 蛋白）
数据：UniProt + PDB/AlphaFold + PubMed PMID
输出：销售分析/蛋白结构与性能工作台.html
"""
import base64
import io
import json
import os
import sys

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

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
PD = W + '/数据/蛋白结构'
IMG = W + '/销售分析/蛋白结构图'
OUT = W + '/销售分析/蛋白结构与性能工作台.html'

AB = json.load(io.open(PD + '/_pmid_abstracts.json', encoding='utf-8'))

PROTEINS = [
    {
        'key': 'Taq', 'acc': 'P19821', 'name': 'Taq DNA 聚合酶', 'org': 'Thermus aquaticus',
        'use': 'PCR / qPCR / 探针法',
        'pdb': '1TAQ', 'cov': (10, 832), 'pred': None, 'rmsd': None, 'plddt': None,
        'domains': [('5′→3′ 外切酶域', 1, 290, '#E08A1E'), ('聚合酶域 · 手指', 291, 420, '#1852B6'),
                    ('聚合酶域 · 手掌', 421, 610, '#2E7DD1'), ('聚合酶域 · 拇指', 611, 832, '#4FA3E3')],
        'assoc': [
            ('5′→3′ 外切酶活性可水解探针，是 TaqMan 类实时荧光 PCR 的酶学基础', '5′→3′ 外切酶域', '10510675', '直接'),
            ('F667Y 突变使 Taq 能掺入 ddNTP，成为 Sanger 测序用酶（AmpliTaq FS）', '聚合酶域 · 手掌', '8891223', '直接'),
        ],
    },
    {
        'key': 'Tth', 'acc': 'P52028', 'name': 'Tth DNA 聚合酶', 'org': 'Thermus thermophilus',
        'use': 'RT-PCR / 单管扩增',
        'pdb': '8XK9', 'cov': (296, 832), 'pred': (1, 295), 'rmsd': 1.86, 'plddt': 91.56,
        'domains': [('5′→3′ 外切酶域（预测）', 1, 290, '#E08A1E'), ('聚合酶域 · 手指', 291, 420, '#1852B6'),
                    ('聚合酶域 · 手掌', 421, 610, '#2E7DD1'), ('聚合酶域 · 拇指', 611, 834, '#4FA3E3')],
        'assoc': [
            ('Mn²⁺ 条件下 Tth 聚合酶具有高效逆转录活性，可在一个管内完成逆转录 + 扩增', '聚合酶域', '1714296', '直接'),
        ],
    },
    {
        'key': 'MMLV', 'acc': 'P03355', 'name': 'M-MLV 逆转录酶', 'org': 'Moloney murine leukemia virus',
        'use': 'cDNA 合成 / RT-qPCR',
        'pdb': '4MH8 + AlphaFold', 'cov': (24, 487), 'pred': (488, 671), 'rmsd': 1.45, 'plddt': 87.65,
        'domains': [('RT 域 · fingers/palm/thumb', 741, 1100, '#1852B6'), ('连接区（预测）', 1101, 1173, '#9AA7B8'),
                    ('RNase H 域（预测）', 1174, 1320, '#E08A1E')],
        'assoc': [
            ('M-MLV RT 由 fingers / palm / thumb / connection 四个亚域加一个 RNase H 域组成', 'RT 域', '25959458', '直接'),
            ('RNase H 域突变会改变 DNA 合成与链转移行为', 'RNase H 域', '7539510', '直接'),
            ('RNase H 域不同突变对 RNase H 活性的影响存在差异', 'RNase H 域', '8576137', '直接'),
        ],
    },
    {
        'key': 'UNG', 'acc': 'P12295', 'name': '尿嘧啶-DNA 糖苷酶（UNG）', 'org': 'Escherichia coli K-12',
        'use': '防污染（dUTP/UNG 体系）',
        'pdb': '3UF7', 'cov': (3, 225), 'pred': None, 'rmsd': None, 'plddt': None,
        'domains': [('UDG 催化域', 1, 229, '#0F9D8F')],
        'sites': [(64, 'D', '质子受体（催化）')],
        'assoc': [
            ('UNG 用于去除 PCR 遗留污染（carryover contamination）', 'UDG 催化域', '33469645', '直接'),
            ('热不稳定 UNG 设计可降低对后续扩增的干扰、减少假阴性', 'UDG 催化域', '35183522', '直接'),
            ('大西洋鳕 UNG 为冷适应酶，最适反应温度低于人源 UNG', 'UDG 催化域', '11126771', '直接'),
            ('冷适应 UNG 的热展开研究支持其“低温高活、升温失活”的特性', 'UDG 催化域', '21959147', '直接'),
        ],
    },
]


_CACHE = {}


def _len(p):
    if p['acc'] not in _CACHE:
        _CACHE[p['acc']] = json.load(io.open('%s/%s.json' % (PD, p['acc']), encoding='utf-8'))['length']
    return _CACHE[p['acc']]


def img_b64(path):
    im = Image.open(path).convert('RGB')
    im.thumbnail((1000, 1000))
    buf = io.BytesIO()
    im.save(buf, 'JPEG', quality=88, optimize=True)
    return base64.b64encode(buf.getvalue()).decode()


def domain_svg(prot, width=1180):
    L = 1738 if prot['key'] == 'MMLV' else (832 if prot['key'] in ('Taq', 'Tth') else 229)
    pad = 8
    s = ['<svg viewBox="0 0 %d 112" class="domtrack">' % width]
    for name, a, b, col in prot['domains']:
        x = (a - 1) / L * width
        w = (b - a + 1) / L * width
        pred = '（预测）' in name
        s.append('<rect x="%.1f" y="24" width="%.1f" height="30" rx="3" fill="%s" opacity="%s"></rect>' % (x, w, col, 0.45 if pred else 1))
        if pred:
            s.append('<rect x="%.1f" y="24" width="%.1f" height="30" rx="3" fill="url(#hatch)" opacity="0.55"></rect>' % (x, w))
        narrow = w < 130
        ly = 70 if not narrow else 88
        s.append('<text x="%.1f" y="%d" font-size="%s" text-anchor="middle" fill="#43505f">%s</text>' % (x + w / 2, ly, '11' if narrow else '13', name.replace('（预测）', '')))
        s.append('<text x="%.1f" y="%d" font-size="11" text-anchor="middle" fill="#8a94a3">%d–%d</text>' % (x + w / 2, ly + 13, a, b))
    s.append('<defs><pattern id="hatch" width="6" height="6" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">'
             '<line x1="0" y1="0" x2="0" y2="6" stroke="#fff" stroke-width="2"/></pattern></defs>')
    s.append('</svg>')
    return ''.join(s)


def seq_html(prot):
    seq = json.load(io.open('%s/%s.json' % (PD, prot['acc']), encoding='utf-8'))['sequence']
    if prot['key'] == 'MMLV':
        start, end = 700, 1340
        seq = seq[start - 1:end]
    else:
        start, end = 1, len(seq)
    out = []
    for i, ch in enumerate(seq, start=start):
        col = ''
        for name, a, b, c in prot['domains']:
            if a <= i <= b:
                col = c
                break
        cls = ' class="sq" style="background:%s22;border-bottom:2px solid %s"' % (col, col) if col else ' class="sq"'
        mark = ''
        for site in prot.get('sites', []):
            if site[0] == i:
                mark = ' style="background:#C0392B;color:#fff;font-weight:800"'
                cls = ''
        out.append('<span%s%s>%s</span>' % (cls, mark, ch))
    return '<div class="seq">%s</div>' % ''.join(out)


panels = []
for p in PROTEINS:
    img = img_b64('%s/%s.png' % (IMG, p['key']))
    rows = []
    for claim, dom, pmid, strength in p['assoc']:
        t = AB.get(pmid, {})
        rows.append('<tr><td>%s</td><td>%s</td><td class="n"><a href="https://pubmed.ncbi.nlm.nih.gov/%s/" target="_blank">%s</a></td>'
                    '<td>%s</td><td class="pj">%s</td></tr>' % (
                        claim, dom, pmid, pmid, strength, (t.get('title', '') or '')[:80]))
    legend = ('<span><i style="background:#1852B6"></i>实验解析（%s %d–%d）</span>' % (p['pdb'], p['cov'][0], p['cov'][1]))
    if p['pred']:
        legend += '<span><i class="hatchbox"></i>AlphaFold 预测（%d–%d，pLDDT %s）</span>' % (p['pred'][0], p['pred'][1], p['plddt'])
    site_note = ''
    if p.get('sites'):
        site_note = '<div class="fn">催化位点：%s</div>' % '；'.join('%s%d（%s）' % (s[1], s[0], s[2]) for s in p['sites'])
    panels.append("""
<div class="panel%(on)s" id="p-%(key)s">
  <div class="card"><h2>%(name)s <span>%(acc)s · %(org)s · %(len)d aa · %(use)s</span></h2>
    <div class="grid2">
      <div><img class="struct" src="data:image/jpeg;base64,%(img)s" alt="%(name)s 结构">
        <div class="legend">%(legend)s</div>
        <div class="fn">%(struct_note)s</div></div>
      <div><h3 style="margin-top:0">关键事实</h3>
        <table class="facts"><tbody>%(facts)s</tbody></table>
        %(site_note)s
      </div>
    </div>
    <h3>结构域轨道</h3>
    %(domsvg)s
    <div class="fn">斜纹＝预测区（AlphaFold），实色＝实验解析区。结构域边界依据 UniProt 注释 + 文献（见“来源”）。</div>
  </div>
  <div class="card"><h2>序列 <span>按结构域着色；深红为催化位点</span></h2>%(seq)s</div>
  <div class="card"><h2>结构域 ↔ 性能关联 <span>每条附 PMID</span></h2>
    <table><thead><tr><th style="width:38%%">关联论断</th><th style="width:16%%">结构域</th><th style="width:10%%" class="n">PMID</th><th style="width:9%%">证据强度</th><th>文献标题</th></tr></thead>
    <tbody>%(rows)s</tbody></table>
    <div class="fn">“直接”＝文献结论直接支持该论断；未读原文或间接推断的条目不收录。</div>
  </div>
</div>""" % {
        'on': ' on' if p is PROTEINS[0] else '',
        'key': p['key'], 'name': p['name'], 'acc': p['acc'], 'org': p['org'], 'use': p['use'],
        'len': _len(p),
        'img': img, 'legend': legend,
        'struct_note': ('实验结构 %s，覆盖 %d–%d；其余区段为 AlphaFold 预测（对齐 RMSD %.2f Å）。' % (p['pdb'], p['cov'][0], p['cov'][1], p['rmsd']))
        if p['pred'] else ('实验结构 %s，覆盖 %d–%d。' % (p['pdb'], p['cov'][0], p['cov'][1])),
        'domsvg': domain_svg(p), 'site_note': site_note,
        'facts': ''.join('<tr><td style="color:#6b7684;width:38%%">%s</td><td>%s</td></tr>' % kv for kv in [
            ('UniProt', p['acc']), ('长度', '%d aa' % _len(p)),
            ('实验结构', p['pdb']), ('解析区间', '%d–%d' % p['cov']),
            ('预测补全', ('%d–%d（pLDDT %s）' % (p['pred'][0], p['pred'][1], p['plddt'])) if p['pred'] else '无'),
            ('对齐 RMSD', ('%.2f Å' % p['rmsd']) if p['rmsd'] else '—'),
            ('主要用途', p['use']),
        ]),
        'seq': seq_html(p), 'rows': '\n'.join(rows),
    })

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.6}
.wrap{max-width:1240px;margin:0 auto;padding:0 18px 40px}
h1{font-size:21px;margin:14px 0 4px}.meta{color:#5f6b7a;font-size:12.2px;margin-bottom:12px}
.pbar{display:flex;gap:6px;flex-wrap:wrap;margin:10px 0 14px}
.pbtn{padding:8px 16px;border:1px solid #cfd8e6;background:#fff;border-radius:8px;cursor:pointer;font-weight:600;font-size:13px}
.pbtn.on{background:#1852B6;color:#fff;border-color:#1852B6}
.panel{display:none}.panel.on{display:block}
.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.8px;color:#8a94a3;font-weight:600;margin-left:6px}
h3{font-size:13px;color:#1F4E79;margin:14px 0 6px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
img.struct{width:100%;border-radius:8px;background:#fff}
.legend{display:flex;gap:14px;flex-wrap:wrap;font-size:11.5px;color:#43505f;margin-top:8px}
.legend span{display:inline-flex;align-items:center;gap:5px}
.legend i{width:12px;height:12px;border-radius:2px;display:inline-block}
.legend i.hatchbox{background:repeating-linear-gradient(45deg,#c9d3e0,#c9d3e0 2px,#fff 2px,#fff 5px);border:1px solid #b7c3d3}
svg.domtrack{width:100%;height:auto;display:block;margin:4px 0 2px}
.seq{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11.2px;line-height:1.85;word-break:break-all;background:#fbfcfe;border:1px solid #eef1f6;border-radius:6px;padding:10px;max-height:260px;overflow:auto}
.sq{padding:1px 0}
table{width:100%;border-collapse:collapse;font-size:11.8px}
th{background:#1F4E79;color:#fff;padding:7px;text-align:left}th.n{text-align:right}
td{padding:6px 7px;border-bottom:1px solid #eef1f6;vertical-align:top}
td.n{text-align:right;white-space:nowrap}td.pj{color:#6b7684;font-size:11.2px}
a{color:#1852B6;text-decoration:none}a:hover{text-decoration:underline}
.fn{color:#6b7684;font-size:11.3px;margin-top:7px}
.box{background:#fffaf0;border:1px solid #f3e2bf;border-radius:8px;padding:10px 12px}
.facts{font-size:11.8px}.facts td{padding:4px 6px;border-bottom:1px solid #f1f4f9}
@media(max-width:900px){.grid2{grid-template-columns:1fr}.bior-tagwrap{display:none!important}.seq{font-size:10.2px}}
"""

NAV = ''.join('<button class="pbtn%s" data-p="%s">%s</button>' % (' on' if i == 0 else '', p['key'], p['name'].split('（')[0])
              for i, p in enumerate(PROTEINS))

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>__CSS__</style></head><body><div class="wrap">
<h1>蛋白结构与性能关联工作台</h1>
<div class="meta">首批 4 个 IVD 常用酶：Taq / Tth / M-MLV RT / UNG（E. coli）。数据：UniProt（序列/结构域）、PDB（实验结构）、AlphaFold DB（补全预测）、PubMed（性能证据）。截至 2026-10-17。</div>
<div class="pbar" id="pbar">__NAV__</div>
__PANELS__
<div class="card"><h2>口径与来源</h2>
<div class="grid2">
<div><h3 style="margin-top:0">数据源</h3><ul>
<li>UniProt：P19821（Taq）、P52028（Tth）、P03355（M-MLV Gag-Pol）、P12295（E. coli UNG）</li>
<li>实验结构：1TAQ（Taq，2.4 Å）、8XK9（Tth，2.4 Å）、4MH8（M-MLV RT）、3UF7（UNG，1.2 Å）</li>
<li>预测模型：AlphaFold DB（MMLV 661–1331，pLDDT 87.65；Tth 1–834，pLDDT 91.56）</li>
<li>文献：PubMed，逐条列 PMID，链接可点</li>
</ul></div>
<div class="box"><h3 style="margin-top:0">必须知道的 3 个限制</h3><ul>
<li><b>预测区不是实验结构</b>：斜纹区为 AlphaFold 模型，只能用于示意与假设，不能用于精确判断构象。</li>
<li><b>Tth 外切域无实验结构</b>：8XK9 只解析到 296 位，1–295 为预测。</li>
<li><b>结构域边界有争议</b>：Taq/Tth 的手指/手掌/拇指边界按文献近似划分，不同文献略有差异。</li>
</ul></div>
</div></div>
__FOOTER__
</div>
<script>
document.getElementById('pbar').addEventListener('click', function(e){
  var b=e.target.closest('.pbtn'); if(!b) return;
  document.querySelectorAll('.pbtn').forEach(function(x){x.classList.toggle('on',x===b);});
  document.querySelectorAll('.panel').forEach(function(p){p.classList.toggle('on', p.id==='p-'+b.dataset.p);});
});
</script></body></html>"""

html = (html.replace('__CSS__', CSS).replace('__NAV__', NAV).replace('__PANELS__', '\n'.join(panels)))
html = BK.inject(html, theme='dark', tag='蛋白结构与性能关联 · 4 酶',
                 footer_extra='内部资料 · 请勿外发 ｜ UniProt / PDB / AlphaFold DB / PubMed')
html = html.replace('__FOOTER__', '')
io.open(OUT, 'w', encoding='utf-8').write(html)
print('✅ %.1f MB' % (len(html.encode()) / 1024 / 1024))
