---
name: baoyu-dashboard-update
description: |
  宝锐生物综合管理仪表盘更新 — 涵盖销售数据、产品看板、客户分级、KPI考核、
  三年月度对比等多模块的数据注入。当用户提供工作汇报/ERP文件，要求更新仪表盘
  中任何模块时使用。支持从Excel提取结构化数据，用patch精准替换HTML中的JS数据数组。
triggers:
  - 用户说"更新仪表盘""更新销售仪表盘""仪表盘更新到X月"
  - 用户提供 .xlsx 文件要求更新仪表盘数据
  - 用户说"把X数据加进仪表盘""在仪表盘中展示X"
  - 月度/季度工作汇报后刷新仪表盘
  - 用户要求更新产品看板、客户分级、KPI考核、月度对比等特定模块
category: productivity
---


# 宝锐生物综合管理仪表盘月度更新


## 何时用

- 用户给出月度 ERP / 汇报 Excel，要求更新**综合管理仪表盘**的任一模块
- 仪表盘出现「数据有、页面看不到」，或同一客户/年份在不同模块数字不一致
- 需要**新增 / 删除**仪表盘 Tab 模块（含 Chart.js 图表 + 可筛表格）
- 更新 KPI 考核、三年月度对比、产品看板、客户维度价格、集团聚合等数据

**不适用**：纯页面样式改造 → `baoyu-workbench-theming`；数据管道每日刷新 → `baoyu-workbench-data-pipeline`。

## 仪表盘文件

**主仪表盘**（华为管理体系多模块版）：
`~/Desktop/Hermes输出-工作类/工具/仪表盘/宝锐综合管理仪表盘.html`

**诊断原料作战室**（四角色分权视图，JSON数据驱动）：
`~/Desktop/Hermes输出-工作类/诊断原料作战室.html`

⚠️ 仪表盘文件在 `工具/仪表盘/` 子目录下，工作台入口页链接必须用 `/工具/仪表盘/` 而非 `/仪表盘/`。HTTP 服务器 root = `~/Desktop/Hermes输出-工作类/`。

作战室维护见 `baoyu-html-audit`（Bug 模式 + 优化提升模式）。数据更新通过 `diag_*.json` 文件，重建流程见模式F。

包含：驾驶舱(月度批注)、销售LTC、产品看板(SKU TOP20+新品)、客户关系(集团ABC分级)、团队风控

**旧版V6**（已废弃，仅保留）：
`~/work/secure/销售支持部-秘书/销售仪表盘_V6.html`

## 数据源约定

**规范数据源文件（1-6月）**：
- `~/Desktop/Hermes输出-工作类/销售分析/2026订单分析/20260101-20260626销售客户.xlsx`
  - Sheet `1-6.26月`：16K+行明细，Col1=合同日期, Col5=区域, Col6=销售员, Col7=价税合计
  - Sheet `销售部门区域对应`：销售员→区域→部门 映射表
  - Sheet `2026对比2025`：部门/区域级双年对比汇总
- **大客户部映射**：文件用 `大客户1`/`大客户2` 编码 → 仪表盘用 `张立娅`/`赵云浩`

三种数据源模式：

**模式A：工作汇报Excel（用户发送 .xlsx 附件）**
- Sheet "汇总" 包含全量销售员数据
- 列映射：`销售部门 | 子部门 | 部门年度目标 | 部门年度达成 | 达成率 | 销售员 | 2026销售额目标 | 年度达成 | 1-6月目标/达成...`
- 使用 `data_only=True` 读取，用 `safe_int()` 处理 #N/A
- 姓名清洗：去掉 `\n`、`（离职）`、`(离职）` 等标记，status 单独提取

**模式B：结构化工作汇报文件夹**
- `~/work/secure/2026/{N}月工作汇报/`
- 不常用，优先使用模式A（用户直接发文件）

