Files
Neo-ZQYY/docs/prd/2026-04-07__board-audit-fix-plan.md
2026-04-10 06:24:13 +08:00

243 lines
12 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.
# 看板全面排查修复计划
> 排查日期2026-04-07
> 范围客户看板BOARD-28 维度 + 助教看板BOARD-14 维度
> 状态:待实施(新对话执行)
---
## 一、已完成的修复
| # | 问题 | 修复内容 | 文件 |
|---|------|----------|------|
| ✅ | SPI 消费口径用 `pay_amount` 而非 `items_sum` | 3 处 settlement_head 查询改为 `items_sum`DWD 规则 #1 | `apps/etl/connectors/feiqiu/tasks/dws/index/spending_power_index_task.py` |
| ✅ | 潜力标签阈值 60 但数据范围 0-10 | 阈值 60→6返回 `[{text, theme}]` 对象数组 | `apps/backend/app/services/fdw_queries.py` `_derive_potential_tags` |
---
## 二、待实施修复(共 10 项)
### P1 — 功能性 Bug
#### Fix-1客户看板项目筛选崩溃C-1
- **问题**`_project_filter_clause()` 硬编码 `vd.member_id`,但 6/8 维度主表别名不是 `vd`,选择项目筛选时后端 SQL 500
- **影响维度**recall / balance / recharge / spend60 / freq60 / loyal
- **不受影响**recent别名 `vd`、potential自写子查询
- **修复方案**:统一改为独立子查询模式 `member_id IN (SELECT member_id FROM app.v_dws_member_project_tag WHERE category_code = %s AND is_tagged = true)`,不依赖外层别名(参考 potential 维度已有写法)
- **文件**`apps/backend/app/services/fdw_queries.py``_project_filter_clause()` 函数及 6 个维度查询函数
#### Fix-2助教看板 task 维度 callback 映射错误A-3
- **问题**callback 统计映射到 `relationship_building`,但业务上"回访"应对应 `follow_up_visit`
- **修复**`board_service.py` `_query_coach_tasks()` 中 callback 的 task_type 从 `relationship_building` 改为 `follow_up_visit`
- **文件**`apps/backend/app/services/board_service.py`
### P2 — 数据口径错误
#### Fix-3recall 维度 visits_30d 实为 14dC-2
- **问题**`dws_member_winback_index``visits_30d` 字段,后端用 `visits_14d` 近似,前端显示"30天到店"
- **修复**
1. DDL`dws.dws_member_winback_index` 加列 `visits_30d INTEGER DEFAULT 0`
2. ETLWBI 计算任务增加 30 天到店次数统计
3. RLS 视图:`dws.v_dws_member_winback_index``app.v_dws_member_winback_index` 加新列
4. 后端:查询改用新字段
5. DDL 文档同步更新
- **文件**
- `db/etl_feiqiu/schemas/dws.sql`DDL
- `apps/etl/connectors/feiqiu/tasks/dws/index/winback_index_task.py`ETL
- `db/etl_feiqiu/schemas/app.sql`RLS 视图)
- `apps/backend/app/services/fdw_queries.py`(查询)
- `docs/database/ddl/etl_feiqiu__dws.sql` / `etl_feiqiu__app.sql`DDL 文档)
#### Fix-4balance 维度月均消耗和可用月数偏差 2 倍C-3 + C-4
- **问题**
- `monthlyConsume`:直接用 `consume_amount_60d`60天总额标签"月均消耗"应为 `consume_amount_60d / 2`
- `availableMonths``balance / consume_amount_60d`,应为 `balance / (consume_amount_60d / 2)``2 * balance / consume_amount_60d`
- **示例**:余额 49780 / 60天消费 14521 → 当前显示 3.4 个月,实际应 6.9 个月
- **文件**`apps/backend/app/services/fdw_queries.py``get_customer_board_balance()`
#### Fix-5freq60 柱状图数据源不一致C-7
- **问题**:汇总数据来自 `v_dws_member_consumption_summary`(消费维度),但 8 周柱状图来自 `v_dwd_assistant_service_log`(助教服务维度),口径不一致
- **修复**:柱状图改为从消费汇总或结算维度获取周数据,与汇总口径一致
- **文件**`apps/backend/app/services/fdw_queries.py` — freq60 相关查询
#### Fix-6助教看板 sv 维度不响应时间筛选A-4
- **问题**:函数接收 `start_date/end_date` 但 SQL 未使用,"消耗"始终是固定 60 天
- **修复**`sv_consume` 的查询加入 `start_date/end_date` 过滤,使其随时间筛选联动
- **文件**`apps/backend/app/services/fdw_queries.py``get_coach_sv_data()`
### P3 — 标签/文案修正
#### Fix-7loyal 维度标签"近60天"→"近90天"
- **问题**ETL 关系指数实际使用 90 天窗口(`lookback_days: 90`),非 60 天
- **修复**:下拉选项文本 `最专一 近60天``最专一 近90天`
- **文件**`apps/miniprogram/miniprogram/pages/board-customer/board-customer.ts``DIMENSION_OPTIONS`
#### Fix-8potential 维度"月均到店"→"近30天到店"
- **问题**:实际是 30 天到店天数(同一天多次只算一天),非"月均到店次数"
- **位置**board-customer 页面 → 最大消费潜力 tab → 4 列网格第 2 格
- **文件**`apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxml`
#### Fix-9recharge 维度"充值"→"累计充值"
- **问题**:实际是 `SUM(pay_amount)` 历史累计充值总额,标签仅"充值"易误解为单次
- **位置**board-customer 页面 → 最近充值 tab → 4 列网格第 2 格
- **文件**`apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxml`
#### Fix-10freq60 维度标签确认
- 此维度标签"最频繁 近60天"与实际口径一致(`visit_count_60d`),无需修改
---
## 三、DWD 合规检查结果(通过)
| 检查项 | 客户看板 | 助教看板 |
|--------|----------|----------|
| consume_money 禁止直接使用 | ✅ 全部走 DWS items_sum | ✅ |
| settle_type IN (1,3) | ✅ 不直查结算表 | ✅ |
| DQ-6 会员姓名通过 dim_member | ✅ | ✅ |
| DQ-7 会员卡通过 dim_member_card_account | ✅ | ✅ |
| 助教费用拆分 pd/cx | N/A | ✅ |
---
## 四、已知但不修的项
| 项 | 原因 |
|----|------|
| A-2sv 维度客户重复计算 | 业务定义确认:每个助教看"我的客户总余额",允许跨助教重叠,作为催促消耗的参考值 |
| A-5后端 `dim_type` 冗余 | 功能无影响,两端映射一致 |
---
## 五、recall_detector 修复 + 任务统计需求(同期实施)
> 需求确认日期2026-04-07
### 背景
`biz.coach_tasks` 当前 255 条记录completed = 0。排查确认
- 系统完成逻辑设计正确(`recall_detector.py` 匹配服务记录→标记 completed
- 但两个技术问题导致完成检测不可靠
- 缺少"助教主动标记完成"能力
- 任务完成统计需要三个维度
### 当前架构(调研结果)
| 服务 | 文件 | 职责 | 调度方式 |
|------|------|------|----------|
| `recall_detector` | `app/services/recall_detector.py` | 检测客户到店→标记召回任务完成→生成回访任务 | event: `etl_data_updated`(当前无人触发) |
| `task_generator` | `app/services/task_generator.py` | 根据 WBI/NCI/RS 指数生成召回/关系维护任务 | cron: `0 4 * * *`(每日凌晨 4 点) |
生成规则(四级漏斗):
- `max(WBI, NCI) > 7``high_priority_recall`
- `max(WBI, NCI) > 5``priority_recall`
- `1 < RS < 6``relationship_building`
- 不满足 → 不生成;`session_count > 0` 无任务时保底补充 `relationship_building`
回溯逻辑(已有):`recall_detector` 检测到新服务记录时,如匹配到活跃的 `follow_up_visit`,关闭旧任务(`superseded_by_new_visit`)并创建新回访。
### Fix-11recall_detector 增量时序 Bug + 编排顺序
**问题**`recall_detector``create_time > last_run_at` 全局增量指针。任务创建与客户到店时序不一致时漏匹配。
**示例时间线**
1. 4/5 08:00 — `recall_detector` 运行,`last_run_at = 08:00`
2. 4/5 14:00 — 客户张三到店,服务记录 `create_time = 14:00`
3. 4/6 02:00 — `task_generator` 为张三创建召回任务
4. 4/6 08:00 — `recall_detector` 再次运行,但 `last_run_at` 已更新,张三 4/5 的记录在上轮已扫描过(当时无任务)→ 永远不会重新匹配
**修复方案**
1. 废弃 `last_run_at` 增量指针,改为:对所有活跃任务,检查 `dwd_settlement_head` 中是否有 `pay_time > task.created_at` 的结算记录(`settle_type IN (1,3)`),匹配 `(site_id, assistant_id, member_id)`
2. 合并运行顺序ETL 完成后,统一编排器按顺序执行:
```
ETL 完成 → HTTP callback → 后端编排器:
Step 1: recall_detector.run() # 先检查完成(含回溯)
Step 2: task_generator.run() # 再生成新任务
```
3. 保留 `task_generator` 的每日 cron`0 4 * * *`)作为兜底
4. 两个服务保持独立文件,仅在调度层串联
**文件**`apps/backend/app/services/recall_detector.py`、调度编排器
### Fix-12ETL 完成后自动触发HTTP callback
**问题**`etl_data_updated` 事件无调用方recall_detector 仅靠手动触发。
**业务影响**ETL 每小时同步完新的飞球数据后,系统不会自动检查召回完成情况。客户回店后,助教看不到任务完成,必须有人手动在管理后台触发。
**修复方案**ETL `api_full` pipeline 完成后,通过 HTTP callback 通知后端。
实现:
1. 后端新增 API`POST /api/internal/etl-completed`(内部接口,仅限本机调用)
2. 该接口触发统一编排:`recall_detector.run()` → `task_generator.run()`
3. ETL orchestrator 在 pipeline 完成后调用此接口
4. 安全:校验来源 IP 或 shared secret
**文件**
- `apps/backend/app/routers/internal.py`(新增)
- `apps/etl/connectors/feiqiu/orchestration/` — pipeline 完成回调
- `apps/backend/app/services/trigger_scheduler.py` — 编排逻辑
### Fix-13任务完成统计三维度
**需求确认**:任务完成分三个统计维度:
| 统计维度 | 触发条件 | 是否需已有任务 | 判定数据源 |
|----------|----------|---------------|-----------|
| 召回完成(广义) | 关联客户来店 + 有结算单 | 不需要,只要是 `dws_member_assistant_relation_index` 中的关联客户 | `dwd_settlement_head``settle_type IN (1,3)` |
| 优先/高优先召回完成 | 已有 `priority_recall` / `high_priority_recall` 任务 + 客户到店 | 需要 | 同上 + `coach_tasks.status = 'completed'` |
| 回访完成 | 完成 `follow_up_visit` 任务(含回溯完成) | 需要 | `coach_tasks` + 回溯检测 |
关键定义:
- **"关联客户"**`dws_member_assistant_relation_index` 中有关系记录的客户
- **"来店"判定**`dwd_settlement_head` 中有结算单(`settle_type IN (1,3)`
- **时间窗口**:不限间隔,只要来了就算一次
- **统计时间范围**:全量(任务系统上线至今)
完成类型标记(两种都要记录):
| 完成类型 | 触发方式 | 当前状态 |
|----------|----------|----------|
| 自动完成 | 客户到店,系统匹配结算记录 | 逻辑存在但有 BugFix-11 + Fix-12 |
| 手动完成 | 助教在小程序中主动标记 | 当前不存在,需新建 |
手动完成需要:
- 小程序端:任务卡片增加"标记完成"按钮
- 后端 API`POST /api/xcx/tasks/{task_id}/complete`body: `{type: "manual", note?: string}`
- 数据库:`coach_tasks` 增加 `completion_type` 字段(`auto` / `manual`
- `coach_task_history` 增加 `action='manual_completed'`
### Fix-14助教看板 task 维度展示
**展示位置**:微信小程序 board-coach 页面 task_desc 排序维度
**统计口径**(按助教/按月):
- 召回完成数:`coach_task_history` 中 `action='completed'` 的任务数(优先+高优先)
- 回访完成数:`follow_up_visit` 类型的完成数(含自动+手动+回溯)
- 广义召回(关联客户来店):从 `dwd_settlement_head` + 关系表统计
**数据来源**`biz.coach_tasks` + `biz.coach_task_history` + ETL 关系/结算表
**时间范围**:全量
**后续**:更详细的趋势报表作为独立需求
### 实施依赖关系
```
Fix-11时序Bug+编排顺序)──┐
├→ Fix-14看板展示
Fix-12ETL 自动触发)────────┤
Fix-13手动完成+三维度统计)─┘
```
建议实施顺序Fix-11 → Fix-12 → Fix-13 → Fix-14