npm package: @pn-data/customer-service
Description: Cursor Agent Skill: 客诉 listProject (pn_data_analysis), getTroubleList/getTroubleDetailBySubjectId/replyTroubleBySubjectId (skill_for_customer_service), statisticCustomerServiceReply (pn_data_analysis), AI reply draft, work order APIs (skill, no pid). pn_m
Author: pn
Latest version: 0.0.45

# PN 中台 客诉 / Customer Service

Cursor Agent Skill — 文档与说明 pn_mid_v2 中台**项目列表**（`listProject`）、**客诉列表**、**客诉详情**、**客服回复统计**（`statisticCustomerServiceReply` / `pn_data_analysis`，与 `Api::STATISTIC_CUSTOMER_SERVICE_REPLY` 1101 同源）、**回复草稿**、**中台协助工单**（`work_order`：**取最旧一批** `getOldestWaitingWorkOrder`（**JSON 数组**，至多 3 条）、**按 id 获取** `getWorkOrderById`（单对象，字段与数组元素一致；两者均**不传 `pid`**，含 `troubles` 与 `recentWorkOrders`）；**按 id 更新正文** `updateWorkOrderById`（更新 `data.title/content`，不改状态）；**标为出错** `markWorkOrderError`（仅 **`WAITING` → `STATUS_ERROR`（5）**，可选 `reason`/`message` → `data.errorNote`，**不参与**最旧队列）；**`getWorkOrderWaitingList` 已废弃**；**`updateWorkOrder`** **不传 `pid`**，支持两条流转：`WAITING -> REVIEWING`（需 `title`+`content`）与 `OPERATION_HANDLED -> DONE`（可不传 `title`/`content`）；另有 **`UPDATE_WORK_ORDER_STATUS`**、详情 **`data.workOrder`**、**`CREATE_WORK_ORDER`**、前端 **`LIST_OPERATION_PROCESSING_WORK_ORDERS`** 等）及相关接口（`ProjectManager::getListForPnDataAnalysis`、`Api::OP_CS_SUBJECT_*`、`OP_CS_AI_APPROVE`、`SkillManager::forCustomerService`、`Customer::skill` / `Customer::op`、`WorkOrder` 等）。**细则以 `customer-service/SKILL.md` 为准。**

> **Skill 版本**：v0.0.45（以 **`customer-service/SKILL.md`** 顶部为准）。发布 **`@pn-data/customer-service`** 时请保持 **`package.json` 的 `version`** 与本版本一致。  
> **完整约定与代码位置**见 **`customer-service/SKILL.md`**。

## 本版要点（v0.0.45，发布用）

