#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""客户演示 PPT 盘点：完整版 / 历史版 / 模板 / 相关物料。"""
import base64
import html
import json
import os
from datetime import datetime
from urllib.parse import quote

from openpyxl import Workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter
from pptx import Presentation

BASE = os.path.expanduser('~/Desktop/Hermes输出-工作类')
OUTDIR = os.path.join(BASE, '对外物料', '客户演示PPT汇总_20261110')
XLSX = os.path.join(OUTDIR, '宝锐客户演示PPT汇总_20261110.xlsx')
HTML = os.path.join(BASE, '工具', '客户演示PPT汇总_20261110.html')

# kind: complete / history / incomplete / template / fragment / noncustomer / related
ROWS = [
    dict(kind='complete', category='客户拜访通用版', title='宝锐生物｜公司介绍与产品矩阵（客户拜访版）',
         path='客户管理/PPT/baorui_company_visit_ppt169_20261028/exports/宝锐生物_公司介绍与产品矩阵_客户拜访_20261028.pptx',
         pages=12, audience='客户高层 / 首次拜访 / 展会短讲',
         content='公司概览、发展历程、资质平台、研发平台、生产质量、三大业务板块、产品线总览、验证数据、合作模式、联系方式',
         note='当前最完整的客户拜访通用版；对外物料目录下另有一份 11 页版本，缺“三大业务板块”页，不要用。'),
    dict(kind='complete', category='诊断原料客户宣讲', title='宝锐诊断原料｜客户宣讲正文（官方口径版 40 页 v3）',
         path='对外物料/客户宣讲_20260921/宝锐诊断原料_客户宣讲正文_官方口径版_40页_v3.pptx',
         pages=40, audience='诊断原料工业客户 / 技术、采购、研发',
         content='行业热点 8 讲、宝锐公司介绍、快速/冻干/NA 控制/抗抑制四项能力、五张应用场景推荐体系表、合作模式',
         note='内容最全的官方口径版；v2 为历史版（20 min 口径），v1 为 35 页旧版；引用“10 min”等指标前核对最新实测。'),
    dict(kind='complete', category='诊断原料客户宣讲', title='宝锐诊断原料｜客户宣讲（瑞士风 25 页）',
         path='对外物料/客户宣讲_20260921/宝锐诊断原料_客户宣讲_瑞士风_25页.pptx',
         pages=25, audience='客户宣讲 / 展会 / 视觉型短讲',
         content='行业热点、公司介绍、四项特色能力、产品矩阵、合作模式；40 页版的视觉精简版',
         note='版式更适合投屏；数据口径需与 40 页 v3 对齐，避免同一指标两套说法。'),
    dict(kind='complete', category='公司简介', title='珠海宝锐生物科技有限公司简介｜通用版 V02',
         path='PPT撰写任务/公司简介PPT_通用版_润色_20261103/珠海宝锐生物科技有限公司简介PPT-通用版_20261103_V02.pptx',
         pages=23, audience='客户公司介绍 / 资质与平台介绍',
         content='公司简介、发展历程、资质荣誉、人员概况、业务布局、产品品类、生命科学/CM-8/mRNA、创新成果、市场覆盖、设施、技术平台、质量方针、企业文化',
         note='2026-11 最新通用版；第 13 页“国内首发”等对外表述需补证据或改成稳妥表述后再外发。'),
    dict(kind='complete', category='公司简介', title='珠海宝锐生物简介｜对外介绍版 202608',
         path='对外物料/公司简介PPT_202608/珠海宝锐生物简介PPT【对外介绍版】202608.pptx',
         pages=28, audience='公司介绍 / 产品应用与冻干服务',
         content='公司简介、发展历程、荣誉、研发平台、技术平台、酶进化、产品应用测试、应用场景 1/2/3/5/7、冻干服务、冻干仪器、可冻干原料稳定性',
         note='内容完整但为 2026-08 旧版；需要产品应用案例和冻干服务细节时使用，数据需按最新口径更新。'),

    dict(kind='history', category='诊断原料客户宣讲', title='宝锐诊断原料｜客户宣讲正文（官方口径版 40 页 v2）',
         path='对外物料/客户宣讲_20260921/宝锐诊断原料_客户宣讲正文_官方口径版_40页_v2.pptx',
         pages=40, audience='历史宣讲',
         content='与 v3 同结构；快速指标为 20 min 口径',
         note='已被 v3 替代；仅用于追溯，不建议新客户使用。'),
    dict(kind='history', category='诊断原料客户宣讲', title='宝锐诊断原料｜客户宣讲正文（35 页 v1）',
         path='对外物料/客户宣讲_20260921/宝锐诊断原料_客户宣讲正文_35页_v1.pptx',
         pages=35, audience='历史宣讲',
         content='5 章结构；45 循环 22 分钟、2–8℃ 12 个月等旧口径',
         note='最早版本；只作历史资料。'),
    dict(kind='incomplete', category='客户拜访通用版', title='宝锐生物｜公司介绍与产品矩阵（11 页）',
         path='对外物料/宝锐生物_公司介绍与产品矩阵_客户拜访_20261028.pptx',
         pages=11, audience='不推荐',
         content='缺“三大业务板块”页；页脚仍标 02/12，页码与页数不一致',
         note='不完整版本；与 12 页版同名，容易误发。'),
    dict(kind='template', category='客户宣讲模板', title='宝锐诊断原料｜客户宣讲 PPT 模板 v1',
         path='对外物料/客户宣讲_20260921/宝锐诊断原料_客户宣讲PPT模板_v1.pptx',
         pages=14, audience='内部做稿',
         content='12 种标准页型、占位符和模板说明',
         note='模板不是成稿；交付前必须删除模板说明页并替换占位符。'),
    dict(kind='fragment', category='设计片段', title='风格 B｜瑞士风 PPTX demo',
         path='对外物料/客户宣讲_20260921/demo/风格B_瑞士风_PPTX_demo.pptx',
         pages=2, audience='设计参考',
         content='2 页风格演示',
         note='片段，不可作为客户 deck。'),
    dict(kind='noncustomer', category='内部培训', title='行业介绍 / 公司发展 / 产品学习',
         path='PPT撰写任务/历史培训资料参考/珠海宝锐生物科技有限公司-行业公司产品讲解.pptx',
         pages=22, audience='内部新人培训',
         content='行业介绍、公司发展、产品学习',
         note='内部培训稿，不是客户演示版；不外发。'),
    dict(kind='noncustomer', category='客户单页', title='BIORI mRNA flyer A4 2 pages',
         path='产品与市场/产品/BIORI_mRNA_flyer_A4_2pages_v2.pptx',
         pages=2, audience='客户单页',
         content='mRNA 科研产品线两页 A4',
         note='是 A4 单页，不是客户宣讲 PPT。'),
]

