#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""在蛋白工作台上加「销售场景」TAB（Top5 场景，品类级）
输入：销售分析/蛋白结构与性能工作台.html（v2 产物）
输出：同路径覆盖（加 TAB）
"""
import io
import re

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

SCENES = [
    {
        'no': '01', 'title': '冻干 / 常温稳定', 'freq': '活动 2,397 次 · 客诉 34 条',
        'quote': '“冻干后饼状萎缩、复溶不完全”“常温放了 7 天，Ct 整体往后漂”',
        'root': '脱水时酶表面电荷与构象改变 → 活性中心局部塌陷；缓冲盐在浓缩相中结晶 → 局部 pH 骤变；分装球型/体积影响复溶速度。',
        'ask': ['冻干前配方里有没有保护剂（海藻糖/甘露醇/表面活性剂）？浓度多少？',
                '复溶后 Ct 比冻干前差几个循环？是所有靶标还是个别？',
                '运输条件与包装（西林瓶/八连管/球）是什么？'],
        'give': ['冻干保护剂体系（品类）', '耐冻干 Taq 预混（品类）', '液体稳定缓冲体系（品类）'],
        'red': '不承诺“任意配方都能冻干”；不承诺常温 12 个月；不替代客户的冻干工艺验证。',
        'link': 'Taq 聚合酶域（手掌催化中心）· 冻干保护剂',
    },
    {
        'no': '02', 'title': 'Ct 异常 / 扩增失败', 'freq': '活动 851 次 · 客诉 18 条',
        'quote': '“同样的模板，Ct 比上次晚 3–5 个循环”“阳性对照正常，样本就是不出峰”',
        'root': '先分三类：①样本抑制（血红素/腐殖酸/肝素）→ 聚合酶被抑制；②引物探针降解或二聚 → 有效引物浓度下降；③酶活/Mg²⁺/dNTP 不足 → 延伸效率下降。三类根因对应完全不同的解法。',
        'ask': ['模板是什么样本、怎么提取的？', '复孔之间一致吗？阳性对照正常吗？', '是所有靶标都晚，还是只有低拷贝靶标？'],
        'give': ['抗抑制 Taq（品类）', 'Mg²⁺ 优化缓冲（品类）', 'dNTP 套装（品类）'],
        'red': '没看阳性对照与复孔一致性之前，不判定“酶的问题”。',
        'link': 'Taq 手掌域（催化中心）· 拇指域（持续合成）',
    },
    {
        'no': '03', 'title': '一步法 RT-qPCR', 'freq': '活动 718 次',
        'quote': '“一步法 Ct 偏晚，长片段干脆扩不出来”“分开两步做就正常”',
        'root': 'RT 与 Taq 共用缓冲时，Mg²⁺/dNTP 与 pH 要同时满足两种酶；MMLV 的 RNase H 域会降解 RNA–cDNA 杂交链中的 RNA，影响长片段与低丰度模板；RT 热稳定性不足会在预变性阶段失活。',
        'ask': ['目标片段多长？有没有高 GC 区？', 'RT 与 qPCR 是同一管还是两步？', 'RNA 投入量多少、有没有降解？'],
        'give': ['RNase H⁻ MMLV（品类）', '耐热逆转录酶（品类）', '一步法预混（品类）'],
        'red': '不承诺 >4 kb 的一步法；不承诺未纯化 RNA 的直接扩增。',
        'link': 'MMLV RNase H 域 · RT 域 fingers/palm/thumb',
    },
    {
        'no': '04', 'title': '多重 PCR / 引物二聚体', 'freq': '活动 538 次',
        'quote': '“三重里总有一重不出”“引物二聚体压得很高，Ct 起不来”',
        'root': '多套引物竞争同一池 dNTP 与 Mg²⁺；二聚体优先扩增消耗资源；酶的持续合成能力不足时，长扩增子先掉。',
        'ask': ['几重？引物浓度各多少？', '退火温度是按哪一重优化的？', '做过二聚体/发夹结构检查吗？'],
        'give': ['高持续合成 Taq（品类）', '热启动酶（品类）', 'dNTP/Mg²⁺ 套装（品类）'],
        'red': '不承诺“任意 10 重都能做”；多重体系必须客户自己完成引物验证。',
        'link': 'Taq 拇指域（持续合成）',
    },
    {
        'no': '05', 'title': '批间差 / 酶活', 'freq': '活动 359 次 · 客诉 71 条',
        'quote': '“这批酶活性明显比上批低”“同一个配方，换批号 Ct 差 1–2”',
        'root': '先分清是酶活（单位定义与标定方法）、缓冲盐批次（纯度/含水）、还是冻干水分（残余水分影响长期稳定性）。三者表现都像“批间差”。',
        'ask': ['两个批号的 CoA 与酶活数据能提供吗？', '差异体现在 Ct 还是终点荧光？', '存储条件与开封时间？'],
        'give': ['酶活标定（品类）', '缓冲盐批次管理（品类）', '冻干水分控制（品类）'],
        'red': '没有 CoA 与平行复孔数据，不认“批间差”；不替代客户的入厂检验。',
        'link': 'Taq 活性中心 · 冻干水分',
    },
]

cards = []
for s in SCENES:
    cards.append("""
