---
name: customer-service
description: Customer service skill aligned with server CustomerSkill.php, covering pn_data_analysis and skill_for_customer_service current public APIs.
---

# Customer Service

该 Skill 仅保留与当前服务端实现一致的能力，避免调用在服务端已注释或未开放的接口。

- Skill 版本：`1.0.2`
- 代码对齐基线：`server/app/Service/CustomerSkill.php`

## 龙虾必看

- 术语统一：本 Skill 中“未处理工单/待处理工单”统一指运营处理中工单（`status=3`）。
- 术语统一：“工单草稿”指未交给运营处理的工单，取待草稿客诉使用 `getWaitingDraftTroubles`。
- 数量口径：用户泛问“工单数量/几条工单”时，默认按运营处理中统计，调用 `getOperationProcessingWorkOrderCount`（脚本 `--operation-processing-count`）。
- 禁止误用：不要把 `getWaitingDraftTroubles` 的数组长度当“工单总数”。
- 入口统一：协助工单语境里，“未处理工单/待处理工单”走运营处理中统计；“工单草稿”映射到 `api=getWaitingDraftTroubles`。
- 参数规则：`work order` 相关接口（`getWaitingDraftTroubles`、`getWaitingDraftTroubleById`、`updateWorkOrderDraft`、`updateDraft`、`markWorkOrderError`、`markWorkOrderOperationHandled`、`getOperationProcessingWorkOrderCount`）请求体不传 `pid`。
- 调用约束：只有在提供客诉 `subjectId` 时，才调用 `getTroubleDetailBySubjectId`。
- 列表特殊筛选：`aiReply`（AI 回复）和 `humanRequest`（玩家申请转人工）仅在启用筛选时传字符串 `"yes"`；不筛选时省略对应字段。
- 列表标签筛选：通过 `--data-json` 的 `tags` 传标签路径数组，可直接在服务端筛选；按一级、二级、三级顺序填写实际保存的 Tag 值。
- 用户时区：客诉详情 `data.list[].userTimezone` 为 IANA 时区（如 `Asia/Shanghai`）；空字符串表示无法从 IP 识别。回复或工单草稿涉及时间时，按该时区理解用户当地时间。
- AI 草稿回复：先根据客诉详情 `data.subject.appid` 确定项目，并通过项目列表确认 `aiReply === true`；未开启时不要调用。只使用该客诉 `data.aiDrafts[]` 中实际存在的 `id` 调用 `replyByDraftId`，不要传入或改写回复正文；服务端会再次校验草稿归属和项目开关。
- Token 处理：当没有 `PN_DATA_ANALYSIS_TOKEN` 时，先看本地变量里有没有；有则直接执行 `source ~/.zshrc` 或 `source ~/.bashrc`。若没有则提示获取密钥：登录 `pn_mid_v2` 中台，点击右上角头像/菜单 -> 生成数据分析密钥（或设置/密钥相关入口），复制生成的 token（仅展示一次，请妥善保存）。
- 版本检查：使用 skill 前先确认版本是否最新；不是最新则自动更新（`npm install @pn-data/customer-service@latest`），并同步更新 `.cursor/skills/customer-service/` 下内容。

## 客诉状态说明

与 `client/src/customer/Trouble.vue` 的查询条件对齐：

- `-1`：查询全部状态
- `-2`：加急（Urgent）
- `0`：未处理（Untreated）
- `1`：已回复（Reply）
- `2`：待处理（Wait）
- `3`：客服已处理（Completed）
- `10`：用户已关闭（Close）
- `11`：已删除（Delete）

用于 `getTroubleListByProject` / `getTroubleListForAllProjects` 时，在 `data.status` 中传单个值或数组（如 `[0, 1]`）；查询全部时显式传 `-1`，省略则服务端默认查未处理（`0`）。返回记录的 `status=-1` 或 `4` 在页面按未处理展示，与查询条件 `status=-1` 的“全部”含义不同。

## 当前可用能力

### 项目列表（pn_data_analysis）

- 路径：`POST /service/pn_data_analysis`
- Body：`{ "dataType": "listProject" }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：无需 `pid`
- 返回：每个项目的 `aiReply` 为严格布尔值；只有 `true` 才表示该项目允许 AI 草稿回复

```bash
python3 <SKILL_DIR>/customer_service.py --list-projects
python3 <SKILL_DIR>/customer_service.py --list-projects --json
```

### 客服回复统计（pn_data_analysis）

- 路径：`POST /service/pn_data_analysis`
- Body：`{ "dataType": "statisticCustomerServiceReplyForSkill", "timeSpan": [<start>, <end>] }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：仅需 Unix 秒级 `timeSpan`
- 返回口径：`reply` = 人工回复数，`aireply` = AI 直接采用数（`AIReply=100`，老数据 `yes` 按 100），`aimodifiedreply` = AI 修改后采用数（`90 <= AIReply < 100`）；低于 90 或老数据 `no` 不计入 AI 采用（重要字段，必须保留）