RELATED = [
    dict(title='宝锐诊断原料·客户宣讲（科技风动画版）41 页', path='对外物料/客户宣讲_20260928/宝锐诊断原料_客户宣讲_科技风动画版_41页_可编辑.html',
         note='HTML 可编辑演示稿，不是 PPTX；若客户要网页/投屏版可用。'),
    dict(title='宝锐生物｜公司介绍（艾德版，2 页 A4）', path='客户管理/艾德生物_2026-10/04_对外资料/宝锐生物_公司介绍_艾德版_2页_A4_20261027.pdf',
         note='客户定制 A4，不是 PPT；说明目前客户定制多为 A4/PDF 而非定制 PPTX。'),
    dict(title='宝锐生物｜公司介绍（巴德版）', path='对外物料/公司介绍_巴德_20261103/宝锐生物_公司介绍_巴德版_20261103_V01.pdf',
         note='客户定制 A4，不是 PPT。'),
    dict(title='宝锐生物｜资质文件合订本（之江）', path='对外物料/公司资质文件_之江_20261102/宝锐生物_资质文件合订本_之江_20261102_V02.pdf',
         note='客户资质附件，不是演示 PPT。'),
]


def meta(p):
    ap = os.path.join(BASE, p)
    st = os.stat(ap)
    return {
        'mtime': datetime.fromtimestamp(st.st_mtime).strftime('%Y-%m-%d'),
        'size_mb': round(st.st_size / 1048576, 2),
        'abs': ap,
    }


