# 客户拉通大表 · 可复用数据源盘点

- 盘点日期：2026-10-10
- 盘点范围：`数据/` 全部 JSON、ERP 订单导出、飞书 MCP 管道、CloudBase 集合
- 原则：**只盘点、不改数据**。所有字段名逐字来自脚本或 JSON；读不出的写「未找到，待确认」。
- 目标表设计：客户 → 商机 → 每日活动 → 资源协助待办（研发立项 / 技术支持测试单 / 售后跟进）→ 部门反馈闭环 → 订单（测试 / 销售），落在 CloudBase，可延展至成交或闭环。

---

## §0 数据源总览（实测数量）

| 层 | 位置 | 数量（实测） | 命令 |
|---|---|---|---|
| 数据层（根 JSON） | `数据/*.json` | **146**（其中非 `_` 前缀的正式数据 **116**） | `ls -1 数据/*.json \| wc -l` = 146；`grep -v '/_'` = 116 |
| 数据层（含子目录） | `find 数据 -name '*.json'` | **1544** | （`pricing/` 一个目录就 1206 个价格 JSON） |
| 数据层脚本 | `数据/*.py` | 47 | |
| 脚本层 | `脚本/*.py` | 295 | |
| ERP 订单导出（诊断原料） | `~/Desktop/hermas输入-工作台数据库/月度销售额数据/销售订单_*.xlsx` | 最新 `销售订单_20260101-20260930.xlsx`（27 列） | |
| ERP 订单导出（生命科学） | `销售分析/源数据/生命科学销售订单_20261009_含作废终止标记.xlsx`（30 列，**含「商机(飞书)#」列**） | | |
| 飞书侧 | MCP `search_by_mql`（`project.feishu.cn/mcp_server/v1`） | 6 张工作项表 | 见 §2 |
| CloudBase | env `bier-sales-d0gatbvlx288724e9` | 4 个集合（`activities` / `todos` / `user_roles` / `visit_cards`） | 见 §3.3 |

`数据/` 顶层 28 个子目录（`activities`/`上传`/`orders_placed`/`月报库`/`pricing`/`market_intel`/`活动附件`/`源文件` 等）。**大部分为落地件、附件、备份**（`_backup`/`_archive`/`_bak_*`），拉通大表应只吃「根 JSON 的正式层」。

---

## §1 一盘清：A–G 表逐字段覆盖

图例：**✓已有**（附文件/字段名）· **◐部分**（覆盖或粒度不足）· **✗需新建** · **⧉需外部提供**

### 表 A · 客户主档

| 字段 | 状态 | 来源（文件 / 字段名 / 列） |
|---|---|---|
| 客户主键 | ✓已有 | 飞书客户表 `work_item_id`（`数据/mcp_customers.json` → `customers[].work_item_id`，例 `6878118191`）|
| 客户名 | ✓已有 | `mcp_customers.json` → `name`；`数据/customer_360.json` → `customers[].name` |
| 客户简称 | ✓已有 | `mcp_customers.json` → `field_c3224b`（例「越秀集团」）；`customer_360.json` → `customers[].short` |
| 所属集团 | ✓已有 | `数据/customer_360.json` → `customers[].group` / `groups[]`（1263 客户 / 126 集团）|
| 归属销售员 | ✓已有 | `数据/customer_sp_map.json` → `map{客户名:销售员}`（443 条）；`customer_360.json` → `customers[].sales`（权威口径 `sales_source`）|
| 部门 / 区域 | ✓已有 | 飞书客户 `field_5ed7ab`（`诊断原料销售拓展部`/`诊断原料大客户销售部`/`浙江`/`广东`，见 `脚本/patch_customer_scope.py:11`）|
| 客户等级 / 分级 | ✓已有 | `field_17186c`（tier，A类/…）+ `field_c8e80d`（level，见 `脚本/pull_customer_levels.py:41`）；`数据/customer_levels.json`（140 条）|
| 联系人 | ✓已有 | `数据/customer_contacts.json` → `contacts{客户名:[人名]}`（8804 人 / 1786 客户）；飞书客户 `field_79aa0b`（疑似联系人字段，**未确认**）|
| 客户状态 / 活跃度 | ✓已有 | `customer_360.json` → `latest_date`/`orders`/`total`；`数据/diag_customers.json` → `status` |
| 客户表所在空间/表 ID | ✓已有 | `6593cd71471290e3cc6be6e6`.`65ae1e403c87b152f3365ca6`（`脚本/销售工作台服务.py:2345`）|

