1
This commit is contained in:
@@ -12,14 +12,14 @@
|
||||
1. 作为助教,我每天打开小程序能看到系统为我分配的任务列表,按优先级排序。
|
||||
2. 作为助教,我可以置顶/放弃任务,放弃时必须填写原因。
|
||||
3. 作为助教,我完成召回任务后(客户到店被服务),系统自动标记任务完成。
|
||||
4. 作为助教,我给客户添加备注后,系统自动通过 AI 分析备注内容(应用 6),提取维客线索并评分。回访备注评分 ≥6 分算回访完成。
|
||||
4. 作为助教,我给客户添加备注后,系统自动通过 AI 分析备注内容(应用 6),提取维客线索并评分。助教为客户提交备注即算回访完成(AI 评分仅用于后续绩效评估,不参与完成判定)。
|
||||
5. 作为助教,我在添加备注时可以对客户进行星星评分(再次服务意愿、再来店可能性,各 1-5 星),回访任务默认展开评分区域,其他任务类型通过"展开评价"按钮手动打开。
|
||||
6. 作为系统,回访任务至少保留 48 小时,到期后自动失效。
|
||||
7. 作为系统,当 ETL 数据延迟导致召回完成晚于备注提交时,需要回溯重分类备注。
|
||||
|
||||
### 验收标准
|
||||
|
||||
- AC1:任务生成器每日 4:00 后运行,正确按 max(WBI,NCI) 分配 4 种任务类型
|
||||
- AC1:任务生成器每日 7:00 后运行,正确按 max(WBI,NCI) 分配 4 种任务类型
|
||||
- AC2:同客户-助教-类型的任务跳过;不同类型则关闭旧任务+新建
|
||||
- AC3:回访任务 48 小时滞留机制正常(生成时间算起)
|
||||
- AC4:任务有效/无效状态 + 有效期字段正确流转
|
||||
@@ -40,19 +40,48 @@
|
||||
|--------|------|---------|---------|
|
||||
| 0 | 高优先召回 | max(WBI,NCI) > 7 | 助教为该客户服务(ETL 检测) |
|
||||
| 0 | 优先召回 | max(WBI,NCI) > 5 | 同上 |
|
||||
| 1 | 客户回访 | 完成召回后未备注 | 提交备注且应用 6 备注分析评分 ≥ 6 |
|
||||
| 1 | 客户回访 | 完成召回后未备注 | 助教为该客户提交备注(AI 评分仅绩效用途) |
|
||||
| 2 | 关系构建 | RS < 6 | 无自动完成条件(手动标记或指数变化) |
|
||||
|
||||
### 任务类型与任务状态的关系
|
||||
|
||||
**核心原则**:任务类型(task_type)和任务状态(status)是两套独立的维度,交叉描述一个任务,互不干扰。
|
||||
|
||||
- **任务类型**(task_type):描述任务的业务性质,由系统根据客户指数自动分配,不因用户操作而改变
|
||||
- `high_priority_recall`(高优先召回)
|
||||
- `priority_recall`(优先召回)
|
||||
- `follow_up_visit`(客户回访)
|
||||
- `relationship_building`(关系构建)
|
||||
|
||||
- **任务状态**(status):描述任务的生命周期阶段,可由用户操作或系统自动流转
|
||||
- `active`(有效):任务当前有效,显示在列表中
|
||||
- `inactive`(无效):任务已过期或被系统标记为无效,不显示在列表中
|
||||
- `completed`(已完成):任务已完成
|
||||
- `abandoned`(已放弃):用户主动放弃任务,记录放弃原因
|
||||
|
||||
- **置顶状态**(is_pinned):独立于任务状态,用户可对任何有效任务置顶,已放弃任务不可置顶
|
||||
|
||||
- **前端展示规则**:
|
||||
- 置顶区域:显示 `is_pinned=true` 且 `status=active` 的任务
|
||||
- 一般任务区域:显示 `is_pinned=false` 且 `status=active` 的任务
|
||||
- 已放弃区域:显示 `status=abandoned` 的任务(任务类型保留,但灰化显示)
|
||||
- 排序规则:`is_pinned DESC → priority_score DESC NULLS LAST → created_at ASC`,已放弃任务排在所有有效任务之后
|
||||
|
||||
- **长按菜单规则**:
|
||||
- 置顶/一般任务:显示"置顶/取消置顶"、"备注"、"问问AI"、"放弃任务"
|
||||
- 已放弃任务:显示"取消放弃"(直接执行,无需二次确认,将任务恢复至 `status=active` 且 `is_pinned=false`)
|
||||
|
||||
### 任务状态机
|
||||
|
||||
```
|
||||
[生成] → 有效(无有效期)
|
||||
├── 类型变更 → 旧任务无效(无有效期) + 新任务有效
|
||||
├── 指数不再满足 → 有效(填充有效期=生成时间+48h)
|
||||
│ └── 轮询检查 → 超过有效期 → 无效
|
||||
├── 新回访任务顶替 → 旧任务无效 + 新任务有效
|
||||
├── 助教放弃 → 无效(记录放弃原因)
|
||||
└── 完成 → 已完成(记录完成时间和完成时状态)
|
||||
[生成] → 有效(status=active, 无有效期)
|
||||
├── 类型变更 → 旧任务无效(status=inactive, 无有效期) + 新任务有效
|
||||
├── 指数不再满足 → 有效(status=active, 填充有效期=生成时间+48h)
|
||||
│ └── 轮询检查 → 超过有效期 → 无效(status=inactive)
|
||||
├── 新回访任务顶替 → 旧任务无效(status=inactive) + 新任务有效
|
||||
├── 助教放弃 → 无效(status=abandoned, 记录放弃原因)
|
||||
├── 助教取消放弃 → 有效(status=active, is_pinned=false, 清除放弃原因)
|
||||
└── 完成 → 已完成(status=completed, 记录完成时间和完成时状态)
|
||||
```
|
||||
|
||||
### coach_tasks 表核心字段
|
||||
@@ -80,15 +109,20 @@ biz.notes
|
||||
- content (TEXT)
|
||||
- rating_service_willingness (SMALLINT 1-5, 可空,再次服务此客户意愿)
|
||||
- rating_revisit_likelihood (SMALLINT 1-5, 可空,再来店可能性)
|
||||
- task_id (关联任务 ID, 可空)
|
||||
- task_id (关联任务 ID, 可空,备注可独立于任务创建)
|
||||
- created_at, updated_at
|
||||
```
|
||||
|
||||
> 备注与任务的关系:
|
||||
> - 备注不要求通过 `task_id` 显式关联回访任务才能触发完成
|
||||
> - 助教为某客户提交备注时,系统检查该助教×客户是否有有效的回访任务,有则标记完成
|
||||
> - `task_id` 为可选字段,备注可独立于任务创建(如直接在客户详情页写备注)
|
||||
|
||||
> 星星评分说明:
|
||||
> - 回访任务(follow_up_visit):备注弹窗默认展开评分区域
|
||||
> - 其他任务类型:评分区域默认隐藏,通过"展开评价"按钮手动打开
|
||||
> - 数据迟到场景:助教在召回任务中完成服务后顺手写备注,此时 ETL 数据未到,任务仍为召回类型,评分区域隐藏但可手动展开;当 ETL 数据到达、召回完成检测触发后,回溯机制将该备注重分类为回访备注,星星评分数据一并保留
|
||||
> - 星星评分不参与回访完成判定(完成判定仅看应用 6 评分 ≥6),不参与应用 6 分析,仅作辅助数据存储,后期功能扩展使用
|
||||
> - 星星评分不参与回访完成判定(回访完成判定为:助教为该客户提交备注即完成),不参与应用 6 分析,仅作辅助数据存储,后期功能扩展使用
|
||||
|
||||
### 触发器机制
|
||||
|
||||
@@ -103,7 +137,7 @@ biz.trigger_jobs
|
||||
```
|
||||
|
||||
预置触发器:
|
||||
1. `task_generator` — cron: 每日 04:00
|
||||
1. `task_generator` — cron: 每日 07:00
|
||||
2. `task_expiry_check` — interval: 每小时
|
||||
3. `recall_completion_check` — event: ETL 数据更新后
|
||||
4. `note_reclassify_backfill` — event: 召回完成时
|
||||
|
||||
145
docs/prd/specs/P5.2-p4-prerequisite-fixes.md
Normal file
145
docs/prd/specs/P5.2-p4-prerequisite-fixes.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# P5.2:P4 前置依赖修复 — p4-prerequisite-fixes
|
||||
|
||||
> 优先级:P5.2(P6 前置,必须在 P6 开发前完成)
|
||||
> 预估工作量:小(6 个定点修复,无新表/新接口)
|
||||
> 依赖:P4 已完成
|
||||
> 来源:P4 Spec vs 实现差异分析(`docs/reports/P4-spec-vs-implementation-gap-analysis.md`)
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
P4 核心业务层已实现并通过属性测试,但对比最新 Spec 发现 6 处实现偏差。
|
||||
这些偏差会影响 P6(前端任务模块)的正常开发,必须前置修复。
|
||||
|
||||
参考文档:
|
||||
- 差异分析:`docs/reports/P4-spec-vs-implementation-gap-analysis.md`
|
||||
- 生命周期全景:`docs/reports/P4-task-lifecycle-panorama.md`
|
||||
|
||||
---
|
||||
|
||||
## 需求(Requirements)
|
||||
|
||||
### 验收标准
|
||||
|
||||
- AC1:任务列表 API 返回 active + abandoned 两种状态,abandoned 排在最后且包含 abandon_reason
|
||||
- AC2:召回完成检测器仅完成 high_priority_recall / priority_recall 两种任务类型
|
||||
- AC3:备注回溯重分类器创建回访任务时,已有 active 回访 → 顶替(旧→无效),已完成 → 跳过
|
||||
- AC4:回访任务完成条件为「助教为该客户提交备注」,不依赖 AI 评分
|
||||
- AC5:trigger_scheduler 的 last_run_at 更新具备事务安全性
|
||||
- AC6:任务生成器 cron 时间为 07:00
|
||||
|
||||
---
|
||||
|
||||
## 任务清单
|
||||
|
||||
### T1:任务列表返回已放弃任务(GAP-3)
|
||||
|
||||
文件:`apps/backend/app/services/task_manager.py` → `get_task_list()`
|
||||
|
||||
修改内容:
|
||||
1. SQL WHERE 条件:`status = 'active'` → `status IN ('active', 'abandoned')`
|
||||
2. SQL ORDER BY 增加:`CASE WHEN status = 'abandoned' THEN 1 ELSE 0 END` 排在 `is_pinned DESC` 之前
|
||||
3. SELECT 增加 `abandon_reason` 字段
|
||||
4. 返回结构增加 `abandon_reason` 字段
|
||||
|
||||
验证:
|
||||
- 有 abandoned 任务时,列表末尾出现灰化任务且包含 abandon_reason
|
||||
- 有 active + pinned 任务时,排序为:置顶 → 一般 → 已放弃
|
||||
|
||||
---
|
||||
|
||||
### T2:召回完成检测器过滤任务类型(GAP-6)
|
||||
|
||||
文件:`apps/backend/app/services/recall_detector.py` → `_process_service_record()`
|
||||
|
||||
修改内容:
|
||||
1. SQL 增加 `AND task_type IN ('high_priority_recall', 'priority_recall')`
|
||||
|
||||
验证:
|
||||
- follow_up_visit 和 relationship_building 类型的 active 任务不会被召回检测器完成
|
||||
- high_priority_recall 和 priority_recall 正常完成
|
||||
|
||||
---
|
||||
|
||||
### T3:备注回溯重分类器冲突处理(GAP-7)
|
||||
|
||||
文件:`apps/backend/app/services/note_reclassifier.py` → `run()`
|
||||
|
||||
修改内容:
|
||||
1. 创建回访任务前,先查询是否已有同 (site_id, assistant_id, member_id) 的 follow_up_visit 任务
|
||||
2. 已有 completed 的回访任务 → 跳过创建(回访完成 +1 的语义已满足)
|
||||
3. 已有 active 的回访任务 → 旧任务标记 inactive,创建新任务
|
||||
4. 不存在 → 正常创建
|
||||
|
||||
验证:
|
||||
- 重复触发不会产生唯一约束冲突
|
||||
- 已完成的回访任务不会被覆盖
|
||||
|
||||
---
|
||||
|
||||
### T4:回访完成条件改为「有备注即完成」(新-1)
|
||||
|
||||
文件:
|
||||
- `apps/backend/app/services/note_service.py` → `create_note()`
|
||||
- `apps/backend/app/services/note_reclassifier.py` → `run()`
|
||||
|
||||
修改内容:
|
||||
1. `note_service.create_note()` 增加逻辑:创建备注后,通过 user_assistant_binding 查 assistant_id,
|
||||
再查该 (site_id, assistant_id, member_id) 是否有 active 的 follow_up_visit 任务,有则标记 completed
|
||||
2. `note_reclassifier.py`:去掉 AI 评分判定逻辑,有备注 → 回访任务直接标记 completed;
|
||||
无备注 → 创建 active 回访任务(等待备注)
|
||||
|
||||
验证:
|
||||
- 助教提交备注后,对应客户的 active 回访任务自动完成
|
||||
- 不依赖 AI 评分返回值
|
||||
|
||||
---
|
||||
|
||||
### T5:trigger_scheduler last_run_at 事务安全(GAP-9)
|
||||
|
||||
文件:`apps/backend/app/services/trigger_scheduler.py`
|
||||
|
||||
修改内容:
|
||||
1. `fire_event()` 和 `check_scheduled_jobs()` 中,将 last_run_at 更新纳入 handler 的事务范围,
|
||||
或改为执行前更新(at-most-once 语义,配合 handler 幂等性)
|
||||
|
||||
验证:
|
||||
- handler 成功但 commit 失败的场景不会导致重复处理(或重复处理是幂等的)
|
||||
|
||||
---
|
||||
|
||||
### T6:任务生成器 cron 改为 07:00(新-2)
|
||||
|
||||
文件:
|
||||
- `db/zqyy_app/migrations/2026-03-15__p52_update_cron_0700.sql`(新建迁移脚本)
|
||||
- `apps/backend/app/services/trigger_scheduler.py` → `_calculate_next_run()` 默认值
|
||||
|
||||
修改内容:
|
||||
1. 新建迁移脚本:`UPDATE biz.trigger_jobs SET trigger_config = '{"cron_expression": "0 7 * * *"}' WHERE job_name = 'task_generator'`
|
||||
2. `_calculate_next_run()` 中 cron 默认值从 `"0 4 * * *"` 改为 `"0 7 * * *"`
|
||||
|
||||
验证:
|
||||
- trigger_jobs 表中 task_generator 的 cron_expression 为 `0 7 * * *`
|
||||
- 下次运行时间计算正确
|
||||
|
||||
---
|
||||
|
||||
## 执行顺序
|
||||
|
||||
```
|
||||
批次 1(无依赖,可并行):T1、T2、T6
|
||||
批次 2(依赖 T2 确认):T3、T4
|
||||
批次 3(依赖 T3/T4 验证):T5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 不在本 SPEC 范围
|
||||
|
||||
| 项目 | 归属 | 说明 |
|
||||
|------|------|------|
|
||||
| GAP-5 备注分页 | P6-T6 | 已写入 P6 Spec |
|
||||
| GAP-8 问问 AI | P5 | P5 AI 集成层已覆盖 |
|
||||
| GAP-2 cancel_abandon 重置 is_pinned | 已修复 | 2026-03-14 |
|
||||
| GAP-4 completed_task_type 快照 | 无差异 | 实现正确 |
|
||||
@@ -27,7 +27,90 @@
|
||||
- AC7:跳档激励"到达XXX即得YYY"计算正确
|
||||
- AC8:任务卡片展示应用 4 的一句话总结(ai_cache 缓存读取)
|
||||
- AC9:备注卡片以打星方式展示应用 6 评分(1-10 分,映射公式:`星数 = score ÷ 2`,5 颗星,支持半星;如 score=7 → 3.5 星)
|
||||
- AC10:维客线索提供者按 source 字段显示"By:系统"或"By:备注",不显示具体人名
|
||||
- AC10:维客线索提供者按规则显示(见下方"维客线索提供者显示规则"章节)
|
||||
|
||||
---
|
||||
|
||||
## 维客线索提供者显示规则
|
||||
|
||||
### 后端过滤策略(安全性优先)
|
||||
|
||||
⚠️ **重要**:维客线索的过滤必须在后端完成,客户端不进行任何过滤逻辑,以防止数据泄露风险。
|
||||
|
||||
**后端返回规则**:
|
||||
|
||||
当客户端请求任务详情时,后端应返回已过滤的维客线索列表,仅包含以下类型:
|
||||
|
||||
| 线索类型 | 返回条件 | 说明 |
|
||||
|---------|--------|------|
|
||||
| 系统生成 | 总是返回 | `source = 'ai_consumption'`(应用 3 消费分析) |
|
||||
| 我的记录 | 总是返回 | `source = 'manual'` 且 `recorded_by_assistant_id = 当前登录用户ID` |
|
||||
| 他人记录 | 返回脱敏版本 | `source = 'manual'` 且 `recorded_by_assistant_id ≠ 当前登录用户ID`,返回时需脱敏 |
|
||||
| 备注分析 | 返回脱敏版本 | `source = 'ai_note'`,返回时需脱敏 |
|
||||
|
||||
**脱敏规则**:
|
||||
|
||||
对于"他人记录"和"备注分析"类型的线索,后端返回时应:
|
||||
- 移除或不返回 `recorded_by_assistant_id` 字段
|
||||
- 移除或不返回 `recorded_by_name` 字段
|
||||
- 保留 `source` 字段用于前端判断显示"By:某人"
|
||||
|
||||
### 前端显示规则
|
||||
|
||||
前端接收后端返回的维客线索后,直接根据 `source` 字段显示,无需额外过滤:
|
||||
|
||||
| source 值 | 显示文案 |
|
||||
|----------|--------|
|
||||
| `ai_consumption` | By:系统 |
|
||||
| `manual` 且有 `recordedByAssistantId` | By:我 |
|
||||
| `manual` 或 `ai_note` 且无 `recordedByAssistantId` | By:某人 |
|
||||
|
||||
**注意**:
|
||||
- "某人"是字符串字面量,不是真实人名
|
||||
- 不显示具体的助教姓名或ID
|
||||
- 与客户详情页不同(客户详情页显示具体人名)
|
||||
|
||||
### 多人情况处理
|
||||
|
||||
当同一客户有多条维客线索时,前端按以下规则展示:
|
||||
|
||||
1. **按时间排序**:按 `created_at` 升序排列(最早的在前)
|
||||
2. **取最先反馈**:
|
||||
- 如果有"By:系统"的线索,优先显示最早的"By:系统"
|
||||
- 如果没有"By:系统",则显示最早的"By:我"或"By:某人"
|
||||
3. **单条展示**:维客线索卡片中仅显示一条(最优先的),其他线索可通过"查看全部"或列表展开查看
|
||||
|
||||
### 前端实现示例
|
||||
|
||||
```typescript
|
||||
// 前端:直接根据后端返回的数据显示,无需过滤
|
||||
function getClueProvider(clue: RetentionClue): string {
|
||||
if (clue.source === 'ai_consumption') {
|
||||
return 'By:系统'
|
||||
}
|
||||
// 后端已确保:如果是 manual 且有 recordedByAssistantId,则是当前用户
|
||||
if (clue.source === 'manual' && clue.recordedByAssistantId) {
|
||||
return 'By:我'
|
||||
}
|
||||
// 其他情况(manual 无 ID、ai_note)都显示"By:某人"
|
||||
return 'By:某人'
|
||||
}
|
||||
|
||||
// 前端:从多条线索中选择最优先的
|
||||
function selectPrimaryClue(clues: RetentionClue[]): RetentionClue {
|
||||
// 1. 按 createdAt 升序排序
|
||||
const sorted = clues.sort((a, b) =>
|
||||
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
||||
)
|
||||
|
||||
// 2. 优先返回 source='ai_consumption' 的最早线索
|
||||
const systemClue = sorted.find(c => c.source === 'ai_consumption')
|
||||
if (systemClue) return systemClue
|
||||
|
||||
// 3. 否则返回最早的线索
|
||||
return sorted[0]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -43,14 +126,14 @@
|
||||
- 客户信息卡片
|
||||
- 近期服务记录(时间+时长)
|
||||
- AI 区域:
|
||||
- 维客线索(应用 8 整合线索 + 人工,读取 `member_retention_clue`;Emoji 作为二级标签、提供者逗号分隔展示)
|
||||
- 维客线索(应用 8 整合线索 + 人工,读取 `member_retention_clue`;Emoji 作为二级标签、提供者按规则展示)
|
||||
- 客户分析(应用 7 缓存:运营策略数组 + 总结)
|
||||
- 关系分析 + 任务建议 + 一句话总结(应用 4 缓存,读取应用 8 最新线索)
|
||||
- 话术参考(应用 5 缓存)
|
||||
- 备注分析评分(应用 6 缓存:1-10 分)
|
||||
- 备注入口(提交后触发回访完成判定);备注弹窗含星星评分(再次服务意愿+再来店可能性,各 1-5 星):回访任务默认展开评分区域,其他任务类型默认隐藏通过"展开评价"按钮手动打开
|
||||
- 备注卡片展示应用 6 的 `score`(1-10 分),以打星方式呈现(`星数 = score ÷ 2`,5 颗星,支持半星;从 `ai_cache` cache_type=app6_note_analysis 读取)
|
||||
- 维客线索(应用 8)提供者显示规则:`source=ai_consumption` 显示"By:系统",`source=ai_note` 显示"By:备注",不显示具体人名
|
||||
- 维客线索提供者显示规则(见上方"维客线索提供者显示规则"章节)
|
||||
- "问问助手"按钮 → chat.html
|
||||
|
||||
### notes(备注管理)
|
||||
@@ -81,5 +164,5 @@
|
||||
- T4-a:细化 P5 应用 4(关系分析)Prompt JSON 结构,实现 `service_history`、`assistant_info` 等字段的拼接函数(对应 P5-T7-完整)
|
||||
- T4-b:细化 P5 应用 5(话术参考)Prompt JSON 结构,实现拼接函数(对应 P5-T8-完整)
|
||||
- [ ] T5:实现备注提交功能(集成回访完成判定 + 星星评分:回访任务默认展开,其他任务类型通过"展开评价"按钮手动打开)
|
||||
- [ ] T6:实现 notes 页面(列表 + 删除)
|
||||
- [ ] T6:实现 notes 页面(列表 + 删除 + 分页懒加载)
|
||||
- [ ] T7:实现通用组件(爱心 icon、喜好标签、跟/弃 icon、预估标记)
|
||||
|
||||
@@ -66,16 +66,18 @@
|
||||
|
||||
### 财务看板数据字段映射(已校准,数据源:DWD-DOC 03-财务全景)
|
||||
|
||||
收入结构(settle_type=1,台桌结账):
|
||||
收入结构(settle_type IN (1, 3),台桌结账 + 商城订单):
|
||||
|
||||
| 收入来源 | DWS 字段 | 占比 | 说明 |
|
||||
|----------|----------|-----:|------|
|
||||
| 台费 | `table_charge_money` | 56.6% | 按秒计时,台区单价固定 |
|
||||
| 助教陪打 | `assistant_pd_money` | 30.6% | 按秒计时,单价按等级 |
|
||||
| 商品 | `goods_money` | 10.1% | 酒水食品 |
|
||||
| 助教超休 | `assistant_cx_money` | 0.9% | 按课时,190 元/课时 |
|
||||
| 助教超休 | `assistant_cx_money` | 0.9%(仅 type=1) | 按课时,190 元/课时;settle_type=3 含 477 笔纯超休订单(cx=245,480),占全口径超休 85%+ |
|
||||
| 灯控电费 | `electricity_money` | 0% | 门店未启用,全为 0 |
|
||||
|
||||
> ⚠️ settle_type=3(商城订单)中包含大量纯超休/激励课订单,财务看板取数必须 `settle_type IN (1, 3)`,否则严重低估超休收入。
|
||||
|
||||
> ⚠️ 消费金额口径:使用 `items_sum`(= tc + goods + pd + cx + electricity),不得使用 `consume_money`(三种历史口径混合)。
|
||||
|
||||
充值收入(settle_type=5):从 `dws_finance_recharge_summary` 读取,充值退款(settle_type=7)需扣除。
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
- 商城订单:助教列表(花名+级别+课程类型+服务时长+定档绩效)、支付金额、食品酒水总金额
|
||||
- 充值:充值金额、支付方式
|
||||
- 备注列表
|
||||
- AI 维客线索(应用 8 整合线索 + 人工,读取 `member_retention_clue`;Emoji 作为二级标签、提供者逗号分隔展示)
|
||||
- AI 维客线索(应用 8 整合线索 + 人工,读取 `member_retention_clue`;Emoji 作为二级标签、提供者显示规则见下方"维客线索提供者显示规则"章节)
|
||||
- AI 客户分析(应用 7 缓存:运营策略数组 + 总结,结账单出现后自动生成;从 `ai_cache` cache_type=app7_customer_analysis 读取)
|
||||
- "问问助手"入口 → chat.html
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
| settle_type | 含义 | 数量占比 | 消费记录样式 |
|
||||
|:-----------:|------|---------|-------------|
|
||||
| 1 | 台桌结账 | 78.6% | 下沉到 `dwd_table_fee_log` 台费明细 |
|
||||
| 3 | 商城订单 | 21.4% | 助教列表 + 支付金额 + 食品酒水 |
|
||||
| 3 | 商城订单 | 21.4% | 助教列表 + 支付金额 + 食品酒水(含 477 笔纯超休/激励课订单) |
|
||||
| 5 | 正常充值 | — | 从 `dwd_recharge_order` 单独查询 |
|
||||
| 7 | 充值退款 | 极少(10 笔) | 不在消费记录中展示 |
|
||||
| 6 | 结算退款 | 极少(1 笔) | 不在消费记录中展示 |
|
||||
@@ -126,6 +126,79 @@ items_sum = table_charge_money + goods_money + assistant_pd_money
|
||||
|
||||
---
|
||||
|
||||
## 维客线索提供者显示规则
|
||||
|
||||
### 后端过滤策略(安全性优先)
|
||||
|
||||
⚠️ **重要**:维客线索的过滤必须在后端完成,客户端不进行任何过滤逻辑,以防止数据泄露风险。
|
||||
|
||||
**客户详情页后端返回规则**:
|
||||
|
||||
后端应返回所有相关的维客线索(包括系统生成、当前用户、他人记录、备注分析),但需要根据用户权限进行脱敏:
|
||||
|
||||
| 线索类型 | 返回条件 | 说明 |
|
||||
|---------|--------|------|
|
||||
| 系统生成 | 总是返回 | `source = 'ai_consumption'`,完整返回 |
|
||||
| 我的记录 | 总是返回 | `source = 'manual'` 且 `recorded_by_assistant_id = 当前用户ID`,完整返回 |
|
||||
| 他人记录 | 返回完整版本 | `source = 'manual'` 且 `recorded_by_assistant_id ≠ 当前用户ID`,返回 `recorded_by_name` |
|
||||
| 备注分析 | 返回完整版本 | `source = 'ai_note'`,返回原备注的 `recorded_by_name` |
|
||||
|
||||
### 前端显示规则
|
||||
|
||||
**客户详情页**:前端直接显示后端返回的所有维客线索,显示具体人名:
|
||||
|
||||
| source 值 | 显示文案 |
|
||||
|----------|--------|
|
||||
| `ai_consumption` | By:系统 |
|
||||
| `manual` 且有 `recordedByAssistantId` | By:我 |
|
||||
| `manual` 或 `ai_note` 且有 `recordedByName` | By:{recordedByName} |
|
||||
|
||||
多条线索时,提供者用逗号分隔展示(如"By:系统, 王芳, 李明")
|
||||
|
||||
**任务详情页**:前端根据后端返回的脱敏数据显示(详见 P6 规范)
|
||||
|
||||
### 多人情况处理
|
||||
|
||||
当同一客户有多条维客线索时,前端按以下规则展示:
|
||||
|
||||
1. **按时间排序**:按 `created_at` 升序排列(最早的在前)
|
||||
2. **取最先反馈**:
|
||||
- 如果有"By:系统"的线索,优先显示最早的"By:系统"
|
||||
- 如果没有"By:系统",则显示最早的"By:我"或其他人名
|
||||
3. **单条展示**:维客线索卡片中仅显示一条(最优先的),其他线索可通过"查看全部"或列表展开查看
|
||||
|
||||
### 前端实现示例
|
||||
|
||||
```typescript
|
||||
// 客户详情页:直接显示后端返回的数据
|
||||
function getClueProvider(clue: RetentionClue): string {
|
||||
if (clue.source === 'ai_consumption') {
|
||||
return 'By:系统'
|
||||
}
|
||||
if (clue.source === 'manual' && clue.recordedByAssistantId) {
|
||||
return 'By:我'
|
||||
}
|
||||
if (clue.recordedByName) {
|
||||
return `By:${clue.recordedByName}`
|
||||
}
|
||||
return 'By:某人' // 备用
|
||||
}
|
||||
|
||||
// 从多条线索中选择最优先的
|
||||
function selectPrimaryClue(clues: RetentionClue[]): RetentionClue {
|
||||
const sorted = clues.sort((a, b) =>
|
||||
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
||||
)
|
||||
|
||||
const systemClue = sorted.find(c => c.source === 'ai_consumption')
|
||||
if (systemClue) return systemClue
|
||||
|
||||
return sorted[0]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 小程序前端开发强制规范
|
||||
|
||||
> 以下规范适用于本 SPEC 中所有小程序页面实现,具有强制约束力。
|
||||
|
||||
Reference in New Issue
Block a user