#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成「天隆 · MD2104-003006-DB（批次20260725）现场信息收集表」A4 可打印 HTML
输出：工具/天隆003006信息收集表_20260917.html
背景事实全部来自：ERP 订单（2026-07-21）、飞书活动记录（2026-07-22 / 2026-09-03 / 2026-09-07）
"""
import os, base64, datetime

ROOT = os.path.expanduser('~/Desktop/Hermes输出-工作类')
OUT = os.path.join(ROOT, '工具/天隆003006信息收集表_20260917.html')
LOGO = os.path.expanduser('~/work/公司资料/logo_transparent.png')
logo = ('data:image/png;base64,' + base64.b64encode(open(LOGO, 'rb').read()).decode()) if os.path.exists(LOGO) else ''
today = datetime.date.today().isoformat()

# 12 靶标预设行（现场填写）
TARGET_ROWS = ''.join(
    '<tr><td class="c">%d</td><td></td><td></td><td class="c"><span class="bx"></span></td>'
    '<td class="c"><span class="bx"></span></td><td class="c"><span class="bx"></span></td><td></td></tr>' % i
    for i in range(1, 15))

def q(no, text, note=''):
    n = '<div class="note">%s</div>' % note if note else ''
    return ('<tr><td class="qn">%s</td><td class="qt">%s%s</td><td class="qa"></td></tr>' % (no, text, n))

# ── 模块1 ──
M1 = (
    q('1.1', '本批次目前用在<b>哪些项目</b>？（要具体项目名，能说几个说几个）')
    + q('1.2', '当前处于哪个阶段？（可多选）', '□ 入库检验　□ 小试筛选　□ 放大验证　□ 已配制成成品　□ 已发终端　□ 暂停使用')
    + q('1.3', '产品形式与规格？', '□ 液体全预混　□ 冻干球　□ 单管　□ 其他：______　｜　规格：______ µl/T　｜　分装：______ T/包')
    + q('1.4', '30,000T 目前<b>已使用 / 剩余</b>多少？（T 与包数）')
    + q('1.5', '上机平台 / 仪器型号？（ABI、宏石、其他）')
    + q('1.6', '性能表现：灵敏度 / Ct / 荧光值 / 型线 / 复孔一致性 —— 请客户具体描述')
    + q('1.7', '与替代前 <b>M5244-ZJ4</b> 对比如何？', '□ 更好　□ 相当　□ 更差　｜　差异点：________________________________')
    + q('1.8', '是否存在客户端（终端）断货风险？目前替代进度？')
    + q('1.9', '客户目前对这个体系的<b>整体态度</b>？', '□ 继续使用　□ 观察/限量使用　□ 准备替换　□ 要求迭代　｜ 是否影响 9/7 提到的「单批 2 万 T 正式采购」：____________')
)
# ── 模块2 ──
M2 = (
    q('2.1', '问题靶标是否<b>就是张建老师反馈的那 12 个</b>？有无遗漏或新增？', '现场清点：共 ______ 个（下表登记）')
    + q('2.2', '客户共验证了<b>多少个靶标</b>？其中<b>可行 ______ 个</b>、问题 ______ 个（可行率 ______ %）')
    + q('2.3', '问题现象是否一致？', '□ 型线低趴/不好看　□ Ct 偏后　□ 灵敏度不足　□ 非特异/杂峰　□ 荧光值低　□ NTC/NA 偏高　□ 其他：______')
    + q('2.4', '问题是否与 <b>NA 控制</b>相关？（张建老师负责大肠、铜绿方向）', '大肠：______　铜绿：______　其他靶标：______')
    + q('2.5', '复现性：同批次换人 / 换仪器 / 换项目 是否一致复现？', '□ 稳定复现（____/____ 次）　□ 偶发　□ 无法复现')
    + q('2.6', '同项目里其他体系（ZJ4 / 竞品）表现如何？是否只有我们的体系有问题？')
    + q('2.7', '客户的判定：', '□ 个别靶标不适用（其余可继续用）　□ 体系性问题（影响替代推进）　□ 待内部再验证')
)
# ── 模块3 ──
M3 = (
    q('3.1', '本批次是否做了<b>入库检测</b>？检测项有哪些？',
      '□ 做了　□ 未做　｜ 项目：□ 样本检出　□ 灵敏度　□ 重复性　□ NA/阴性　□ 加速稳定性　□ 其他：______')
    + q('3.2', '入库检测结论？', '□ 合格　□ 有条件合格（条件：__________）　□ 不合格　｜ 检测报告：□ 有（可否提供）　□ 无')
    + q('3.3', '客户入库后是我们自己配制还是直接分装？（对接口径）')
    + q('3.4', '终端反馈：是否已有终端客户反馈？反馈性质？',
      '□ 有终端反馈（内容：____________________）　□ 暂无　｜ 性质：□ 性能 OK　□ 型线问题　□ 其他：______')
    + q('3.5', '剩余物料存放条件与批次号？', '□ −20 ℃　□ 4 ℃　□ 室温　｜ 剩余批次号：____________')
)
M4 = (
    q('4.1', '<b>加速测试前的流程确认</b>：是不是先把体系配制好，放 <b>−20 ℃ 保存 7 天</b>，然后再取一部分去做 37 ℃ 加速？',
      '□ 是（按此流程）　□ 否 → 客户实际流程：______________________________')
    + q('4.2', '⚠ <b>关键问题</b>：从 −20 ℃ 取出来之后，是<b>直接放进 37 ℃ 烘箱</b>吗？',
      '□ 直接放入　□ 先回温 ______ min 后放入　□ 先融化/离心/避光处理：____________　□ 其他：____________')
    + q('4.3', '37 ℃ 加速时长与取样点？', '加速 ______ 天　｜ 取样点：______　｜ 检测方式：□ Ct　□ 荧光值　□ 型线　□ 其他：______')
    + q('4.4', '对照组怎么设？', '□ 留 −20 ℃ 基准样　□ 同管同批　□ 无对照　｜ 说明：__________________')
    + q('4.5', '合格判据是什么？', 'ΔCt ≤ ______ 视为合格　｜ 荧光值下降 ≤ ______ %　｜ 型线变化是否计入：□ 是　□ 否')
    + q('4.6', '液体体系与冻干球体系是否用<b>同一套加速方案</b>？差异点？')
    + q('4.7', '是否与其他供应商用同一方案做对比？（决定数据可比性）')
    + q('4.8', '我们建议的加速方案与客户现行方案的差异点（现场对齐）：')
    + q('4.9', '客户是否愿意按我们建议的方案<b>复测一次</b>？', '□ 愿意（时间：______）　□ 需内部确认　□ 不愿意（原因：______）')
)

EJS = r"""var LS_KEY='__LSKEY__';
var FILE_BASE='天隆003006信息收集表';
function pageEl(){return document.querySelector('.page');}
function fieldsList(){return document.querySelectorAll('.page input,.page textarea,.page select');}
function snapshot(){
 var vals=[];
 for(var i=0;i<fieldsList().length;i++){var el=fieldsList()[i];
  if(el.type==='checkbox'||el.type==='radio'){vals.push(el.checked?'1':'0');}else{vals.push(el.value);}}
 return {t:new Date().toISOString(),html:(pageEl()?pageEl().innerHTML:''),vals:vals};
}
function applyVals(vals){
 var fs=fieldsList();
 for(var i=0;i<fs.length&&i<vals.length;i++){var el=fs[i];
  if(el.type==='checkbox'||el.type==='radio'){el.checked=(vals[i]==='1'||vals[i]===true);}else{el.value=vals[i];}}
}
function saveNow(){
 var st=document.getElementById('saveState');
 try{localStorage.setItem(LS_KEY,JSON.stringify(snapshot()));
  if(st)st.textContent='已保存 '+new Date().toTimeString().slice(0,5);
 }catch(e){if(st)st.textContent='保存失败：浏览器存储已满';}
}
function autoSave(){
 var st=document.getElementById('saveState');
 try{localStorage.setItem(LS_KEY,JSON.stringify(snapshot()));
  if(st)st.textContent='自动保存 '+new Date().toTimeString().slice(0,5);
 }catch(e){}
}
function restore(){
 var raw=null;
 try{raw=localStorage.getItem(LS_KEY);}catch(e){return;}
 if(!raw)return;
 try{
  var d=JSON.parse(raw);
  if(d.html&&pageEl()){pageEl().innerHTML=d.html;}
  if(d.vals)applyVals(d.vals);
  var st=document.getElementById('saveState');
  if(st)st.textContent='已恢复上次填写内容';
 }catch(e){}
}
function setEditable(on){
 window.EDIT=on;
 document.body.setAttribute('contenteditable',on?'true':'false');
 var prot=document.querySelectorAll('.editbar,button,input,textarea,select,.bx,.print-btn');
 for(var i=0;i<prot.length;i++){prot[i].setAttribute('contenteditable','false');}
 var b=document.getElementById('btnEdit');
 if(b)b.textContent=on?'🔒 锁定':'✏️ 编辑';
 document.body.classList.toggle('locked',!on);
}
function toggleEdit(){setEditable(!window.EDIT);}
function download(name,mime,content){
 var b=new Blob([content],{type:mime+';charset=utf-8'});
 var a=document.createElement('a');
 a.href=URL.createObjectURL(b);
 a.download=name;
 document.body.appendChild(a);a.click();
 setTimeout(function(){a.remove();},500);
}
function fileBase(){return FILE_BASE;}
function exportHtml(){
 var src=fieldsList();
 var clone=document.documentElement.cloneNode(true);
 var dst=clone.querySelectorAll('.page input,.page textarea,.page select');
 for(var i=0;i<dst.length;i++){
  var el=dst[i],v=(src[i]?src[i].value:'');
  if(el.tagName==='INPUT'){
   if(el.type==='checkbox'||el.type==='radio'){if(src[i]&&src[i].checked){el.setAttribute('checked','checked');}else{el.removeAttribute('checked');}}
   else{el.setAttribute('value',v);}
  }else if(el.tagName==='TEXTAREA'){el.textContent=v;}
 }
 var st=clone.querySelector('#saveState');
 if(st)st.textContent='已导出 '+new Date().toLocaleString();
 download(fileBase()+'_已填写.html','text/html','<!DOCTYPE html>'+clone.outerHTML);
}
function exportJson(){download(fileBase()+'_数据.json','application/json',JSON.stringify(snapshot(),null,1));}
function resetAll(){
 if(!confirm('确定清空本页所有填写内容并恢复初始状态？'))return;
 try{localStorage.removeItem(LS_KEY);}catch(e){}
 location.reload();
}
document.addEventListener('input',function(){autoSave();});
function boot(){restore();setEditable(true);}
if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',boot);}else{boot();}"""

