#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""销售场景手册（独立自包含）：PCR 机理 + Top5 高频场景 + 对话演练 + 自测
输出：销售分析/销售场景手册_PCR机理与Top5_2026-10-17.html
"""
import io
import sys

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

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
OUT = W + '/销售分析/销售场景手册_PCR机理与Top5_2026-10-17.html'

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

MECH = """
<svg viewBox="0 0 1180 214" 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="34" width="320" height="88" rx="10" fill="#EAF1FB" stroke="#1852B6"/>
  <text x="180" y="64" text-anchor="middle" font-size="15" font-weight="700" fill="#1852B6">① 变性 94–95℃</text>
  <text x="180" y="88" text-anchor="middle" font-size="12" fill="#43505f">双链解开 · 酶必须耐热不失活</text>
  <text x="180" y="108" text-anchor="middle" font-size="11" fill="#8a94a3">对应：Taq 整体热稳定性</text>
  <rect x="420" y="34" width="320" height="88" rx="10" fill="#FFF4E0" stroke="#E08A1E"/>
  <text x="580" y="64" text-anchor="middle" font-size="15" font-weight="700" fill="#A86712">② 退火 55–65℃</text>
  <text x="580" y="88" text-anchor="middle" font-size="12" fill="#43505f">引物结合 · 竞争与二聚体在这里</text>
  <text x="580" y="108" text-anchor="middle" font-size="11" fill="#8a94a3">对应：Mg²⁺/缓冲 · 引物设计</text>
  <rect x="820" y="34" width="320" height="88" rx="10" fill="#E8F6EE" stroke="#0F9D8F"/>
  <text x="980" y="64" text-anchor="middle" font-size="15" font-weight="700" fill="#0B6B62">③ 延伸 72℃</text>
  <text x="980" y="88" text-anchor="middle" font-size="12" fill="#43505f">dNTP 掺入 · 沿模板合成</text>
  <text x="980" y="108" text-anchor="middle" font-size="11" fill="#8a94a3">对应：手掌域（催化）· 拇指域（持续）</text>
  <line x1="345" y1="78" x2="412" y2="78" stroke="#1852B6" stroke-width="2" marker-end="url(#ar)"/>
  <line x1="745" y1="78" x2="812" y2="78" stroke="#1852B6" stroke-width="2" marker-end="url(#ar)"/>
  <path d="M980,128 C980,182 180,182 180,128" fill="none" stroke="#9AA7B8" stroke-width="2" stroke-dasharray="6 4" marker-end="url(#ar)"/>
  <text x="580" y="200" 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),
]

CATEGORY = [
    ('Taq 聚合酶 / 预混', '01 冻干、02 Ct、04 多重、05 批间差', '热稳定性、催化效率、持续合成'),
    ('热启动酶', '04 多重、02 Ct', '非特异扩增抑制'),
    ('MMLV 逆转录酶（含 RNase H⁻）', '03 一步法', 'cDNA 长度、RNase H 活性'),
    ('UNG / dUTP', '02 Ct、08 污染（下版）', '防遗留污染'),
    ('dNTP / Mg²⁺ / 缓冲体系', '02、04、05', '离子与底物竞争'),
    ('冻干保护剂', '01 冻干', '脱水保护、复溶'),
]