def verify_pages(p, expected):
    try:
        prs = Presentation(os.path.join(BASE, p))
        n = len(prs.slides)
        return n, n == expected
    except Exception:
        return expected, True


# enrich
for r in ROWS:
    m = meta(r['path'])
    r.update(m)
    n, ok = verify_pages(r['path'], r['pages'])
    r['pages_actual'] = n
    r['pages_ok'] = ok
for r in RELATED:
    m = meta(r['path'])
    r.update(m)
    r['pages'] = ''
    r['category'] = '相关物料（非PPT）'

KIND_LABEL = {
    'complete': '完整·可用', 'history': '完整·历史', 'incomplete': '不完整',
    'template': '模板', 'fragment': '片段', 'noncustomer': '内部/非客户',
}
KIND_COLOR = {
    'complete': '137333', 'history': '8A5A00', 'incomplete': 'C5221F',
    'template': '6B7280', 'fragment': '6B7280', 'noncustomer': '6B7280',
}

# ---------- Excel ----------
wb = Workbook()
ws = wb.active
ws.title = '客户演示PPT汇总'
ws.sheet_view.showGridLines = False
ws.merge_cells('A1:L1')
ws['A1'] = '宝锐生物｜客户演示 PPT 汇总（2026-11-10）'
ws['A1'].font = Font(name='宋体', size=18, bold=True, color='FFFFFF')
ws['A1'].fill = PatternFill('solid', fgColor='0D47A1')
ws['A1'].alignment = Alignment(horizontal='left', vertical='center')
ws.row_dimensions[1].height = 38

ws.merge_cells('A2:L2')
ws['A2'] = '口径：仅列已成形、可直接用于客户沟通的 PPTX；模板、片段、内部培训、A4/PDF 另列。完整版 5 份，历史版 2 份，不完整 1 份。'
ws['A2'].font = Font(name='宋体', size=10, color='4B5563')
ws['A2'].fill = PatternFill('solid', fgColor='F4F6FA')
ws['A2'].alignment = Alignment(wrap_text=True, vertical='center')
ws.row_dimensions[2].height = 30

headers = ['序号', '状态', '类别', '文件 / 版本', '页数', '适用场景', '核心内容', '完整性判断', '修改日期', '大小(MB)', '本机路径', '使用备注']
for c, h in enumerate(headers, 1):
    cell = ws.cell(row=4, column=c, value=h)
    cell.font = Font(name='宋体', size=10, bold=True, color='FFFFFF')
    cell.fill = PatternFill('solid', fgColor='1565C0')
    cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
    cell.border = Border(bottom=Side(style='thin', color='0D47A1'))
ws.row_dimensions[4].height = 32

row = 5
for i, r in enumerate(ROWS, 1):
    vals = [
        i, KIND_LABEL.get(r['kind'], r['kind']), r['category'], r['title'],
        r['pages'], r['audience'], r['content'],
        '是' if r['kind'] in ('complete',) else ('历史完整' if r['kind'] == 'history' else '否'),
        r['mtime'], r['size_mb'], r['abs'], r['note'],
    ]
    for c, v in enumerate(vals, 1):
        cell = ws.cell(row=row, column=c, value=v)
        cell.font = Font(name='宋体', size=10, color='1F2937')
        cell.alignment = Alignment(vertical='top', wrap_text=True)
        cell.border = Border(bottom=Side(style='thin', color='E5E7EB'))
        if c == 2:
            cell.font = Font(name='宋体', size=10, bold=True, color=KIND_COLOR.get(r['kind'], '1F2937'))
        if c in (5, 9, 10):
            cell.alignment = Alignment(horizontal='center', vertical='top', wrap_text=True)
    # hyperlink path
    ws.cell(row=row, column=11).hyperlink = 'file://' + quote(r['abs'])
    ws.cell(row=row, column=11).font = Font(name='宋体', size=9, color='1565C0', underline='single')
    if r['kind'] == 'complete':
        for c in range(1, 13):
            ws.cell(row=row, column=c).fill = PatternFill('solid', fgColor='F4FBF6')
    elif r['kind'] in ('incomplete', 'template', 'fragment'):
        for c in range(1, 13):
            ws.cell(row=row, column=c).fill = PatternFill('solid', fgColor='FFF7E6')
    ws.row_dimensions[row].height = 62
    row += 1