<div class="scene">
  <div class="shead"><span class="sno">%(no)s</span><b>%(title)s</b><span class="sfreq">%(freq)s</span></div>
  <div class="srow"><span class="slab">客户通常这样说</span><div class="sval quote">%(quote)s</div></div>
  <div class="srow"><span class="slab">技术根因</span><div class="sval">%(root)s</div></div>
  <div class="srow"><span class="slab">你要问的 3 个问题</span><div class="sval"><ol class="asks">%(asks)s</ol></div></div>
  <div class="srow"><span class="slab">我们能给（品类）</span><div class="sval chips">%(give)s</div></div>
  <div class="srow"><span class="slab">不能说</span><div class="sval red">%(red)s</div></div>
  <div class="srow"><span class="slab">对应结构域</span><div class="sval link">%(link)s</div></div>
</div>""" % {
        'no': s['no'], 'title': s['title'], 'freq': s['freq'], 'quote': s['quote'], 'root': s['root'],
        'asks': ''.join('<li>%s</li>' % a for a in s['ask']),
        'give': ''.join('<span class="chip">%s</span>' % g for g in s['give']),
        'red': s['red'], 'link': s['link'],
    })

MECH = """
<svg viewBox="0 0 1180 210" class="mech">
  <defs><marker id="ar" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto">
    <path d="M0,0 L0,6 L9,3 z" fill="#1852B6"/></marker></defs>
  <rect x="20" y="40" width="320" height="86" rx="10" fill="#EAF1FB" stroke="#1852B6"/>
  <text x="180" y="70" text-anchor="middle" font-size="15" font-weight="700" fill="#1852B6">① 变性 94–95℃</text>
  <text x="180" y="94" text-anchor="middle" font-size="12" fill="#43505f">双链解开 · 酶必须耐热不失活</text>
  <text x="180" y="112" text-anchor="middle" font-size="11" fill="#8a94a3">对应：Taq 热稳定性（聚合酶域整体）</text>
  <rect x="420" y="40" width="320" height="86" rx="10" fill="#FFF4E0" stroke="#E08A1E"/>
  <text x="580" y="70" text-anchor="middle" font-size="15" font-weight="700" fill="#A86712">② 退火 55–65℃</text>
  <text x="580" y="94" text-anchor="middle" font-size="12" fill="#43505f">引物结合 · 竞争与二聚体发生在这里</text>
  <text x="580" y="112" text-anchor="middle" font-size="11" fill="#8a94a3">对应：Mg²⁺/缓冲 · 引物设计</text>
  <rect x="820" y="40" width="320" height="86" rx="10" fill="#E8F6EE" stroke="#0F9D8F"/>
  <text x="980" y="70" text-anchor="middle" font-size="15" font-weight="700" fill="#0B6B62">③ 延伸 72℃</text>
  <text x="980" y="94" text-anchor="middle" font-size="12" fill="#43505f">dNTP 掺入 · 沿模板合成</text>
  <text x="980" y="112" text-anchor="middle" font-size="11" fill="#8a94a3">对应：手掌域（催化）· 拇指域（持续合成）</text>
  <line x1="345" y1="83" x2="412" y2="83" stroke="#1852B6" stroke-width="2" marker-end="url(#ar)"/>
  <line x1="745" y1="83" x2="812" y2="83" stroke="#1852B6" stroke-width="2" marker-end="url(#ar)"/>
  <path d="M980,132 C980,178 180,178 180,132" fill="none" stroke="#9AA7B8" stroke-width="2" stroke-dasharray="6 4" marker-end="url(#ar)"/>
  <text x="580" y="196" text-anchor="middle" font-size="12" fill="#5f6b7a">循环 30–45 次；探针法在延伸/置换阶段由 5′→3′ 外切酶切出荧光</text>