- **运营处理中工单条数**：Skill **`api=getOperationProcessingWorkOrderCount`**，响应 **`data.total`** + **`data.byProject`**（各 **`pid` / projectName / count**）；脚本 **`--operation-processing-work-order-count`**。
- **运营处理中工单列表**：Skill **`api=getOperationProcessingWorkOrderList`**（无 pid，JSON 数组，每项含 troubles）；前端运营摘要仍用 **830（3+4）**；脚本 **`--operation-processing-work-order-list`**。
- **给 Agent（工单数量）**：默认指 **运营处理中（status=3）**；**按项目区分**用 **`getOperationProcessingWorkOrderCount`**（**`byProject`**）；只要全库合计看 **`total`**；明细列表用 **`getOperationProcessingWorkOrderList`**。**勿**用 **`getOldestWaitingWorkOrder`** 的长度。
- **正式回复客诉**：新增 skill **`api=replyTroubleBySubjectId`**（`skill_for_customer_service`）；脚本 **`--reply-trouble <subjectId> --reply-content "..."`**，可选 **`--reply-solve`** / **`--reply-translate-lang`**；`pid` 可省略（按 `subjectId` 解析项目）。
- **客服回复统计**：`pn_data_analysis` **`dataType=statisticCustomerServiceReply`**（**`StatisticManager::statisticCustomerServiceReply`**）；脚本 **`--statistic-reply <start_unix> <end_unix>`**；权限客诉 **MANAGER/APPROVER**（与 **`STATISTIC_CUSTOMER_SERVICE_REPLY` 1101** 一致）。
- **客诉详情**：`--detail` / `--trouble-detail-by-subject` 仅走 skill **`getTroubleDetailBySubjectId`**，请求 **不传 `pid`**，由服务端按 `subjectId` 解析项目（见 `SKILL.md`）。
- **给 Agent（协助工单怎么说都一样）**：用户或指令若写「**取未处理工单**」「**待处理工单**」「**工单草稿**」等（**`skill_for_customer_service` / 协助工单**语境，非客诉 `troubleList`），**一律**调用 **`api=getOldestWaitingWorkOrder`**；响应为 **JSON 数组**（至多 **3** 条）；同一条目内同项目摘要看该项的 **`recentWorkOrders`**。**勿**再对接 **`getWorkOrderWaitingList`**。若只问**数量**，见上条 **「给 Agent（工单数量）」**，不要用本接口的数组长度当「工单总数」。
- **给 Agent（术语统一）**：**「工单草稿」**指 **`getOldestWaitingWorkOrder`** 拉取的中台协助工单最旧一批（至多 3 条，状态 0/4）。**提示龙虾**：话术里 **「工单草稿」**固定指该队列；泛问 **「工单数量」**时按 **「给 Agent（工单数量）」**，指 **运营处理中（3）** 条数。
- **新增工单 skill**：`getWorkOrderById`（按工单 id 取工单，返回结构同最旧工单）、`updateWorkOrderById`（按工单 id 更新 `title/content`，不改状态）、`markWorkOrderError`（**仅** `WAITING` → **`STATUS_ERROR`（5）**，可选说明写入 `data.errorNote`）。
- **`updateWorkOrder` 状态分支**：`status=0` 时必填 `title` + `content`（写入 `work_order.data` 并转审核中）；`status=4` 时可仅传工单 id（转已完成）。
- **`getOldestWaitingWorkOrder`**：响应 **JSON 数组** 0～3 条；每条附加 **`recentWorkOrders`**（与该条**同一项目**下按更新时间倒序 **20** 条摘要，每项仅 **`title`**、**`content`**）。**`id` / `subjectId` / `data` / `troubles`** 等为**每条**各自一份。
- **工单技能不带 `pid`**：`POST /service/skill_for_customer_service`（或 `3037`）中 **`api=getOldestWaitingWorkOrder`** / **`api=getOperationProcessingWorkOrderCount`** / **`api=getOperationProcessingWorkOrderList`** / **`api=getWorkOrderById`** / **`api=updateWorkOrder`** / **`api=updateWorkOrderById`** / **`api=markWorkOrderError`** 的 Body **不要求、不携带 `projectId`/`pid`**；`forCustomerService` 只做 Token 与 **`user`**，**无** `setRequest`。**`UPDATE_WORK_ORDER_STATUS`、`LIST_OPERATION_PROCESSING_WORK_ORDERS`** 等**业务 API** 仍须按中台约定传 **`pid`**（见 `TroubleDetail.vue`、`WorkOrderList.vue`）。
- **本版（0.0.44）**：与中台一致：`getOldestWaitingWorkOrder` 响应体为 **JSON 数组**（0～3）；`getWorkOrderById` 为单对象；`format_oldest_waiting_work_order` 优先解析数组并兼容旧版 `workOrders`；**`markWorkOrderError`** 将等待中单标为 **出错（5）**，**不进入**最旧队列。
- **全库最旧工单**：`WorkOrder::listOldestWaiting(3)`（等待处理或运营已处理，按 **`created_at` 升序**至多 **3** 条）；**`troubles`** 按**每条**工单所属 **`pid`** 解 **`Project`** 后 **`fetchTroubleListByUid`**（缺 **`subjectId`** 时首部补足）。
- **给 Agent（执行规则）**：
  - 取到 `status=0`（待处理）工单：先生成 AI 回复草稿，再调用 `updateWorkOrder`（带 `title` + `content`）把工单置为 `STATUS_REVIEWING`。
  - 取到 `status=4`（运营已处理）工单：参考工单 `title/content` 生成新的 AI 回复草稿，再调用 `updateWorkOrder`（仅 `id`）把工单置为 `STATUS_DONE`。
- **给 Agent（主单与参考客诉）**：数组**下标 0** 为**最旧**；多项时**逐条**处理。每条以**该项** **`subjectId`** 为主单，**`troubles`** 中其余 id 为**参考客诉**。
- **客诉详情**：`OP_CS_SUBJECT_DETAIL` 可合并 **`data.workOrder`**。
- **工单状态（中台 `work_order.status`）**：0 等待处理、1 审核中、2 已完成、3 运营处理中、4 运营已处理、5 出错（**`markWorkOrderError`**；**流转与列表 API** 见 SKILL.md）。
- **前端协助工单「直接回复」**：走 **`OP_CS_AI_APPROVE`** + **`subjectId`** / **`workOrderId`**（见 SKILL.md）。