```bash
python3 <SKILL_DIR>/customer_service.py --statistic-reply <start_unix> <end_unix>
python3 <SKILL_DIR>/customer_service.py --statistic-reply 1712505600 1712591999 --json
```

### 待草稿客诉（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "getWaitingDraftTroubles" }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：Body 不传 `pid`；返回队首 1 条可用客诉，无可用客诉时返回 `[]`。每项仅含 `subjectId`、`mainTrouble`，只查询当前客诉详情。

返回示例（结构示意）：

```json
[
  {
    "subjectId": 123456,
    "mainTrouble": { "...": "..." }
  }
]
```

```bash
python3 <SKILL_DIR>/customer_service.py --waiting-draft-troubles
python3 <SKILL_DIR>/customer_service.py --waiting-draft-troubles --json
```

### 按客诉 ID 查询待草稿详情（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "getWaitingDraftTroubleById", "subjectId": <subjectId> }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：`subjectId` 与 `troubleId` 二选一；脚本默认传 `subjectId`。返回数组，每项仅含 `subjectId`、`mainTrouble`，结构与 `getWaitingDraftTroubles` 相同，只查询当前客诉详情。

```bash
python3 <SKILL_DIR>/customer_service.py --waiting-draft-trouble-by-id 123456
python3 <SKILL_DIR>/customer_service.py --waiting-draft-trouble-by-id 123456 --json
```

### 更新/新建工单草稿（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：
  `{ "api": "updateWorkOrderDraft", "subjectId": <subjectId>, "title": "<title>", "content": "<content>" }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：`title` 与 `content` 建议每次都传

```bash
python3 <SKILL_DIR>/customer_service.py --update-work-order-draft 123456 --title "工单标题" --content "工单内容"
python3 <SKILL_DIR>/customer_service.py --update-work-order-draft 123456 --title "工单标题" --content "工单内容" --json
```

### 同时更新工单草稿与回复草稿（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：
  `{ "api": "updateDraft", "subjectId": <subjectId>, "workOrderTitle": "<title>", "workOrderContent": "<work_order_content>", "replyContent": "<reply_content>", "aiSource": "<optional>" }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：`subjectId`、`workOrderTitle`、`workOrderContent`、`replyContent` 必填；`aiSource` 可选

```bash
python3 <SKILL_DIR>/customer_service.py --update-draft 123456 --work-order-title "工单标题" --work-order-content "工单内容" --reply-content "请您先提供角色 ID，我们尽快核查"
python3 <SKILL_DIR>/customer_service.py --update-draft 123456 --work-order-title "工单标题" --work-order-content "工单内容" --reply-content "请您先提供角色 ID，我们尽快核查" --ai-source ai_v2 --json
```

### 将工单标记为错误（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "markWorkOrderError", "subjectId": <subjectId> }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：提交的 `id` 必须是客诉 ID（`subjectId`），服务端会按客诉反查工单并标记错误状态（`status=5`）

```bash
python3 <SKILL_DIR>/customer_service.py --mark-work-order-error 123456
python3 <SKILL_DIR>/customer_service.py --mark-work-order-error 123456 --json
```

### 将工单标记为运营已处理（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "markWorkOrderOperationHandled", "subjectId": <subjectId>, "title": "<title>", "content": "<content>" }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：提交的 `id` 必须是客诉 ID（`subjectId`），服务端会按客诉反查工单并更新为运营已处理（`status=4`）；因状态流转校验，`title` 与 `content` 必填

```bash
python3 <SKILL_DIR>/customer_service.py --mark-work-order-operation-handled 123456 --title "处理结果标题" --content "处理结果正文"
python3 <SKILL_DIR>/customer_service.py --mark-work-order-operation-handled 123456 --title "处理结果标题" --content "处理结果正文" --json
```

### 更新 AI 回复草稿（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "updateReplyDraft", "subjectId": <subjectId>, "content": "<reply_content>", "aiSource": "<optional>" }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：`subjectId` 与 `content` 必填，`aiSource` 可选

```bash
python3 <SKILL_DIR>/customer_service.py --update-reply-draft 123456 --content "请您先提供角色 ID，我们尽快核查"
python3 <SKILL_DIR>/customer_service.py --update-reply-draft 123456 --content "请您先提供角色 ID，我们尽快核查" --ai-source ai_v2 --json
```