**复用率 ≈ 85%**。缺口：客户「唯一编码」目前以飞书名或 `work_item_id` 为主，ERP 侧只有客户名（无编码），需靠名称归一（已有 `customer_sp_map`/`customer_360` 的简称与集团映射可用）。

### 表 B · 商机

| 字段 | 状态 | 来源 |
|---|---|---|
| 商机主键 | ✓已有 | 飞书 `story` 工作项 `work_item_id`（`数据/opps_by_customer.json` → `[].id`，例 `7038571941`；URL `story/detail/<id>`）|
| 商机名称 | ✓已有 | `opps_by_customer.json` → `name`；`ketizu_list.json` → 商机名称（含「课题组」）|
| 状态 | ✓已有 | `opps_by_customer.json` → `status`（沟通需求确认/.product试用中/订单签订中/实现销售）；飞书 `work_item_status`/`business`（`销售工作台服务.py:2349`）|
| 负责人 | ✓已有 | `opps_by_customer.json` → `creator`；飞书 `owner` |
| 关联客户 | ◐部分 | `opps_by_customer.json` **只按客户名分组**，无客户 `work_item_id` 字段落地 |
| 课题组模式 / 一级课题组 | ✓已有 | 飞书商机 `课题组模式`(=`field_d3c543`)、`一级课题组`(=`field_2c3bee`)（`脚本/pull_ketizu_list.py:86`；`脚本/_diag_ketizu_cov.py:30`）|
| 金额目标 | ◐部分 | `数据/opportunity_sandbox.json`（339 条，来自 `2026商机达成沙盘.xlsx`，口径=沙盘，非飞书）|
| 商机→客户显式外键 | ✗需新建 | 飞书商机表未拉「关联客户」字段，现无 ID 级外键 |

**复用率 ≈ 70%**。缺口：商机↔客户的**显式 ID** 未落地（现靠客户名分组）；商机金额两套口径（飞书/沙盘）需统一。

### 表 C · 每日活动

| 字段 | 状态 | 来源（逐字字段名） |
|---|---|---|
| 活动主键 | ✓已有 | 飞书活动 `work_item_id` → JSON `id` = `'FS'+work_item_id`（`脚本/pull_activities_mcp.py:181`；例 `FS7132642710`）|
| 日期 | ✓已有 | 飞书活动 `创建时间`→`date`；原字段 `start_time` / `活动日期 field_a2b3f6` |
| 销售 / 提交人 | ✓已有 | 飞书活动 `创建者`→`sales`/`submitted_by`；管道内 `owner` |
| 客户 | ◐部分 | 飞书活动 `活动关联客户`→`customer`（`pull_activities_mcp.py:185`）；底层字段 `field_5f20fc`，**读回只有 label（客户名），不是客户 work_item_id** |
| 活动形式 / 渠道 | ✓已有 | 飞书活动 `活动类型`(=`field_76654e`)→`channel`（offline/online/cold/joint）（`pull_activities_mcp.py:31-38`）|
| 活动细分类型 | ✓已有 | 飞书活动 `名称`→`type`（`pull_activities_mcp.py:189`）|
| 活动描述 | ✓已有 | 飞书活动 `活动描述`(=`field_b99055`)→`text` |
| 跟进部门 | ✓已有 | 飞书活动 `跟进部门`(=`field_5ed7ab`)→`dept_raw` |
| 活动→商机 / 订单 外键 | ✗需新建 | 活动中无「关联商机」「关联订单」字段（现有 `type` 仅名称文本）|

**复用率 ≈ 95%（字段层），关联层 ≈ 30%**。`数据/activities_feishu.json`（**6210 条**，2026-07~10 四个月，去重后）；`数据/daily_activities_mcp.json`（滚动近 70 天）。

### 表 D · 资源协助待办（研发立项 / 技术支持测试单 / 售后跟进）

