#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
《客户拉通大表》示例数据 · 唯一数据源（single source of truth）。

被两个生成器共用，保证「基线页」与「经营仪表盘」跑的是**同一批示例数据**：
  · 脚本/build_customer_thread_demo.py      → 工具/客户拉通大表_demo.html（基线·两层）
  · 脚本/build_customer_thread_dashboard.py → 工具/客户拉通大表_仪表盘.html

口径：
  · 客户名为真实公司名；商机 / 日期 / 负责人 / 金额 / 事件内容均为**示例值**，未上线。
  · 节点 8 个（流程顺序）；资源泳道 5 类（技术支持测试项目单/研发项目单/售后/其他/活动）。
  · 节点单元格 {state, start, end, owner, unlinked}；event {d, k, who, t, lane, [six/todo/ord/fb]}。
  · 未收口节点（进行中/卡住）无 end → 由前端按「今天」计算停留天数。
"""
TODAY = "2026-10-11"

NODE_COLS = ["陌生开发", "方案匹配", "中试", "临床试验",
             "已确认被用于注册", "已转产", "客户已获证", "新证迭代"]
NODE_SHORT = {"陌生开发": "陌生开发", "方案匹配": "方案匹配", "中试": "中试", "临床试验": "临床",
              "已确认被用于注册": "注册", "已转产": "转产", "客户已获证": "获证", "新证迭代": "迭代"}

BASE_COLS = ["部门", "客户编码", "客户名称", "商机名称", "销售员", "商机规模"]
RES_COLS = ["技术支持测试项目单1"]
ACT_TYPES = ["测试结果跟进", "采购跟进", "客户档案完善"]
SUPPORT_TYPES = ["技术支持测试项目单", "研发项目单", "售后", "其他"]
SUPPORT_ENUM = {"技术支持测试项目单": "tech_test", "研发项目单": "rnd_project",
                "售后": "after_sales", "其他": "other"}
LANES = ["技术支持测试项目单", "研发项目单", "售后", "其他", "活动"]
DEPTS = ["大客户部", "销售拓展部", "生物制药销售部", "国际业务部"]

# ── 第二层「活动描述」模板（按活动类型切换）· 与结构清单 §12 一致 ─────────────
DESC_TEMPLATES = {
    "测试结果跟进": """【客户需求】
· 应用场景：           · 技术平台：qPCR/测序/冻干/其他
· 项目状态：立项/研发/注册/量产   · 时间节点：
· 参数要求：（量化，如 CV≤5%、Ct 漂移≤0.5）
· 竞对情况：品牌+型号+量化对比（无则写"无"）
【宝锐方案】
· 货号+名称：          · 关联订单/测试单编号：
· 测试条件：体系/仪器/程序
【测试结果】（逐项：指标 → 实测值 → 判定）
· 1.             · 2.
· 结论：通过 / 超阈（超阈必写偏差方向与幅度）
【问题现象】含客户原话（加引号）
【客户反馈 / 下一步诉求】
【待办事项】做什么 / 怎么做 / 做到什么程度 / 关联资源 / 负责人 / 日期""",
    "采购跟进": """【采购阶段】
· 当前阶段：询价 / 送样 / 试用 / 比价 / 定标 / 下单   · 时间节点：
【预算与数量】
· 预算：             · 预计数量：
· 参数要求：（量化）
【决策链与关键人】
· 决策人 / 影响人 / 使用人：
【竞对报价与差距】
· 竞品：品牌+报价+量化对比（无则写"无"）
· 我方差距：
【下一步动作与时间】
· 动作：             · 负责人：             · 时间节点：""",
    "客户档案完善": """【本次补齐的字段】