### 采用 AI 草稿回复客诉（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "replyByDraftId", "subjectId": <subjectId>, "draftId": "<draftId>", "resolved": "yes|no" }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：`subjectId` 和 `draftId` 必填；`draftId` 必须来自该客诉详情的 `data.aiDrafts[].id`。接口使用 `OP_CS_AI_APPROVE`，回复正文只取服务端保存的草稿，并检查目标项目已开启 AI 回复。

```bash
python3 <SKILL_DIR>/customer_service.py --reply-by-draft-id 98765 --subject-id 123456
python3 <SKILL_DIR>/customer_service.py --reply-by-draft-id 98765 --subject-id 123456 --resolved --json
```

### 按客诉 ID 查询详情（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "getTroubleDetailBySubjectId", "subjectId": <subjectId> }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：`data.list` 每项含 `userTimezone`（IANA 时区，由 `userip` 经 GeoIP 解析；无法识别时为空字符串 `""`）。涉及时间表述（如维护窗口、回复时效）时优先参考该字段；与前端 `TroubleDetail.vue` 一致。待草稿接口的 `mainTrouble.list` 同源，同样带此字段。

```bash
python3 <SKILL_DIR>/customer_service.py --trouble-detail 123456
python3 <SKILL_DIR>/customer_service.py --trouble-detail 123456 --json
```

### 按项目查询客诉列表（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "getTroubleListByProject", "pid": <pid>, "data": {...} }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：`data` 通过 `--data-json` 原样传入，默认 `{}`；省略 `status` 时服务端默认查未处理（`0`），查询全部时显式传 `status: -1`。

两个客诉列表接口使用相同的有效查询参数：

| 参数 | 类型 | 含义 |
| --- | --- | --- |
| `status` | 整数或整数数组 | 客诉状态：`-1` 全部、`-2` 加急、`0` 未处理、`1` 已回复、`2` 待处理、`3` 已完成、`10` 已关闭、`11` 已删除；多选如 `[0, 1]`。省略默认 `0`。 |
| `workstatus` | 字符串或字符串数组 | 工单状态；`"3"` 运营处理中、`"4"` 运营已处理，多选如 `["3", "4"]`。与客诉 `status` 分开传；查询所有客诉状态下的工单时搭配 `status: -1`。 |
| `serverid` | 整数或字符串 | 服务器 ID；省略或 `""` 不筛选。 |
| `language` | 字符串或字符串数组 | 原始语言代码，如 `"tw"` 或 `["tw", "jp"]`；省略或 `""` 不筛选。 |
| `language_detect` | 字符串或字符串数组 | 检测语言代码，与页面语言筛选一致，如 `["tw", "jp"]`；省略或 `""` 不筛选。 |
| `typeid` | 整数或字符串 | 客诉分类 ID；省略或 `""` 不筛选。 |
| `tags` | 字符串数组 | 标签路径，按一级、二级、三级顺序传实际 Tag 值，最多 3 个。可只传前一级或前两级筛选对应路径；省略或 `[]` 不筛选。 |
| `gm_score` | 整数 | 客服评分上限，筛选已评分且分值不高于此值的客诉，页面使用 `1`、`2`、`3`、`4`；`0`、省略或 `""` 不筛选。 |
| `paystar` | 整数 | VIP 等级筛选，`4` 为 2 星、`6` 为 3 星、`8` 为 4 星；省略或 `""` 不筛选。 |
| `startTS` | 整数 | 创建时间范围的开始时间，Unix 秒级时间戳；必须与 `endTS` 成对传入，单独传入不生效。 |
| `endTS` | 整数 | 创建时间范围的结束时间，Unix 秒级时间戳；必须与 `startTS` 成对传入，不限制时间时同时省略。 |
| `passportid` | 字符串 | 用户 UID（通行证 ID）筛选。 |
| `subjectid` | 整数或数字字符串 | 客诉 ID 筛选；国内客诉使用中台展示的 ID，服务端自动移除国内 ID 偏移。 |
| `keywords` | 字符串 | 关键词搜索（标题/内容检索）。 |
| `onlyme` | 字符串 | 是否仅看当前操作者处理的客诉，`"yes"` 启用；`"no"` 或省略不筛选。 |
| `aiReply` | 字符串 | 仅看 AI 回复的客诉，启用传 `"yes"`，不筛选时省略。 |
| `humanRequest` | 字符串 | 仅看玩家申请转人工的客诉，启用传 `"yes"`，不筛选时省略。 |
| `gmName` | 字符串 | GM 昵称筛选，如 `"PicoClaw"`；省略或 `""` 不筛选。 |
| `sort` | 字符串 | 排序字段：`"created_at"` 创建时间或 `"updated_at"` 更新时间；与 `order` 一起传入。 |
| `order` | 字符串 | 排序方向：`"ASC"` 升序或 `"DESC"` 降序；与 `sort` 一起传入。 |
| `limit` | 正整数 | 每页条数，页面使用 `30`。 |
| `page` | 正整数 | 页码，从 `1` 开始。 |

