#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""宝锐生物 · 公司介绍（诊断原料销售新人培训）V03
- 公司 2026 PPT 模板底图
- 市场章节扩到 12 页
- 删掉原 P18 的三问页，补正式结尾
"""
from pathlib import Path
import sys

BASE = Path('/Users/liuxinyuan/Desktop/Hermes输出-工作类')
sys.path.insert(0, str(BASE / '脚本'))
import build_company_intro_training_20261102 as B
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR

OUTDIR = BASE / 'PPT撰写任务/诊断原料销售新人培训_20261102/04_公司介绍与产品解决方案'
OUT = OUTDIR / '宝锐生物_公司介绍_诊断原料新人培训_20261103_V04.pptx'
TPL_COVER = Path('/tmp/tpl_imgs/s1_1.png')
TPL_BG = Path('/tmp/tpl_imgs/s2_1.png')
LOGO = B.LOGO
LOGO_WHITE = Path('/Users/liuxinyuan/work/公司资料/logo_white_transparent.png')

# colors from B
NAVY, BLUE, TEAL, LIGHT, PALE, INK, GREY, MID, WHITE, ORANGE = (
    B.NAVY, B.BLUE, B.TEAL, B.LIGHT, B.PALE, B.INK, B.GREY, B.MID, B.WHITE, B.ORANGE)


def bg(slide, img, prs):
    slide.shapes.add_picture(str(img), 0, 0, width=prs.slide_width, height=prs.slide_height)


def cover(prs):
    s = B.new_slide(prs)
    bg(s, TPL_COVER, prs)
    # 深色封面：白色透明 Logo；版式按用户 2026-11-03 确认的模板
    s.shapes.add_picture(str(LOGO_WHITE), Inches(0.62), Inches(0.38), width=Inches(1.75))
    B.text(s, 2.30, 2.30, 8.75, 0.80, '宝锐生物', 48, True, WHITE, PP_ALIGN.CENTER)
    B.text(s, 2.30, 3.08, 8.75, 0.80, '公司介绍', 48, True, WHITE, PP_ALIGN.CENTER)
    B.text(s, 2.30, 4.05, 8.75, 0.40, '2026版', 22, False, WHITE, PP_ALIGN.CENTER)
    B.text(s, 7.10, 6.72, 5.60, 0.30, '守护生命健康 · 缔造美好生活', 12, False, WHITE, PP_ALIGN.RIGHT)


def content(prs, section, page):
    s = B.new_slide(prs)
    bg(s, TPL_BG, prs)
    # 页眉/页脚按 26 页版本重写，避免继承 V02 的 /18 页码
    B.rect(s, 0, 0, 13.333, 0.07, NAVY)
    s.shapes.add_picture(str(LOGO), Inches(0.55), Inches(0.22), width=Inches(1.25))
    B.text(s, 9.55, 0.24, 3.2, 0.3, section, 8, False, GREY, PP_ALIGN.RIGHT)
    B.line(s, 0.65, 7.08, 12.68, 7.08, B.MID, 0.7)
    B.text(s, 0.65, 7.16, 7.0, 0.2, '宝锐生物 ｜ 专注原料开发 · 提供优质服务', 7.5, False, GREY)
    B.text(s, 11.7, 7.16, 1.0, 0.2, f'{page:02d} / 26', 8, False, GREY, PP_ALIGN.RIGHT)
    return s


def title(s, kicker, t, sub=None):
    B.text(s, 0.65, 0.62, 11.8, 0.3, kicker, 9, True, TEAL)
    B.text(s, 0.65, 0.92, 11.8, 0.55, t, 24, True, NAVY)
    B.line(s, 0.65, 1.55, 12.68, 1.55, TEAL, 1.4)
    if sub:
        B.text(s, 0.65, 1.68, 11.7, 0.32, sub, 10.5, False, GREY)


def source(s, txt):
    B.text(s, 0.65, 6.8, 12.0, 0.2, txt, 6.6, False, MID)


def card(s, x, y, w, h, head, body, value=None, color=TEAL):
    B.rect(s, x, y, w, h, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF) if hasattr(B, 'RGBColor') else None, True)
    B.rect(s, x, y, 0.07, h, color)
    if value:
        B.text(s, x + 0.22, y + 0.12, w - 0.4, 0.5, value, 24, True, color)
        B.text(s, x + 0.22, y + 0.68, w - 0.4, 0.3, head, 10, True, NAVY)
        B.text(s, x + 0.22, y + 1.0, w - 0.4, h - 1.1, body, 9, False, GREY, line=1.35)
    else:
        B.text(s, x + 0.22, y + 0.14, w - 0.4, 0.35, head, 12.5, True, NAVY)
        B.text(s, x + 0.22, y + 0.62, w - 0.4, h - 0.72, body, 9.5, False, GREY, line=1.4)


def vbars(s, cats, vals, x, y, w, h, color=TEAL, maxv=None):
    maxv = maxv or max(vals) * 1.18
    base = y + h
    B.line(s, x, base, x + w, base, B.RGBColor(0xB9, 0xD4, 0xE3) if hasattr(B, 'RGBColor') else MID, 0.8)
    n = len(vals)
    gap = w / n
    bw = min(0.7, gap * 0.45)
    for i, (c, v) in enumerate(zip(cats, vals)):
        cx = x + gap * (i + 0.5)
        bh = h * v / maxv
        B.rect(s, cx - bw / 2, base - bh, bw, bh, color)
        B.text(s, cx - 0.42, base - bh - 0.26, 0.84, 0.22, f'{v:,}', 8.5, True, NAVY, PP_ALIGN.CENTER)
        B.text(s, cx - 0.6, base + 0.08, 1.2, 0.25, c, 8.5, False, GREY, PP_ALIGN.CENTER)


def grouped(s, cats, series, x, y, w, h, colors):
    maxv = max(v for _, vals in series for v in vals) * 1.2
    base = y + h
    B.line(s, x, base, x + w, base, B.RGBColor(0xB9, 0xD4, 0xE3) if hasattr(B, 'RGBColor') else MID, 0.8)
    n = len(cats)
    gap = w / n
    bw = min(0.45, gap * 0.22)
    for i, cat in enumerate(cats):
        cx = x + gap * (i + 0.5)
        for j, (name, vals) in enumerate(series):
            bh = h * vals[i] / maxv
            bx = cx - bw + j * bw
            B.rect(s, bx, base - bh, bw, bh, colors[j])
            B.text(s, bx - 0.25, base - bh - 0.23, bw + 0.5, 0.2, f'{vals[i]:,}', 7.8, True, NAVY, PP_ALIGN.CENTER)
        B.text(s, cx - 0.7, base + 0.08, 1.4, 0.25, cat, 8.5, False, GREY, PP_ALIGN.CENTER)
    for j, (name, _) in enumerate(series):
        B.rect(s, x + j * 1.5, y - 0.32, 0.12, 0.12, colors[j])
        B.text(s, x + j * 1.5 + 0.18, y - 0.36, 1.2, 0.22, name, 7.8, False, GREY)


def hbars(s, rows, x, y, w, h):
    maxv = max(v for _, v in rows)
    for i, (name, v) in enumerate(rows):
        yy = y + i * h
        B.text(s, x, yy, 1.3, 0.28, name, 8.8, False, INK)
        bw = (w - 2.2) * v / maxv
        B.rect(s, x + 1.35, yy + 0.03, max(bw, 0.05), 0.24, TEAL if i == 0 else BLUE)
        B.text(s, x + 1.35 + max(bw, 0.05) + 0.1, yy, 0.9, 0.25, f'{v:.1f}%', 8.2, True, NAVY)


def simple_table(s, rows, x, y, w, h, widths, fs=8.8):
    tbl = B.add_table(s, rows, x, y, w, h, widths, fs, True)
    for r in tbl.rows:
        r.height = Inches(h / len(rows))
    return tbl


def company_section(prs):
    # P03
    s = content(prs, '01 / 公司背景', 3)
    title(s, '01 / 公司背景', '2012 年，公司在珠海成立')
    B.text(s, 0.65, 1.9, 1.5, 0.8, '2012', 44, True, TEAL)
    B.text(s, 0.65, 2.85, 1.8, 0.3, '公司成立', 10, False, GREY)
    B.text(s, 2.0, 1.95, 4.5, 1.2, '珠海宝锐生物科技有限公司成立。\n先做核酸诊断用酶和预混液。', 11.5, False, INK, line=1.55)
    milestones = [('2017', '国家高新技术企业'), ('2018', 'ISO 9001 体系认证'),
                  ('2020', '南屏新厂投产；新冠核酸原料覆盖数十家企业'),
                  ('2021', '广东省专精特新；研发生产中心扩建'), ('2022', 'mRNA 原料酶上市')]
    B.line(s, 2.1, 4.35, 12.0, 4.35, B.RGBColor(0xC9, 0xDE, 0xE9), 1.1)
    for i, (yr, lab) in enumerate(milestones):
        x = 2.1 + i * 2.25
        B.rect(s, x - 0.07, 4.28, 0.14, 0.14, TEAL, None, True)
        B.text(s, x - 0.12, 3.75, 0.75, 0.25, yr, 12, True, NAVY)
        B.text(s, x - 0.12, 4.55, 2.0, 0.8, lab, 8.8, False, GREY, line=1.3)
    B.text(s, 0.65, 5.65, 4.6, 0.6, '2012 年成立时，公司先做核酸诊断用酶和预混液；后来才扩展到三条业务线。', 10.5, False, BLUE, line=1.45)
    source(s, '来源：公司资料、公开报道；时间线按公司历史资料整理。')

    # P04
    s = content(prs, '02 / 业务布局', 4)
    title(s, '02 / 业务布局', '三条业务线，共用一套酶工程平台')
    rows = [['业务线', '主要产品', '服务客户'],
            ['诊断原料', '分子酶、qPCR/RT-qPCR/LAMP 预混、核酸提取、冻干保护剂', 'IVD、POCT、病原检测'],
            ['生命科学', '工具酶、分子试剂、核酸提取纯化、测序试剂、蛋白研究', '高校、科研院所、CRO'],
            ['生物医药', 'mRNA 原料酶、加帽酶、DNase I、RNase H II、生物药 QC', 'mRNA 药物、生物药']]
    simple_table(s, rows, 0.65, 1.85, 12.0, 3.2, [1.55, 7.4, 3.0], 10.5)
    B.rect(s, 0.65, 5.45, 12.0, 0.65, LIGHT, None, True)
    B.text(s, 0.9, 5.6, 11.5, 0.3, '共用底座：蛋白进化 · 重组表达 · 抗体/适配体 · 工艺开发 · 质量体系', 10, True, NAVY)
    B.text(s, 0.65, 6.3, 11.8, 0.25, '三条线共用同一套酶工程平台。', 9.5, False, GREY)

    # P05
    s = content(prs, '02 / 技术平台', 5)
    title(s, '02 / 技术平台', '公司自己完成酶分子的发现、改造和量产')
    cards = [('蛋白进化', 'MTPS 和 MHTS 筛选\n提高活性和稳定性'), ('重组表达', '工程菌株、三级种子库\n高密度发酵、精细纯化'),
             ('抗体开发', '多克隆/单克隆抗体\n免疫原设计与筛选'), ('适配体筛选', 'SELEX 筛选\n化学/配基修饰'),
             ('工艺开发', '配方、冻干、放大\n批间一致性'), ('应用研究', 'qPCR、LAMP、直扩\n客户体系验证')]
    for i, (h, bdy) in enumerate(cards):
        x = 0.65 + (i % 3) * 4.12
        y = 1.95 + (i // 3) * 2.1
        B.rect(s, x, y, 3.85, 1.75, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
        B.rect(s, x, y, 0.08, 1.75, TEAL)
        B.text(s, x + 0.25, y + 0.18, 3.3, 0.3, h, 13, True, NAVY)
        B.text(s, x + 0.25, y + 0.65, 3.3, 0.8, bdy, 9, False, GREY, line=1.35)
    B.text(s, 0.65, 6.35, 11.8, 0.25, '客户拿到的是酶、缓冲和冻干工艺配好的一套东西。', 10, True, TEAL)

    # P06
    s = content(prs, '03 / 资质与质量', 6)
    title(s, '03 / 资质与质量', '客户审厂，先翻资质')
    quals = [('ISO 9001:2015', '质量管理体系'), ('ISO 13485:2016', '医疗器械质量管理体系'),
             ('国家高新技术企业', '高新技术企业认定'), ('广东省专精特新中小企业', '广东省专精特新'),
             ('广东省分子诊断原料酶工程技术研究中心', '省级工程技术研究中心'), ('珠海市企业技术中心', '市级企业技术中心'),
             ('广东省博士后创新实践基地', '博士后创新实践基地'), ('国家专精特新“小巨人”企业', '专精特新“小巨人”')]
    for i, (name, sub) in enumerate(quals):
        x = 0.65 + (i % 4) * 3.08
        y = 1.9 + (i // 4) * 1.8
        B.rect(s, x, y, 2.85, 1.5, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
        B.rect(s, x, y, 0.07, 1.5, TEAL)
        B.text(s, x + 0.2, y + 0.18, 2.45, 0.55, name, 9.8, True, NAVY, line=1.15)
        B.text(s, x + 0.2, y + 0.95, 2.4, 0.25, sub, 7.8, False, GREY)
    B.text(s, 0.65, 5.85, 12.0, 0.5, '客户问资质，别只报名字。ISO 13485 是医疗器械的入场券，ISO 9001 是通用质量体系；说得出区别，才算答上来。', 9.5, False, INK)
    source(s, '来源：公司资质文件合订本；按公司现有资质罗列。')


def market_section(prs):
    # P07 口径
    s = content(prs, '04 / 市场分析', 7)
    title(s, '04 / 市场分析', '先定口径，再谈市场')
    data = [('中国 IVD', '1,080 亿元', '2025 实际', NAVY), ('中国分子诊断', '319 亿元', '2025E', BLUE),
            ('中国 IVD 原料', '228 亿元', '2025E', TEAL), ('分子诊断原料酶', '52 亿元', '2025E', ORANGE)]
    for i, (h, v, yr, col) in enumerate(data):
        x = 0.65 + i * 3.08
        B.rect(s, x, 1.85, 2.85, 2.1, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
        B.rect(s, x, 1.85, 0.07, 2.1, col)
        B.text(s, x + 0.2, 2.02, 2.45, 0.55, v, 20, True, col)
        B.text(s, x + 0.2, 2.7, 2.45, 0.25, h, 9.5, True, NAVY)
        B.text(s, x + 0.2, 3.05, 2.45, 0.22, yr, 8, False, GREY)
    B.text(s, 0.65, 4.25, 12.0, 1.15,
           '1,080 和 1,400 是两套数，319 和 450 也是。这里用协会 2025 实际值 1,080，上游用 Frost 的 2025E；四个数不互相换算。',
           10, False, INK, line=1.5)
    B.text(s, 0.65, 5.65, 12.0, 0.5, '先确定看哪一层，再决定卖什么。四层口径混在一起，结论一定错。', 10.5, True, TEAL)
    source(s, '来源：中国医疗器械行业协会体外诊断分会《中国体外诊断行业年度报告（2021–2025）》；Frost & Sullivan；2025E 为预测值。')

    # P08 全球
    s = content(prs, '04 / 市场分析', 8)
    title(s, '04 / 市场分析', '全球市场：温和增长')
    B.text(s, 0.65, 1.9, 3.2, 0.3, '2025', 9, True, TEAL)
    B.text(s, 0.65, 2.15, 4.0, 0.7, '1,155.6 亿美元', 24, True, NAVY)
    B.text(s, 0.65, 2.95, 3.2, 0.3, '2030E', 9, True, TEAL)
    B.text(s, 0.65, 3.2, 4.0, 0.7, '1,504.5 亿美元', 24, True, BLUE)
    B.text(s, 0.65, 4.1, 3.6, 0.5, '2025–2030E\n年复合增速 5.4%', 11, True, TEAL, line=1.4)
    B.rect(s, 5.1, 1.8, 7.35, 3.9, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
    B.text(s, 5.4, 2.05, 6.7, 0.3, '读法', 9, True, TEAL)
    B.text(s, 5.4, 2.45, 6.7, 2.8,
           '全球IVD增速只有 5.4%。2025 到 2030 年，年均增长 5.4%。\n\n中国区更难：集采、DRG/DIP 把终端价格压下来，2025 年总量已经回到 1,080 亿元。\n\n看全球，是看长期方向；看中国，是看眼前怎么打。',
           10.5, False, INK, line=1.55)
    source(s, '来源：Kalorama Information《The Worldwide Market for IVD Tests, 19th Edition》2026；全球口径，单位美元。')

    # P09 中国总量
    s = content(prs, '04 / 市场分析', 9)
    title(s, '04 / 市场分析', '中国 IVD：总量回落')
    vbars(s, ['2021', '2022', '2023', '2024', '2025'], [1300, 1700, 1200, 1200, 1080], 0.65, 1.85, 7.6, 4.2, NAVY)
    B.text(s, 8.6, 1.95, 4.0, 0.3, '2022 峰值', 9, True, TEAL)
    B.text(s, 8.6, 2.2, 4.0, 0.65, '1,700 亿元', 26, True, BLUE)
    B.text(s, 8.6, 2.95, 4.0, 0.3, '2025', 9, True, TEAL)
    B.text(s, 8.6, 3.2, 4.0, 0.65, '1,080 亿元', 26, True, NAVY)
    B.text(s, 8.6, 4.1, 3.5, 0.9, '较峰值回落约 36%\n集采、DRG/DIP 继续压价。', 10, False, INK, line=1.45)
    source(s, '来源：中国医疗器械行业协会体外诊断分会《中国体外诊断行业年度报告（2021–2025）》。')

    # P10 分子诊断
    s = content(prs, '04 / 市场分析', 10)
    title(s, '04 / 市场分析', '分子诊断：还在增长')
    vbars(s, ['2019', '2024E', '2025E'], [175, 277, 319], 0.65, 1.85, 6.8, 4.2, TEAL)
    B.text(s, 8.0, 1.95, 4.5, 0.3, '2019 → 2025E', 9, True, TEAL)
    B.text(s, 8.0, 2.2, 4.5, 0.7, '175 → 319 亿元', 22, True, NAVY)
    B.text(s, 8.0, 3.0, 4.2, 0.3, '2025E 占 IVD 约 30%', 10, False, GREY)
    B.text(s, 8.0, 3.55, 4.3, 1.5, '2020 年的 286 亿元是疫情峰值，不能拿来算常态增长。\n\n分子诊断仍是 IVD 里少数还在增长的板块。', 10.5, False, INK, line=1.5)
    source(s, '来源：Frost & Sullivan；头豹研究院《2022年中国分子诊断概览》；2024E/2025E 为预测值。')

    # P11 应用结构
    s = content(prs, '04 / 市场分析', 11)
    title(s, '04 / 市场分析', '需求结构：传染病是基本盘，肿瘤是增量')
    data = [('传染病', 'qPCR / RT-qPCR\n流感、HBV、HPV、呼吸道', '量大，集采压价', TEAL),
            ('肿瘤', 'NGS / dPCR\n伴随诊断、MRD、早筛', '增速快，门槛高', ORANGE),
            ('NIPT / 遗传', 'NGS / qPCR\n产前筛查、遗传病', '稳定，集中度高', BLUE),
            ('血筛', 'PCR\nHBV/HCV/HIV', '稳定，法规严', NAVY)]
    for i, (h, mid, note, col) in enumerate(data):
        x = 0.65 + (i % 2) * 6.1
        y = 1.85 + (i // 2) * 2.05
        B.rect(s, x, y, 5.8, 1.75, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
        B.rect(s, x, y, 0.07, 1.75, col)
        B.text(s, x + 0.22, y + 0.13, 2.0, 0.3, h, 13, True, NAVY)
        B.text(s, x + 2.4, y + 0.14, 3.2, 0.6, mid, 9, False, GREY, line=1.3)
        B.text(s, x + 2.4, y + 1.0, 3.0, 0.3, note, 9, True, col)
    B.text(s, 0.65, 6.1, 12.0, 0.3, '原料酶的主战场在 qPCR/RT-qPCR；NGS 酶是另一套能力，不要混着卖。', 10, True, TEAL)
    source(s, '来源：头豹研究院、Frost & Sullivan；应用结构按公开资料整理。')

    # P12 技术路线
    s = content(prs, '04 / 市场分析', 12)
    title(s, '04 / 市场分析', '技术路线：先看我们接得住哪一段')
    rows = [['路线', '主要酶', '需求特点', '宝锐动作'],
            ['qPCR / RT-qPCR', 'Taq、热启动、逆转录酶', '量大、验证周期长', '主攻'],
            ['dPCR', '高保真、热启动', '项目型、灵敏度要求高', '跟项目'],
            ['NGS', '文库酶、聚合酶', '门槛高、客户集中', '跟项目'],
            ['LAMP / POCT', 'Bst、RT、冻干保护剂', '现场、冻干、快速', '主攻']]
    simple_table(s, rows, 0.65, 1.85, 12.0, 3.7, [2.3, 3.5, 3.5, 2.2], 9.8)
    B.text(s, 0.65, 6.0, 12.0, 0.3, '主攻 qPCR/RT-qPCR 和 LAMP/POCT；NGS/mRNA 不铺开，跟项目做。', 10, True, TEAL)
    source(s, '来源：公开资料、公司产品资料；路线选择按宝锐现有酶平台判断。')

    # P13 上游
    s = content(prs, '04 / 市场分析', 13)
    title(s, '04 / 市场分析', '上游：原料增长快于酶')
    grouped(s, ['IVD 原料', '分子诊断原料酶'], [('2020', [104, 32]), ('2025E', [228, 52])], 0.65, 2.1, 6.8, 3.9, [MID, TEAL])
    B.text(s, 7.9, 1.95, 4.5, 0.3, 'IVD 原料', 9, True, TEAL)
    B.text(s, 7.9, 2.2, 4.5, 0.6, '104 → 228 亿元', 18, True, NAVY)
    B.text(s, 7.9, 2.8, 4.3, 0.3, '2020–2025E CAGR 17.0%', 9, False, GREY)
    B.text(s, 7.9, 3.45, 4.5, 0.3, '分子诊断原料酶', 9, True, TEAL)
    B.text(s, 7.9, 3.7, 4.5, 0.6, '32 → 52 亿元', 18, True, NAVY)
    B.text(s, 7.9, 4.3, 4.3, 0.3, '2020–2025E CAGR 10.0%', 9, False, GREY)
    B.text(s, 7.9, 5.0, 4.2, 0.8, '原料涨得快，酶涨得慢。\n酶的价格压力更大，靠定制和冻干做差异。', 10, False, INK, line=1.45)
    source(s, '来源：Frost & Sullivan；2025E 为预测值，单位：亿元人民币。')

    # P14 原料酶细分
    s = content(prs, '04 / 市场分析', 14)
    title(s, '04 / 市场分析', '原料酶：五类产品，五种卖法')
    rows = [['产品', '用在哪', '客户为什么买', '宝锐怎么卖'],
            ['Taq / 热启动', '常规 PCR、qPCR', '量大、价格透明', '走量 + 定制'],
            ['逆转录酶', 'RT-qPCR', '稳定性、抗抑制', '按项目配'],
            ['Bst', 'LAMP、POCT', '恒温、冻干', '项目型'],
            ['UDG', '防污染体系', '配套、法规要求', '搭配销售'],
            ['高保真酶', 'NGS、克隆', '保真度、扩增速度', '跟项目']]
    simple_table(s, rows, 0.65, 1.85, 12.0, 3.9, [2.2, 3.0, 3.4, 3.1], 9.8)
    B.text(s, 0.65, 6.1, 12.0, 0.3, 'Taq/RT 是基本盘，Bst 项目型，UDG 配套，高保真跟项目。', 10, True, TEAL)
    source(s, '来源：公司产品资料、公开行业资料；产品分类按分子诊断常用酶整理。')

    # P15 竞争
    s = content(prs, '04 / 市场分析', 15)
    title(s, '04 / 市场分析', '竞争：进口占八成，国产替代靠验证')
    B.text(s, 0.65, 1.8, 6.5, 0.3, '2021 年国产厂商内部份额', 9, True, NAVY)
    hbars(s, [('诺唯赞', 63.4), ('菲鹏', 20.5), ('翌圣', 10.8), ('康为', 4.2), ('近岸', 0.9), ('其他', 0.2)], 0.65, 2.2, 6.2, 0.55)
    B.rect(s, 7.3, 1.85, 5.2, 3.6, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
    B.text(s, 7.55, 2.05, 4.7, 0.3, '全市场', 9, True, TEAL)
    B.text(s, 7.55, 2.35, 4.7, 0.7, '进口约 80%', 24, True, NAVY)
    B.text(s, 7.55, 3.3, 4.6, 1.8, '国产厂商分剩下的 20%。\n客户换供应商，先看批间稳定和应用支持。\n一个原料从测试到量产，常见 3–6 年。', 10, False, INK, line=1.5)
    source(s, '来源：Frost & Sullivan（2021，国产厂商内部份额）；华经产业研究院（2025，进口占比约 80%）；上量周期来自历史培训资料。')

    # P16 客户
    s = content(prs, '04 / 市场分析', 16)
    title(s, '04 / 市场分析', '客户：四种类型，四种买法')
    rows = [['客户类型', '要什么', '谁拍板', '上量节奏'],
            ['IVD 试剂厂', '稳定供货、注册变更、成本', '研发 + 质量 + 采购', '3–6 年'],
            ['POCT', '快速、冻干、现场', '研发 + 项目负责人', '项目制'],
            ['科研', '品类全、单量小', 'PI / 采购', '快'],
            ['生物医药', 'mRNA / QC、法规', '工艺 + 质量', '项目制']]
    simple_table(s, rows, 0.65, 1.85, 12.0, 3.6, [2.4, 4.4, 3.0, 2.2], 9.8)
    B.text(s, 0.65, 5.9, 12.0, 0.3, '诊断原料的主战场是 IVD 试剂厂；科研是补充，生物医药是期权。', 10, True, TEAL)
    source(s, '来源：公开行业资料、公司历史培训资料；上量周期为行业常见区间。')

    # P17 情景
    s = content(prs, '04 / 市场分析', 17)
    title(s, '04 / 市场分析', '2026–2030：三种情景')
    rows = [['情景', '2025', '2030E', '2025–2030 CAGR', '假设'],
            ['保守', '52 亿元', '47 亿元', '-2%', '集采继续压价，国产替代慢'],
            ['基准', '52 亿元', '66 亿元', '5%', '价格稳住，存量替换'],
            ['进取', '52 亿元', '84 亿元', '10%', '海外 + 新项目放量']]
    simple_table(s, rows, 0.65, 1.85, 12.0, 3.5, [1.5, 1.8, 1.8, 2.3, 4.6], 9.8)
    B.text(s, 0.65, 5.7, 12.0, 0.5, '情景测算，按假设推演。即使保守情景，存量替换仍在，关键看拿不拿得到份额。', 10, False, INK)
    source(s, '测算：以 2025E 52 亿元为基数，按 -2%/5%/10% 复合增速推算；基数来自 Frost & Sullivan。')

    # P18 在哪打
    s = content(prs, '04 / 市场分析', 18)
    title(s, '04 / 市场分析', '宝锐在哪打')
    # 2x2 matrix
    B.rect(s, 0.9, 1.85, 6.4, 4.2, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
    B.line(s, 4.1, 1.85, 4.1, 6.05, B.RGBColor(0xB9, 0xD4, 0xE3), 0.8)
    B.line(s, 0.9, 3.95, 7.3, 3.95, B.RGBColor(0xB9, 0xD4, 0xE3), 0.8)
    B.text(s, 1.05, 2.0, 2.8, 0.3, '增长快 · 能力低', 8, True, GREY)
    B.text(s, 4.5, 2.0, 2.8, 0.3, '增长快 · 能力高', 8, True, TEAL)
    B.text(s, 1.05, 4.1, 2.8, 0.3, '增长慢 · 能力低', 8, True, GREY)
    B.text(s, 4.5, 4.1, 2.8, 0.3, '增长慢 · 能力高', 8, True, GREY)
    B.text(s, 4.55, 2.5, 2.5, 0.9, 'qPCR / RT-qPCR\nLAMP / POCT\n冻干', 10.5, True, TEAL, line=1.4)
    B.text(s, 1.1, 2.5, 2.5, 0.9, 'NGS / mRNA\n跟项目', 10.5, True, ORANGE, line=1.4)
    B.text(s, 1.1, 4.55, 2.5, 0.8, '其他\n不投入', 10.5, False, GREY)
    B.text(s, 4.55, 4.55, 2.5, 0.8, '同质化 Taq\n价格战', 10.5, False, GREY)
    B.text(s, 7.8, 1.95, 5.0, 0.3, '打法', 9, True, TEAL)
    B.text(s, 7.8, 2.3, 4.8, 2.8, '主攻 qPCR/RT-qPCR 和 LAMP/POCT。\n\n冻干做差异，NGS/mRNA 跟项目。\n\n不碰同质化 Taq 的价格战。', 11, False, INK, line=1.55)
    source(s, '判断依据：市场增速、宝锐酶平台与现有产品资料；矩阵为内部策略判断；行业数据见前页。')


def pain_section(prs):
    # P19
    s = content(prs, '05 / 客户痛点与宝锐合作', 19)
    title(s, '05 / 客户痛点与宝锐合作', '客户卡在哪')
    pains = [('01', '成本', '集采、DRG/DIP 压价，\n试剂厂先扛，原料端跟着扛。'),
             ('02', '研发效率', '原料筛选反复试，\n项目做完，数据难复用。'),
             ('03', '信息差', '客户做 FFPE，原料厂只给酶参数；\n客户不知道怎么配。')]
    for i, (num, h, bdy) in enumerate(pains):
        x = 0.65 + i * 4.12
        B.rect(s, x, 1.95, 3.85, 3.1, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
        B.rect(s, x, 1.95, 3.85, 0.07, TEAL)
        B.text(s, x + 0.22, 2.2, 0.6, 0.4, num, 18, True, TEAL)
        B.text(s, x + 0.22, 2.7, 3.2, 0.4, h, 15, True, NAVY)
        B.text(s, x + 0.22, 3.3, 3.3, 1.3, bdy, 9.8, False, INK, line=1.5)

    # P20
    s = content(prs, '05 / 客户痛点与宝锐合作', 20)
    title(s, '05 / 客户痛点与宝锐合作', '第一道坎：成本')
    B.text(s, 0.65, 1.9, 4.0, 0.7, '集采 + DRG/DIP', 22, True, NAVY)
    B.text(s, 0.65, 2.75, 4.5, 2.6, '终端价格往下走，试剂厂先扛；\n试剂厂再往上游压，原料端跟着扛。\n\n做血筛的客户，换一批原料要重新做验证。\n便宜但换批不稳，不是降本。', 10.5, False, INK, line=1.55)
    B.rect(s, 5.4, 1.9, 7.1, 3.6, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
    B.text(s, 5.65, 2.1, 6.6, 0.3, '宝锐能做的', 9, True, TEAL)
    B.bullets(s, 5.65, 2.5, 6.4, 2.6, ['按客户体系定制酶，减少不必要的组分和工艺。', '配方和冻干一起看，少做无效试验。', '批间稳定、供应稳定，减少客户换批验证的隐性成本。'], 10, INK, TEAL)
    B.text(s, 0.65, 6.1, 12.0, 0.3, '报价前先算两笔账：原料单价，换批验证。', 10, True, TEAL)

    # P21
    s = content(prs, '05 / 客户痛点与宝锐合作', 21)
    title(s, '05 / 客户痛点与宝锐合作', '第二道坎：研发效率')
    B.text(s, 0.65, 1.9, 4.4, 0.7, '一半以上的工时，花在筛原料', 20, True, NAVY)
    B.text(s, 0.65, 3.0, 4.5, 2.4, '一个项目一套体系，酶和缓冲都要试；\n项目结束后，数据留在个人手里，\n下一个项目重新来一遍。', 10.5, False, INK, line=1.55)
    B.rect(s, 5.4, 1.9, 7.1, 3.6, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
    B.text(s, 5.65, 2.1, 6.6, 0.3, '宝锐能做的', 9, True, TEAL)
    B.bullets(s, 5.65, 2.5, 6.4, 2.6, ['平台化酶库，先把大方向缩到少数几个候选。', '把应用数据一起给，不只给参数表。', '定制开发按项目沉淀，下一个项目能接着用。'], 10, INK, TEAL)
    B.text(s, 0.65, 6.1, 12.0, 0.3, '下次客户说项目慢，先问：卡在筛酶，还是卡在验证方案？', 10, True, TEAL)

    # P22
    s = content(prs, '05 / 客户痛点与宝锐合作', 22)
    title(s, '05 / 客户痛点与宝锐合作', '第三道坎：信息差')
    B.text(s, 0.65, 1.9, 4.6, 0.7, '上下游，互相看不见', 20, True, NAVY)
    B.text(s, 0.65, 3.0, 4.5, 2.4, '做 FFPE 的客户，前端提取和扩增要一起看；\n原料厂只给酶参数，客户不知道怎么配。\n\n新项目立项慢，验证方案来回改。', 10.5, False, INK, line=1.55)
    B.rect(s, 5.4, 1.9, 7.1, 3.6, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
    B.text(s, 5.65, 2.1, 6.6, 0.3, '宝锐能做的', 9, True, TEAL)
    B.bullets(s, 5.65, 2.5, 6.4, 2.6, ['销售、技术一起进客户现场，先看体系，不先报货号。', '把客户的指标翻译成酶的参数，联合定义验证方案。', '样品和数据一起给，缩短客户从立项到验证的时间。'], 10, INK, TEAL)
    B.text(s, 0.65, 6.1, 12.0, 0.3, '第一次拜访，销售和技术一起去；先看反应体系，别先报货号。', 10, True, TEAL)


def solution_section(prs):
    # P23
    s = content(prs, '05 / 客户痛点与宝锐合作', 23)
    title(s, '05 / 客户痛点与宝锐合作', '合作模式：分子酶定制化', '客户给体系，我们配酶和缓冲')
    steps = [('01', '问清体系', '客户现在跑什么体系，样本是什么'), ('02', '挑酶、改酶', '从酶库先挑几个候选，必要时改造'),
             ('03', '在客户体系里试', '用客户样本做灵敏度和稳定性'), ('04', '放大到生产', '定配方、冻干程序，跑批间一致'),
             ('05', '按批供货', '批批检，出 COA')]
    for i, (num, h, bdy) in enumerate(steps):
        x = 0.65 + i * 2.48
        B.rect(s, x, 2.1, 2.1, 2.6, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
        B.text(s, x + 0.15, 2.28, 0.5, 0.3, num, 14, True, TEAL)
        B.text(s, x + 0.15, 2.7, 1.8, 0.6, h, 10.5, True, NAVY, line=1.2)
        B.text(s, x + 0.15, 3.4, 1.8, 1.1, bdy, 8, False, GREY, line=1.35)
        if i < 4:
            B.text(s, x + 2.12, 3.25, 0.3, 0.3, '→', 16, True, TEAL)
    B.text(s, 0.65, 5.35, 12.0, 0.3, '项目开始时，先把客户的体系、样本问清楚。', 10, True, TEAL)

    # P24
    s = content(prs, '05 / 客户痛点与宝锐合作', 24)
    title(s, '05 / 客户痛点与宝锐合作', '场景化方案：按客户体系配')
    cases = [('高灵敏 qPCR / RT-qPCR', '低拷贝和抗抑制', '酶和缓冲一起调，探针按客户体系选'),
             ('LAMP / POCT', '恒温和冻干', 'Bst/RT 酶配冻干保护剂，程序一起定'),
             ('直扩 / 复杂样本', '血浆和植物样本', '耐抑制酶 + 样本兼容性验证')]
    for i, (h, tag, bdy) in enumerate(cases):
        x = 0.65 + i * 4.12
        B.rect(s, x, 1.95, 3.85, 3.4, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
        B.rect(s, x, 1.95, 3.85, 0.07, TEAL)
        B.text(s, x + 0.22, 2.2, 3.3, 0.6, h, 12.5, True, NAVY, line=1.2)
        B.text(s, x + 0.22, 2.9, 3.25, 0.3, tag, 9, True, TEAL)
        B.text(s, x + 0.22, 3.35, 3.3, 1.5, bdy, 9.8, False, INK, line=1.5)
    B.text(s, 0.65, 5.85, 12.0, 0.3, '客户跑 qPCR，就按 qPCR 配；跑 LAMP，就按 LAMP 配。', 10, True, TEAL)

    # P25
    s = content(prs, '05 / 客户痛点与宝锐合作', 25)
    title(s, '05 / 客户痛点与宝锐合作', '冻干服务：把液体试剂变成可运输的产品')
    B.text(s, 0.65, 1.85, 3.6, 0.3, '服务流程', 9, True, TEAL)
    steps = ['看配方', '定程序', '做小试', '跑稳定性', '放大生产']
    for i, step in enumerate(steps):
        y = 2.25 + i * 0.7
        B.rect(s, 0.65, y, 3.4, 0.48, WHITE, B.RGBColor(0xD8, 0xE6, 0xEF), True)
        B.text(s, 0.82, y + 0.1, 3.0, 0.25, step, 9.8, True, NAVY)
        if i < 4:
            B.text(s, 3.85, y + 0.1, 0.3, 0.3, '↓', 12, True, TEAL)
    B.text(s, 4.45, 1.85, 8.2, 0.3, '验证例子', 9, True, TEAL)
    rows = [['产品', '体系/样本', '结果'],
            ['M2071', 'ASFV 质粒', '3 copies/T，100% 检出，Ct CV 2.33%'],
            ['MD2071', 'HBV 血浆直扩', '25% 加样，23 分钟完成扩增'],
            ['FM5071', '人基因组四重检测', '45℃ 90 天、常温 1 年稳定性验证']]
    simple_table(s, rows, 4.45, 2.2, 8.1, 2.6, [1.0, 2.1, 5.0], 9.2)
    B.text(s, 4.45, 5.05, 8.0, 0.5, '数据来自产品验证资料；具体体系以客户自己的验证结果为准。', 8.5, False, GREY)
    B.rect(s, 0.65, 6.0, 3.4, 0.55, LIGHT, None, True)
    B.text(s, 0.8, 6.12, 3.1, 0.3, '冻干要同时定配方、程序、保护剂和包装。', 8.2, False, NAVY)


def closing(prs):
    s = B.new_slide(prs)
    bg(s, TPL_COVER, prs)
    s.shapes.add_picture(str(LOGO_WHITE), Inches(0.62), Inches(0.38), width=Inches(1.75))
    B.text(s, 0.95, 2.35, 7.0, 0.8, '谢谢', 44, True, WHITE)
    B.text(s, 0.95, 3.35, 7.5, 0.5, '宝锐生物 ｜ 诊断原料销售新人培训', 14, False, WHITE)
    B.text(s, 0.95, 4.15, 7.2, 0.4, '刘新元 ｜ 诊断原料销售', 11, True, WHITE)
    B.text(s, 0.95, 4.62, 7.5, 0.3, 'marketing@biori.com ｜ 0756-8699969 ｜ www.biori.com', 8.5, False, WHITE)
    B.text(s, 0.95, 6.4, 6.0, 0.3, '2026 年 11 月 ｜ 珠海', 8.5, False, WHITE)


def main():
    prs = Presentation()
    prs.slide_width = Inches(13.333)
    prs.slide_height = Inches(7.5)
    cover(prs)
    # P02 agenda
    s = content(prs, '公司介绍 / 新人培训', 2)
    title(s, '今天讲什么', '公司、市场、客户、合作')
    items = [('01', '公司', '2012 年成立，三条业务线'), ('02', '市场', 'IVD、分子诊断、原料、酶'),
             ('03', '客户', '成本、研发效率、信息差'), ('04', '合作', '分子酶定制化、场景方案、冻干')]
    for i, (n, h, bdy) in enumerate(items):
        y = 2.0 + i * 0.95
        B.text(s, 0.8, y, 0.6, 0.3, n, 15, True, TEAL)
        B.text(s, 1.45, y, 2.3, 0.3, h, 14, True, NAVY)
        B.text(s, 3.9, y, 7.8, 0.3, bdy, 10, False, GREY)
        B.line(s, 1.45, y + 0.45, 12.0, y + 0.45, B.RGBColor(0xDD, 0xEB, 0xF2), 0.6)
    company_section(prs)
    market_section(prs)
    pain_section(prs)
    solution_section(prs)
    closing(prs)
    OUTDIR.mkdir(parents=True, exist_ok=True)
    prs.save(str(OUT))
    print('written', OUT, OUT.stat().st_size, 'slides', len(prs.slides))


if __name__ == '__main__':
    main()