**模式C：仪表盘框架Excel（`仪表盘框架-*.xlsx`）— 新增**
- 文件命名：`仪表盘框架-YYYYMMDDlxy.xlsx`，用户半年度/季度汇报时发送
- Sheet1（主数据Sheet）列结构：
  - Row1: 部门级表头行（诊断原料销售部年度销售目标 | 年度达成 | 达成率% | 区域/部门 | ...）
  - Row2-3: 大客户部人员（部门列=大客户部）
  - Row4+: 其余人员（部门列=拓展部）
  - Col0-2: 整体汇总（目标/达成/率）
  - Col3-6: 区域/部门级（部门名/目标/达成/率）
  - Col7-10: 个人级（姓名/目标/达成/率）
  - Col11+: 月度目标/达成对（Col11=1月目标, Col12=1月达成, ..., Col22=6月达成, ...最多到Col34=12月达成）
- 姓名可能含标记：`（离职）`、`（6月底入职）` 等，需清洗分离为 name + status
- **年度达成列包含当前所有月度的累计**（如6月文件=1-6月合计）
- **月度达成对中12+n列是当月达成**（如Col12=1月达成, Col22=6月达成）
- 部门分类从原Excel的部门列 + 已知人名映射推导：
  - 大客户部 = {主云浩, 张立娅, 黄明月}
  - 销售拓展部 = {冯贤平, 刘虹晶, 路要峰, 宋学伟, 杨天雄, 陈溪子, 黄文才, 林潇, 龙友波, 任鑫, 李江辉, 吴云, 张文蝶, 徐卓凡}
  - 生命科学部 = {王慧洁, 刘静文, 王诗香, 韩远怀, 黄建华, 刘斌, 侯海瑜, 叶信福, 袁殿果}（浙江+广东合并）

**模式D：官方月度报告Excel（诊断原料部门）— 2026-07-04 新增**

用于诊断原料作战室数据更新。官方月度报告比ERP导出更丰富：含月度目标达成率、过程指标（拜访/测试/反馈）、部门三年逐月明细。

详见 `references/monthly-report-extraction.md`。

**模式E：原始表格式月度数据（非Excel文件）— 2026-07-04 新增**

当用户以纯文本/表格形式直接提供月度总额时（非Excel文件），更新连锁反应与从Excel提取不同——数据已是最终值，但大量的下游文件（部门级/个人级/客户级）无法刷新。详见 `references/raw-monthly-update-cascade.md`。

**模式F：从原始数据源完整重建 — 2026-07-04 新增**

**模式G：客户价格表生成（2026-07-10 新增）** — 从多年ERP数据提取单个客户的完整采购历史，按货号#聚合生成JSON+交互式HTML价格表。详见 `references/customer-pricing-pipeline.md`。

## 更新流程

### 1. 提取数据
- 读取用户发送的 `.xlsx` 文件，定位 "汇总" Sheet
- 遍历所有行，跳过表头，提取人员数据
- 用 `safe_int()` 处理 #N/A 等非数值
- 清洗姓名（去换行符、去离职标记），status 字段单独设置

### 2. 生成新 allSalesData 数组
- dept：从部门列推导，按框架或工作汇报中的部门字段归类。当前有效部门：大客户部 / 销售拓展部 / 生命科学部（浙江+广东已合并）
- name：清洗后的姓名，黄明月→张立娅 保持一行
- target / achieved / may：从表格提取
- rate = achieved/target*100（target为0时rate=0）
- risk：low(≥50) / medium(20-50) / high(<20)
- status：离职人员标注 '离职' 或 'X月离职'

### 3. 更新仪表盘HTML
用 patch 工具精准替换以下内容：

| 替换目标 | 内容 |
|---------|------|
| allSalesData 数组 | 完整替换，保留 sort 语句 |
| 驾驶舱 KPI | 总目标、已完成、完成率、剩余、日均 — 从新数据重算 |
| 风险预警人数 | 统计新数据 risk 分布（高/中/低） |
| 月度趋势图 | 5月达成值（汇总所有人员may字段） |
| 趋势线 | 累计值数组第5月更新 |

**不需要改的**：图表JS（部门聚合自动从allSalesData计算）、筛选/排序/批注/导出

### 4. 打开验证
- `open` 命令打开 HTML 确认正常
- 检查：KPI卡片数值一致、图表数据刷新、筛选功能正常

