包含多个会话的累积代码变更: - backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔 - admin-web: ETL 状态页、任务管理、调度配置、登录优化 - miniprogram: 看板页面、聊天集成、UI 组件、导航更新 - etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强 - tenant-admin: 项目初始化 - db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8) - packages/shared: 枚举和工具函数更新 - tools: 数据库工具、报表生成、健康检查 - docs: PRD/架构/部署/合约文档更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
517 lines
20 KiB
Markdown
517 lines
20 KiB
Markdown
# P17:助教客户归属与任务生成引擎 — 商业逻辑 PRD
|
||
|
||
> 版本:v1.0 | 日期:2026-03-24 | 作者:Neo
|
||
> 依赖:P4(核心业务层)、ETL INDEX 层(RS/OS/MS/ML/WBI/NCI)
|
||
|
||
---
|
||
|
||
## 0. 文档背景与目标
|
||
|
||
### 0.1 问题来源
|
||
|
||
本文档源自 2026-03-24 的三次会话(#20 / #21 / #22)对任务生成器的持续审查,核心发现两大系统性问题:
|
||
|
||
**问题 A — 客户归属失控**:任务生成器在分配"召回类"任务时,仅凭"是否绑定微信"来圈定助教候选池,而不是依据助教与客户之间真实发生的服务关系。导致全店所有客户的召回任务都堆给唯一绑定了微信的助教,任务量严重失衡。
|
||
|
||
**问题 B — 客户转移无保护**:当召回连续失败后需要将客户转给其他助教跟进,但现行逻辑缺少任何保护机制:
|
||
- 没有"门店助教规模保护"——助教数量未达标时就启动转移,容易混乱
|
||
- 没有"入驻时间保护"——新助教未经历足够交互就被分配陌生客户
|
||
- 没有"服务关系门槛"——客户有可能被转给从未服务过他的助教,关系冷启动成本极高
|
||
|
||
### 0.2 类比:助教 = 台球厅的销售 + 客户运营
|
||
|
||
助教的角色本质上同时承担两件事:
|
||
|
||
| 职能 | 销售视角 | 客户运营视角 |
|
||
|------|---------|-------------|
|
||
| 核心工作 | 把"流失/新客"召回到店并成交 | 和已服务客户维持稳定关系 |
|
||
| 核心指标 | WBI(流失风险分)、NCI(新客转化分) | RS(关系强度)、MS(升温动量) |
|
||
| 任务类型 | 高优先召回、优先召回 | 关系构建、客户回访 |
|
||
| 客户归属逻辑 | 谁有机会承接该客户的召回 | 谁是该客户的主责助教 |
|
||
|
||
因此,**客户归属**和**任务分配**需要两套紧密联动但逻辑独立的规则。
|
||
|
||
### 0.3 本文档目标
|
||
|
||
1. 定义清晰、可落地的**客户归属算法**(基于 OS/RS 四象限模型)
|
||
2. 定义完整、可解释的**任务生成算法**(基于归属约束 + 指数门槛 + 四种任务类型)
|
||
3. 定义**客户转移保护机制**(三重保护:规模保护 + 时间保护 + 关系门槛)
|
||
4. 给出每个决策点的**参数化方案**,支持门店级配置调整
|
||
|
||
---
|
||
|
||
## 1. 概念词典
|
||
|
||
| 术语 | 通俗解释 | 技术对应 |
|
||
|------|---------|----------|
|
||
| **RS(关系强度分)** | 助教和客户之间服务关系的紧密程度,上过的课越多、越近期,分越高(0-10分) | `rs_display`,来自 `dws_member_assistant_relation_index` |
|
||
| **OS(归属份额)** | 在服务过该客户的所有助教中,该助教占多大比重;决定"主责/共管/待认领"标签 | `os_label ∈ {MAIN, COMANAGE, POOL, UNASSIGNED}` |
|
||
| **MS(升温动量分)** | 最近服务是在增多还是减少,升温则分高,降温则分低(0-10分) | `ms_display` |
|
||
| **ML(付费关联分)** | 客户的消费台账中有多少是由该助教直接带来的(0-10分) | `ml_display` |
|
||
| **WBI(流失风险分)** | 这个客户有多久没来了、来的频率是否下降,分越高越需要主动联系(0-10分) | `display_score`,来自 `dws_member_winback_index` |
|
||
| **NCI(新客转化分)** | 新客户被转化为回头客的紧迫程度(0-10分) | `display_score`,来自 `dws_member_newconv_index` |
|
||
| **客户转移** | 原主责助教召回失败超过阈值后,系统将该客户的召回任务扩展给其他有服务关系的助教 | task_generator 中的转移逻辑 |
|
||
| **门店规模保护** | 若店内绑定微信的在职助教比例不足50%,禁用客户转移功能 | `guard_assistant_coverage_ratio` |
|
||
| **入驻时间保护** | 助教绑定微信后10天内,不接收转移客户 | `guard_new_assistant_days` |
|
||
| **服务关系门槛** | 只把客户转给曾经服务过该客户的助教 | `os_label ≠ UNASSIGNED` |
|
||
|
||
---
|
||
|
||
## 2. 客户归属算法
|
||
|
||
### 2.1 设计原则
|
||
|
||
客户归属解决的问题是:**一个客户应该由哪个(些)助教负责跟进?**
|
||
|
||
台球厅的实际场景是:一个客户可能被多个助教服务过,但服务次数和亲密度差别很大。归属算法需要把这种模糊关系量化为清晰的"主责/共管/待认领/未归属"四个层级。
|
||
|
||
> 参考 CRM 行业最佳实践(Salesforce/HubSpot 的 Account Ownership 模型):
|
||
> 当多名销售都服务过同一客户时,最优解不是"谁先认领谁拥有",而是用历史交互深度加权判断,避免资深关系被新人抢占。
|
||
|
||
### 2.2 OS 归属标签定义
|
||
|
||
OS 由 ETL `RelationIndexTask` 已实现,输出 `os_label` 字段,定义如下:
|
||
|
||
```
|
||
MAIN — 主责助教:在服务过该客户的助教中,该助教的 os_share 显著高于其他人
|
||
COMANAGE — 共管助教:多名助教的 os_share 差距不大(RS 相对差 < 50%),均视为负责人
|
||
POOL — 待认领:有过服务记录,但 os_share 较低,属于潜在接管候选
|
||
UNASSIGNED — 无关联:从未为该客户提供过服务记录
|
||
```
|
||
|
||
**RS 相对差公式(来自 PRD 审阅 Q3.2)**:
|
||
|
||
```
|
||
对于助教 A(rs=8)和助教 B(rs=5):
|
||
相对差 = (8 - 5) / 8 = 0.375 < 0.5
|
||
→ 两人共管该客户(COMANAGE)
|
||
|
||
对于助教 A(rs=9)和助教 B(rs=4):
|
||
相对差 = (9 - 4) / 9 = 0.556 > 0.5
|
||
→ 助教 A 为主责(MAIN),助教 B 降为 POOL
|
||
```
|
||
|
||
### 2.3 归属判定流程
|
||
|
||
```
|
||
输入:某客户的所有 (assistant_id, rs_display) 记录
|
||
|
||
Step 1 — 过滤无效记录
|
||
rs_display = 0 → 视为无有效服务,os_label = UNASSIGNED
|
||
|
||
Step 2 — 排序
|
||
按 rs_display DESC 排列所有助教
|
||
|
||
Step 3 — 主责判定
|
||
取最高分助教 A;
|
||
若 A 是唯一有效助教,或与第二名 B 的相对差 ≥ 50%
|
||
→ A 标记为 MAIN,其余有效助教标记为 POOL
|
||
|
||
Step 4 — 共管判定
|
||
若 A 与 B 的相对差 < 50%
|
||
→ 继续对 B 与 C 执行同样判断
|
||
→ 所有满足"与最高分相对差 < 50%"的助教标记为 COMANAGE
|
||
→ 其余有效助教标记为 POOL
|
||
|
||
Step 5 — 输出
|
||
写入 os_label + os_share + os_rank 字段(由 ETL 层计算,本 PRD 只消费结果)
|
||
```
|
||
|
||
### 2.4 归属与任务分配的映射关系
|
||
|
||
| os_label | 召回类任务 | 关系构建任务 |
|
||
|----------|-----------|-------------|
|
||
| MAIN | ✅ 有资格接收 | ✅ 有资格接收 |
|
||
| COMANAGE | ✅ 有资格接收 | ✅ 有资格接收 |
|
||
| POOL | ❌ 常规不分配;仅在"客户转移"条件触发后分配召回 | ❌ 不分配 |
|
||
| UNASSIGNED | ❌ 永不分配 | ❌ 永不分配 |
|
||
|
||
>
|
||
**关键改变**:将 WBI/NCI 召回任务的候选池从「绑定微信的助教」改为「对该客户 os_label ∈ {MAIN, COMANAGE} 的助教」。这是修复"小燕任务爆炸"问题的核心。
|
||
|
||
---
|
||
|
||
## 3. 客户转移机制
|
||
|
||
### 3.1 触发条件
|
||
|
||
客户转移是召回失败后的兜底机制,类似销售中的「线索升级」:
|
||
|
||
```
|
||
触发条件:
|
||
某客户的主责/共管助教(MAIN/COMANAGE)对该客户的召回任务
|
||
在连续 N 个任务周期内均未完成(status ≠ completed),
|
||
且 WBI 或 NCI 持续高于门槛值
|
||
|
||
默认参数:
|
||
consecutive_recall_fail_cycles = 3 (连续3个生成周期未完成)
|
||
min_wbi_for_transfer = 5.0 (WBI > 5 才触发转移)
|
||
```
|
||
|
||
### 3.2 三重保护机制
|
||
|
||
客户转移在触发前,必须通过三道检查。任一检查不通过,本次不转移。
|
||
|
||
#### 保护 1 — 门店助教规模保护
|
||
|
||
```
|
||
规则:
|
||
若 (店内绑定微信的在职助教数 / 店内全部在职助教总数) < 0.5
|
||
→ 客户转移功能全局禁用
|
||
|
||
业务意义:
|
||
门店大部分助教都没绑定小程序时,系统对助教团队的覆盖率太低,
|
||
此时启动转移会造成信息盲区(被转出的任务助教看不到)。
|
||
只有当绑定率超过 50% 时,才能保障转移链路有效。
|
||
|
||
参数:guard_assistant_coverage_ratio = 0.5(可配置)
|
||
```
|
||
|
||
#### 保护 2 — 入驻时间保护
|
||
|
||
```
|
||
规则:
|
||
助教首次绑定微信的时间(binding_created_at)距今不足 10 天
|
||
→ 该助教本轮不参与转移候选池
|
||
|
||
业务意义:
|
||
新助教刚入驻,还没有建立足够的客户印象,
|
||
贸然分配陌生客户会降低召回成功率,也打击新助教积极性。
|
||
10 天保护期给新助教建立自己客户基础的空间。
|
||
|
||
参数:guard_new_assistant_days = 10(可配置)
|
||
```
|
||
|
||
#### 保护 3 — 服务关系门槛
|
||
|
||
```
|
||
规则:
|
||
待转入的助教对该客户的 os_label 必须 ∈ {POOL}
|
||
(即曾经服务过该客户,但目前归属份额较低)
|
||
os_label = UNASSIGNED 的助教永远不参与转移候选
|
||
|
||
业务意义:
|
||
从未服务过该客户的助教,关系完全冷启动。
|
||
不论技术上可以转,业务上也不应该这样做。
|
||
转移只在"有过接触但目前不是主责"的助教之间发生,
|
||
最大化利用已有的关系温度。
|
||
|
||
参数:transfer_eligible_labels = ['POOL'](固定,不可放开到 UNASSIGNED)
|
||
```
|
||
|
||
### 3.3 转移候选排序
|
||
|
||
通过三重保护后,对候选助教按以下优先级排序,取得分最高的 1 名(或多名,取决于配置):
|
||
|
||
```
|
||
转移得分 = w_rs × rs_display + w_ms × ms_display + w_ml × ml_display
|
||
|
||
默认权重:
|
||
w_rs = 0.5 (关系强度,历史服务深度)
|
||
w_ms = 0.3 (升温动量,关系是在改善还是冷却)
|
||
w_ml = 0.2 (付费关联,客户是否在该助教服务期间消费)
|
||
|
||
业务意义:
|
||
优先把客户转给"之前有服务基础、且关系正在升温、且有消费记录"的助教,
|
||
而不是随机转给"历史最高分"。MS 权重确保选的是当下状态最好的关系,
|
||
而不是过去最好的关系。
|
||
```
|
||
|
||
### 3.4 转移后的归属处理
|
||
|
||
```
|
||
转移发生后:
|
||
新助教的任务状态 = active(高优先召回 or 优先召回)
|
||
原主责助教的同类型召回任务 status = 'transferred'(新增任务状态)
|
||
—— 不关闭原任务,而是标记为"已转移",供审计和历史查询
|
||
|
||
若转移后新助教也失败(连续 consecutive_recall_fail_cycles 次),
|
||
且 POOL 中还有其他候选助教,可再次转移
|
||
但每个客户的累计转移次数上限 = max_transfer_count(默认 2 次)
|
||
超过上限后,任务进入 PENDING_REVIEW 状态,等待人工介入
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 任务生成算法(重新设计版)
|
||
|
||
### 4.1 总体流程
|
||
|
||
```
|
||
每日 07:00 任务生成器 run() 执行:
|
||
|
||
Step 1 — 确定全店有效助教池
|
||
查询 dws_member_assistant_relation_index
|
||
取 os_label ∈ {MAIN, COMANAGE} 的所有 (assistant_id, member_id) 对
|
||
(放弃以 user_assistant_binding 为入口的旧逻辑)
|
||
|
||
Step 2 — 读取指数
|
||
对每个 assistant_id 关联的 member_id 集合:
|
||
WBI = dws_member_winback_index.display_score(按 member_id 查)
|
||
NCI = dws_member_newconv_index.display_score(按 member_id 查)
|
||
RS = dws_member_assistant_relation_index.rs_display(按 assistant_id + member_id 查)
|
||
OS = dws_member_assistant_relation_index.os_label(同上)
|
||
MS = dws_member_assistant_relation_index.ms_display(同上)
|
||
|
||
Step 3 — 归属过滤(新增)
|
||
对每个 (assistant_id, member_id) 对:
|
||
若 os_label ∉ {MAIN, COMANAGE} → 跳过召回类任务判断
|
||
(POOL 助教只在"客户转移"触发后才参与)
|
||
|
||
Step 4 — 任务类型判定 determine_task_type()
|
||
见第 4.2 节
|
||
|
||
Step 5 — 任务状态检查与写入
|
||
见第 4.3 节
|
||
|
||
Step 6 — 客户转移检查(独立子流程)
|
||
见第 3 节
|
||
|
||
Step 7 — 更新 trigger_jobs 时间戳
|
||
```
|
||
|
||
### 4.2 任务类型判定算法(四级漏斗)
|
||
|
||
```
|
||
function determine_task_type(os_label, wbi, nci, rs, has_pending_recall, has_follow_up_note):
|
||
|
||
priority_score = max(wbi, nci)
|
||
|
||
-- 漏斗第一级:高优先召回
|
||
if priority_score > 7 AND os_label ∈ {MAIN, COMANAGE}:
|
||
return 'high_priority_recall'
|
||
|
||
-- 漏斗第二级:优先召回
|
||
if priority_score > 5 AND os_label ∈ {MAIN, COMANAGE}:
|
||
return 'priority_recall'
|
||
|
||
-- 漏斗第三级:客户回访
|
||
-- (召回已完成 ETL 确认,但助教尚未提交备注)
|
||
if has_pending_recall == True AND has_follow_up_note == False:
|
||
return 'follow_up_visit'
|
||
|
||
-- 漏斗第四级:关系构建
|
||
-- RS ≤ 1 视为无有效交互,不生成任务
|
||
if 1 < rs < 6 AND os_label ∈ {MAIN, COMANAGE}:
|
||
return 'relationship_building'
|
||
|
||
return None -- 不生成任务
|
||
```
|
||
|
||
**四级漏斗的业务逻辑说明**:
|
||
|
||
| 级别 | 任务 | 触发信号 | 业务含义 |
|
||
|------|------|---------|----------|
|
||
| 1 | 高优先召回 | WBI 或 NCI > 7,且本人是主责/共管 | 客户流失风险极高,必须今天联系 |
|
||
| 2 | 优先召回 | WBI 或 NCI > 5,且本人是主责/共管 | 客户有流失迹象,本周内联系 |
|
||
| 3 | 客户回访 | 召回成功但未备注(ETL 已确认到店) | 召回成功后的温度维护,不能凉掉 |
|
||
| 4 | 关系构建 | RS 在 1-6 之间,关系有提升空间 | 日常维护客情,升温关系 |
|
||
|
||
> 注意:漏斗是互斥优先的。一个客户-助教对同一时刻只生成一条最高优先级的任务。
|
||
|
||
### 4.3 任务状态检查与写入逻辑
|
||
|
||
```
|
||
对每个 (assistant_id, member_id, new_task_type):
|
||
|
||
Case A — 已存在相同类型的 active 任务:
|
||
→ 跳过(skip),不更新 created_at
|
||
stats['skipped'] += 1
|
||
|
||
Case B — 已存在不同类型的 active 任务:
|
||
→ 将旧任务 status 改为 'inactive'
|
||
→ 创建新任务(status = 'active')
|
||
→ 记录 coach_task_history
|
||
stats['replaced'] += 1
|
||
|
||
Case C — 不存在 active 任务:
|
||
→ 直接创建新任务
|
||
stats['created'] += 1
|
||
|
||
Case D — new_task_type = None:
|
||
→ 检查 follow_up_visit 是否超过48小时 → inactive
|
||
stats['skipped'] += 1
|
||
```
|
||
|
||
### 4.4 关系构建任务的 RS 门槛说明
|
||
|
||
| RS 区间 | 含义 | 是否生成任务 |
|
||
|---------|------|------------|
|
||
| RS = 0 | 无有效服务数据 | 否 |
|
||
| RS ≤ 1 | 仅 1 次以下交互,关系未建立 | 否 |
|
||
| 1 < RS < 6 | 有初步关系但未牢固,黄金维护窗口 | 是(关系构建) |
|
||
| RS ≥ 6 | 关系已牢固,无需系统催动 | 否 |
|
||
|
||
---
|
||
|
||
## 5. 参数总览与配置说明
|
||
|
||
所有参数存储于 `biz.cfg_task_generator_params`,支持按 `site_id` 级别覆盖。
|
||
|
||
| 参数名 | 默认值 | 说明 |
|
||
|--------|--------|------|
|
||
| `high_priority_recall_threshold` | 7.0 | max(WBI,NCI) 超过此值生成高优先召回 |
|
||
| `priority_recall_threshold` | 5.0 | max(WBI,NCI) 超过此值生成优先召回 |
|
||
| `rs_min_for_relationship` | 1.0 | RS ≤ 此值不生成关系构建 |
|
||
| `rs_max_for_relationship` | 6.0 | RS ≥ 此值不生成关系构建 |
|
||
| `consecutive_recall_fail_cycles` | 3 | 连续失败多少轮触发客户转移 |
|
||
| `min_wbi_for_transfer` | 5.0 | WBI 低于此值不触发转移 |
|
||
| `guard_assistant_coverage_ratio` | 0.5 | 绑定率低于此值禁用转移 |
|
||
| `guard_new_assistant_days` | 10 | 新助教入驻保护天数 |
|
||
| `transfer_score_w_rs` | 0.5 | 转移候选排序:RS 权重 |
|
||
| `transfer_score_w_ms` | 0.3 | 转移候选排序:MS 权重 |
|
||
| `transfer_score_w_ml` | 0.2 | 转移候选排序:ML 权重 |
|
||
| `max_transfer_count` | 2 | 单客户最大累计转移次数 |
|
||
| `follow_up_visit_retention_hours` | 48 | 回访任务最低保留时长(小时) |
|
||
|
||
## 6. 数据库变更需求
|
||
|
||
### 6.1 新增字段:`biz.coach_tasks`
|
||
|
||
```sql
|
||
-- 新增任务状态枚举值
|
||
ALTER TYPE task_status ADD VALUE IF NOT EXISTS 'transferred';
|
||
ALTER TYPE task_status ADD VALUE IF NOT EXISTS 'pending_review';
|
||
|
||
-- 新增转移追踪字段
|
||
ALTER TABLE biz.coach_tasks
|
||
ADD COLUMN transfer_count INTEGER NOT NULL DEFAULT 0,
|
||
ADD COLUMN transferred_from BIGINT REFERENCES biz.coach_tasks(id),
|
||
ADD COLUMN transferred_at TIMESTAMPTZ;
|
||
```
|
||
|
||
### 6.2 新增表:`biz.cfg_task_generator_params`
|
||
|
||
```sql
|
||
CREATE TABLE biz.cfg_task_generator_params (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
site_id BIGINT, -- NULL 表示全局默认值
|
||
param_key VARCHAR(64) NOT NULL,
|
||
param_value NUMERIC NOT NULL,
|
||
description TEXT,
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
UNIQUE (site_id, param_key)
|
||
);
|
||
```
|
||
|
||
### 6.3 新增表:`biz.coach_task_transfer_log`
|
||
|
||
```sql
|
||
CREATE TABLE biz.coach_task_transfer_log (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
site_id BIGINT NOT NULL,
|
||
member_id BIGINT NOT NULL,
|
||
from_assistant_id BIGINT NOT NULL,
|
||
to_assistant_id BIGINT NOT NULL,
|
||
from_task_id BIGINT NOT NULL REFERENCES biz.coach_tasks(id),
|
||
to_task_id BIGINT REFERENCES biz.coach_tasks(id),
|
||
transfer_reason TEXT,
|
||
guard_checks JSONB, -- 三重保护检查结果
|
||
transfer_score NUMERIC,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 核心流程伪代码(供开发参考)
|
||
|
||
### 7.1 任务生成器主流程(重写版)
|
||
|
||
```python
|
||
def run() -> dict:
|
||
stats = {"created": 0, "replaced": 0, "skipped": 0, "transferred": 0}
|
||
params = load_params() # 从 cfg_task_generator_params 加载
|
||
|
||
# Step 1: 以 OS 归属为入口(取代旧的 user_assistant_binding 入口)
|
||
ownership_pairs = query("""
|
||
SELECT assistant_id, member_id, os_label,
|
||
rs_display, ms_display, ml_display
|
||
FROM app.v_dws_member_assistant_relation_index
|
||
WHERE os_label IN ('MAIN', 'COMANAGE')
|
||
""")
|
||
|
||
# Step 2: 批量读取 WBI / NCI
|
||
member_ids = {p['member_id'] for p in ownership_pairs}
|
||
wbi_map = fetch_wbi(member_ids)
|
||
nci_map = fetch_nci(member_ids)
|
||
|
||
# Step 3: 逐对生成任务
|
||
for pair in ownership_pairs:
|
||
process_pair(pair, wbi_map, nci_map, params, stats)
|
||
|
||
# Step 4: 客户转移子流程
|
||
run_transfer_check(params, stats)
|
||
|
||
update_trigger_timestamp('task_generator')
|
||
return stats
|
||
```
|
||
|
||
### 7.2 客户转移子流程
|
||
|
||
```python
|
||
def run_transfer_check(params, stats):
|
||
# 保护 1: 门店规模检查
|
||
if coverage_ratio() < params['guard_assistant_coverage_ratio']:
|
||
return # 全局禁用
|
||
|
||
# 查找连续失败达阈值的 (member_id, assistant_id) 对
|
||
for candidate in find_failed_recall_candidates(params):
|
||
pool = get_pool_assistants(candidate['member_id'])
|
||
|
||
eligible = [
|
||
a for a in pool
|
||
if days_since_binding(a) >= params['guard_new_assistant_days'] # 保护 2
|
||
and a['os_label'] == 'POOL' # 保护 3
|
||
]
|
||
if not eligible:
|
||
continue
|
||
|
||
# 按转移得分选最优候选
|
||
best = max(eligible, key=lambda a:
|
||
params['w_rs'] * a['rs'] +
|
||
params['w_ms'] * a['ms'] +
|
||
params['w_ml'] * a['ml']
|
||
)
|
||
do_transfer(candidate, best, stats)
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 验收标准(Acceptance Criteria)
|
||
|
||
| # | 验收项 | 判定方式 |
|
||
|---|--------|----------|
|
||
| AC1 | 召回任务只分配给 os_label ∈ {MAIN, COMANAGE} 的助教 | 数据库核查,无 UNASSIGNED/POOL 助教的召回任务 |
|
||
| AC2 | 关系构建任务 RS 门槛正确(1 < RS < 6) | 检查 relationship_building 任务对应的 rs_display |
|
||
| AC3 | 客户转移通过三重保护 | transfer_log.guard_checks 全部 pass |
|
||
| AC4 | 新助教10天内不接收转移 | transfer_log 中 to_assistant binding 距转移时间 ≥ 10 天 |
|
||
| AC5 | 绑定率 < 50% 全局禁用转移 | 低覆盖率场景下 transfer_log 无新记录 |
|
||
| AC6 | 相同类型任务不重复生成 | 重复运行两次,第二次 skipped = 第一次 created |
|
||
| AC7 | 回访任务最低保留48小时 | 将 created_at 回拨49小时验证 expiry check |
|
||
| AC8 | 转移累计上限生效 | 第3次转移触发 pending_review 状态 |
|
||
|
||
---
|
||
|
||
## 9. 开放问题与后续讨论
|
||
|
||
| # | 问题 | 优先级 |
|
||
|---|------|--------|
|
||
| O1 | OS 标签每4小时更新,是否足以支撑7:00任务生成?需确认 ETL 完成时间窗口 | 高 |
|
||
| O2 | follow_up_visit 由召回完成检测器触发 vs 本 PRD Step 3 漏斗判定,两者需对齐触发逻辑 | 高 |
|
||
| O3 | POOL 助教何时晋升为 COMANAGE/MAIN?OS 算法是否有晋升路径,还是纯由 RS 数据自然演进 | 中 |
|
||
| O4 | pending_review 状态的任务如何人工干预?需要管理后台支持(P10 租户管理后台范畴) | 中 |
|
||
| O5 | 多门店场景下 cfg_task_generator_params 的继承逻辑(全局默认 → 门店覆盖) | 低 |
|
||
|
||
---
|
||
|
||
## 10. 与现有 PRD/代码的关系
|
||
|
||
| 文档/模块 | 关系说明 |
|
||
|-----------|----------|
|
||
| `docs/prd/specs/P4-miniapp-core-business.md` | 本 PRD 是 P4 中任务生成章节的细化和纠错版,以本文档为准 |
|
||
| `apps/backend/app/services/task_generator.py` | 需按本 PRD 重写 `run()` 和 `_process_assistant()`,主要改动是入口改为 OS 归属 |
|
||
| `apps/backend/app/services/fdw_queries.py` | 需新增 `get_ownership_pairs()` 查询方法 |
|
||
| `docs/prd/PRD审阅-Q&A.md` Q3.2 | RS 50% 相对差公式来源,本 PRD 已完整引用 |
|
||
| ETL `RelationIndexTask` | OS/RS/MS/ML 的计算源头,本 PRD 只消费其结果,不修改 ETL 层 |
|