项目范围由顶层 `pid` / `eid` 决定，`data.appid`、`data.gameid` 会被服务端删除或覆盖，不能改变这两个接口的项目范围。标签过滤使用 `tags`，单传 `tagPath` 不生效。前端内部字段 `idsOnly`、`filterStatus` 会被服务端删除，不作为 Skill 查询参数。

返回 `data.list` 为当前页客诉，`data.count` 为筛选后的总条数；获取全部结果时按 `limit`、`page` 分页，不能将当前页条数当作总数。

```bash
python3 <SKILL_DIR>/customer_service.py --trouble-list-by-project 2000 --data-json '{"status": -1, "aiReply": "yes", "limit": 10, "page": 1}'
python3 <SKILL_DIR>/customer_service.py --trouble-list-by-project 2000 --data-json '{"status": -1, "humanRequest": "yes", "limit": 10, "page": 1}' --json
python3 <SKILL_DIR>/customer_service.py --trouble-list-by-project 2000 --data-json '{"status": -1, "tags": ["充值问题", "礼包购买", "购买未到账"], "limit": 10, "page": 1}' --json
python3 <SKILL_DIR>/customer_service.py --trouble-list-by-project 2000 --data-json '{"status": -1, "language_detect": ["tw", "jp"], "workstatus": ["3", "4"], "sort": "updated_at", "order": "DESC", "limit": 10, "page": 1}' --json
```

### 查询全项目客诉列表（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "getTroubleListForAllProjects", "eid": <eid>, "data": {...} }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：当前支持 `eid=1` 或 `eid=5`
- 参数说明：`eid` 为环境分组标识；`data` 字段与 `getTroubleListByProject` 完全一致，含义同上。

```bash
python3 <SKILL_DIR>/customer_service.py --trouble-list-all-projects 1 --data-json '{"status": -1, "limit": 10, "page": 1}'
python3 <SKILL_DIR>/customer_service.py --trouble-list-all-projects 5 --data-json '{"status": -1, "aiReply": "yes", "humanRequest": "yes", "limit": 10, "page": 1}' --json
python3 <SKILL_DIR>/customer_service.py --trouble-list-all-projects 1 --data-json '{"status": -1, "tags": ["充值问题"], "limit": 10, "page": 1}' --json
```

### 查询运营处理中工单数（skill_for_customer_service）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "getOperationProcessingWorkOrderCount" }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`

```bash
python3 <SKILL_DIR>/customer_service.py --operation-processing-count
python3 <SKILL_DIR>/customer_service.py --operation-processing-count --json
```

### 更新客诉标签（skill_for_customer_service -> pn_go）

- 路径：`POST /service/skill_for_customer_service`（或 `/service/3037`）
- Body：`{ "api": "updateCustomerServiceTags", "subjectId": <subjectId>, "tags": ["vip", "bug"] }`
- Header：`X-Pn-Token: $PN_DATA_ANALYSIS_TOKEN`
- 说明：`subjectId` 必填；`tags` 为字符串数组，最多 3 个；传 `[]` 可清空标签

```bash
python3 <SKILL_DIR>/customer_service.py --update-customer-service-tags 123456 --tags-json '["vip","bug"]'
python3 <SKILL_DIR>/customer_service.py --update-customer-service-tags 123456 --tags-json '[]' --json
```

## 环境变量

- `PN_DATA_ANALYSIS_TOKEN`：必填
- `PN_DATA_ANALYSIS_BASE`：可选，默认 `https://pnv2.17995api.net`
- 可在 `customer_service.py` 同目录或项目根目录放 `.env.customer-service`（或 `.env`）自动加载以上变量

## Token 自查

```bash
python3 <SKILL_DIR>/customer_service.py --show-pn-token
python3 <SKILL_DIR>/customer_service.py --show-pn-token "<token>" --json
```

## 发布前校验

1. `package.json` 的 `version` 与本文档、`README.md` 的 Skill 版本一致。
2. `customer_service.py --help` 中参数与本文档命令示例一致。
3. 能力清单需与 `CustomerSkill::skill()` 的 switch case 保持一致（无额外、无缺失）。