## 常见陷阱

> **📁 2024年数据矫正 + Patch工具转义完整文档**：`references/data-calibration-pitfalls.md`
> 含：ERP16K无2024数据矫正、王昕伟537w遗漏修复、patch `\\n`转义修复、HTTP服务器僵尸检查、新ERP列结构变化。

> **⚠️ YOY_GRADES 与 customerGroups 不同步（2026-07-07）**：仪表盘中有两套分级数据——`customerGroups`（集团级，line ~396）和 `YOY_GRADES`（客户异动明细用，line ~999）。YOY_GRADES 是手动维护的静态映射，容易过时。常见错误：非大客户部客户被标为KA（如九州通、宏微特斯、郑州纳克等）。修复时必须同时更新两套数据，以客户清单 Sheet 4 为铁律。
>
> **验证方法**：
> ```python
> import re
> with open('dashboard.html') as f: html = f.read()
> # 提取 YOY_GRADES 中所有 KA 条目
> yoy_match = re.search(r'var YOY_GRADES=(\{[^;]+)', html)
> ka_in_yoy = re.findall(r'"([^"]+)": "KA"', yoy_match.group(1))
> # 与客户清单 Sheet 4 比对
> # 合法KA：在Sheet 4中的集团 → OK
> # 非法KA：不在Sheet 4中 → 降级为A/B/C（按年化阈值）
> ```
>
> **⚠️ 遗漏KA**：部分KA集团可能未在 YOY_GRADES 中标为KA（如许昌学院=C、中道集团=B），需补标为KA。以客户清单为唯一标准。

**⚠️ 大客户部独立客户（集团简称为空）也是KA**：`diagnostic_customer_list.xls` Sheet 4 中部分客户没有集团简称（如杭州迪安生物、苏州华益美、北京明日达、郑州纳克、洛阳普泰、深圳新产业）。这些是独立客户而非集团成员，但仍属于大客户部 → 应标为KA。早期只统计了有集团名的KA导致这6家遗漏。正确做法：Sheet 4 中**所有行**（无论集团简称是否为空）都是KA。

**⚠️ 生命科学销售员部门归属（2026-07-07）**：`allSalesData` 中部分销售员的 `dept` 字段可能归属错误。典型案例：吴云在客户清单 Sheet 6 中属于「生命科学-浙江」，但仪表盘中曾被标为「销售拓展部」。必须以客户清单 Sheet 6 为准：生命科学-浙江={吴云, 王慧洁, 刘静文, 袁殿果, 侯海瑜, 叶信福}，生命科学-广东={韩远怀, 黄建华, 王诗香}。修正后需同步更新 deptOrder、图表颜色数组、筛选下拉选项。

- **综合仪表盘 vs 旧版V6**：主仪表盘现在是 `宝锐综合管理仪表盘.html`，不要再去更新 `销售仪表盘_V6.html`
- **黄明月→张立娅保持一行**：不要拆成两行，一行追踪全部
- **目标为0的人员不参与排序**：离职且target=0的人员 rate=0，排在末尾
- **新加入人员**：工作汇报中可能出现之前仪表盘没有的销售员（如吴云、韩远怀），正常加入数组
- **⚠️ \"只更新已有销售员\"模式（2026-06-28新增）**：用户有时明确要求\"之前在仪表盘上没有出现的销售员就不用统计他们的数据了\"。此时必须从新数据中过滤，只保留 `allSalesData` 中已存在的 name，新人不加入。判断依据是现有HTML中 `allSalesData` 数组的 name 字段。
- **⚠️ ERP pivot增量更新模式（2026-06-28新增）**：当用户提供的不是完整框架Excel而是简单的ERP透视表（2-Sheet：Sheet2=销售员→当月金额pivot，Sheet1=明细），只需更新 `may` 字段。计算方式：`new_achieved = old_achieved - old_may + new_may`，`new_rate = new_achieved / target * 100`。然后重新评估 risk（low≥50, medium 20-50, high<20）。同时更新KPI卡片、月度vals数组、风险人数统计。无需触碰 customerGroups、data3Y、图表初始化等其他模块。