| 字段 | 状态 | 来源 |
|---|---|---|
| 待办主键 | ✓已有 | `数据/todos.json` → `users{}`/`closed[].id`；CloudBase `todos` 集合（`user` 字段）|
| 待办文本 / 类型 / 范围 | ✓已有 | `todos.json` → `text`/`type`/`cat`/`scope`；口径常量 `TD_SCOPE`（`销售工作台服务.py:1151`，如 `external_test_req`/`external_project`）|
| 归属人 / 到期 / 客户 | ✓已有 | `todos.json` → `owner`/`due`/`client`；`source{kind:'activity', activity_id, customer, channel}`（`销售工作台服务.py:1160`）|
| 闭环字段 | ✓已有 | `todos.json` → `done`/`closed`/`conclusion`/`closedBy`；`closed[]` 归档 |
| 售后跟进（客诉单） | ✓已有 | `数据/mcp_complaints.json`（**365 条**）→ `id`(=飞书 work_item_id)/`customer`/`creator`/`status`/`urgency`/`node`/`days` |
| 技术支持测试单 | ◐部分 | `数据/test_reports/`（服务端 `TEST_REPORT_DIR`，当前 `_index.json` 空）；`数据/test_ledger.json`（SKU×客户台账：orders 563 / activities 159 / feedback 68）|
| 研发立项 | ◐部分 | `数据/feishu_blockers.json`（`project:'客户项目管理'`，14 条卡点）；立项原件在 ERP 输入侧 `NGS技术信息及立项信息`（**未入库，需外部提供**）|
| 待办↔活动/客诉闭环链 | ✓已有 | `source.activity_id` + `source{draft_id}` 幂等指纹 `_todo_fp`（`销售工作台服务.py:1124-1208`）|

**复用率 ≈ 55%**。缺口：研发立项无独立实体；测试单本地库为空、无飞书来源。

### 表 E · 部门反馈闭环

| 字段 | 状态 | 来源 |
|---|---|---|
| 反馈主键 / 内容 | ✓已有 | `数据/test_feedback_202608.json`（807KB，`raw`）；`数据/sr_review.json`（售后问题 review，含 `by_customer`/`by_category`）；`数据/sr_review_kac.json`（126 条，含 `activity_id`/`activity_date`/`salesperson`）|
| 反馈情绪 | ✓已有 | `test_ledger.json` → `feedback[].sentiment`；`数据/test_feedback.json` → `positives`/`negatives` |
| 闭环节点 / 状态 | ✓已有 | 客诉 `mcp_complaints.json` → `status`/`node`/`finish_time`/`finish_status`（飞书 `工作台活动类型…`，见 `销售工作台服务.py:2347`）|
| 部门反馈动作（谁回、回了没） | ◐部分 | 客诉有 `creator`/`node`；`todos` 有 `closedBy`/`conclusion`；**跨部门派单/回复表未建成** |
| 反馈↔活动↔客户 关联 | ✓已有 | `sr_review_kac.json` → `activity_id`/`activity_date`/`activity_owner`/`customer` |

**复用率 ≈ 45%**。缺口：真正的「部门反馈闭环」（谁接单→干了什么→回没回）无独立实体，现散在客诉 status + todos 归档。

### 表 F · 订单（测试 / 销售）

| 字段 | 状态 | 来源 |
|---|---|---|
| 订单主键（**对接键**） | ✓已有 | 飞书销售订单 `name`=单据编号（例 `ZHBR260907070`）；ERP `单据编号` 同值（**两侧靠它对接**）|
| 飞书工作项 ID | ✓已有 | 飞书订单 `work_item_id`（`数据/mcp_orders_input.json` → `工作项id`，例 `7109150199`）|
| 客户 | ✓已有 | 飞书 `购货单位#`(`field_e1001d`) / ERP `客户` 列 |
| 销售员 / 下单人 | ✓已有 | 飞书订单角色「下单人」(`get_workitem_brief`→`role_members`，`数据/pull_orders.py:189-196`) / ERP `销售员` 列 |
| 货号# | ✓已有 | 飞书 `field_8c4b7b` / ERP `货号#` 列 |
| 价税合计 | ✓已有 | 飞书 `金额`(`field_51d592`) / ERP `价税合计` 列 |
| 出货批号# | ✓已有 | ERP `出货批号#` 列；飞书 `field_8d9153`（值形如 `20260417`，**待确认对应关系**）|
| 备货部门 | ✓已有 | ERP `备货部门` 列 |
| 产品类型# / 产品大类# | ✓已有 | ERP `产品类型#`/`产品大类#` 列 |
| 业务关闭 | ✓已有 | ERP `业务关闭` 列；`关闭状态` 列 |
| 单据类型（测试/销售） | ✓已有 | 飞书 `field_a6e501`（`标准销售订单`/`测试销售订单`/`预测订单`，`数据/_archive/mcp_orders.json`）；ERP `单据类型` 列 |
| 是否赠品 / 发货日期 / 物流单号 | ✓已有 | ERP `是否赠品`/`发货日期`；飞书 `field_96a245`(物流单号)/`field_98f9e8`(发货日期) |
| 订单→商机/课题组 外键 | ◐部分 | 生命科学订单有 `商机(飞书)#` 列（课题组名）；诊断原料 ERP 无此列；飞书 `field_2e119c 关联一级课题组` **覆盖仅 1.5%** |