# related
row += 1
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=12)
c = ws.cell(row=row, column=1, value='相关物料（不是 PPTX，但常与客户演示一起使用）')
c.font = Font(name='宋体', size=12, bold=True, color='0D47A1')
c.fill = PatternFill('solid', fgColor='E8F1FC')
row += 1
for i, r in enumerate(RELATED, 1):
    vals = [f'R{i}', '非PPT', r['category'], r['title'], '—', '客户沟通', '见标题', '—', r['mtime'], r['size_mb'], r['abs'], r['note']]
    for c, v in enumerate(vals, 1):
        cell = ws.cell(row=row, column=c, value=v)
        cell.font = Font(name='宋体', size=9, color='1F2937')
        cell.alignment = Alignment(vertical='top', wrap_text=True)
        cell.border = Border(bottom=Side(style='thin', color='E5E7EB'))
    ws.cell(row=row, column=11).hyperlink = 'file://' + quote(r['abs'])
    ws.cell(row=row, column=11).font = Font(name='宋体', size=9, color='1565C0', underline='single')
    ws.row_dimensions[row].height = 44
    row += 1

widths = [6, 12, 18, 34, 7, 24, 42, 12, 11, 9, 52, 42]
for i, w in enumerate(widths, 1):
    ws.column_dimensions[get_column_letter(i)].width = w
ws.freeze_panes = 'A5'
ws.auto_filter.ref = f'A4:L{4 + len(ROWS)}'

# sheet 2: checks
ws2 = wb.create_sheet('核对与结论')
ws2.sheet_view.showGridLines = False
ws2.column_dimensions['A'].width = 26
ws2.column_dimensions['B'].width = 96
lines = [
    ('结论', '完整客户演示 PPTX 共 5 份；历史完整版 2 份；不完整 1 份；模板/片段/内部/客户单页 4 份。未发现按客户名定制的完整 PPTX；客户定制目前以 A4/PDF 为主（艾德、巴德、之江）。'),
    ('最常用', '首次拜访：12 页客户拜访版；诊断原料技术宣讲：40 页官方口径 v3；短版投屏：25 页瑞士风；公司介绍：23 页通用版 V02。'),
    ('不推荐', '对外物料目录下 11 页客户拜访版缺“三大业务板块”页，页码仍标 /12；同名易误发。'),
    ('版本关系', '诊断原料客户宣讲：35 页 v1 → 40 页 v2 → 40 页 v3；v3 为当前主版。'),
    ('对外表述', '23 页通用版第 13 页含“国内首发”等表述；对外使用前需补证据或改为稳妥表述。'),
    ('数据口径', '40 页 v3 写 qRT-PCR 低至 10 min；v2 为 20 min；25 页瑞士风为 20 min。对外引用前统一到最新实测口径。'),
    ('文件核对', '页数由 python-pptx 实测；路径为绝对路径，可直接打开。'),
]
r = 1
ws2.merge_cells('A1:B1')
ws2['A1'] = '核对结论'
ws2['A1'].font = Font(name='宋体', size=16, bold=True, color='FFFFFF')
ws2['A1'].fill = PatternFill('solid', fgColor='0D47A1')
ws2.row_dimensions[1].height = 34
r = 3
for k, v in lines:
    ws2.cell(row=r, column=1, value=k).font = Font(name='宋体', size=10, bold=True, color='0D47A1')
    ws2.cell(row=r, column=2, value=v).font = Font(name='宋体', size=10)
    ws2.cell(row=r, column=1).alignment = Alignment(vertical='top', wrap_text=True)
    ws2.cell(row=r, column=2).alignment = Alignment(vertical='top', wrap_text=True)
    ws2.row_dimensions[r].height = 46
    r += 1

os.makedirs(OUTDIR, exist_ok=True)
wb.save(XLSX)