</svg>"""

DIALOG = [
    ('第一轮：确认现状', ['“您现在这个体系是几步法？Ct 大概多少？”',
                    '“这个现象是从换批号开始的，还是换配方之后？”',
                    '“您现在的对照（阳性/阴性）表现正常吗？”']),
    ('第二轮：归因到结构域', ['“如果是所有靶标一起晚，我们先看酶活和缓冲；只有低拷贝晚，更像样本抑制。”',
                     '“一步法长片段掉，通常是 RNase H 把 RNA 链切了，不是酶量不够。”',
                     '“多重里某一重不出，先查二聚体，再看持续合成能力。”']),
    ('第三轮：给方案与边界', ['“我们可以先给您一个品类级的替换方案，做平行对比，不改您现有体系。”',
                     '“这个结论需要您的复孔数据支持，我们不做口头承诺。”',
                     '“超出这个范围（比如 >4 kb 一步法），我们不建议硬上。”']),
]

QUIZ = [
    ('客户说“冻干后萎缩”，最优先排查什么？', ['换酶', '保护剂与冻干工艺', '加 Mg²⁺'], 1),
    ('Ct 晚 3 个循环，第一步该做什么？', ['直接换 Taq', '看阳性对照与复孔一致性', '加大引物浓度'], 1),
    ('一步法长片段扩不出，最可能的结构域原因？', ['Taq 拇指域', 'MMLV RNase H 域', 'UNG 催化域'], 1),
    ('多重 PCR 某一重不出，优先怀疑？', ['酶批间差', '引物二聚体竞争', 'dNTP 过多'], 1),
    ('“批间差”最先要客户提供什么？', ['两个批号的 CoA', '更多模板', '新的引物'], 0),
    ('假阳性/污染优先排查？', ['换酶', '气溶胶与 UNG/dUTP 体系', '提高退火温度'], 1),
    ('Taq 的 5′→3′ 外切域主要对应哪类应用？', ['探针法水解探针', '长片段扩增', '逆转录'], 0),
    ('Tth 聚合酶的特殊能力？', ['耐冻干', 'Mn²⁺ 下的逆转录活性', '切探针'], 1),
    ('UNG 在体系里的作用？', ['防遗留污染', '提高保真度', '加速延伸'], 0),
    ('客户要求“常温 12 个月”时，正确回应？', ['可以直接承诺', '不承诺，需冻干+稳定性验证', '换 MMLV'], 1),
]
quiz_html = ''.join("""
<div class="q"><div class="qt">%d. %s</div>%s</div>""" % (
    i + 1, q, ''.join('<label><input type="radio" name="q%d" value="%d"> %s</label>' % (i, j, o) for j, o in enumerate(opts)))
    for i, (q, opts, _) in enumerate(QUIZ))

SALES = """
<div class="card"><h2>一、先懂机理：PCR 三步，酶在每个位置干什么 <span>销售能指着图讲</span></h2>
%(MECH)s
<div class="fn">记忆点：<b>变性看耐热、退火看竞争、延伸看催化与持续合成</b>；探针法多一步“外切域切探针”。</div>
</div>

<div class="card"><h2>二、五个高频场景 <span>按我们活动/客诉频次排序；品类级方案</span></h2>
<div class="scenes">%(CARDS)s</div>
<div class="fn">品类级方案，具体货号在后续版本挂接（框架已留）。</div>
</div>

<div class="card"><h2>三、对话演练：三段式 <span>不要一上来推产品</span></h2>
<div class="dialog">%(DIALOG)s</div>
</div>