**复用率 ≈ 95%**。`数据/orders_detail_2026.json`(8.4MB)/`orders_2026_index.json`/`mcp_orders_input.json`(975 条) 与 ERP xlsx 双通道。

### 表 G · 拉通视图（客户→商机→活动→待办→反馈→订单）

| 字段 | 状态 | 来源 |
|---|---|---|
| 全表 | ✗需新建 | 现有各表可 join，但**没有一张统一视图**；建议落 CloudBase 或 `数据/customer_thread_view.json` |
| 客户统一键 | ◐部分 | 飞书客户 `work_item_id` 有；ERP 只有客户名 → 需 `customer_sp_map`/`customer_360` 做名称归一 |
| 商机统一键 | ◐部分 | 商机 `work_item_id` 有；但活动/订单未挂商机 ID |
| 订单统一键 | ✓已有 | 单据编号（两侧一致，现成对接键）|

**复用率 ≈ 40%**（数据都在，缺统一视图与跨表外键）。

---

## §2 飞书可调用 ID 清单（逐字字段名 + 逐条证据）

> MCP 工具：`search_by_mql`（查）、`get_workitem_brief`（取工作项角色/明细）、`create_workitem`（建工作项）。
> 端点：`POST https://project.feishu.cn/mcp_server/v1`，Header `X-Mcp-Token: <TK>`（`数据/mcp_activities_pipeline.py:12`）。
> Token（逐字）：`TK = "m-abfb29e8-3104-434f-9944-8d0bb592f8cd"`（`mcp_activities_pipeline.py:12`）。
> 销售管理空间 key：`6593cd71471290e3cc6be6e6`（`mcp_activities_pipeline.py:13` / `销售工作台服务.py:50 MCP_PK`）。