- **⚠️ 大客户部名称映射（2026-06-28新增）**：ERP导出的原始订单文件中，大客户部销售员使用编码 `大客户1` 和 `大客户2`，而非 `张立娅` 和 `赵云浩`。从ERP文件提取数据时必须映射：
  ```python
  sp_map = {'大客户1': '张立娅', '大客户2': '赵云浩'}
  ```
  映射依据：`20260101-20260626销售客户.xlsx` 的 `销售部门区域对应` Sheet。不映射会导致张立娅/赵云浩5月数据归零。

- **⚠️ 部分月文件不应覆盖全量月数据（2026-06-28新增）**：当仪表盘已有完整1-6月数据，用户又发送仅含某月子集的ERP文件（如\"5月1～25日\"），**不要用部分数据覆盖全量数据**。正确做法：
  1. 先检查用户文件的时间范围（对比现有数据）
  2. 如果只是子集，询问用户是否有完整文件，或仅更新该月字段而不删除其他月
  3. 优先使用 `订单分析/20260101-20260626销售客户.xlsx` 作为1-6月规范数据源
  4. 如果被迫用了部分文件，等用户提供完整文件后立即回补

- **⚠️ 宝锐系内部采购必须剔除（2026-07-04）**：珠海宝锐生物科技、珠海横琴宝锐生物科技、珠海宝泰仪生物科技 三家为关联公司，内部采购不计入诊断原料销售额。当前累计约35.9w（全部来自刘新元，归属\"全国\"区域）。剔除后需同步更新：diag_customers.json（移除3条客户记录）、diag_monthly.json（逐月扣除）、diag_regions.json（对应区域扣除）、diag_salespeople.json（对应销售员扣除）、diag_summary.json（总营收+客户数重算）。排查方法：用 `'宝锐' in name or '宝泰仪' in name` 扫描所有数据文件中的客户名。\n\n- **⚠️ 王昕伟数据断层（2026-07-04）**：王昕伟仅出现在2024全年订单文件中（ERP16K和2026文件均无其记录），导致 build_diag.py 遗漏其537w核酸诊断订单。大客户1的2024年数据因此从537w→0。修复：直接从 `2024全年订单.xlsx` 按 `Col4=王昕伟 + Col25=核酸诊断` 提取全部577行，补入 diag_regions.json（大客户1 Y24）、diag_salespeople.json（新增王昕伟条目）、diag_monthly.json（逐月补入12个月数据）。\n\n- **⚠️ 数据断层手动修正通用模式（2026-07-04）**：当 build_diag.py 漏掉某销售员/某区域时，不要重新跑全量聚合（成本高且可能引入新错误）。定点修正四步骤：①从原始订单文件按条件提取全量（`销售员=X + 产品大类#=核酸诊断`）；②更新 diag_regions.json（对应区域的 Y24/Y25/Y26）；③更新 diag_salespeople.json（对应销售员）；④更新 diag_monthly.json（对应月份）。涉及区域归属变更时（如黄文才2024从华南2移至华中336w），同时调整双方区域。修正后验证区域三年合计与月度报告一致。\n\n- **⚠️ 月度vals的单位是万元（2026-06-28新增）**：`renderMonthNotes()` 中 `html += v + '万'` 直接拼接\"万\"字，因此 `var vals` 数组必须用万元值（整数），不能用元。例如：`[441,215,473,332,319,464]` 而非 `[4414943,2153881,...]`。
- **⚠️ KPI卡片是硬编码**：修改 allSalesData 后必须手动更新 KPI 卡片中的总目标/达成/完成率等数值
- **⚠️ 完成率≠同比增长率（2026-06-28新增）**：驾驶舱KPI的"▲ 32.8%"容易误导为增长率，实际是目标完成率。更新后应改为"完成率 32.8% ｜ 同比 +54.3%"双指标显示格式：
  ```html
  <!-- 旧（易误解） -->
  <div class="sub" style="color:var(--ok);">▲ 32.8%</div>
  <!-- 新（清晰） -->
  <div class="sub">完成率 <b style="color:var(--ok);">32.8%</b> ｜ 同比 <b style="color:#28a745;">+54.3%</b></div>
  ```
  同比增幅 = (今年1-6月同口径26人合计 - 去年1-6月同口径26人合计) / 去年1-6月 × 100%。数据来自`20260101-20260626销售客户.xlsx`的`2025.1-6` Sheet，同口径指仪表盘当前26人（排除离职人员）。