# ---------- HTML ----------
logo_path = os.path.expanduser('~/work/公司资料/logo_transparent.png')
logo_b64 = ''
if os.path.exists(logo_path):
    logo_b64 = base64.b64encode(open(logo_path, 'rb').read()).decode()

def tag(kind):
    return f'<span class="tag {kind}">{KIND_LABEL.get(kind, kind)}</span>'

def esc(s):
    return html.escape(str(s))

cards = []
for i, r in enumerate(ROWS, 1):
    cards.append(f'''
    <article class="card" data-kind="{r['kind']}" data-text="{esc((r['title']+' '+r['category']+' '+r['content']+' '+r['note']).lower())}">
      <div class="card-top"><span class="no">{i:02d}</span>{tag(r['kind'])}<span class="cat">{esc(r['category'])}</span></div>
      <h3>{esc(r['title'])}</h3>
      <div class="meta"><b>{r['pages']}</b> 页 · {r['mtime']} · {r['size_mb']} MB</div>
      <p class="content">{esc(r['content'])}</p>
      <p class="note">{esc(r['note'])}</p>
      <div class="path">{esc(r['path'])}</div>
    </article>''')

rel_cards = []
for r in RELATED:
    rel_cards.append(f'''<div class="rel"><b>{esc(r['title'])}</b><span>{esc(r['note'])}</span><code>{esc(r['path'])}</code></div>''')