| ID 名称 | 字段名（逐字） | 来源（脚本:行 或 JSON 路径） | 能关联到哪张表 | 覆盖率 / 备注 |
|---|---|---|---|---|
| 销售管理空间 | `6593cd71471290e3cc6be6e6` | `销售工作台服务.py:50`（MCP_PK） | 全部飞书表 | — |
| 售后管理空间 | `658288abfb8bd616b17025f1` | `数据/pull_complaints.py:11`（PK） | 客诉 | — |
| 客户 · 工作项类型 | `65ae1e403c87b152f3365ca6` | `销售工作台服务.py:2345`；`数据/_recover_pull_offset.py:13`（TYPE_CUSTOMER） | 表A客户 | — |
| 销售订单 · 工作项类型 | `662612aea6bb7089fea044ef` | `销售工作台服务.py:2346` | 表F订单 | — |
| 活动 · 工作项类型 | `65ae1e5d44338dbe7c39a29a` | `销售工作台服务.py:2348,3423`；`销售工作台服务.py:51`（ACTIVITY_TYPE_KEY）；`_recover_pull_offset.py:14`（TYPE_ACTIVITY） | 表C活动 | — |
| 客诉(客户反馈单) · 工作项类型 | `6669433056a98249604376de` | `销售工作台服务.py:2347` | 表D/E售后 | 空间=`658288abfb8bd616b17025f1` |
| 商机 · 工作项类型 | `story` | `销售工作台服务.py:2349,3424` | 表B商机 | 类型名即 `story` |
| 客户 · 工作项 ID | `work_item_id` | `数据/mcp_customers.json` → `customers[].work_item_id`（`pull_customers.py:48` SELECT 含 `work_item_id`） | 表A/B/F | — |
| 客户 · 名称 | `name` | 同上 | 表A | — |
| 客户 · 部门/区域 | `field_5ed7ab` | `pull_customers.py:48`；语义见 `脚本/patch_customer_scope.py:11` | 表A/C | — |
| 客户 · 等级(tier) | `field_17186c` | `pull_customers.py:48`；`脚本/pull_customer_levels.py:41` | 表A | — |
| 客户 · 分级(level) | `field_c8e80d` | `pull_customers.py:48`；`脚本/pull_customer_levels.py:41` | 表A | — |
| 客户 · 简称? | `field_c3224b` | `pull_customers.py:48` | 表A | **语义待确认**（值如「越秀集团」）|
| 客户 · 联系人数? | `field_8c870a` | `数据/pull_customers.py:48`；`_recover_pull_offset.py:177` | 表A | **语义待确认**（文件注释称「联系人数量」）|
| 订单 · 单据编号（**对接键**） | `name` | `数据/pull_orders.py:75,266` | 表F → ERP | — |
| 订单 · 工作项 ID | `work_item_id` | `pull_orders.py:75,272` | 表F | — |
| 订单 · 购货单位/关联客户 | `field_e1001d` | `pull_orders.py:75,249` | 表F→A | — |
| 订单 · 单据类型 | `field_a6e501` | `数据/_archive/mcp_orders.json` | 表F | 标准/测试/预测销售订单 |
| 订单 · 客户(名称) | `field_2d6367` | `_archive/mcp_orders.json` | 表F | — |
| 订单 · 合同日期 | `field_2e095b` | `_archive/mcp_orders.json` | 表F | — |
| 订单 · 合同编号# | `field_4a1b47` | `pull_orders.py:75,279` | 表F | — |
| 订单 · 物料编码 | `field_29d947` | `_archive/mcp_orders.json` | 表F | — |
| 订单 · 物料名称 | `field_3a3ccb` | `_archive/mcp_orders.json` | 表F | — |
| 订单 · 货号# | `field_8c4b7b` | `_archive/mcp_orders.json`；`pull_customers.py` 同族 | 表F | — |
| 订单 · 规格 | `field_5b2b3b` | `_archive/mcp_orders.json` | 表F | — |
| 订单 · 销售单位 | `field_dbd4ab` | `_archive/mcp_orders.json` | 表F | — |
| 订单 · 税率% | `field_a98458` | `_archive/mcp_orders.json` | 表F | — |
| 订单 · 金额/价税合计 | `field_51d592` | `pull_orders.py:75,271` | 表F | — |
| 订单 · 物流单号 | `field_96a245` | `pull_orders.py:75,270` | 表F | — |
| 订单 · 发货日期 | `field_98f9e8` | `pull_orders.py:75,286` | 表F | — |
| 订单 · 备注(内部)# | `field_5e49e1` | `pull_orders.py:75,282` | 表F | 合并发货信息在此 |
| 订单 · 备注(客户)# | `field_a1ada1` | `pull_orders.py:75,283` | 表F | — |
| 订单 · 收货人电话# | `field_3f38c1` | `pull_orders.py:75,275` | 表F | — |
| 订单 · 收货人姓名 | `field_0e6822` | `pull_orders.py:75,276` | 表F | — |
| 订单 · 收货地址 | `field_290c00` | `pull_orders.py:75,277` | 表F | — |
| 订单 · 关联一级课题组 | `关联一级课题组`(`field_2e119c`) | `脚本/pull_order_ketizu.py:106,136` | 表F→课题组 | **⚠ 覆盖仅 1.5%**（1037 单仅 16 单有值，`_bak_order_ketizu_map_20261008.json`）|
| 订单 · 下单人角色 | `role_members.name=='下单人'` | `数据/pull_orders.py:182-196`（`get_workitem_brief`） | 表F | 缓存 `数据/order_role_cache.json` |
| 活动 · 工作项 ID | `work_item_id` | `脚本/pull_activities_mcp.py:133,181` | 表C（`id='FS'+wid`）| — |
| 活动 · 名称 | `名称` | `pull_activities_mcp.py:132,189` | 表C | — |
| 活动 · 活动类型（渠道） | `活动类型`(`field_76654e`) | `pull_activities_mcp.py:132,176`；`销售工作台服务.py:1016,3382` | 表C | 选项：线下拜访/线上跟进/陌拜/协同拜访 |
| 活动 · 活动关联客户 | `活动关联客户`(`field_5f20fc`) | `pull_activities_mcp.py:132,185`；`_pull_segmented.py:47` | 表C→A | **读回仅 label（客户名），非客户 work_item_id** |
| 活动 · 跟进部门 | `跟进部门`(`field_5ed7ab`) | `pull_activities_mcp.py:132,188` | 表C | — |
| 活动 · 创建者 | `创建者`(管道 `owner`) | `pull_activities_mcp.py:132,178` | 表C→人 | — |
| 活动 · 创建时间 | `创建时间`(`start_time`) | `pull_activities_mcp.py:132,182` | 表C | — |
| 活动 · 活动描述 | `活动描述`(`field_b99055`) | `pull_activities_mcp.py:132,191`；`销售工作台服务.py:3386` | 表C | — |
| 活动 · 活动日期 | `field_a2b3f6` | `销售工作台服务.py:3383`（写入 ms 时间戳）| 表C | — |
| 商机 · 名称 | `名称` | `脚本/pull_ketizu_list.py:86,109` | 表B | — |
| 商机 · 课题组模式 | `课题组模式`(`field_d3c543`) | `pull_ketizu_list.py:86,111`；`_diag_ketizu_cov.py:30` | 表B | — |
| 商机 · 一级课题组 | `一级课题组`(`field_2c3bee`) | `pull_ketizu_list.py:86,111`；`_diag_ketizu_cov.py:30` | 表B | 课题组 1744 个（`ketizu_list.json`）|
| 商机 · 工作项 ID | `work_item_id` | `销售工作台服务.py:2349`（SELECT 含 work_item_id，类型 `story`）| 表B | — |
| 客诉 · 工作项 ID | `work_item_id` | `数据/pull_complaints.py:80` | 表D/E | — |
| 客诉 · 创建人/状态/节点 | `field_3ce9fe` / `work_item_status` / `field_ac5caf` | `pull_complaints.py:80`；`销售工作台服务.py:2347` | 表D/E | `mcp_complaints.json` 365 条 |
| 商机视图（MCP） | 视图 `_rz-opbvg` | `脚本/build_ls_from_orders.py:39,194`；`脚本/gen_promo_tier_xlsx.py:327` | 表B/课题组 | 用于课题组「在谈状态 + region」|