- **图表自动重算**：renderCharts 中的部门聚合从 allSalesData 动态计算，但月度达成线和趋势线的具体数值数组需手动更新
- **风险预警数字**：驾驶舱中的高/中/低风险人数要重新统计后手动替换
- **避免过度更新**：只替换 allSalesData + KPI卡片 + 风险数字 + 图表月度值，不要修改筛选/批注/导出/产品看板等其他模块
- **safe_int 处理 #N/A**：openpyxl 的 data_only=True 模式下，含公式的单元格可能返回 "#N/A" 字符串，必须用 `safe_int()` 包装：
  ```python
  def safe_int(v):
      try: return int(v)
      except: return 0
  ```
- **姓名清洗**：去除 `\\n`、`\n`、`（离职）`、`(离职）` 等标记，拆分为 name 和 status 两个字段
- **总目标随文件变化**：新工作汇报可能包含目标调整（如袁殿果60→35万），每次更新时从数据重新计算，不依赖硬编码

- **⚠️ Patch工具 `\\n` 转义陷阱（2026-07-04 实战教训）**：当用patch工具修改HTML中`<script>`内的JS代码时，`new_string`中的真实换行符会被转义为字面字符串`\\n`（两个字符：反斜杠+n）。这**不会触发任何语法检查工具报错**——文件保存正常、div平衡正常——但浏览器加载时JS解析失败，页面空白。
  
  **症状**：`node --check` 提取JS时报 `Unexpected token` 或 `Invalid or unexpected token`，且错误行号指向被patch过的代码区域。
  
  **修复**：在所有patch操作完成后，用Python脚本将`<script>`标签内的`\\n`替换回真实换行符，然后重新验证：
  ```python
  import re
  with open('dashboard.html') as f: html = f.read()
  # 仅修复<script>标签内的转义换行
  def fix(m): return '<script>' + m.group(1).replace('\\n', '\n') + '</script>'
  html = re.sub(r'<script>(.*?)</script>', fix, html, flags=re.DOTALL)
  with open('dashboard.html', 'w') as f: f.write(html)
  ```
  **预防**：每次patch修改JS代码后，立即运行 `node --check` 验证。如果patch后JS语法从OK变成ERROR，优先排查`\\n`转义问题。

## 修改后验证清单

仪表盘HTML的JS错误不会在文件层面报错，必须在浏览器中打开才能发现。修改后必须执行以下验证：

### 自动化检查（在terminal中运行）
```bash
# 1. 变量重复声明检查
grep -c "var monthly_2025\|var monthly_2026\|var top20Data\|var top10TableData" dashboard.html
# 每个应恰好为1

# 2. 括号平衡检查
python3 -c "
h=open('dashboard.html').read()
print('{:',h.count('{'),'} /',h.count('}'))
"
# 差值应为0（在JS代码块中）

# 3. 图表初始化完整性
grep -c "echarts.init" dashboard.html     # 应与HTML中chart-*容器数一致
grep -n "})();" dashboard.html            # 不应有孤立的IIFE残骸

# 4. 全局变量残留检查
grep -c "warnFullData\|renderWarnTable\|chartWarnBar" dashboard.html  # 删除模块后应为0
```

### 数据一致性检查
- 关键集团（圣湘/天隆/丽珠）在 **所有模块** 中的年度金额必须一致
- 用 `grep` 检查：`grep '圣湘集团.*v26' dashboard.html` 应在top20Data和custTop50中返回相同数值
- 症状：同一客户在图表中显示85万，在表格中显示45万 → 数据源不统一