· 联系人：（姓名 / 职务 / 联系方式）
· 组织架构：（部门 / 汇报关系 / 课题组）
· 设备：（仪器型号 / 数量 / 平台）
· 项目：（新增 / 更新项目及进度）""",
}
# 「测试结果跟进」的必填段（段级校验：逐段非空，缺段高亮并提示缺哪段）
REQ_SEGS = ["客户需求", "宝锐方案", "测试结果", "问题现象", "客户反馈", "待办事项"]


USD_CNY = 7.2   # 示例汇率：用于把国际业务部的 US$ 商机规模折算成人民币（仪表盘口径）


def nd(state, start=None, end=None, owner=None, unlinked=False, act=None):
    d = {"state": state}
    if start:
        d["start"] = start
    if end:
        d["end"] = end
    if owner:
        d["owner"] = owner
    if act:
        d["act"] = act
    if unlinked:
        d["unlinked"] = True
    return d


def EV(d, k, who, t, lane, **kw):
    e = {"d": d, "k": k, "who": who, "t": t, "lane": lane}
    e.update(kw)
    return e


SIX_SX = {
    "① 客户需求（分点）": "冻干后 37℃ 加速 7 天 Ct 漂移 ≤1；需兼容现有一步法 RT-qPCR 程序（不额外增时）。",
    "② 技术参数（货号+量化）": "MD2104 冻干保护剂 5%(W/V)；复溶后酶活回收 ≥95%；批内 CV ≤3%。",
    "③ 测试单结果跟进": "送样 5 组配方（TS-20260812-07），客户回传 3 组：A/B 组通过，C 组加速后 Ct 漂移 1.4（超阈）。",
    "④ 问题现象（含客户原话）": "C 组第 6 天曲线抬不起来；客户原话：「怀疑保护剂析晶」。",
    "⑤ 客户反馈": "认可 A/B 组，要求就 C 组补一批调整海藻糖配比后复测。",
    "⑥ 竞对信息（品牌+量化）": "竞品（品牌A）同条件 Ct 漂移 0.9；我方 A 组 0.6，已优于竞品。",
}

# ── 第一层：一行一商机（13 行 · 4 部门）────────────────────────────
RAW_ROWS = [
    # ===== 大客户部 =====
    {"dept": "大客户部", "cid": "6878118191", "cust": "圣湘生物科技股份有限公司",
     "opp": "多重呼吸道病原体冻干体系开发", "sales": "赵云浩", "scale": "¥1,200,000",
     "tech": "李工", "comp": "某进口冻干保护剂（品牌A）",
     "nodes": {"陌生开发": nd("已完成", "2026-02-10", "2026-03-05", "赵云浩"),
               "方案匹配": nd("已完成", "2026-03-10", "2026-05-12", "赵云浩"),
               "中试": nd("已完成", "2026-06-01", "2026-08-10", "王工"),
               "临床试验": nd("进行中", "2026-09-01", None, "赵云浩")},
     "res": {"技术支持测试项目单1": nd("已完成", "2026-08-12", None, "李工")},
     "events": [
         EV("2026-06-05", "活动", "赵云浩", "需求确认拜访：明确冻干后 37℃ 加速 7 天 Ct 漂移 ≤1", "活动"),
         EV("2026-06-18", "订单", "赵云浩", "SO-20260618-011 · 货号 MD2104 · 送样 2 kg", "技术支持测试项目单",
            ord={"单号": "SO-20260618-011", "货号": "MD2104", "数量": "2 kg", "备注": "免费送样"}),
         EV("2026-06-25", "待办发起", "赵云浩", "研发按客户配方重做 C 组保护剂配比", "研发项目单",
            todo={"事项": "重做 C 组配方并附加速数据", "负责人": "王工", "时间节点": "2026-07-15", "状态": "已完成"}),
         EV("2026-08-10", "部门反馈", "王工", "研发部反馈：中试配方完成，A/B 组冻干成型良好", "研发项目单"),
         EV("2026-08-12", "活动", "李工", "技术支持送样 5 组配方并启动 37℃ 加速试验", "技术支持测试项目单"),
         EV("2026-09-15", "部门反馈", "李工", "技术支持反馈：A/B 组通过，C 组加速第 6 天 Ct 漂移 1.4（超阈）", "技术支持测试项目单"),
         EV("2026-10-08", "活动", "赵云浩", "测试结果跟进：客户回传 3 组数据，要求补做 C 组", "技术支持测试项目单",
            six=SIX_SX,
            fb=[{"who": "王工", "t": "2026-10-08", "tx": "研发已排期，10-12 前出调整后配方"},
                {"who": "李工", "t": "2026-10-09", "tx": "技术支持准备好加速箱位，随时接样"}]),
         EV("2026-10-10", "闭环", "赵云浩", "客户认可 A/B 组，C 组补做进入研发排期（本活动闭环）", "其他")]},
    {"dept": "大客户部", "cid": "6878118210", "cust": "深圳市新产业生物医学工程股份有限公司",
     "opp": "磁珠法核酸提取原料替换评估", "sales": "徐卓凡", "scale": "¥860,000",
     "tech": "王工", "comp": "进口磁珠（品牌B）",
     "nodes": {"陌生开发": nd("已完成", "2026-06-15", "2026-08-30", "徐卓凡"),
               "方案匹配": nd("已完成", "2026-09-01", "2026-09-20", "徐卓凡"),
               "中试": nd("进行中", "2026-09-25", None, "王工")},
     "res": {},
     "events": [
         EV("2026-06-20", "活动", "徐卓凡", "陌生拜访：客户提出磁珠结合效率对标进口需求", "活动"),
         EV("2026-07-05", "待办发起", "徐卓凡", "技术支持安排磁珠与进口品平行对比测试", "技术支持测试项目单",
            todo={"事项": "安排 3 批磁珠平行对比", "负责人": "王工", "时间节点": "2026-07-28", "状态": "已完成"}),
         EV("2026-08-01", "售后", "王工", "处理客户到货磁珠包装破损，补寄 1 批", "售后"),
         EV("2026-08-18", "订单", "徐卓凡", "SO-20260818-004 · 货号 MD2201 · 磁珠 1 kg", "技术支持测试项目单",
            ord={"单号": "SO-20260818-004", "货号": "MD2201", "数量": "1 kg", "备注": "对比测试用量"}),
         EV("2026-09-20", "部门反馈", "王工", "技术支持反馈：磁珠结合效率与进口持平（回收率 96% vs 95%）", "技术支持测试项目单"),
         EV("2026-09-28", "活动", "徐卓凡", "中试方案确认会：客户同意进入中试", "活动")]},
    {"dept": "大客户部", "cid": "6878118103", "cust": "深圳迈瑞生物医疗电子股份有限公司",
     "opp": "质控原料国产替代导入评估", "sales": "孙立", "scale": "¥780,000",
     "tech": "王工", "comp": "进口质控原料（品牌J）",
     "nodes": {"陌生开发": nd("已完成", "2026-03-05", "2026-04-20", "孙立"),
               "方案匹配": nd("卡住", "2026-05-01", None, "孙立")},
     "res": {},
     "events": [
         EV("2026-03-05", "活动", "孙立", "陌生拜访：客户提出质控原料国产替代需求", "活动"),
         EV("2026-04-20", "部门反馈", "王工", "技术支持反馈：初步样品指标达标", "技术支持测试项目单"),
         EV("2026-05-15", "待办发起", "孙立", "研发按客户内控标准调整配方", "研发项目单",
            todo={"事项": "按内控标准调整配方", "负责人": "王工", "时间节点": "2026-06-20", "状态": "进行中"}),
         EV("2026-06-30", "其他", "孙立", "客户采购流程调整，方案匹配暂停（本商机沉默）", "其他")]},
    # ===== 销售拓展部 =====
    {"dept": "销售拓展部", "cid": "6878118250", "cust": "广州达安基因股份有限公司",
     "opp": "数字PCR扩增酶体系开发", "sales": "陈嘉怡", "scale": "¥540,000",
     "tech": "黄工", "comp": "进口高保真酶（品牌C）",
     "nodes": {"陌生开发": nd("已完成", "2026-05-01", "2026-06-20", "陈嘉怡"),
               "方案匹配": nd("已完成", "2026-06-25", "2026-08-01", "陈嘉怡"),
               "中试": nd("进行中", "2026-08-05", None, "黄工")},
     "res": {"技术支持测试项目单1": nd("进行中", "2026-08-18", None, "李工")},
     "events": [
         EV("2026-06-25", "活动", "陈嘉怡", "方案沟通会：确定数字 PCR 高保真酶体系指标", "活动"),
         EV("2026-07-10", "待办发起", "陈嘉怡", "研发提供高保真酶小样用于体系搭建", "研发项目单",
            todo={"事项": "提供 1E102 小样 500 U", "负责人": "黄工", "时间节点": "2026-07-25", "状态": "已完成"}),
         EV("2026-08-01", "订单", "陈嘉怡", "SO-20260801-002 · 货号 1E102 · 高保真酶 500 U", "技术支持测试项目单",
            ord={"单号": "SO-20260801-002", "货号": "1E102", "数量": "500 U", "备注": "中试用量"}),
         EV("2026-08-05", "活动", "黄工", "启动数字 PCR 体系验证测试", "技术支持测试项目单"),
         EV("2026-09-10", "部门反馈", "黄工", "技术支持反馈：扩增效率 98%，曲线均一性良好", "技术支持测试项目单"),
         EV("2026-09-25", "其他", "陈嘉怡", "客户要求提供该体系的定制化验证报告", "其他"),
         EV("2026-09-30", "活动", "陈嘉怡", "中试跟进：关注微滴分离度与假阳性率", "活动")]},
    {"dept": "销售拓展部", "cid": "待关联", "cust": "上海纳全生物医学科技有限公司",
     "opp": "RT单管全预混体系开发", "sales": "张文蝶", "scale": "¥320,000",
     "tech": "张工", "comp": "某国产全预混体系（品牌D）", "unlinked": True,
     "nodes": {"陌生开发": nd("已完成", "2026-08-01", "2026-08-25", "张文蝶"),
               "方案匹配": nd("已完成", "2026-09-01", "2026-09-05", "张文蝶"),
               "中试": nd("卡住", "2026-09-20", None, "张工", unlinked=True)},
     "res": {"技术支持测试项目单1": nd("进行中", "2026-09-08", None, "张工")},
     "events": [
         EV("2026-09-05", "活动", "张文蝶", "方案匹配完成：确定全预混体系配方路线", "活动"),
         EV("2026-09-08", "活动", "张工", "技术支持送样：全预混体系 3 组", "技术支持测试项目单"),
         EV("2026-09-20", "待办发起", "张文蝶", "研发调整全预混体系配方（37℃ 加速不稳定）", "研发项目单",
            todo={"事项": "调整全预混配方稳定性", "负责人": "张工", "时间节点": "2026-10-15", "状态": "进行中"}),
         EV("2026-09-30", "部门反馈", "张工", "技术支持反馈：37℃ 加速 5 天酶活衰减 12%，等待研发新配方", "技术支持测试项目单"),
         EV("2026-10-08", "其他", "张文蝶", "客户连续两周未回复，商机停滞；客户编码待关联", "其他")]},
    {"dept": "销售拓展部", "cid": "6878118271", "cust": "东莞华大智造科技股份有限公司",
     "opp": "测序建库酶原料导入", "sales": "陈嘉怡", "scale": "¥410,000",
     "tech": "黄工", "comp": "进口建库酶（品牌K）",
     "nodes": {"陌生开发": nd("已完成", "2026-05-25", "2026-06-05", "陈嘉怡"),
               "方案匹配": nd("已完成", "2026-06-10", "2026-07-15", "陈嘉怡"),
               "中试": nd("进行中", "2026-07-20", None, "黄工")},
     "res": {},
     "events": [
         EV("2026-06-10", "活动", "陈嘉怡", "方案沟通：确定测序建库酶原料指标", "活动"),
         EV("2026-07-15", "部门反馈", "黄工", "技术支持反馈：建库效率符合预期", "技术支持测试项目单"),
         EV("2026-08-25", "其他", "陈嘉怡", "客户内部预算冻结，跟进暂停（本商机沉默）", "其他")]},
    {"dept": "销售拓展部", "cid": "6878118282", "cust": "广州金域医学检验集团股份有限公司",
     "opp": "检验所原料国产化替换", "sales": "张文蝶", "scale": "¥260,000",
     "tech": "张工", "comp": "进口核酸提取原料（品牌L）",
     "nodes": {"陌生开发": nd("已完成", "2026-05-20", "2026-06-30", "张文蝶"),
               "方案匹配": nd("进行中", "2026-07-05", None, "张文蝶")},
     "res": {},
     "events": [
         EV("2026-05-20", "活动", "张文蝶", "陌生拜访：客户检验所提出原料国产化诉求", "活动"),
         EV("2026-06-30", "待办发起", "张文蝶", "研发提供小样用于方法学验证", "研发项目单",
            todo={"事项": "提供小样用于方法学验证", "负责人": "张工", "时间节点": "2026-07-20", "状态": "进行中"}),
         EV("2026-08-01", "其他", "张文蝶", "客户项目优先级下调，跟进延后（本商机沉默）", "其他")]},
    {"dept": "销售拓展部", "cid": "待关联", "cust": "浙江大学生命科学研究院（课题组·示例）",
     "opp": "mRNA 原料试用", "sales": "陈嘉怡", "scale": "¥80,000",
     "tech": "黄工", "comp": "进口 mRNA 原料（品牌M）", "unlinked": True,
     "nodes": {"陌生开发": nd("已完成", "2026-09-10", "2026-09-20", "陈嘉怡"),
               "方案匹配": nd("进行中", "2026-10-02", None, "陈嘉怡")},
     "res": {},
     "events": [
         EV("2026-09-10", "活动", "陈嘉怡", "课题组接触：PI 提出 mRNA 原料试用需求", "活动"),
         EV("2026-10-02", "活动", "陈嘉怡", "送样试用，等待反馈", "技术支持测试项目单")]},
    # ===== 生物制药销售部 =====
    {"dept": "生物制药销售部", "cid": "6878118301", "cust": "上海复宏汉霖生物技术股份有限公司",
     "opp": "质控用酶原料导入验证", "sales": "周敏", "scale": "¥450,000",
     "tech": "赵工", "comp": "进口质控酶（品牌G）",
     "nodes": {"陌生开发": nd("已完成", "2026-06-01", "2026-09-02", "周敏"),
               "方案匹配": nd("进行中", "2026-09-25", None, "周敏")},
     "res": {},
     "events": [
         EV("2026-06-05", "活动", "周敏", "陌生拜访：客户质控部门提出酶原料国产化诉求", "活动"),
         EV("2026-07-20", "待办发起", "周敏", "研发评估质控酶在客户体系中的适用性", "研发项目单",
            todo={"事项": "评估质控酶活与批间差", "负责人": "赵工", "时间节点": "2026-08-30", "状态": "已完成"}),
         EV("2026-09-02", "部门反馈", "赵工", "研发反馈：质控酶活满足客户内控标准", "研发项目单"),
         EV("2026-09-25", "活动", "周敏", "方案匹配启动：对接客户 QA 与采购", "活动"),
         EV("2026-10-05", "订单", "周敏", "SO-20261005-009 · 货号 1D303 · 质控酶 200 U", "技术支持测试项目单",
            ord={"单号": "SO-20261005-009", "货号": "1D303", "数量": "200 U", "备注": "验证用量"})]},
    {"dept": "生物制药销售部", "cid": "6878118312", "cust": "百济神州（广州）生物科技有限公司",
     "opp": "生物药QC残留DNA检测酶导入", "sales": "孙立", "scale": "¥600,000",
     "tech": "李工", "comp": "进口残留DNA检测酶（品牌E）",
     "nodes": {"陌生开发": nd("已完成", "2026-04-10", "2026-05-30", "孙立"),
               "方案匹配": nd("已完成", "2026-06-01", "2026-08-15", "孙立"),
               "中试": nd("已完成", "2026-08-20", "2026-09-28", "李工"),
               "临床试验": nd("进行中", "2026-10-01", None, "孙立")},
     "res": {},
     "events": [
         EV("2026-04-15", "活动", "孙立", "陌生开发拜访：QC 残留 DNA 检测方法学导入", "活动"),
         EV("2026-06-10", "待办发起", "孙立", "技术支持做残留 DNA 检测限对比", "技术支持测试项目单",
            todo={"事项": "对比检测限与回收率", "负责人": "李工", "时间节点": "2026-07-30", "状态": "已完成"}),
         EV("2026-08-15", "部门反馈", "李工", "技术支持反馈：检测限 0.1 pg/µL，回收率 92%", "技术支持测试项目单"),
         EV("2026-09-28", "活动", "孙立", "中试完成评审：客户认可数据", "活动"),
         EV("2026-10-01", "活动", "孙立", "临床试验阶段启动（客户送样进入方法学验证）", "活动"),
         EV("2026-10-09", "售后", "李工", "客户反馈运输温控异常，已安排补寄 1 批", "售后")]},
    {"dept": "生物制药销售部", "cid": "6878118320", "cust": "上海药明生物技术有限公司",
     "opp": "生物药原料市场化供应", "sales": "周敏", "scale": "¥900,000",
     "tech": "赵工", "comp": "进口同类原料（品牌N）",
     "nodes": {"陌生开发": nd("已完成", "2026-01-10", "2026-02-20", "周敏"),
               "方案匹配": nd("已完成", "2026-03-01", "2026-04-30", "周敏"),
               "中试": nd("已完成", "2026-05-10", "2026-07-01", "赵工"),
               "临床试验": nd("已完成", "2026-07-10", "2026-08-20", "赵工"),
               "已确认被用于注册": nd("已完成", "2026-08-25", "2026-09-20", "周敏"),
               "已转产": nd("进行中", "2026-09-25", None, "周敏")},
     "res": {"研发项目单1": nd("已完成", "2026-05-20", None, "赵工")},
     "events": [
         EV("2026-03-01", "活动", "周敏", "方案匹配启动：确认原料规格与年用量", "活动"),
         EV("2026-05-20", "待办发起", "周敏", "研发推进中试放大与稳定性数据", "研发项目单",
            todo={"事项": "中试放大 + 稳定性", "负责人": "赵工", "时间节点": "2026-07-01", "状态": "已完成"}),
         EV("2026-08-20", "部门反馈", "赵工", "研发反馈：临床阶段数据完备，可提交注册", "研发项目单"),
         EV("2026-09-20", "活动", "周敏", "注册资料确认：客户确认可用于其注册申报", "活动"),
         EV("2026-10-03", "活动", "周敏", "转产准备：产线验证启动", "活动")]},
    # ===== 国际业务部 =====
    {"dept": "国际业务部", "cid": "待关联", "cust": "海外经销商 BioSun Trading（示例）",
     "opp": "核酸提取试剂盒 OEM 询价", "sales": "Kevin", "scale": "US$60,000",
     "tech": "李工", "comp": "本地代理商（品牌H）", "unlinked": True,
     "nodes": {"陌生开发": nd("已完成", "2026-09-15", "2026-09-18", "Kevin"),
               "方案匹配": nd("进行中", "2026-09-25", None, "Kevin")},
     "res": {},
     "events": [
         EV("2026-09-18", "活动", "Kevin", "询价邮件 + 视频会议：确认 OEM 试剂盒规格", "活动"),
         EV("2026-09-25", "待办发起", "Kevin", "国际业务准备 OEM 报价与技术规格书", "其他",
            todo={"事项": "出 OEM 报价单 + 规格书", "负责人": "Kevin", "时间节点": "2026-10-10", "状态": "进行中"}),
         EV("2026-10-06", "活动", "Kevin", "方案匹配：发送技术规格书，等待客户反馈", "活动")]},
    {"dept": "国际业务部", "cid": "待关联", "cust": "NordiLab Diagnostics（示例）",
     "opp": "RT-qPCR 全预混体系 OEM 询价", "sales": "Kevin", "scale": "US$95,000",
     "tech": "李工", "comp": "本地代理商（品牌I）", "unlinked": True,
     "nodes": {"陌生开发": nd("已完成", "2026-07-01", "2026-08-20", "Kevin", unlinked=True),
               "方案匹配": nd("卡住", "2026-09-01", None, "Kevin")},
     "res": {},
     "events": [
         EV("2026-07-01", "活动", "Kevin", "首次接触：客户明确全预混 OEM 需求", "活动"),
         EV("2026-08-20", "部门反馈", "Kevin", "陌生开发完成：客户进入供应商名单（客户编码待关联）", "其他"),
         EV("2026-08-25", "待办发起", "Kevin", "研发提供全预混样品用于客户内部测试", "研发项目单",
            todo={"事项": "寄送全预混样品 2 套", "负责人": "李工", "时间节点": "2026-09-15", "状态": "已完成"}),
         EV("2026-09-01", "活动", "Kevin", "方案匹配启动：对接客户采购与技术", "活动"),
         EV("2026-09-20", "其他", "Kevin", "客户方采购负责人变动，方案匹配停滞（卡住）", "其他")]},
]

ACTIVITIES = [
    {"actId": "ACT-20261010-001", "dept": "大客户部", "cid": "6878118191",
     "cust": "圣湘生物科技股份有限公司", "opp": "多重呼吸道病原体冻干体系开发",
     "sales": "赵云浩", "tech": "李工", "scale": "¥1,200,000",
     "date": "2026-10-10", "contact": "张工", "order": "SO-20261009-006",
     "type": "测试结果跟进",
     "desc": "【客户需求】\n· 应用场景：冻干体系放大   · 技术平台：冻干/RT-qPCR\n· 项目状态：研发   · 时间节点：2026-10-20\n· 参数要求：37℃ 加速 7 天 Ct 漂移≤1；批内 CV≤3%\n· 竞对情况：品牌A 同条件 Ct 漂移 0.9\n"
             "【宝锐方案】\n· 货号+名称：MD2104 冻干保护剂   · 关联订单/测试单编号：SO-20261009-006\n· 测试条件：体系/仪器/程序：一步法 RT-qPCR / ABI 7500 / 标准程序\n"
             "【测试结果】（逐项：指标 → 实测值 → 判定）\n· 1. A/B 组 Ct 漂移 0.6 → 通过   · 2. C 组 Ct 漂移 1.4 → 超阈\n· 结论：超阈（偏差方向：Ct 漂移偏大 +0.4）\n"
             "【问题现象】客户原话：“C 组第 6 天开始曲线抬不起来，怀疑保护剂析晶。”\n"
             "【客户反馈 / 下一步诉求】认可 A/B 组，要求就 C 组补一批调整海藻糖配比后复测。\n"
             "【待办事项】研发按新配比重做 C 组 1 批并附加速数据；负责人 王工；2026-10-20",
     "todos": [{"item": "研发按新配比重做 C 组配方 1 批并附加速数据", "owner": "王工", "due": "2026-10-20"}],
     "supportTypes": [{"type": "研发项目单", "note": ""}]},
    {"actId": "ACT-20261009-002", "dept": "销售拓展部", "cid": "6878118250",
     "cust": "广州达安基因股份有限公司", "opp": "数字PCR扩增酶体系开发",
     "sales": "陈嘉怡", "tech": "黄工", "scale": "¥540,000",
     "date": "2026-10-09", "contact": "赵经理", "order": "SO-20261008-003",
     "type": "采购跟进",
     "desc": "【采购阶段】\n· 当前阶段：比价   · 时间节点：2026-10-18\n"
             "【预算与数量】\n· 预算：¥540,000   · 预计数量：500 U×4\n· 参数要求：扩增效率≥97%、微滴分离度良好\n"
             "【决策链与关键人】\n· 决策人：采购总监 赵经理 / 使用人：数字 PCR 平台 张主管\n"
             "【竞对报价与差距】\n· 竞品：品牌C 报价 1,180 U/¥；我方差距：交期略长\n"
             "【下一步动作与时间】\n· 动作：推动二次报价   · 负责人：陈嘉怡   · 时间节点：2026-10-18",
     "todos": [{"item": "跟进客户数字 PCR 验证进度并推动二次报价", "owner": "陈嘉怡", "due": "2026-10-18"}],
     "supportTypes": [{"type": "技术支持测试项目单", "note": ""}, {"type": "售后", "note": ""}]},
]


# ═══════════════════════════════════════════════════════════════════
# §13.1 商机性质标记（机会性 / 持续性 / 待判）· opportunities.nature
# ═══════════════════════════════════════════════════════════════════
NATURE_ENUM = {"opportunistic": "机会性", "continuous": "持续性", "unknown": "待判"}
LOSS_N_MONTHS = 3           # 机会性商机连续 N 个月无活动/无订单 → 黄预警
LOSS_OVERDUE_DAYS = 60      # 过预计成交窗口 N 天仍未成交 → 红预警
LOSS_PORTFOLIO_RATIO = 30   # 机会性商机占本季度预计收入 > N% → 组合风险提示
NATURE_RULES = {
    "opportunistic": "满足任一：①该客户近 12 个月该品类订单 ≤1 单（无历史复购）；"
                     "②无框架/年度协议字段；③预计金额占客户年采购 >40% 且集中在单一项目",
    "continuous": "①近 12 个月该品类有稳定复购；②有框架/年度协议；"
                  "③预计金额分散、占客户年采购 ≤40%（三项均不满足机会性）",
    "unknown": "复购 / 框架 / 金额占比三项数据不足，待人工判定",
}
# 逐商机判定结果（示例值）· 依据见 NATURE_RULES
NATURE_MAP = {
    "多重呼吸道病原体冻干体系开发": ("continuous", "该客户近 12 个月同类冻干原料复购 ≥3 单，已签年度框架协议"),
    "磁珠法核酸提取原料替换评估": ("continuous", "磁珠原料为常态复购品类，近 12 个月 4 单，有年度采购框架"),
    "质控原料国产替代导入评估": ("opportunistic", "单一替代项目、预计金额占客户年采购 >40%；无历史复购、无框架协议"),
    "数字PCR扩增酶体系开发": ("continuous", "扩增酶为复购品类，近 12 个月同类订单 3 单，有框架协议"),
    "RT单管全预混体系开发": ("opportunistic", "新客户新产品导入，近 12 个月该类订单 0 单（无复购）、无框架协议"),
    "测序建库酶原料导入": ("opportunistic", "单一建库酶项目集中、无复购记录、无框架协议"),
    "检验所原料国产化替换": ("continuous", "核酸提取原料常态复购，有年度框架协议"),
    "mRNA 原料试用": ("unknown", "新课题组试用，复购/框架/金额占比三项数据均不足，待人工判定"),
    "质控用酶原料导入验证": ("continuous", "质控酶有年度框架协议与复购记录"),
    "生物药QC残留DNA检测酶导入": ("continuous", "残留 DNA 检测酶为复购品类，有框架协议"),
    "生物药原料市场化供应": ("continuous", "年用量稳定、已进入转产，有年度框架协议"),
    "核酸提取试剂盒 OEM 询价": ("opportunistic", "海外一次性 OEM 询价、无历史复购、无框架协议"),
    "RT-qPCR 全预混体系 OEM 询价": ("unknown", "海外询价，复购与框架数据不足，待人工判定"),
}
for _r in RAW_ROWS:
    _nat, _basis = NATURE_MAP.get(_r["opp"], ("unknown", "数据不足，待人工判定"))
    _r["nature"] = _nat
    _r["nature_label"] = NATURE_ENUM[_nat]
    _r["nature_basis"] = _basis
    _r["nature_confirmed_by"] = "系统判定（示例）" if _nat != "unknown" else ""
    _r["nature_confirmed_at"] = "2026-10-11" if _nat != "unknown" else ""

# 机会性商机「上一年度未延续」历史（规则② 的输入 · 真实案例）
OPP_LOSS_HISTORY = [
    {"customer": "天隆集团", "opp": "HPV 海外检测原料供给（PFM4211）",
     "amount": "¥1,098,000", "nature": "opportunistic", "window": "2026-06",
     "status": "本年度未延续（客户市场开拓原因）",
     "reason": "上一年度机会性商机（2025 机会性 ¥109.8 万），本年度已过预计成交窗口 60 天仍未成交 → 断尾",
     "basis": {"商机性质": "机会性", "该客户近 12 个月该品类订单": "≤1 单（无历史复购）",
               "框架/年度协议": "无", "预计金额占客户年采购": ">40%（单项集中）",
               "预计成交窗口": "2026-06-30", "当前状态": "2026 未延续（客户市场开拓原因）",
               "2025 机会性金额": "¥1,098,000"}},
]

# ═══════════════════════════════════════════════════════════════════
# §13.2 客诉 → 替换 → 采购恢复 周期 · replacement_cases
# ═══════════════════════════════════════════════════════════════════
REPL_STAGES = ["客诉受理", "定位中", "替品确认", "送样验证", "替换完成", "采购恢复确认"]
REPL_YELLOW_DAYS = 30       # 停留某阶段 > 30 天 → 黄
REPL_RED_DAYS = 60          # 停留某阶段 > 60 天（超基线）→ 红
REPL_BASELINE_DAYS = 60     # 天隆 M5244-ZJ4 实测替换周期基线（天）
REPLACE_CASES = [
    {"case_id": "RC-202607-001", "customer": "天隆集团（西安天隆科技有限公司）",
     "ticket": "AS-20260701-012", "orig_sku": "M5244-ZJ4", "repl_sku": "MD2104-003006",
     "complaint_date": "2026-07-01", "affected_month": "2026-07",
     "replace_done_date": "2026-08-30", "purchase_recover_date": "2026-09-15",
     "status": "闭环（采购已恢复）",
     "note": "稳定性客诉 → 2026-08 月底以 MD2104-003006 完成替换，实测替换周期约 60 天，直接影响 Q3 采购",
     "stages": [{"name": "客诉受理", "in": "2026-07-01", "out": "2026-07-03"},
                {"name": "定位中", "in": "2026-07-03", "out": "2026-07-08"},
                {"name": "替品确认", "in": "2026-07-08", "out": "2026-07-15"},
                {"name": "送样验证", "in": "2026-07-15", "out": "2026-08-30"},
                {"name": "替换完成", "in": "2026-08-30", "out": "2026-09-15"},
                {"name": "采购恢复确认", "in": "2026-09-15", "out": "2026-09-15"}]},
    {"case_id": "RC-202608-002", "customer": "上海复宏汉霖生物技术股份有限公司",
     "ticket": "AS-20260805-003", "orig_sku": "1D303", "repl_sku": "1D305-000801",
     "complaint_date": "2026-08-05", "affected_month": "2026-08",
     "replace_done_date": None, "purchase_recover_date": None,
     "status": "进行中 · 送样验证",
     "note": "质控酶批间差客诉，替品已确认，送样验证中（停留已超 30 天 → 黄）",
     "stages": [{"name": "客诉受理", "in": "2026-08-05", "out": "2026-08-07"},
                {"name": "定位中", "in": "2026-08-07", "out": "2026-08-12"},
                {"name": "替品确认", "in": "2026-08-12", "out": "2026-08-20"},
                {"name": "送样验证", "in": "2026-08-20", "out": None},
                {"name": "替换完成", "in": None, "out": None},
                {"name": "采购恢复确认", "in": None, "out": None}]},
    {"case_id": "RC-202607-003", "customer": "百济神州（广州）生物科技有限公司",
     "ticket": "AS-20260718-007", "orig_sku": "MD3100-ZJ2", "repl_sku": "（待定）",
     "complaint_date": "2026-07-10", "affected_month": "2026-07",
     "replace_done_date": None, "purchase_recover_date": None,
     "status": "进行中 · 定位中",
     "note": "残留 DNA 检测酶结果偏差客诉，定位中停留已超 60 天（超基线）→ 红",
     "stages": [{"name": "客诉受理", "in": "2026-07-10", "out": "2026-07-12"},
                {"name": "定位中", "in": "2026-07-15", "out": None},
                {"name": "替品确认", "in": None, "out": None},
                {"name": "送样验证", "in": None, "out": None},
                {"name": "替换完成", "in": None, "out": None},
                {"name": "采购恢复确认", "in": None, "out": None}]},
]


def layer1_depts():
    """按部门分组、保持 DEPTS 顺序 → [{name, rows:[...]}]（表格视图与轨道视图共用）。"""
    out = []
    for d in DEPTS:
        rows = [r for r in RAW_ROWS if r["dept"] == d]
        if rows:
            out.append({"name": d, "rows": rows})
    return out


def flat_rows():
    """扁平化（与前端全局下标一致：先部门顺序、再行顺序）。"""
    out = []
    for d in DEPTS:
        out.extend([r for r in RAW_ROWS if r["dept"] == d])
    return out


def data():
    return {
        "layer1": {"depts": layer1_depts(), "nodeCols": NODE_COLS, "resCols": RES_COLS, "baseCols": BASE_COLS},
        "activities": ACTIVITIES,
        "meta": {"actTypes": ACT_TYPES, "supportTypes": SUPPORT_TYPES, "supportEnum": SUPPORT_ENUM,
                 "descTemplates": DESC_TEMPLATES, "reqSegs": REQ_SEGS,
                 "natureEnum": NATURE_ENUM, "natureRules": NATURE_RULES,
                 "lossNMonths": LOSS_N_MONTHS, "lossOverdueDays": LOSS_OVERDUE_DAYS,
                 "lossPortfolioRatio": LOSS_PORTFOLIO_RATIO, "oppLossHistory": OPP_LOSS_HISTORY,
                 "replStages": REPL_STAGES, "replaceCases": REPLACE_CASES,
                 "replYellowDays": REPL_YELLOW_DAYS, "replRedDays": REPL_RED_DAYS,
                 "replBaselineDays": REPL_BASELINE_DAYS},
        "nodes": NODE_COLS, "short": NODE_SHORT, "lanes": LANES, "today": TODAY,
        "depts": DEPTS, "usd_cny": USD_CNY,
    }


if __name__ == '__main__':
    import json
    d = data()
    fr = flat_rows()
    print('部门 %d / 商机 %d / 节点 %d / 泳道 %d' % (len(DEPTS), len(fr), len(NODE_COLS), len(LANES)))
    for g in d['layer1']['depts']:
        print('  %-12s %d 行' % (g['name'], len(g['rows'])))
    print('事件合计 %d 条' % sum(len(r['events']) for r in fr))
    print(json.dumps({'rows': len(fr)}, ensure_ascii=False))