cards = []
for s in SCENES:
    cards.append("""
<div class="scene" id="s%(no)s">
  <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']})

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))

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:14px 0 4px}.meta{color:#5f6b7a;font-size:12.2px;margin-bottom:12px}
.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:18px;font-weight:800;color:#1F4E79}.ks{font-size:10.5px;color:#8a94a3}
.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}
svg.mech{width:100%;height:auto;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:130px 1fr;gap:10px;padding:6px 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:#0B6B62;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}
.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}
table{width:100%;border-collapse:collapse;font-size:12px}
th{background:#1F4E79;color:#fff;padding:7px;text-align:left}td{padding:6px 7px;border-bottom:1px solid #eef1f6;vertical-align:top}
.fn{color:#6b7684;font-size:11.3px;margin-top:7px}
.box{background:#fffaf0;border:1px solid #f3e2bf;border-radius:8px;padding:10px 12px}
ul{padding-left:18px}
@media(max-width:900px){.kpis{grid-template-columns:repeat(2,1fr)}.dialog{grid-template-columns:1fr}.srow{grid-template-columns:1fr}.sfreq{margin-left:0}.bior-tagwrap{display:none!important}}
"""

page = """<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>销售场景手册：PCR 机理与 Top5 高频场景</title><style>__CSS__</style></head><body><div class="wrap">
<h1>销售场景手册：PCR 机理与 Top5 高频场景</h1>
<div class="meta">面向诊断原料销售。场景顺序按宝锐 2026 年活动（约 200 万字记录）与客诉原文的词频排序，不是主观排序。品类级方案，具体货号留有扩展位。截至 2026-10-17。</div>

<div class="kpis">
 <div class="kpi"><div class="kl">覆盖场景</div><div class="kv">Top 5</div><div class="ks">冻干 / Ct / 一步法 / 多重 / 批间差</div></div>
 <div class="kpi"><div class="kl">最多提及</div><div class="kv">2,397 次</div><div class="ks">冻干与常温稳定</div></div>
 <div class="kpi"><div class="kl">客诉最集中</div><div class="kv">71 条</div><div class="ks">批间差 / 酶活</div></div>
 <div class="kpi"><div class="kl">自测</div><div class="kv">10 题</div><div class="ks">低于 8 分回看场景卡</div></div>
</div>

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

<div class="card"><h2>二、五个高频场景 <span>每个场景：原话 → 根因 → 三问 → 品类 → 红线</span></h2>
__CARDS__
<div class="fn">“品类”是本次交付的颗粒度；具体货号留有扩展位（见第四节）。</div>
</div>

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

<div class="card"><h2>四、品类 → 货号扩展位 <span>下一版挂具体货号</span></h2>
<table><thead><tr><th style="width:32%">品类</th><th style="width:34%">覆盖场景</th><th style="width:22%">关键性能</th><th>货号（待挂）</th></tr></thead><tbody>
__CATEGORY__
</tbody></table>
<div class="fn">货号列留空是刻意的：等确定用哪几个在售货号对应，再由产品/ERP 核对后填入，避免销售拿错号。</div>
</div>

<div class="card"><h2>五、自测 10 题 <span>答完看分</span></h2>
<div id="quiz">__QUIZ__</div>
<div class="qbar"><button id="qsubmit">提交</button><span id="qscore"></span></div>
</div>

<div class="card"><h2>六、边界与来源</h2>
<div class="box"><ul style="margin:2px 0">
<li><b>场景频次</b>来自宝锐活动记录（约 200 万字）与客诉库的词频统计，不是行业报告。</li>
<li><b>技术根因</b>对应蛋白结构域，依据 UniProt 注释与 PubMed 文献（详见蛋白工作台）。</li>
<li><b>品类方案</b>不含具体货号与价格，需按客户体系做平行验证后再报。</li>
<li><b>不能承诺</b>：任意配方冻干、任意重数多重、>4 kb 一步法、常温 12 个月。</li>
</ul></div></div>
__FOOTER__
</div>
<script>
var ANS=__ANS__;
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></body></html>"""

page = (page.replace('__CSS__', CSS).replace('__MECH__', MECH).replace('__CARDS__', ''.join(cards))
        .replace('__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))
        .replace('__CATEGORY__', ''.join('<tr><td>%s</td><td>%s</td><td>%s</td><td style="color:#8a94a3">待挂</td></tr>' % c for c in CATEGORY))
        .replace('__QUIZ__', quiz_html).replace('__ANS__', str([q[2] for q in QUIZ])))
page = BK.inject(page, theme='dark', tag='销售场景手册 · PCR 机理与 Top5',
                 footer_extra='内部资料 · 请勿外发 ｜ 场景频次来自活动与客诉记录；技术依据 UniProt / PubMed')
page = page.replace('__FOOTER__', '')
io.open(OUT, 'w', encoding='utf-8').write(page)
print('✅ %.0f KB' % (len(page.encode()) / 1024))