## 何时使用

- 用户问：**项目列表**、listProject、**客诉列表**、**客诉详情**、**客服回复统计**、statisticCustomerServiceReply、1101、**回复草稿**、**协助工单**、**取最旧工单**、getOldestWaitingWorkOrder、**工单数量**、**几条工单**、**运营处理中工单列表**、getOperationProcessingWorkOrderCount、getOperationProcessingWorkOrderList、**按 id 取工单**、getWorkOrderById、**按 id 更新正文**、updateWorkOrderById、**工单出错**、**标记出错**、markWorkOrderError、**recentWorkOrders**、updateWorkOrder、**工单 title**、skill_for_customer_service、**无 pid 工单**、work_order、CREATE_WORK_ORDER、UPDATE_WORK_ORDER_STATUS、运营工单列表、……
- 需要查找或修改客诉 / 工单相关代码（Customer、SkillManager、Api、WorkOrder、TroubleDetail）。

## 配置

| 环境变量 | 用途 |
|----------|------|
| **PN_DATA_ANALYSIS_TOKEN** | `pn_data_analysis` 与 **`skill_for_customer_service`** 共用：**`X-Pn-Token`** |

**可选**：`PN_DATA_ANALYSIS_BASE`。

## 获取项目列表 / 客诉列表 / 客诉详情

```bash
python3 customer-service/customer_service.py --list-projects
python3 customer-service/customer_service.py <pid>
# 提醒龙虾：取某个项目的客诉列表可直接用 --csAppid=-1（当前项目）
python3 customer-service/customer_service.py <pid> --csAppid=-1
python3 customer-service/customer_service.py --detail <troubleId>   # 未传 pid 时默认 2000
python3 customer-service/customer_service.py <pid> --detail <troubleId> --json
```

详见 SKILL.md。

## 协助工单（脚本与 HTTP 均不传 pid）

`SkillManager::forCustomerService` → `Customer::skill`。Body 仅需 **`api`**（以及对应参数），**不要**带 **`pid`**。

```bash
# 全库创建时间最早至多 3 条协助工单（JSON 数组；每条含 troubles + recentWorkOrders）
python3 customer-service/customer_service.py --oldest-waiting-work-order
python3 customer-service/customer_service.py --oldest-waiting-work-order --json

# 全库运营处理中（status=3）协助工单条数（data.total + data.byProject 按项目）
python3 customer-service/customer_service.py --operation-processing-work-order-count
python3 customer-service/customer_service.py --operation-processing-work-order-count --json

# 全库运营处理中（status=3）协助工单列表（JSON 数组；每项结构同上）
python3 customer-service/customer_service.py --operation-processing-work-order-list
python3 customer-service/customer_service.py --operation-processing-work-order-list --json

# 按工单 id 获取（返回结构同上）
python3 customer-service/customer_service.py --get-work-order-by-id 42
python3 customer-service/customer_service.py --get-work-order-by-id 42 --json

# 更新工单（status=0 时需 title/content -> 审核中；status=4 时可仅 id -> 已完成）
python3 customer-service/customer_service.py --update-work-order <工单id> --work-order-title "标题" --work-order-content "工单正文"
python3 customer-service/customer_service.py --update-work-order 42 --work-order-title "…" --work-order-content "…" --json
python3 customer-service/customer_service.py --update-work-order 42

# 仅更新工单正文（不改变状态）
python3 customer-service/customer_service.py --update-work-order-by-id 42 --work-order-title "标题" --work-order-content "工单正文"
python3 customer-service/customer_service.py --update-work-order-by-id 42 --work-order-title "…" --work-order-content "…" --json

# 将等待处理(0)的工单标为出错(5)，不参与最旧队列；可选 --work-order-error-reason
python3 customer-service/customer_service.py --mark-work-order-error 42
python3 customer-service/customer_service.py --mark-work-order-error 42 --work-order-error-reason "拉取客诉详情失败" --json
```

详见 SKILL.md「待处理协助工单」「更新协助工单」。

## 回复草稿

提交 AI 回复草稿走 **`skill_for_customer_service`** 的 **`api=submitReplyDraft`**，仅需 `PN_DATA_ANALYSIS_TOKEN`，**不需要 `pid`**；服务端使用 `Config::ZX_AI_REPLY_API_URL` / `Config::ZX_AI_REPLY_API_KEY` 转发。该能力与 `getTroubleList` / `getTroubleDetailBySubjectId` / `replyTroubleBySubjectId` / `updateWorkOrder` / `updateWorkOrderById` 一致，**不处理项目 `2173`**。