CSS = """
*{box-sizing:border-box}
@page{size:A4;margin:12mm 11mm}
body{margin:0;background:#eef2f7;font:13px/1.65 -apple-system,"PingFang SC","Microsoft YaHei",sans-serif;color:#1f2d3d}
.print-btn{position:fixed;right:18px;bottom:18px;z-index:99;padding:11px 18px;background:#1a73c9;color:#fff;border:0;border-radius:24px;font-size:14px;font-weight:600;box-shadow:0 6px 18px rgba(26,115,201,.35);cursor:pointer}
.page{max-width:210mm;margin:14px auto;background:#fff;padding:13mm 12mm;box-shadow:0 2px 14px rgba(16,42,80,.12);-webkit-print-color-adjust:exact;print-color-adjust:exact}
.hd{display:flex;align-items:center;gap:12px;border-bottom:3px solid #1a73c9;padding-bottom:9px}
.hd img{height:40px}
.hd .nm{font-weight:800;color:#12325b;font-size:15px;letter-spacing:.4px}
.hd .nm small{display:block;font-weight:500;font-size:10.5px;color:#7286a0;letter-spacing:1.5px}
h1{font-size:17.5px;margin:14px 0 4px;color:#12325b;text-align:center;line-height:1.45}
h1 span{display:block;font-size:12.5px;font-weight:500;color:#5b7a99}
h1 b{color:#d94b4b}
.sub{text-align:center;font-size:12.5px;color:#7286a0;margin-bottom:11px}
.fillbar{display:grid;grid-template-columns:repeat(3,1fr);gap:9px 20px;background:#f7fafd;border:1px solid #dbe6f3;border-radius:8px;padding:10px 13px;margin-bottom:13px;font-size:12.8px}
.fillbar div{display:flex;align-items:center;gap:5px;white-space:nowrap}
.fillbar div input{flex:1;min-width:60px}
.fillbar .lab{color:#33506f;font-weight:600}
.bg{background:#eef6ff;border:1px solid #cfe0f2;border-left:4px solid #1a73c9;border-radius:8px;padding:10px 13px;margin-bottom:14px;font-size:12.6px;line-height:1.85;color:#2f4b6b}
.bg b{color:#12325b}
.bg .t{font-weight:800;color:#12325b;font-size:13px;display:block;margin-bottom:4px}
.bg ul{margin:3px 0 0;padding-left:19px}
.bg .hl{background:#fff4d6;padding:0 3px;border-radius:3px}
h2{font-size:14px;color:#12325b;margin:16px 0 7px;padding-left:9px;border-left:4px solid #1a73c9;display:flex;align-items:center;gap:8px}
h2 em{font-style:normal;font-size:11.5px;font-weight:500;color:#8a9bb0;margin-left:auto}
table{width:100%;border-collapse:collapse;font-size:12.6px}
table.grid th{background:#f2f7fd;color:#12325b;font-weight:600;text-align:left;padding:6px 8px;border:1px solid #dbe6f3}
table.grid td{border:1px solid #dbe6f3;padding:5px 8px;vertical-align:top}
table.qa td{border:1px solid #dbe6f3;padding:6px 8px;vertical-align:top}
table.qa td.qn{width:34px;text-align:center;font-weight:700;color:#1a73c9;background:#f7fafd}
table.qa td.qt{width:47%}
table.qa td.qa{background:#fff}
td.c{text-align:center}
.note{margin-top:4px;font-size:11.8px;color:#5b7a99;line-height:2.1}
.bx{display:inline-block;width:11px;height:11px;border:1.5px solid #9ab4cf;border-radius:2px;vertical-align:-1px}
input[type=text]{border:0;border-bottom:1px dotted #b9cde3;background:transparent;font:inherit;color:#1a3a5c;width:100%;padding:1px 2px;outline:none}
input[type=text]:focus{border-bottom:1.5px solid #1a73c9;background:#f7fafd}
.lines{margin:5px 0 3px}
.lines div{border-bottom:1px dotted #c3d5e8;height:22px}
.two{display:grid;grid-template-columns:1fr 1fr;gap:5px 16px}
.warn{background:#fff8e6;border:1px solid #f0d080;color:#7a5200;border-radius:7px;padding:7px 10px;font-size:12.4px;margin:7px 0}
.foot{margin-top:14px;border-top:1px solid #dbe6f3;padding-top:7px;font-size:11px;color:#8a9bb0;line-height:1.8}
.tag{display:inline-block;background:#12325b;color:#fff;border-radius:3px;padding:0 5px;font-size:11px;margin-right:4px}
/* ── 可编辑层 ── */
.editbar{position:fixed;right:16px;bottom:16px;z-index:99;display:flex;flex-direction:column;gap:6px;align-items:stretch;
 background:rgba(255,255,255,.97);border:1px solid #cfe0f2;border-radius:12px;padding:9px;box-shadow:0 8px 26px rgba(16,42,80,.2)}
.editbar button{padding:7px 13px;border:1px solid #cfe0f2;background:#fff;color:#12325b;border-radius:8px;font-size:12.5px;font-weight:600;cursor:pointer;white-space:nowrap}
.editbar button:hover{border-color:#1a73c9;color:#1a73c9;background:#f5f9ff}
.editbar button.pri{background:#1a73c9;color:#fff;border-color:#1a73c9}
.editbar #saveState{font-size:11px;color:#7286a0;text-align:center;padding-bottom:2px;border-bottom:1px solid #eef3f9;margin-bottom:3px}
.edithint{background:#e8f5ec;border:1px solid #a9dcbe;color:#14663c;border-radius:7px;padding:7px 11px;font-size:12.3px;margin-bottom:11px}
.edithint b{color:#0d4a2b}
body[contenteditable=true] .page :is(h1,h2,p,td,th,li,span,div:not(.editbar)){outline:none}
body[contenteditable=true] .page :hover{box-shadow:inset 0 0 0 1px #e3ecf6}
body.locked .page :hover{box-shadow:none}
/* 可编辑聚焦提示 */
.ed:focus{background:#fffbe9;outline:none}
@media print{
 body{background:#fff}
 .print-btn,.editbar,.edithint{display:none!important}
 .page{box-shadow:none;margin:0;padding:0;max-width:none}
 h2{page-break-after:avoid;break-after:avoid}
 table{page-break-inside:auto}
 tr{page-break-inside:avoid;break-inside:avoid}
 .bg,.fillbar{page-break-inside:avoid;break-inside:avoid}
}
"""