### §2.1 飞书侧弱覆盖字段（已求证）

| 字段 | 覆盖率 | 证据 |
|---|---|---|
| 销售订单 `关联一级课题组`(`field_2e119c`) | **1.5%**（1037 单仅 16 单有值）| `数据/_bak_order_ketizu_map_20261008.json`（`orders:1037, with_ketizu:16, coverage:1.5`）；`脚本/_diag_ketizu_cov.py:31` |
| 活动 ↔ 客户 ID 级外键 | **弱** | `field_5f20fc` 读回仅 `{label:客户名}`（`脚本/_act_trend_da_sep.py:80,122`；`build_feishu_activities.py:57`），非客户 `work_item_id` |
| 商机 ↔ 客户 ID 级外键 | **缺** | `销售工作台服务.py:2349` 商机 SELECT 未含「关联客户」字段 |
| 活动 ↔ 商机/订单 外键 | **缺** | 活动表无此类字段 |

> 说明：现有「课题组」覆盖（**88.68%**）不是靠飞书 `field_2e119c`，而是靠 **ERP 生命科学订单的 `商机(飞书)#` 列**（`数据/order_ketizu_map.json`：`orders:1714, with_ketizu:1520, coverage:88.68`，`src='ERP 生命科学销售订单 商机(飞书)#（含课题组）'`）。**勿重跑 `pull_order_ketizu.py`**，会把 88.68% 覆盖回 1.5%（见该文件 `_note`）。

---

## §3 主键与关联设计建议

### 3.1 客户主键
- **推荐主键**：飞书客户 `work_item_id`（数字，唯一，已有）。
- **落库辅助键**：客户全名 `name`（归一键）。ERP 侧只有客户名 → 用 `数据/customer_sp_map.json`（443）+ `customer_360.json`（1263 客户 / 126 集团）做**名称→标准名**归一。
- 简名/集团：`customer_360.short`（简名）、`customer_360.group`（集团），可做二级主键。

### 3.2 商机主键
- **推荐主键**：飞书商机 `story.work_item_id`（例 `7038571941`）。
- **课题组商机**：`ketizu_list.json` 的课题组名（1744 个）作为业务键，`模式`(一类/二类/三类)+`一级` 为属性。

### 3.3 ERP ↔ 飞书对接键（已知）
- **订单靠「单据编号」对接**：飞书 `销售订单.name` == ERP `单据编号`（例 `ZHBR260907070`），**两侧完全一致，现成对接键**。
- 参考：`脚本/gen_activity_entry_docs.py:242`「订单取 ERP、客户/商机取飞书；两边靠单号与工作项 id 关联」。
- 订单→课题组：生命科学走 ERP `商机(飞书)#` 列；诊断原料无此列（需 `order_ketizu_map` 或飞书补齐）。

### 3.4 活动 ↔ 客户/商机
- 现状：活动只带客户**名**（`field_5f20fc` label），无客户 ID。
- 建议：落库时用「活动关联客户名 → 客户主键」映射补一列 `customer_id`；活动↔商机需新建关联（当前无字段，需产品部在飞书加字段，或按客户+时间近似）。