## 客服回复统计

需 **`<pid>`** 与 Unix 秒级 **`timeSpan`**；客诉权限 **MANAGER 或 APPROVER**。详见 SKILL.md。

```bash
python3 customer-service/customer_service.py <pid> --statistic-reply <start_unix> <end_unix>
python3 customer-service/customer_service.py 2000 --statistic-reply 1712505600 1712591999 --json
```

## 功能入口一览

| 功能 | 方式 | 说明 |
|------|------|------|
| **项目列表** | 脚本 | `--list-projects`，`pn_data_analysis` / `listProject` |
| **客诉列表/详情/正式回复** | 脚本 / skill_for_customer_service | 列表 `getTroubleList` 可省略 `pid`；详情 `getTroubleDetailBySubjectId` 默认不传 `pid`；正式回复 `replyTroubleBySubjectId`（`--reply-trouble`） |
| **客服回复统计** | 脚本 / pn_data_analysis | **`--statistic-reply`**；`dataType=statisticCustomerServiceReply`；权限 **MANAGER/APPROVER** |
| **最早待处理工单** | 脚本 / HTTP | **`--oldest-waiting-work-order`**；Body **`{ "api": "getOldestWaitingWorkOrder" }`**，**无 pid**；响应 **JSON 数组**（至多 3 条），每条含 **`recentWorkOrders`**（同项目 20 条摘要） |
| **运营处理中工单条数** | 脚本 / Skill HTTP | **`--operation-processing-work-order-count`**；Body **`{ "api": "getOperationProcessingWorkOrderCount" }`**；响应 **`data.total`**、**`data.byProject[]`**（pid、projectName、count）；**无 pid** |
| **运营处理中工单列表** | 脚本 / Skill HTTP | **`--operation-processing-work-order-list`**；Body **`{ "api": "getOperationProcessingWorkOrderList" }`**；响应 **JSON 数组**；**830** 仍为前端 3+4 摘要 |
| **按 id 获取工单** | 脚本 / HTTP | **`--get-work-order-by-id`**；Body **`{ "api": "getWorkOrderById", "id": <id> }`**，**无 pid**；**单对象**，字段与 `getOldest` 数组元素一致 |
| **更新协助工单** | 脚本 / HTTP | **`--update-work-order`** + **`--work-order-title`** + **`--work-order-content`**；Body **无 pid**，**`WorkOrder::getById`** |
| **按 id 更新工单正文** | 脚本 / HTTP | **`--update-work-order-by-id`** + `title/content`；Body **`{ "api":"updateWorkOrderById", "id":..., "title":"...", "content":"..." }`**，**无 pid**（旧名 `updateWorkOrderContentById` 已废弃） |
| **标记工单出错** | 脚本 / HTTP | **`--mark-work-order-error`**；可选 **`--work-order-error-reason`**；Body **`{ "api":"markWorkOrderError", "id":... }`**，可选 **`reason`**，**无 pid**；**仅** `WAITING`→`STATUS_ERROR`（5） |
| **工单状态 / 运营列表** | 中台 API / Skill | `UPDATE_WORK_ORDER_STATUS`（829）、`LIST_OPERATION_PROCESSING_WORK_ORDERS`（830，3+4 摘要）等需 **`pid`**（见前端）；**仅运营处理中条数**用 **`getOperationProcessingWorkOrderCount`**；要 **troubles** 全量列表用 **`getOperationProcessingWorkOrderList`** |
| **回复草稿** | 脚本 / skill_for_customer_service | `--draft-subject` / `--draft-content`，走 `api=submitReplyDraft`（仅需 `PN_DATA_ANALYSIS_TOKEN`） |

## 安装

```bash
npm install @pn-data/customer-service
# 将 node_modules/@pn-data/customer-service/ 下 customer-service/ 与 README 复制或链接到 .cursor/skills/customer-service/
```

Skill 主文档：`.cursor/skills/customer-service/customer-service/SKILL.md`。

## 发布前检查

```bash
npm view @pn-data/customer-service version --registry https://registry.npmjs.org/
```

确认 **`package.json` `version`** 与 **`SKILL.md`** 顶部 **Skill 版本** 一致后 **`npm publish`**。

## License

MIT