Files
yunke/docs/superpowers/specs/2026-08-07-yunke-staff-chat-stats-design.md
T
浅念 78eea7f08f docs(spec): 微信员工 5 数字 modal (Phase A) 设计——全本地消息聚合
数据源真机核实(wxStatsByTypes/wxChatInfo);从 message 表聚合 5 数字+客户明细;
复用 YkStaffDetailModal;口径近似(跟进等)。姓名drawer/主badge = Phase B。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 17:36:05 +08:00

127 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 微信员工 沟通数/及时回复 5 数字 modal(Phase A)设计
> 日期:2026-08-07|分支:`feat/yunke-data-mirror`|路线图 #2 Phase A
> 前置:Plan 1(StaffList 微信员工列表已对齐云客,含 `YkStaffDetailModal` 通用件、数字→modal 模式);message 表已同步(含 `owner_account_id`/`talker_id`/`is_send`/`timestamp`)。
> 数据来源:**全本地 message 表聚合**(用户定,符合 [[yunke-verify-data-in-browser]] 全本地原则;数字口径**近似**云客——已与用户确认接受)。
## 1. 目标与范围
**目标**:StaffList(微信员工列表)每行的「沟通数」「及时回复」复合列里 **5 个数字可点 → 弹 modal 展示对应客户列表**,对齐云客。数据**从本地 message 表实时聚合**(按 员工微信 × 客户 × 日期窗),运行时不碰云客。
**5 个数字**(云客 `/cms/wechat/wechatlist` 微信员工列表,真机核实 2026-08-07):
- 沟通数:沟通人数 / 有效沟通数 / 跟进客户数
- 及时回复:员工最后回复数 / 未回复客户数
**在本 plan(Phase A)**:
- 后端:新聚合 endpoint `GET /api/staff-chat-stats`(per 员工微信 + 日期窗 → 5 个汇总数 + 客户明细行)。
- 前端:StaffList 5 个数字可点 → 复用 `YkStaffDetailModal` 弹对应客户子集 + 日期选择器(默认近 3 天)。
**不在本 plan(Phase B / 后续)**:
- 员工姓名 → 员工详情(降级 drawer)、主 badge → 聊天窗口(Phase B,小前端)。
- modal 内「客户微信/员工微信 → 聊天记录」二级可点(Phase B/后)。
- WA 员工表同类数字 modal(WA 员工表数字 modal 待复核,本 plan 只做微信员工)。
- 与云客数字**精确对齐**(口径近似,见 §4)。
## 2. 数据来源(真机核实)
云客微信员工列表两个接口(2026-08-07 真机):
- 行的 5 数字 ← `POST /wechatweb/statistics/wxStatsByTypes`(per 员工聚合,固定窗)。
- 数字 modal 内容 ← `POST /wechatweb/statistics/wxChatInfo`(body `{weChatId, userId, ymdS, ymdE, page, size}`;**5 个 modal 共用此 endpoint**,仅筛选/展示不同;modal 带日期选择器 = 动态窗)。响应每条 = 一个客户 + `sendNum`/`receiveNum`/`noResponse`/`responseCount`/`saleSendLastNum`/`customerSendLastNum` 等字段。
**我们的方案**(用户定):**不抓 wxStatsByTypes/wxChatInfo**,从本地 `message` 表实时聚合(动态日期窗没法预抓,且全本地原则)。message 表字段齐(smoke.db 实测 wechat 消息 `owner_account_id`/`talker_id`/`is_send`/`timestamp` 全非空)。
## 3. 数据模型 / 聚合
**无新表**。聚合在查询时算(`message` 表按 `owner_account_id` + 日期窗 GROUP BY `talker_id`)。
**每个客户(talker_id)的聚合字段**(窗口内):
- `send_count` = `is_send=1` 消息数(员工发)
- `receive_count` = `is_send=0` 消息数(客户发)
- `interaction_days` = 不同 `timestamp` 日期数
- `last_is_send` = 窗口内最后一条消息的 `is_send`(1=员工最后发,0=客户最后发)
- `last_time` = 最后一条时间
- 客户身份:`talker_id` → 关联 `customer` 表取 `friend_nickname`/`friend_remark`/`real_name`(left join,缺失则用 talker_id)。
**建议索引**:`message(owner_account_id, timestamp)` 加速(若不存在,plan 里加 Alembic 迁移;先确认现有索引)。
## 4. 5 数字口径(近似,从聚合字段派生)
| 数字 | 口径(本系统) | 云客原口径 |
|---|---|---|
| 沟通人数 `chat_count` | 窗口内 ≥1 条消息的去重客户数 | 同(去重沟通客户) |
| 有效沟通数 `effective_count` | 双向客户数(`send_count≥1 且 receive_count≥1`) | 双向有效沟通 |
| 跟进客户数 `follow_count` | 窗口内 `interaction_days≥2` 的客户数(**近似**,跟进精确口径云客未公开) | 跟进(精确口径未知) |
| 员工最后回复数 `last_reply_count` | `last_is_send=1` 的客户数(员工最后发) | 员工最后回复 |
| 未回复客户数 `no_reply_count` | `last_is_send=0` 的客户数(客户最后发、员工未回) | 未回复客户 |
> **近似声明**:跟进客户数口径为合理近似(云客精确标准未公开);其余 4 个与云客语义一致,但**数字可能因日期窗/数据完整性略有出入**。modal 内显示口径说明(小字)。
## 5. API
`GET /api/staff-chat-stats`(新 router,镜像现有 list endpoints 风格)
- query:`owner_account_id`(必填,员工微信号=wxid)、`start?`/`end?`(ms-epoch,默认近 3 天)。
- 响应:
```json
{
"owner_account_id": "wxid_...",
"start": ..., "end": ...,
"summary": { "chat_count": N, "effective_count": N, "follow_count": N, "last_reply_count": N, "no_reply_count": N },
"customers": [
{ "talker_id": "...", "friend_name": "...", "send_count": N, "receive_count": N,
"interaction_days": N, "last_is_send": 0|1, "last_time": <ms> }
]
}
```
- 前端 `getStaffChatStats({owner_account_id, start, end})`。
- 聚合一条 SQL(GROUP BY talker_id)+ 5 个汇总在 Python 侧从 customers 派生(或 SQL 同时算)。
## 6. 前端(StaffList + modal)
- StaffList 每行(员工微信)「沟通数」「及时回复」复合列:把现有静态数字(若有)改/补成**可点** 5 个数字。每行加载时调 `getStaffChatStats({owner_account_id: row.<account>, start, end})`(默认近 3 天)填 summary。
- 性能:每页 N 行 × 1 次聚合;首版顺序调用可接受(消息表有索引),或后续优化为批量 endpoint。
- 点数字 → 复用 `YkStaffDetailModal`(Plan 1 通用件,支持自定义列 + 数据源)弹客户子集:
- 沟通人数 → customers 全集
- 有效沟通 → 过滤 `send≥1 && receive≥1`
- 跟进 → 过滤 `interaction_days≥2`
- 最后回复 → 过滤 `last_is_send=1`
- 未回复 → 过滤 `last_is_send=0`
- modal 列(对齐云客):序号 / 客户微信(昵称+号) / [有效沟通多 发送数/接收数 列] / 员工信息 / 员工微信。modal 内客户微信可点→(Phase B)。
- modal 顶部日期选择器(el-date-picker,默认近 3 天)→ 改 window 重查。
- modal 标题对齐云客(沟通人数(已去重) / 有效沟通数(已去重) / 跟进客户数 / 员工最后回复数 / 未回复客户数)+ 近似口径小字说明。
## 7. 关键决策(已与用户确认 2026-08-07)
1. **全本地消息聚合**(不抓 wxStatsByTypes/wxChatInfo):从 message 表算。
2. **口径近似**:跟进客户数用合理近似;数字可能与云客略出入,用户接受。
3. **范围 Phase A**:只做 5 数字 modal;姓名 drawer + 主badge + modal 内二级可点 → Phase B。
4. **默认窗近 3 天**(对齐云客 modal 默认 ymdS/ymdE)。
5. **无新表**:聚合在查询时算(动态窗不宜预存)。
## 8. 风险
- **数据完整性**:smoke.db 微信消息稀疏(4 员工×6 客户×399 条),数字小但机制成立;真实部署数据全后数字才有意义。
- **跟进口径近似**:与云客可能不一致(已声明)。
- **性能**:每行一次聚合;大消息量下需索引 `(owner_account_id, timestamp)`(plan 确认/加)。
- **StaffList 行 ↔ owner_account_id 映射**:确认 StaffList 行有员工微信号字段(plan 核实 Plan 1 的 row shape)。
## 9. 文件清单(预期)
**后端**:
- 新 `app/api/routers/staff_chat_stats.py`(`/api/staff-chat-stats` 聚合查询)
- 改 `app/api/main.py`(注册 router)
- 改 `app/api/schemas.py`(`StaffChatStatsOut`/`StaffChatCustomerOut`)
- (若需)Alembic 加 `message(owner_account_id, timestamp)` 索引
- 新测试 `tests/test_api_staff_chat_stats.py`(种消息 → 聚合 5 数字 + 客户行正确)
**前端**:
- 改 `yunkes/src/api/index.ts` + `types.ts`:`getStaffChatStats` / `StaffChatStats` 类型
- 改 `yunkes/src/views/StaffList.vue`:5 数字可点 + 挂 modal
- 改/复用 `yunkes/src/components/YkStaffDetailModal.vue`:支持日期选择器 + 5 套列配置 + 客户子集过滤
## 10. 验收
- 后端测试全绿(含 `test_api_staff_chat_stats.py`:种若干 message → summary 5 数字 + customers 字段正确;日期窗过滤生效)。
- `vue-tsc -b` exit 0。
- 浏览器:StaffList 微信员工行 5 数字显示且可点 → modal 弹对应客户子集;切日期窗重查;列对齐云客。
- 数据来自本地 message(network 只打 `/api/staff-chat-stats`,不碰云客)。