### 3.5 CloudBase 集合（实测，代码可 grep）
| 集合 | 用途 | 证据 |
|---|---|---|
| `activities` | 活动录入（服务端本地 `数据/activities/` 同步） | `脚本/销售工作台服务.py:700` `ACT_CB_COLLECTION = 'activities'`；插入 `:1237` |
| `todos` | 共享待办 | `销售工作台服务.py:1082`（update todos）；`工具/index.html:3620,3656,3659,3661` `cbDb.collection('todos')` |
| `user_roles` | 用户/角色/权限 | `销售工作台服务.py:1375`（TableName user_roles）；`工具/index.html:1546` `cbDb.collection('user_roles')` |
| `visit_cards` | 拜访卡 | `工具/index.html:5170,5328` `cbDb.collection('visit_cards')` |
- CloudBase env：`bier-sales-d0gatbvlx288724e9`（`cloudbaserc.json`；`销售工作台服务.py:660` `CB_ENV`）。
- 集合结构（activities 记录）：`id/created/submitted_by/sales/type/subtype/customer/contact/date/channel/order_no/sku_count/att_count/opp_count/block_count`（`销售工作台服务.py:1226-1228` `_index` 索引字段）。当前 `数据/activities/_index.json` 为空（该功能尚未产生记录）。
- **注意**：仅 4 个集合在代码中出现；控制台可能还有隐藏集合，**未找到证据的记为「待确认」**。

---

## §4 缺口清单

### 必需（拉通表跑不通）
1. **统一拉通视图/集合**（表G）—— 现在不存在。
2. **活动 → 客户 ID 外键**（`customer_id`）—— 现仅客户名。
3. **活动 / 订单 → 商机 ID 外键** —— 现活动无商机字段。
4. **商机 → 客户 ID 外键** —— 现仅按客户名分组。
5. **研发立项实体** —— 无独立表（仅 `feishu_blockers.json` 14 条卡点 + ERP 侧 xlsx）。
6. **部门反馈闭环实体**（谁接单→动作→回复）—— 现散在客诉 status + todos 归档。

### 可选（增强）
7. 客户唯一编码（现状：飞书 `work_item_id` / 名称归一）。
8. 技术支持测试单入库（`数据/test_reports/` 目前空）。
9. 客诉/反馈 → 订单 直连（现靠客户名近似）。
10. 商机金额统一口径（飞书 vs `opportunity_sandbox.json` 沙盘）。

---

## §5 待确认项（需用户 / 产品部 / 销售运营确认）

1. **飞书可写性**：拉通表若要回写飞书（活动/待办），需确认 `create_workitem` 可用的 `work_item_type` 与 `template_id`（现 `销售工作台服务.py:3392-3397` 用 `template_id:'700373'`，是否通用？）。
2. **客户 ID 是否随活动返回**：`field_5f20fc` 读回只有 label。需确认 MQL 是否有取客户 `work_item_id` 的写法（如关系字段的 key）。
3. **商机「关联客户」字段名**：是否存在于 `story` 表、字段名是什么 —— 未找到证据。
4. **课题组口径**：以 `ketizu_list.json`（1744，飞书商机）还是 ERP `商机(飞书)#`（88.68% 覆盖）为准？
5. **MCP 拉取上限**：`search_by_mql` 的 `LIMIT/OFFSET ≤ 1000`（`数据/pull_orders.py:17-19`）→ 大表需分段拉取，确认落地频率与窗口。
6. **CloudBase 其余集合**：控制台是否还有未在代码出现的集合（如 expense 等）——代码里未见，待确认。
7. **客户表若干字段语义**：`field_c3224b`/`field_8c870a`/`field_79aa0b`/`field_e62869`/`field_6415cf` —— 逐字字段在用，但中文标签未在代码中写明，**待确认**。
8. **订单 `field_8d9153` 是否= 出货批号#** —— 值形如日期（`20260417`），与 ERP `出货批号#` 对应关系待确认。

---

## 附录 A · 验收证据（grep -n 真实片段）

### A1 · `数据/` JSON 清单（实测）
```
数据/ root *.json (all):      146
数据/ root non-underscore:      116
数据/ recursive *.json:     1544
脚本/*.py count:  295
数据/*.py count:   47
```
（`ls -1 数据/*.json | wc -l` / `| grep -v '/_' | wc -l` / `find 数据 -name '*.json' | wc -l`）

