# KPI 进度刷新管道（MCP 直连）

直接从飞书 MCP 拉取当月销售订单 → 按销售员汇总 → `kpi_progress.json` → CloudBase 部署。

## 管道流程

```
MQL SELECT name, field_51d592, work_item_id
  WHERE 创建时间 >= 'YYYY-MM-01'  ← 当月全量，不去重
    ↓
收集所有行（不去重！每行=不同货号，金额都要累加）
收集唯一单据编号 → oid_to_wid 映射
    ↓
对每个唯一单据编号，get_workitem_brief(role_7dd6e0) → 销售员
  ⚠️ 使用独立缓存 kpi_role_cache.json
    ↓
按销售员汇总所有行的价税合计
  ⚠️ 过滤 ¥0 条目（草稿/空订单）
    ↓
写入 kpi_progress.json → merge_workbench_data.py → CloudBase
```

## ⚠️ 缓存碰撞陷阱

**`order_role_cache.json` ≠ `kpi_role_cache.json`** — 两套缓存格式不同，不能共用！

| 缓存文件 | 来源 | 格式 | 角色 |
|---------|------|------|------|
| `order_role_cache.json` | `pull_orders.py` | `{name_cn, email}` | **下单人**（role: name="下单人"） |
| `kpi_role_cache.json` | KPI pipeline | `{salesperson}` | **销售员**（role: key="role_7dd6e0"） |

**症状**：KPI 脚本复用 `order_role_cache.json` → 4803 条全部命中缓存 → 但 `salesperson` key 不存在 → 所有订单销售员显示 "(未知)"。

**修复**：KPI 管道必须用独立的 `kpi_role_cache.json`，并检查缓存条目是否含有效 `salesperson` 字段：

```python
uncached = [oid for oid in all_unique_ids
            if oid not in role_cache
            or not (role_cache.get(oid) or {}).get('salesperson')]
```

## 脚本模板

```python
TK = "m-abfb29e8-3104-434f-9944-8d0bb592f8cd"
PK = "6593cd71471290e3cc6be6e6"
CACHE_FILE = "kpi_role_cache.json"  # 独立缓存，勿用 order_role_cache.json
MONTH = "2026-08"

# 1. MQL 拉取（不去重）
COLUMNS = 'name, field_51d592, work_item_id'
mql = f'SELECT {COLUMNS} FROM `销售管理`.`销售订单` WHERE `创建时间` >= "{MONTH}-01" ORDER BY `创建时间` ASC'

# 2. 收集所有行 + oid_to_wid 映射
for item in items:
    oid = item.get('name', '')
    amt = item.get('field_51d592', 0)  # double_value
    wid = item.get('work_item_id', '')
    all_rows.append({'name': oid, 'amount': amt})
    if oid and oid not in oid_to_wid and wid:
        oid_to_wid[oid] = str(wid)

# 3. 逐个 get_workitem_brief → role_7dd6e0
result = mcp("get_workitem_brief", {"project_key": PK, "work_item_id": wid})
attr = result.get('work_item_attribute', result)
for rm in attr.get('role_members', []):
    if rm.get('key') == 'role_7dd6e0':
        sp = rm.get('members', [{}])[0].get('name', '')

# 4. 汇总 + 过滤 ¥0
by_sp = {}
for row in all_rows:
    sp = role_cache[row['name']]['salesperson']
    if sp and row['amount'] > 0:
        by_sp[sp] = by_sp.get(sp, 0) + row['amount']

# 5. 输出
output = {"updated_at": ..., "total": sum(...), "by_salesperson": {sp: round(amt,2) for sp,amt in sorted(...) if amt>0}}
```

## 部署

```bash
# 单文件
tcb hosting deploy 数据/kpi_progress.json 数据/kpi_progress.json -e bier-sales-d0gatbvlx288724e9

# 合并文件（kpi_progress 包含在 merge_workbench_data.py 的 14 合 1 中）
python3 脚本/merge_workbench_data.py
tcb hosting deploy 数据/workbench_data.json 数据/workbench_data.json -e bier-sales-d0gatbvlx288724e9
```

## 与旧 mcp_kpi_pipeline.py 的区别

旧版 `mcp_kpi_pipeline.py` 按**比例分摊**（部门目标占比 × MCP 总额），不精确。
新版直接按 `get_workitem_brief → role_7dd6e0` 获取每笔订单的真实销售员，精确到人。