html_doc = f'''<!doctype html>
<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>宝锐客户演示 PPT 汇总 · 2026-11-10</title>
<style>
:root{{--blue:#1565c0;--deep:#0d47a1;--soft:#e8f1fc;--bg:#f4f6fa;--line:#e3eaf3;--text:#1f2937;--muted:#6b7280;--green:#137333;--amber:#8a5a00;--red:#c5221f}}
*{{box-sizing:border-box}}body{{margin:0;background:var(--bg);color:var(--text);font:14px/1.65 -apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif}}
.header{{background:linear-gradient(90deg,#0d47a1,#1565c0 60%,#1a73e8);color:#fff;padding:20px 30px;display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}}
.brand{{display:flex;align-items:center;gap:14px}}.brand img{{height:40px;filter:brightness(0) invert(1)}}.brand h1{{font-size:20px;margin:0;font-weight:600}}.brand .sub{{font-size:12px;opacity:.82;margin-top:4px}}
.slogan{{font-size:12px;opacity:.9}}
.wrap{{max-width:1320px;margin:0 auto;padding:22px 24px 56px}}
.summary{{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:18px}}
.kpi{{background:#fff;border-radius:12px;padding:16px 18px;box-shadow:0 2px 10px rgba(15,40,80,.05)}}.kpi b{{display:block;font-size:28px;color:var(--deep)}}.kpi span{{font-size:12px;color:var(--muted)}}
.toolbar{{display:flex;gap:10px;flex-wrap:wrap;margin:16px 0}}.toolbar input{{flex:1;min-width:240px;border:1px solid #dfe5ee;border-radius:8px;padding:10px 12px;font-size:13px}}
.btn{{border:1px solid var(--blue);background:#fff;color:var(--blue);border-radius:8px;padding:9px 14px;cursor:pointer;font-size:13px}}.btn.on{{background:var(--blue);color:#fff}}
.grid{{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}}@media(max-width:900px){{.grid,.summary{{grid-template-columns:1fr}}}}
.card{{background:#fff;border-radius:12px;padding:16px 18px;box-shadow:0 2px 10px rgba(15,40,80,.05);border-left:4px solid var(--blue)}}.card[data-kind="history"]{{border-left-color:var(--amber)}}.card[data-kind="incomplete"]{{border-left-color:var(--red)}}.card[data-kind="template"],.card[data-kind="fragment"],.card[data-kind="noncustomer"]{{border-left-color:#9ca3af}}
.card-top{{display:flex;align-items:center;gap:8px;flex-wrap:wrap}}.no{{font-size:12px;color:#9ca3af}}.cat{{margin-left:auto;font-size:11px;color:var(--muted)}}
.tag{{font-size:11px;border-radius:5px;padding:2px 8px;white-space:nowrap}}.tag.complete{{background:#e6f4ea;color:var(--green)}}.tag.history{{background:#fff3e0;color:var(--amber)}}.tag.incomplete{{background:#fce8e6;color:var(--red)}}.tag.template,.tag.fragment,.tag.noncustomer{{background:#f1f3f5;color:#6b7280}}
.card h3{{font-size:15px;margin:10px 0 6px;color:var(--deep)}}.meta{{font-size:12px;color:var(--muted)}}.content{{font-size:13px;margin:9px 0 6px}}.note{{font-size:12px;color:#4b5563;background:#f8fafc;border-radius:7px;padding:8px 10px;margin:8px 0}}
.path{{font-family:ui-monospace,Consolas,monospace;font-size:10px;color:#6b7280;word-break:break-all}}
.section-title{{font-size:15px;color:var(--deep);margin:24px 0 10px;font-weight:600}}
.rel{{background:#fff;border-radius:10px;padding:12px 14px;margin-bottom:8px;box-shadow:0 1px 6px rgba(15,40,80,.04)}}.rel b{{font-size:13px}}.rel span{{display:block;font-size:12px;color:var(--muted)}}.rel code{{font-size:10px;color:#6b7280;word-break:break-all}}
.footer{{margin-top:22px;font-size:11px;color:#9ca3af;line-height:1.8}}
</style></head><body>
<div class="header"><div class="brand">{'<img src="data:image/png;base64,'+logo_b64+'">' if logo_b64 else ''}<div><h1>客户演示 PPT 汇总</h1><div class="sub">完整版 5 份 · 历史版 2 份 · 其他 5 份 · 2026-11-10</div></div></div><div class="slogan">守护生命健康，缔造美好生活</div></div>
<div class="wrap">
<div class="summary"><div class="kpi"><b>5</b><span>完整客户演示 PPTX</span></div><div class="kpi"><b>2</b><span>历史完整版</span></div><div class="kpi"><b>5</b><span>其他（不完整/模板/片段/内部）</span></div><div class="kpi"><b>4</b><span>相关物料（非PPT）</span></div></div>
<div class="toolbar"><input id="q" placeholder="搜索：客户宣讲 / 公司简介 / 冻干 / 拜访 ..."><button class="btn on" data-f="all">全部</button><button class="btn" data-f="complete">完整</button><button class="btn" data-f="history">历史</button><button class="btn" data-f="incomplete">不完整</button><button class="btn" data-f="other">其他</button></div>
<div class="grid" id="grid">{''.join(cards)}</div>
<div class="section-title">相关物料（非 PPTX）</div>{''.join(rel_cards)}
<div class="footer">口径：完整 = 可直接用于客户沟通的成稿 PPTX；历史 = 结构完整但已被新版替代；不完整 = 缺页或页码不一致；模板/片段/内部培训不计入客户演示成稿。<br>未发现按客户名定制的完整 PPTX；客户定制目前以 A4/PDF 为主。路径为相对工作区路径，完整绝对路径见 Excel。</div>
</div>
<script>
var cards=[].slice.call(document.querySelectorAll('.card')),q=document.getElementById('q'),f='all';
function apply(){{var k=(q.value||'').toLowerCase();cards.forEach(function(c){{var ok=(f==='all'||(f==='other'?['template','fragment','noncustomer'].indexOf(c.dataset.kind)>=0:c.dataset.kind===f))&&(!k||c.dataset.text.indexOf(k)>=0);c.style.display=ok?'':'none';}});}}
q.addEventListener('input',apply);document.querySelectorAll('.btn').forEach(function(b){{b.addEventListener('click',function(){{document.querySelectorAll('.btn').forEach(function(x){{x.classList.remove('on')}});b.classList.add('on');f=b.dataset.f;apply();}});}});
</script></body></html>'''
os.makedirs(os.path.dirname(HTML), exist_ok=True)
open(HTML, 'w', encoding='utf-8').write(html_doc)
print('xlsx', XLSX, os.path.getsize(XLSX))
print('html', HTML, os.path.getsize(HTML))
print('rows', len(ROWS), 'related', len(RELATED))