### A2 · 飞书表/空间 ID 与字段（`销售工作台服务.py`）
```
2345: 'customers':  'SELECT `name`,`work_item_id`,`field_17186c`,`field_6415cf`,`field_5ed7ab`,`field_2d5b6a`,`field_e62869`,`field_c3224b`,`field_c8e80d`,`field_a1ff52`,`field_79aa0b`,`owner`,`start_time`,`business`,`current_status_operator` FROM `6593cd71471290e3cc6be6e6`.`65ae1e403c87b152f3365ca6`'
2346: 'orders':     'SELECT `name`,`work_item_id`,`field_e1001d`,... FROM `6593cd71471290e3cc6be6e6`.`662612aea6bb7089fea044ef`'
2347: 'complaints': 'SELECT ... FROM `658288abfb8bd616b17025f1`.`6669433056a98249604376de`'
2348: 'activities': 'SELECT `name`,`work_item_id`,`field_b54da4`,`field_b99055`,`field_76654e`,`field_a2b3f6`,`field_5f20fc`,`field_5ed7ab`,`field_d6ee93`,`owner`,`start_time`,`business`,`current_status_operator`,`work_item_status` FROM `6593cd71471290e3cc6be6e6`.`65ae1e5d44338dbe7c39a29a`'
2349: 'opportunities': 'SELECT `name`,`work_item_id`,`owner`,`start_time`,`business`,`current_status_operator`,`work_item_status`,`description` FROM `6593cd71471290e3cc6be6e6`.`story`'
```

### A3 · 活动字段（逐字）
```
脚本/pull_activities_mcp.py:132:  mql = ("SELECT `名称`, `活动类型`, `活动关联客户`, `跟进部门`, `创建者`, `创建时间`, "
脚本/pull_activities_mcp.py:133:         "`活动描述`, work_item_id FROM `销售管理`.`活动` "
脚本/pull_activities_mcp.py:181:  'id': 'FS%s' % (r.get('工作项id') or ''),
脚本/pull_activities_mcp.py:31:  # 活动类型（field_76654e）→ 归一渠道
```

### A4 · 订单字段映射（`数据/pull_orders.py`）
```
75: COLUMNS = 'name, start_time, field_96a245, field_e1001d, field_51d592, work_item_id, owner, field_3f38c1, field_0e6822, field_290c00, field_5ed7ab, field_4a1b47, field_98f9e8, field_5e49e1, field_a1ada1'
82: mql = f"SELECT {COLUMNS} FROM `销售管理`.`销售订单` WHERE ..."
265-287: output.append({'单据编号': name, '购货单位#': field_e1001d, '物流单号': field_96a245, '金额': field_51d592, '工作项id': work_item_id, ...})
```

### A5 · 商机 / 课题组
```
脚本/pull_ketizu_list.py:86:  MQL = "SELECT `名称`, `课题组模式`, `一级课题组` FROM `销售管理`.`商机`"
脚本/_diag_ketizu_cov.py:30:  print('  ② 商机(story) 本身 = 课题组，带 field_d3c543 课题组模式(一类/二类/三类)、field_2c3bee 一级课题组')
脚本/_diag_ketizu_cov.py:31:  print('  ③ 销售订单.field_2e119c 关联一级课题组 → 实测覆盖率 1.5%（几乎没填）')
脚本/pull_order_ketizu.py:106: MQL = ("SELECT `单据编号`, `关联一级课题组`, `合同日期`, `价税合计`, `是否赠品`, `单据类型` ...)
```

### A6 · CloudBase 集合名
```
脚本/销售工作台服务.py:700: ACT_CB_COLLECTION = 'activities'
脚本/销售工作台服务.py:1082: cmd = json.dumps({'update': 'todos', ...
脚本/销售工作台服务.py:1375: "TableName": "user_roles",
脚本/销售工作台服务.py:660: CB_ENV = "bier-sales-d0gatbvlx288724e9"
工具/index.html:1546: cbDb.collection('user_roles').limit(100).get()
工具/index.html:3620: return cbDb.collection('todos').where({user: userName}).get()
工具/index.html:5170: cbDb.collection('visit_cards').add(card)
工具/index.html:5328: cbDb.collection('visit_cards').where({salesperson: ...})
```

### A7 · ERP 列（实测 xlsx 表头）
```
诊断原料 销售订单_20260101-20260930.xlsx（27 列）:
合同日期,单据类型,单据编号,单据状态,客户,销售员,关闭状态,物料编码,物料名称,销售单位,销售数量,含税单价,税率%,价税合计,是否赠品,业务关闭,累计发货通知数量,累计出库数量,备货部门,发货日期,合同编号#,货号#,客户物料规格型号#,出货批号#,产品类型#,产品大类#,销售分组（二级）

生命科学 生命科学销售订单_20261009_含作废终止标记.xlsx（30 列，多出）:
商机(飞书)#,作废状态,业务终止,（其余同上）
```

### A8 · 覆盖率证据
```
数据/ketizu_list.json:  total_story=3446, ketizu_count=1744
数据/order_ketizu_map.json:  orders=1714, with_ketizu=1520, coverage=88.68, src='ERP 生命科学销售订单 商机(飞书)#（含课题组）'
数据/_bak_order_ketizu_map_20261008.json:  orders=1037, with_ketizu=16, coverage=1.5
```