<div class="card"><h2>四、自测 10 题 <span>答完看分，低于 8 分回看场景卡</span></h2>
<div id="quiz">%(QUIZ)s</div>
<div class="qbar"><button id="qsubmit">提交</button><span id="qscore"></span></div>
</div>
""" % {
    'MECH': MECH,
    'CARDS': ''.join(cards),
    'DIALOG': ''.join('<div class="dstep"><h4>%s</h4><ul>%s</ul></div>' % (t, ''.join('<li>%s</li>' % x for x in items)) for t, items in DIALOG),
    'QUIZ': quiz_html,
}

html = io.open(P, encoding='utf-8').read()

# 顶部一级 Tab
html = html.replace('<h1>蛋白结构与性能关联工作台</h1>',
                    '<h1>蛋白结构与性能关联工作台</h1>\n<div class="topnav" id="topnav">'
                    '<button class="tbtn on" data-t="protein">蛋白结构</button>'
                    '<button class="tbtn" data-t="sales">销售场景</button></div>')
html = html.replace('<div class="pbar" id="pbar">', '<div id="tab-protein" class="tabpane on"><div class="pbar" id="pbar">')
html = html.replace('__FOOTER__', '</div><div id="tab-sales" class="tabpane">' + SALES + '</div>__FOOTER__')
html = html.replace('__FOOTER__', '')

# 样式
html = html.replace('@media(max-width:900px){',
""".topnav{display:flex;gap:6px;margin:10px 0 6px}
.tbtn{padding:9px 18px;border:1px solid #cfd8e6;background:#fff;border-radius:8px;cursor:pointer;font-weight:700;font-size:13.5px}
.tbtn.on{background:#1F4E79;color:#fff;border-color:#1F4E79}
.tabpane{display:none}.tabpane.on{display:block}
.scene{border:1px solid #e3e8f1;border-radius:10px;padding:12px 14px;margin-bottom:12px;background:#fff}
.shead{display:flex;align-items:center;gap:10px;margin-bottom:8px;flex-wrap:wrap}
.sno{background:#1F4E79;color:#fff;font-size:11px;font-weight:800;padding:2px 8px;border-radius:6px}
.shead b{font-size:14.5px}
.sfreq{margin-left:auto;font-size:11px;color:#8a94a3}
.srow{display:grid;grid-template-columns:120px 1fr;gap:10px;padding:5px 0;border-top:1px dashed #eef1f6}
.slab{color:#6b7684;font-size:11.5px;padding-top:2px}
.sval{font-size:12.6px}
.sval.quote{color:#1F4E79;font-style:italic}
.sval.red{color:#B3261E}
.sval.link{color:#0F6B62;font-size:12px}
.asks{margin:0;padding-left:18px}.asks li{margin:2px 0}
.chips{display:flex;gap:6px;flex-wrap:wrap}
.chip{background:#EAF1FB;color:#1852B6;border:1px solid #D3E2F8;border-radius:14px;padding:2px 10px;font-size:11.8px}
svg.mech{width:100%;height:auto;display:block}
.dialog{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.dstep{background:#fbfcfe;border:1px solid #eef1f6;border-radius:8px;padding:10px 12px}
.dstep h4{margin:0 0 6px;font-size:13px;color:#1F4E79}
.dstep ul{margin:0;padding-left:16px;font-size:12.2px}
.q{padding:7px 0;border-bottom:1px solid #f1f4f9}
.qt{font-size:12.6px;margin-bottom:4px}
.q label{display:inline-block;margin-right:14px;font-size:12.2px;color:#43505f;cursor:pointer}
.qbar{margin-top:10px;display:flex;gap:10px;align-items:center}
.qbar button{padding:8px 16px;background:#1852B6;color:#fff;border:none;border-radius:7px;cursor:pointer}
@media(max-width:900px){.dialog{grid-template-columns:1fr}.srow{grid-template-columns:1fr}.sfreq{margin-left:0}""")

# Tab 切换 + 自测判分
html = html.replace('window.__V=V;\n</script>',
"""window.__V=V;
document.getElementById('topnav').addEventListener('click',function(e){
  var b=e.target.closest('.tbtn'); if(!b) return;
  document.querySelectorAll('.tbtn').forEach(function(x){x.classList.toggle('on',x===b);});
  document.querySelectorAll('.tabpane').forEach(function(p){p.classList.toggle('on',p.id==='tab-'+b.dataset.t);});
  if(b.dataset.t==='protein'){ setTimeout(function(){ initV('Taq'); },30); }
});
var ANS=%(ANS)s;
document.getElementById('qsubmit').addEventListener('click',function(){
  var sc=0, done=0;
  for(var i=0;i<ANS.length;i++){
    var el=document.querySelector('input[name="q'+i+'"]:checked');
    if(el){ done++; if(parseInt(el.value,10)===ANS[i]) sc++; }
  }
  document.getElementById('qscore').textContent='答对 '+sc+'/'+ANS.length+'（已答 '+done+' 题）'+(sc>=8?' ✅':' — 低于 8 分建议回看场景卡');
});
</script>""".replace('%(ANS)s', str([q[2] for q in QUIZ]).replace("'", '')))

io.open(P, 'w', encoding='utf-8').write(html)
print('✅ 已加销售场景 TAB ｜ %.1f MB' % (len(html.encode()) / 1024 / 1024))
