#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""一页纸简报：数据来源与可靠性（A4 单页，HTML + PDF）

源稿 → 注入 Logo → 自检 → 出文件
用法： env -u PYTHONPATH /usr/bin/python3 build_one_pager_reliability.py
"""
import io
import os
import sys

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

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
OUT = W + '/销售分析/数据来源与可靠性_一页纸简报_2026-10-16.html'

# ── 数字现算（不手写估算值）──────────────────────────────────
DOCS = [  # (名称, 全文字数, 冻干, 水分, 复溶)
    ('呼吸道病毒多重核酸检测试剂注册审查指导原则（2019）', 11120, 0, 0, 1),
    ('核酸扩增法检测试剂注册技术审查指导原则（2013）', 3009, 2, 0, 0),
    ('体外诊断试剂稳定性研究注册审查指导原则（2025-4）', 4318, 0, 0, 2),
    ('体外诊断试剂生产质量体系检查要点指南（2017 修订）', 28674, 10, 1, 2),
    ('家用体外诊断医疗器械注册技术审查指导原则（2020-80）', 5578, 0, 0, 0),
    ('体外诊断试剂注册与备案管理办法（48 号令）', 13603, 0, 0, 0),
    ('核酸检测试剂冻干技术及其评价方法研究进展（2025）', 23065, 151, 12, 14),
    ('YY/T 1579-2018（=ISO 23640:2011，OCR）', 6359, 1, 0, 3),
    ('流感病毒核酸检测试剂注册申报资料指导原则（2011，已废止）', 11811, 0, 0, 0),
    ('流感病毒核酸检测试剂注册审查指导原则（2023 修订，现行）', 10272, 0, 0, 0),
]
TOTAL_CHARS = sum(d[1] for d in DOCS)
TOTAL_WAN = TOTAL_CHARS / 10000.0
N_DOCS = len(DOCS)
# 法规类（排除综述）的「水分」命中
REG_WATER = sum(d[3] for d in DOCS if '研究进展' not in d[0])

LOGO = BK.logo_data_uri(64)

CSS = """
@page{size:A4;margin:0}
*{box-sizing:border-box;margin:0;padding:0}
:root{--blue:#1F4E79;--blue2:#2E6CA4;--ink:#1b2430;--muted:#66707d;--line:#dfe5ee;--bg:#f6f8fb;
      --gold:#C8963E;--green:#1c7c54;--red:#b3261e}
html,body{background:#e9edf3;font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;color:var(--ink)}
.sheet{width:210mm;height:297mm;background:#fff;margin:0 auto;position:relative;overflow:hidden;
       display:flex;flex-direction:column;font-size:8.1pt;line-height:1.5}
@media screen{.sheet{box-shadow:0 6px 28px rgba(20,40,70,.14);margin:10mm auto}}

/* 页眉 */
.bior-header{background:linear-gradient(100deg,#173F63 0%,#1F4E79 55%,#2E6CA4 100%);color:#fff;
  padding:6.5mm 9mm 5mm;display:flex;align-items:center;gap:6mm;flex:0 0 auto}
.bior-header .lg{width:26mm;height:13mm;background:#fff;border-radius:2mm;display:flex;align-items:center;justify-content:center;flex:0 0 auto}
.bior-header .lg img{max-width:23mm;max-height:10mm;object-fit:contain}
.bior-header h1{font-size:15pt;letter-spacing:.6pt;font-weight:800;line-height:1.2}
.bior-header .sub{font-size:7.6pt;opacity:.9;margin-top:1.4mm;letter-spacing:.3pt}
.bior-header .rt{margin-left:auto;text-align:right;font-size:7pt;opacity:.92;line-height:1.7}
.bior-header .rt b{font-size:8.2pt}

/* KPI 条 */
.kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:0;border-bottom:.4mm solid var(--line);flex:0 0 auto}
.kpi{padding:2.5mm 4mm 2.2mm;border-right:.3mm solid var(--line);text-align:center}
.kpi:last-child{border-right:0}
.kpi .n{font-size:14pt;font-weight:900;color:var(--blue);line-height:1.05;font-variant-numeric:tabular-nums}
.kpi .l{font-size:6.9pt;color:var(--muted);margin-top:.8mm;letter-spacing:.2pt}

/* 三栏主体 */
.main{flex:1 1 auto;min-height:0;display:grid;grid-template-columns:1fr 1.06fr 1.12fr;gap:0;overflow:hidden}
.col{padding:3.2mm 4.4mm 1mm;border-right:.3mm solid var(--line);min-height:0;display:flex;flex-direction:column}
.col:last-child{border-right:0}
.h2{font-size:9pt;font-weight:800;color:var(--blue);padding-bottom:1.5mm;margin-bottom:2.2mm;
    border-bottom:.5mm solid var(--blue);display:flex;align-items:baseline;gap:2mm}
.h2 span{font-size:6.6pt;font-weight:600;color:var(--muted);letter-spacing:.3pt}

/* 高频质疑 4 卡 */
.qa{flex:0 0 auto;padding:2.2mm 9mm 0;border-top:.4mm solid var(--line)}
.qa .qt{font-size:8.4pt;font-weight:800;color:var(--blue);margin-bottom:2mm;display:flex;align-items:baseline;gap:2mm}
.qa .qt span{font-size:6.5pt;font-weight:600;color:var(--muted)}
.qgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:2.2mm}
.qcard{background:#f7f9fc;border:.3mm solid var(--line);border-top:.7mm solid var(--gold);border-radius:1.2mm;padding:1.6mm 2.2mm}
.qcard .q{font-size:7.6pt;font-weight:800;color:var(--ink);line-height:1.35}
.qcard .a{font-size:6.9pt;color:#3c4654;margin-top:1mm;line-height:1.5}

/* 工具链 */
.tools{flex:0 0 auto;padding:1.8mm 9mm 0;display:flex;align-items:center;gap:2.4mm;font-size:6.7pt;color:#3c4654}
.tools .tl{font-size:6.6pt;font-weight:800;color:var(--blue);white-space:nowrap;letter-spacing:.3pt}
.tools code{font-family:ui-monospace,Menlo,monospace;font-size:6.4pt;background:#eef2f8;border-radius:.8mm;padding:.4mm 1.2mm;color:#24405e;white-space:nowrap}
.tools .sep{color:#c3ccd9}

.item{margin-bottom:2.15mm}
.item .t{font-size:8.1pt;font-weight:800;color:var(--ink);display:flex;align-items:baseline;gap:1.4mm}
.item .t i{width:3.6mm;height:3.6mm;border-radius:50%;background:var(--blue);color:#fff;font-style:normal;
  font-size:5.8pt;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;font-weight:700}
.item .d{font-size:7.5pt;color:#3c4654;margin:.9mm 0 0 5mm;line-height:1.55}

.layer{border-left:.9mm solid var(--blue2);padding:.8mm 0 .8mm 2.6mm;margin-bottom:2mm;background:linear-gradient(90deg,#f7f9fc,transparent)}
.layer .lt{font-size:7.7pt;font-weight:800;color:var(--blue2);display:flex;justify-content:space-between;gap:2mm}
.layer .lt em{font-style:normal;font-size:6.3pt;font-weight:700;color:#fff;background:var(--blue2);border-radius:1mm;padding:.2mm 1.2mm}
.layer .ld{font-size:7.15pt;color:#414b58;margin-top:.7mm;line-height:1.5}

.m{display:flex;gap:1.8mm;margin-bottom:2.05mm;font-size:7.35pt;line-height:1.45}
.m b{flex:0 0 3.8mm;height:3.8mm;border-radius:.8mm;background:#e8eef7;color:var(--blue);font-size:5.9pt;
  display:inline-flex;align-items:center;justify-content:center;margin-top:.2mm}
.m p{flex:1 1 auto}
.m p u{text-decoration:none;font-weight:800;color:var(--ink)}
.chk{margin-top:auto;background:#f4f7fb;border:.3mm solid var(--line);border-radius:1.2mm;padding:2.2mm 2.6mm}
.chk .ct2{font-size:7.2pt;font-weight:800;color:var(--blue);margin-bottom:1.2mm}
.chk .ci{font-size:6.9pt;color:#3c4654;line-height:1.62;padding-left:3.4mm;position:relative}
.chk .ci:before{content:"□";position:absolute;left:0;color:var(--blue2);font-size:7pt}

/* 案例条 */
.case{background:var(--bg);border-top:.4mm solid var(--line);border-bottom:.4mm solid var(--line);padding:2.4mm 9mm;flex:0 0 auto}
.case .ct{font-size:8pt;font-weight:800;color:var(--blue);margin-bottom:1.8mm;display:flex;align-items:center;gap:2mm}
.case .ct em{font-style:normal;font-size:6.2pt;font-weight:700;color:#fff;background:var(--green);border-radius:1mm;padding:.3mm 1.4mm}
.flow{display:grid;grid-template-columns:repeat(5,1fr);gap:1.6mm;align-items:stretch}
.step{background:#fff;border:.3mm solid var(--line);border-radius:1.4mm;padding:1.4mm 2mm;position:relative;text-align:center}
.step .v{font-size:11pt;font-weight:900;color:var(--blue);line-height:1.05;font-variant-numeric:tabular-nums}
.step .k{font-size:6.4pt;color:var(--muted);margin-top:.7mm;line-height:1.35}
.step.hl{border-color:var(--gold);background:#fffdf7}
.step.hl .v{color:#8a5a12}
.arrow{position:absolute;right:-1.15mm;top:50%;transform:translateY(-50%);color:#b9c4d3;font-size:7pt;font-weight:900}

/* 页脚 */
.bior-footer{background:var(--blue);color:#fff;padding:2.6mm 9mm 3mm;flex:0 0 auto;display:flex;align-items:center;gap:4mm}
.bior-footer-slogan{font-size:8.4pt;font-weight:800;letter-spacing:1pt;white-space:nowrap}
.bior-footer-meta{font-size:6.2pt;opacity:.9;line-height:1.45;margin-left:auto;text-align:right}
.bior-footer-extra{font-size:6.2pt;opacity:.85;white-space:nowrap;padding-left:4mm;border-left:.3mm solid rgba(255,255,255,.35)}
"""

COL_A = """
<div class="col">
  <div class="h2">一 · 功能 <span>能做什么</span></div>
  <div class="item"><div class="t"><i>1</i>文献调研 → 证据表</div>
    <div class="d">三源检索（Europe PMC / OpenAlex / Crossref）→ 取开放全文 → 逐篇抽「定量+条件」→ 引用机械校验；每篇标 ✓已读全文 / ⚠仅摘要</div></div>
  <div class="item"><div class="t"><i>2</i>法规 / 标准取证</div>
    <div class="d">先取官方附件原文（.doc/.docx/PDF/扫描件 OCR），全文逐句扫描，输出「全文字数 × 关键词命中数」并落盘全文，结论可复算</div></div>
  <div class="item"><div class="t"><i>3</i>行业 / 竞品调研</div>
    <div class="d">官网、财报、标准、专利入口；先查实测可达矩阵再写抓取，不拿「打不开」当「没有」</div></div>
  <div class="item"><div class="t"><i>4</i>销售数据统计与勾稽</div>
    <div class="d">ERP → JSON → 自包含 HTML/Excel；金额现算，与源文件透视表/进度表交叉校验（差 0.00）</div></div>
  <div class="item"><div class="t"><i>5</i>竞品与市场情报</div>
    <div class="d">竞品官网/财报/注册证/招投标定时扫描；先查可达矩阵再写抓取，抓不到就标「未接入」，不拿「打不开」当「没有」</div></div>
  <div class="item"><div class="t"><i>6</i>对客话术支撑</div>
    <div class="d">把技术结论转成客户可用的证据卡：明确区分「法定要求」与「企业内控」，不让销售在客户现场把内控说成法规</div></div>
  <div class="item"><div class="t"><i>7</i>交付形态</div>
    <div class="d">自包含 HTML（零外链）/ PDF / Excel；附来源清单与缺口清单，任何人可复核</div></div>
  <div class="item"><div class="t"><i>8</i>知识沉淀与复用</div>
    <div class="d">结论存为事实卡 / 证据卡入知识库，工作台看板同步；下次同类问题直接调卡，不重复检索</div></div>
</div>
"""

COL_B = """
<div class="col">
  <div class="h2">二 · 来源 <span>数据从哪来（四层）</span></div>
  <div class="layer"><div class="lt">L1 宝锐一手数据 <em>优先</em></div>
    <div class="ld">ERP 订单原始导出 ｜ 数据层 <b>521 项</b> JSON（客户/价格/商机/客诉/活动/KPI）｜ 飞书活动·商机·客诉（MCP 实查）｜ 产品与公司资料 ｜ IMA 两个知识库</div></div>
  <div class="layer"><div class="lt">L2 本机已审证据语料 <em>先查再联网</em></div>
    <div class="ld">开放获取全文 <b>130 篇</b>（DOI 命名、可逐句检索）｜ 调研稿 <b>4 篇</b> ｜ 已审事实卡（数值三件套 + 使用边界）</div></div>
  <div class="layer"><div class="lt">L3 法规 / 标准 / 期刊原文 <em>唯一可作依据</em></div>
    <div class="ld">NMPA · CMDE 官方附件（.doc/.docx 直链）｜ 地方药监 ｜ YY/T 标准（扫描件 OCR）｜ 期刊 PDF ｜ WHO / EMA / WOAH / PMC ｜ 专利</div></div>
  <div class="layer"><div class="lt">L4 公开检索接口 <em>免 key</em></div>
    <div class="ld">Europe PMC（首选，带摘要/被引/PMCID）｜ OpenAlex（引用分位替代 JCR 分区、含预印本）｜ Crossref（核卷期）｜ PubMed ｜ Semantic Scholar（引文雪球）</div></div>
  <div class="layer" style="border-left-color:#b3261e;background:linear-gradient(90deg,#fdf5f4,transparent)">
    <div class="lt" style="color:#b3261e">不假装有的 <em style="background:#b3261e">边界</em></div>
    <div class="ld">Scopus / Exa / NotebookLM 未接入 ｜ JCR 分区抓不到（403）用 OpenAlex 分位 ｜ 付费墙只给缺口清单 ｜ 站方限流如实写覆盖率</div></div>
  <div class="layer" style="border-left-color:#C8963E;background:linear-gradient(90deg,#fffdf7,transparent)">
    <div class="lt" style="color:#8a5a12">检索纪律 <em style="background:#C8963E">两条</em></div>
    <div class="ld">① <b>先本地后联网</b>：本机语料（全文 130 篇 / 事实卡 / 数据 JSON）先查，查不到再检索外部；② <b>中文词先翻英文</b>：英文库对中文词会「有结果但全错」（实测返回完全无关文献），必先做术语映射</div></div>
  <div class="layer" style="border-left-color:#1c7c54;background:linear-gradient(90deg,#f5faf7,transparent)">
    <div class="lt" style="color:#1c7c54">口径纪律 <em style="background:#1c7c54">唯一权威源</em></div>
    <div class="ld">同一指标只认一个源：年度目标＝<b>目标基础数据_2026.json</b>；营收金额＝<b>ERP</b>；集团归属＝<b>CRM</b>。多处出现不一致时以权威源为准并回溯订正</div></div>
</div>
"""

MECHS = [
    ('原文主义', '结论只来自原文全量；搜索摘要、二手文章、镜像转载不得作依据'),
    ('量化留痕', '每份文件留「来源直链 + 全文字数 + 命中数 + 原句」，全文落盘'),
    ('假阴性三关', '编码（GB2312）· 解析（doc/docx/扫描件）· 覆盖（页数）逐关自检'),
    ('覆盖不足不下结论', '拿不全就写「6/11 页」+ 缺哪页 + 为什么缺，不把「没查到」说成「没有」'),
    ('多方互证', 'OCR 结论 × 标准简介/条款框架 × 等同采用关系（=ISO）；ERP 用源表透视表校验'),
    ('来源分级', '一手（原文/官方附件/期刊全文）＞ 二手（行业网/媒体）＞ 厂商口径（标不可作依据）'),
    ('数字现算', '金额/占比一律脚本现算、可勾稽；无口径的数写缺口，不写「约 X 万」'),
    ('纠错留痕', '错就公开写进交付物（本次 3 处），不悄悄改'),
    ('权限与合规', '客户数据只取授权源、不外发、不进外部 AI；无权客户不确认不否认'),
]
COL_C = '<div class="col"><div class="h2">三 · 可靠性 <span>怎么保证不瞎编</span></div>'
for i, (t, d) in enumerate(MECHS, 1):
    COL_C += '<div class="m"><b>%d</b><p><u>%s</u>　%s</p></div>' % (i, t, d)
COL_C += ('<div class="chk"><div class="ct2">交付前必过 4 道验收</div>'
          '<div class="ci">检索式 / 纳排 / 起止年写进报告，可复现</div>'
          '<div class="ci">每篇标 ✓已读全文 / ⚠仅摘要，全文覆盖数对得上</div>'
          '<div class="ci">引用校验通过：0 个未知 id、0 条空证据</div>'
          '<div class="ci">缺口清单逐条列清（标题 / DOI / 为什么非它不可）</div></div>')
COL_C += '</div>'

CASE = """
<div class="case">
  <div class="ct">实证 · 冻干产品「水分 均值≤2%% / 单瓶≤3%%」依据核查 <em>已结案</em></div>
  <div class="flow">
    <div class="step"><div class="v">%(nd)s 份</div><div class="k">法规/标准/综述<br>全部取原文</div><span class="arrow">›</span></div>
    <div class="step"><div class="v">%(wan).1f 万字</div><div class="k">全文逐句扫描<br>非摘要、非二手</div><span class="arrow">›</span></div>
    <div class="step"><div class="v">%(reg)s 处</div><div class="k">「水分」二字<br>出现次数（仅设备名/方法）</div><span class="arrow">›</span></div>
    <div class="step hl"><div class="v">3 处</div><div class="k">自查纠错<br>误引撤回·假零命中·编码</div><span class="arrow">›</span></div>
    <div class="step"><div class="v">1 条</div><div class="k">结论：法定体系无此条款<br>该数值属<b>企业内控</b></div></div>
  </div>
</div>
""" % {'nd': N_DOCS, 'wan': TOTAL_WAN, 'reg': REG_WATER}

QA = """
<div class="qa">
  <div class="qt">四 · 别人最常问的四句 <span>我们的回答</span></div>
  <div class="qgrid">
    <div class="qcard"><div class="q">数据是不是编的？</div>
      <div class="a">每条结论都挂在原文/原表上；没有来源的断言标 <b>[unverified]</b>，绝不用相近文献冒充。</div></div>
    <div class="qcard"><div class="q">为什么跟网上说法不一样？</div>
      <div class="a">网上多为二手转述。本次实测：某二手文章把「冻干复溶稳定性」记到另一份指导原则名下，核官方附件后<b>撤回该引用</b>。</div></div>
    <div class="qcard"><div class="q">我能自己复核吗？</div>
      <div class="a">原件、提取全文、<b>全文字数 × 命中数</b>、扫描日志全部留存，可按路径逐字比对。</div></div>
    <div class="qcard"><div class="q">查错了怎么办？</div>
      <div class="a">错就公开写进交付物。本次 <b>3 处自查纠错</b>：误引撤回、GB2312 假零命中、编码判定启发式。</div></div>
  </div>
</div>
"""

TOOLS = """
<div class="tools">
  <span class="tl">工具链</span><code>lit_search.py</code>检索/全文/雪球
  <span class="sep">｜</span><code>sources.py</code>引用校验
  <span class="sep">｜</span><code>epmc_probe.py</code>精确查
  <span class="sep">｜</span><code>scan_docs.py</code>全文扫描·字数×命中数
</div>
"""

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)s</style></head><body>
<div class="sheet">
  <div class="bior-header">
    <div class="lg"><img src="%(logo)s" alt="BIORI"></div>
    <div>
      <h1>数据来源与可靠性 · 一页纸简报</h1>
      <div class="sub">BIORI BIOTECH ｜ 宝锐生物 营销中心 ｜ 守护生命健康，缔造美好生活</div>
    </div>
    <div class="rt"><b>2026-10</b><br>内部说明 · 可复核<br>怎么取数 · 怎么不瞎编</div>
  </div>

  <div class="kpis">
    <div class="kpi"><div class="n">4 层</div><div class="l">数据来源分层</div></div>
    <div class="kpi"><div class="n">130 篇</div><div class="l">本地开放全文语料</div></div>
    <div class="kpi"><div class="n">521 项</div><div class="l">工作台数据 JSON</div></div>
    <div class="kpi"><div class="n">%(nd)s 份</div><div class="l">单次取证全文核验</div></div>
    <div class="kpi"><div class="n">0 条</div><div class="l">含 2%%/3%% 限度的条款</div></div>
  </div>

  <div class="main">%(a)s%(b)s%(c)s</div>

  %(qa)s
  %(tools)s

  %(case)s

  <div class="bior-footer">
    <div class="bior-footer-slogan">%(slogan)s</div>
    <div class="bior-footer-extra">复核路径：原件 + 提取全文 + 扫描日志全部留存</div>
    <div class="bior-footer-meta">%(en)s · %(cn)s ｜ 电话 %(tel)s ｜ %(site)s ｜ %(mail)s ｜ %(addr)s</div>
  </div>
</div>
</body></html>"""

page = HTML % {
    'css': CSS, 'logo': LOGO, 'a': COL_A, 'b': COL_B, 'c': COL_C, 'case': CASE,
    'qa': QA, 'tools': TOOLS,
    'slogan': BK.SLOGAN, 'en': BK.BRAND_EN, 'cn': BK.BRAND_CN, 'tel': BK.TEL,
    'site': BK.SITE, 'mail': BK.MAIL, 'addr': BK.ADDR, 'nd': N_DOCS,
}
assert '__LOGO__' not in page
v = BK.verify(page)
bad = [k for k, ok in v.items() if not ok]
assert not bad, bad
io.open(OUT, 'w', encoding='utf-8').write(page)
print('✅ %s（%.0f KB）' % (OUT, os.path.getsize(OUT) / 1024))
print('   文件 %d 份 ｜ 全文字数 %d（%.1f 万）｜ 法规中「水分」命中 %d 处' % (N_DOCS, TOTAL_CHARS, TOTAL_WAN, REG_WATER))
print('   品牌自检: %s' % ' '.join('%s✓' % k for k, ok in v.items() if ok))