HTML = """<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>天隆 · MD2104-003006-DB（批次20260725）现场信息收集表</title>
<style>__CSS__</style>
<script>__EJS__</script></head><body>
<div class="page">
<div class="hd">__LOGO__
<div class="nm">BIORI BIOTECH<small>宝锐生物 · 销售管理</small></div>
<div style="margin-left:auto;text-align:right;font-size:11.5px;color:#7286a0">制表：__TODAY__<br>用途：线下现场沟通</div></div>

<h1>苏州天隆生物科技 · 现场信息收集表
<span>货号 MD2104-003006-DB　批次 20260725　发货 2026 年 7 月下旬（30,000T）　客诉反馈人：张建老师<b>（12 个靶标）</b></span></h1>
<div class="sub">拜访目的：确认本批次客户端使用情况 · 靶标问题边界 · 入库检测与批次流向 · 加速测试方法口径</div>

<div class="edithint">✏️ <b>本页默认可编辑</b>：直接点击任意文字即可修改；填写框与勾选内容<b>自动保存在本机浏览器</b>（关掉再打开还在）。改完点右下角「<b>⬇ 导出 HTML</b>」生成一份带数据的独立文件（可发给他人 / 存档），「🖨 打印 / PDF」出纸质版。</div>
<div class="fillbar">
<div><span class="lab">沟通日期</span><input type="text" value="2026-09-17"></div>
<div><span class="lab">地点</span><input type="text"></div>
<div><span class="lab">记录人</span><input type="text"></div>
<div><span class="lab">我方参与人</span><input type="text"></div>
<div><span class="lab">客户方参与人</span><input type="text" value="张建、马雪情"></div>
<div><span class="lab">本次批次</span><input type="text" value="20260725"></div>
</div>

<div class="bg">
<span class="t">📌 背景速览（这些我们已经知道，现场不必再问，用来对齐口径）</span>
<ul>
<li><b>定位</b>：MD2104-003006-DB 是 <b>M5244-ZJ4 的紧急替代体系</b>（ZJ4 出现批间差客诉），用途是保证客户客户端不断货。</li>
<li><b>本批次</b>：2026-07-21 采购下单 <b>20,000T + 10,000T = 30,000T</b>，抵扣形式、单价 0.038 元/T、25 µl/T、<b>1,000T/包</b>、契约锁签署；批次号 <b>20260725</b>。</li>
<li><b>前置事件</b>：2026-06-10 因 <b>dNTP 问题</b>引发售后，与客户协商走抵货（抵扣 6,000 元）+ 赠送 5,000T（申购单 CGSQ036162）。</li>
<li><b>已知反馈（2026-07-22）</b>：<span class="hl">张建、马雪情老师反馈「有些靶标型线不好看、低趴，但稳定性没问题」</span>。</li>
<li><b>已决议（2026-09-03 现场）</b>：<span class="hl">没有 NA 控制问题的靶标先用起来，有问题的靶标"控下来"</span>；同期决议 M5244-ZJ4 进行迭代。</li>
<li><b>后续商机（2026-09-07）</b>：客户要 <b>PFMD5104-000838 与 PFMD5104-003006 各 1 批、各 2,000T</b> 做放大测试；如通过，将择优选一个货号做<b>单批 2 万 T 冻干球正式采购</b>。</li>
<li><b>本轮客诉时间线</b>：7 月底发货 → 约 1 个月后客诉 → 问题聚焦在<b>部分靶标</b>（NA 控制相关？）。</li>
</ul>
</div>

<h2>① 客户端使用情况 <em>对应问题 1</em></h2>
<table class="qa">__M1__</table>

<h2>② 靶标问题边界 <em>对应问题 2 · 是否只有 12 个靶标有问题</em></h2>
<table class="grid">
<tr><th style="width:32px">序号</th><th style="width:22%">靶标 / 项目</th><th>问题现象（低趴 / Ct 偏后 / 非特异 / NA 高等）</th>
<th style="width:8%">严重度<br>阻断?</th><th style="width:9%">NA 控制<br>相关?</th><th style="width:9%">可复现?</th><th style="width:20%">备注（对照体系表现）</th></tr>
__TROWS__
</table>
<table class="qa" style="margin-top:7px">__M2__</table>

<h2>③ 入库检测 & 30,000T 批次流向 <em>对应问题 3</em></h2>
<table class="qa">__M3__</table>
<div style="font-size:12.4px;margin-top:7px"><b>30,000T 去向拆分（现场与客户逐项核对，合计应为 30,000T）</b></div>
<table class="grid" style="margin-top:4px">
<tr><th style="width:34%">去向</th><th style="width:20%">数量（T）</th><th style="width:20%">数量（包）</th><th>说明（终端客户 / 项目 / 状态）</th></tr>
<tr><td>客户自留库存（未使用）</td><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td></tr>
<tr><td>已配制成成品（在客户手上）</td><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td></tr>
<tr><td>已发往终端使用</td><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td></tr>
<tr><td>已报废 / 退回 / 抵扣</td><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td></tr>
<tr style="background:#f7fafd;font-weight:600"><td>合计（应为 30,000T）</td><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td></tr>
</table>
<div class="warn">⚠ 核心判断：<b>如果 3 万 T 大部分已配成成品发到终端</b>，则问题影响面 = 终端层面，处理优先级最高；<b>如果仍在客户手上</b>，则可先拦截、可换货、可控。现场务必问清「<b>是已经发到终端了，还是还在你们自己手上？</b>」</div>

<h2>④ 加速测试方法逐条确认 <em>对应问题 4 · 现场问张建老师</em></h2>
<div class="warn">这组问题的目的：判断客诉里的「靶标问题」是否是<b>加速测试方法差异</b>造成的（例如 −20 ℃ 取出直接进 37 ℃ 烘箱、缺少回温/对照），以便区分<b>产品问题</b>还是<b>方法问题</b>。</div>
<table class="qa">__M4__</table>

<h2>⑤ 现场结论与行动 <em>离开前填完</em></h2>
<div style="font-size:12.4px;font-weight:600">现场结论（3 条）</div>
<div class="lines"><div></div><div></div><div></div></div>
<div style="font-size:12.4px;font-weight:600;margin-top:8px">立即行动项</div>
<table class="grid">
<tr><th style="width:34%">事项</th><th style="width:14%">责任人</th><th style="width:14%">完成时间</th><th>验收标准 / 备注</th></tr>
<tr><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td></tr>
<tr><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td></tr>
<tr><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td></tr>
<tr><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td><td><input type="text"></td></tr>
</table>
<div class="two" style="margin-top:8px">
<div style="font-size:12.4px;font-weight:600">需回厂验证 / 需寄送清单</div>
<div style="font-size:12.4px;font-weight:600">下次沟通安排（时间 / 议题）</div>
</div>
<div class="lines"><div></div><div></div><div></div></div>

<div class="foot">
<span class="tag">数据依据</span>ERP 销售订单导出-销售管理（2026-07-21 采购 20,000T+10,000T；2026-06-10 dNTP 售后抵扣 6,000 元；2026-05-09、06-15 给张建老师送测 100T/1,000T）｜
飞书活动记录（2026-07-22 张立娅：靶标型线低趴、稳定性没问题；2026-09-03 刘新元：无 NA 问题靶标先用、ZJ4 迭代；2026-09-07 刘新元：838 与 003006 各 2,000T 放大测试，通过后单批 2 万 T 正式采购）。<br>
本表所有背景事实均取自上述记录，未做推测；「12 个靶标」为张建老师本轮反馈口径，现场需逐项登记核对。制表 __TODAY__。
</div>
</div>
<div class="editbar" contenteditable="false">
 <span id="saveState">未保存</span>
 <button class="pri" onclick="saveNow()">💾 保存</button>
 <button onclick="exportHtml()">⬇ 导出 HTML</button>
 <button onclick="exportJson()">⬇ 导出 JSON</button>
 <button onclick="window.print()">🖨 打印 / PDF</button>
 <button id="btnEdit" onclick="toggleEdit()">🔒 锁定</button>
 <button onclick="resetAll()">⟲ 重置</button>
</div>
</body></html>"""

html = (HTML.replace('__EJS__', EJS).replace('__LSKEY__', 'baoyu-form-tianlong-003006-20260917')
        .replace('__CSS__', CSS)
        .replace('__LOGO__', ('<img src="%s" alt="BIORI">' % logo) if logo else '')
        .replace('__TODAY__', today)
        .replace('__M1__', M1).replace('__M2__', M2).replace('__M3__', M3).replace('__M4__', M4)
        .replace('__TROWS__', TARGET_ROWS))
open(OUT, 'w', encoding='utf-8').write(html)
print('✅ %s  %.1f KB' % (OUT, os.path.getsize(OUT) / 1024))
print('   模块1 行数 %d ｜ 模块2 %d ｜ 模块3 %d ｜ 模块4 %d ｜ 靶标表 %d 行'
      % (M1.count('class="qn"'), M2.count('class="qn"'), M3.count('class="qn"'), M4.count('class="qn"'), TARGET_ROWS.count('<tr>')))
print('   宏残留:', [x for x in ['__CSS__', '__M1__', '__M2__', '__M3__', '__M4__', '__TROWS__', '__TODAY__', '__LOGO__'] if x in html])
print('   外链:', html.count('src="http'))