### JS语法验证（Node.js --check）
页面空白的常见原因：patch后遗留孤立代码片段导致JS解析失败。修复后用 `node --check` 验证：
```bash
python3 -c "
import re, subprocess, tempfile, os
html = open('dashboard.html').read()
m = re.search(r'<script>\s*(.*?)\s*</script>', html, re.DOTALL)
js = m.group(1)
with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
    f.write(js); tmp = f.name
r = subprocess.run(['node','--check',tmp], capture_output=True, text=True, timeout=10)
print('JS OK' if r.returncode==0 else f'JS ERROR:\n{r.stderr[:500]}')
os.unlink(tmp)
"
```
常见JS错误：
- `Unexpected token '='` → 孤立的 `= [` / `= {`（var被错误删除后残留）
- `Unexpected token ')'` → IIFE闭合残骸 `})();` 前缺 `(function(){`
- `Identifier has already been declared` → 变量重复声明

### 浏览器验证
- **⚠️ 打开HTML前先验证HTTP服务器健康（2026-07-04 实战教训）**：`curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:8765/"` 必须返回200。若返回000或"Empty reply from server"，进程已僵尸（`lsof`显示进程存在但不响应）——`lsof -ti:8765 | xargs kill` 后 `python3 -m http.server 8765` 重启。跳过此步直接 open 是"页面打开失败"的最常见根因。**此问题不影响文件本身**——JS语法、HTML结构、JSON数据可能全部正确，但僵尸服务器导致浏览器无法加载任何资源。
- 打开HTML后检查：图表渲染（无空白区域）、KPI卡片数值、表格排序/筛选、批注模式

## 其他注意事项
- 更新后核对：总目标=∑各部门目标，每次更新后数值可能不同（当前6650万）
- **跨模块数据一致性**：客户分析仪表盘和综合管理仪表盘应使用同一套聚合逻辑，避免出现同一客户两个不同金额
- 活跃销售员=有achieved>0或may>0的人数
- 图表数据用 `re.sub` + `re.DOTALL` 处理多行匹配
- 所有修改完成后用 `open` 打开验证

---

## 详细资料（按需查阅）

> 以下文件不在本 SKILL.md 内，需要时再读；正文只保留每次都会用到的部分。

- **`references/data-sources.md`** — 含 3 节：⚠️ ERP Sheet 陷阱（2026-07-10）、⚠️ 数据源碎片化问题（关键陷阱），导致同一客户/年份在不同模块中数值不一致。这种情况的根因和修复方法：、⚠️ 新ERP数据源：2025/2026客户价格.xlsx（2026-06-30 新增）
- **`references/group-mapping.md`** — 含 9 节：⚠️ H1-vs-H1 同比方法论陷阱（2026-06-30 新增）、客户调研报告年度同比更新（2026-06-30 新增）、区域同比更新：客户级6月数据映射、Top60集团聚合：多ERP源合并去重 + 仪表盘映射提取 — 2026-06-30 新增、客户集团v26增量更新（regex解析+匹配+回…
- **`references/history.md`** — 含 1 节：6月/半年度扩展清单（5→6月升级）
- **`references/module-recipes.md`** — 含 6 节：产品看板数据更新、异常预警 + 我的指标 JSON 生成（2026-07-01 新增）、客户维度价格看板（2026-06-30 新增）、KPI考核数据更新、三年月度对比数据更新、客户异动分析Tab模式（双年YoY + 批注 + 分级交叉）— 2026-06-27
- **`references/restructure-guide.md`** — 含 3 节：⚠️ Python批量替换陷阱（文件膨胀 — 2026-06-27 实战教训）、删除仪表盘模块的标准流程、新增Tab模块（含Chart.js图表+可筛表格）标准流程

### 既有参考文件（补齐索引，原先未被正文引用）

- **`references/customer-tier-system.md`** — 客户分级体系：KA/A/B/C → 大客户部/重点/非重点
- **`references/erp-column-mapping.md`** — ERP文件列结构差异（旧 vs 新）
- **`references/full-rebuild-from-sources.md`** — 从原始数据源完整重建 JSON 数据文件
- **`references/three-file-sync-update.md`** — 三文件交叉验证仪表盘更新模式
