Files
Neo-ZQYY/docs/_overview/wave1-findings/F1-6-tasks.md
Neo 9f1e35d71a feat(backend): F1-6 sprint1 sandbox_replay 模块脚手架 + get_last_visit_days 迁移试点 (W1)
F1-5b 完成后启动 F1-6 沙箱时光机阶段 B,Sprint 1 范围:
1. 建立 sandbox_replay 模块脚手架
2. 实现 @runtime_aware decorator(自动注入 RuntimeContext)
3. 试点迁移 1 个指标:get_last_visit_days(P1-4 距上次到店天数)
4. MCP 端到端 4a/4b 双口径走查

新增文件:
- apps/backend/app/services/sandbox_replay/__init__.py(模块入口 + re-export)
- apps/backend/app/services/sandbox_replay/_decorator.py(@runtime_aware 实现)
- apps/backend/app/services/sandbox_replay/consumption_replay.py(get_last_visit_days 试点)
- docs/_overview/wave1-findings/F1-6-tasks.md(F1-6 任务清单 4 个 sprint)
- docs/audit/changes/2026-05-05__f1_6_sprint1_sandbox_replay_kickoff.md(Sprint 1 审计)

修改:
- apps/backend/app/services/fdw_queries.py:218-238
  get_last_visit_days 改 thin wrapper,委托 sandbox_replay.consumption_replay
  保持 75+ 现有调用点无感兼容

关键 bug 修复:@trace_service + @runtime_aware 嵌套 sig.bind 失败
- 现象:仅 FastAPI 请求 + 有活跃 TraceContext 时复现
  (直接 Python 脚本 get_current_trace 返回 None,跳过 _build_params_dict)
- 根因:functools.wraps 设置 __wrapped__ 让 inspect.signature 追溯原函数
  → bind 时缺 keyword-only 必传 ctx → TypeError
- 修复:_decorator.py 不用 functools.wraps,手动复制元信息但不设
  __wrapped__,inspect.signature 看到 wrapper 自身 (*args, **kwargs)

测试:
- unit test 10/10 PASS(本地 .gitignore:71 不入仓)
- 直接 customer_service.get_customer_detail PASS(独立诊断脚本)
- etl_conn 复用模式 PASS

MCP 双口径(member=2799207087163141 黄先生):
- 4a live (today=2026-05-05): daysSinceVisit=32(05-05 - 04-03)
- 4b sandbox=2026-04-20: daysSinceVisit=31(04-20 - 03-20)
  通过插 walkthrough 测试快照 stat_date=2026-04-15 演示
  (测试库 dws_member_consumption_summary 仅 stat_date=2026-05-01 一行,
   sandbox=4-20 时被视图层 stat_date <= business_date_now() 过滤)
- 测试快照已清理,sandbox 已切回 live

Sprint 2-4 待启动(见 F1-6-tasks.md):
- Sprint 2:5 个会员 P1 指标(余额/60d 消费/累计交易/GMV/累计服务客户数)
- Sprint 3:5 个助教/门店 P1 + MP-2 完整(含 ETL 改造)
- Sprint 4:5 个 P2 算法重算指标

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:53:19 +08:00

147 lines
6.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.
# F1-6 沙箱时光机阶段 B 任务清单
> 创建日期:2026-05-05 (F1-5b 收尾后启动)
> 完整模块 spec:[`docs/_overview/sandbox-replay-engine-spec.md`](../sandbox-replay-engine-spec.md)
> 上游:F1-5b 全部完成(commit `5d4da0a`)
>
> **状态**:Sprint 1 已启动并完成,Sprint 2-4 待启动
## 一、阶段 B 总览
按 sandbox-replay-engine-spec 阶段 B 拆 4 个 sprint:
| Sprint | 范围 | 工作量 | 状态 |
|--------|------|------|------|
| **Sprint 1** | 框架(sandbox_replay 模块 + runtime_aware decorator) + 1 个试点指标(距上次到店天数迁移) | M ~ 4-5h | ✅ 完成(2026-05-05) |
| Sprint 2 | 5 个会员相关 P1 指标(余额 / 60d 消费 / 累计交易 / GMV / 累计服务客户数) | M ~ 4h | ⏳ 待启动 |
| Sprint 3 | 5 个助教/门店相关 P1 + **MP-2 完整**(daily salary 含 ETL 改造) | L ~ 8-10h | ⏳ 待启动 |
| Sprint 4 | 5 个 P2 指标(RS 重算 / 客户黏性 / 任务完成率 / Excel 修正 / 月度新增流失) | M-L ~ 6-8h | ⏳ 待启动 |
## 二、Sprint 1 收口(2026-05-05)
### 范围
1. ✅ 建立 `apps/backend/app/services/sandbox_replay/` 模块脚手架
2. ✅ 实现 `@runtime_aware(metric=...)` decorator(自动注入 RuntimeContext)
3. ✅ 试点迁移:`get_last_visit_days`(距上次到店天数,P1-4)
- `consumption_replay.py` 新实现
- `fdw_queries.get_last_visit_days` 改 thin wrapper(兼容 75+ 现有调用)
4. ✅ Unit test 10/10 PASS
5. ✅ MCP 端到端 4a/4b 双口径 PASS
### 关键 bug 发现 + 修复
**bug**:`@trace_service` + `@runtime_aware` 嵌套时,trace_service 的 `_build_params_dict``inspect.signature(func).bind(*args, **kwargs)` 做参数 redact。`functools.wraps` 默认设置 `__wrapped__``inspect.signature` 追溯到原函数(含 keyword-only 必传 ctx 参数),导致 sig.bind 抛 `TypeError: missing a required argument: 'ctx'`
只在 FastAPI 请求 + 有活跃 TraceContext 时触发(直接 Python 调用 get_current_trace 返回 None,跳过 _build_params_dict,不复现)。
**修复**:`runtime_aware` 的 wrapper 不用 `functools.wraps`,改用手动复制 `__name__/__qualname__/__module__/__doc__`,**不设 `__wrapped__`**,让 `inspect.signature` 看到 wrapper 自身 `(*args, **kwargs)` 签名,sig.bind 永远成功。
### 双口径验证证据(关键样本)
**目标 member**: 2799207087163141(黄先生)
| 维度 | 4a live (today=2026-05-05) | 4b sandbox=2026-04-20 |
|------|---------------------------|----------------------|
| stat_date 数据 | 2026-05-01 | 2026-04-15(walkthrough 测试快照) |
| last_consume_date | 2026-04-03 | 2026-03-20 |
| daysSinceVisit 计算 | 32 = 05-05 - 04-03 | **31 = 04-20 - 03-20** |
| API 返回 | 32 ✓ | 31 ✓ |
测试快照已清理,sandbox 已切回 live。
### 走查脚本
`_DEL/walkthrough_f1_6/`:
- `step_sprint1_probe_member.py` — 找有 last_consume 的会员
- `step_sprint1_check_view.py` — 视图层 RLS 行为
- `step_sprint1_direct_call.py` — 直接调 sandbox_replay 函数
- `step_sprint1_repro_etl_conn.py` — etl_conn 复用模式
- `step_sprint1_call_service.py` — 直接调 customer_service.get_customer_detail
- `step_sprint1_4b_probe.py` — sandbox=4-20 视图行为
- `step_sprint1_4b_find_member.py` — 找 sandbox 下有数据的 member
- `step_sprint1_4b_stat_dist.py` — stat_date 分布
- `step_sprint1_4b_seed_data.py` — 插测试快照
- `step_sprint1_cleanup.py` — 清理 + 切回 live
### Sprint 1 commit
`feat(backend): F1-6 sprint1 sandbox_replay 模块脚手架 + get_last_visit_days 迁移试点`
---
## 三、Sprint 2 范围(待启动)
### 5 个会员相关 P1 指标
| # | 指标 | 当前实现位置 | 目标迁移到 | 复杂度 |
|---|------|-------------|----------|------|
| 1 | 会员储值卡余额 | `fdw_queries.get_member_balance` | `sandbox_replay/balance_replay.py` | S |
| 2 | 60 天消费 | `fdw_queries.get_consumption_60d` | `sandbox_replay/consumption_replay.py`(扩展) | S |
| 3 | 累计消费总额 | (待补,目前无独立查询) | `sandbox_replay/consumption_replay.py` | S |
| 4 | 累计交易笔数 | (待补,后端无实现) | `sandbox_replay/consumption_replay.py` | S |
| 5 | 累计 GMV | `board_service` 财务总额 | `sandbox_replay/finance_replay.py`(新建) | S |
### Sprint 2 实施模式
所有指标走 `@runtime_aware` decorator + `app.v_dws_*` 视图查询。每个指标:
- 1 个新函数 to sandbox_replay/
- 1 个 thin wrapper in fdw_queries.py(兼容)
- 单元测试 + MCP 走查
- 审计 + 单独 commit
### 估算
5 指标 × 30-50min = 3-4h
---
## 四、Sprint 3 范围(待启动)
### 5 个助教/门店相关 P1 + MP-2 完整
| # | 指标 | 备注 |
|---|------|------|
| 6 | 累计服务客户数(助教) | dws_assistant_customer_stats daily |
| 7 | 助教等级 | dws_assistant_daily_detail.assistant_level_code |
| 8 | 月度课时(助教) | dws_assistant_daily_detail.base_hours 聚合 |
| 9 | 月度计费金额(助教) | dws_assistant_daily_detail.total_ledger_amount 聚合 |
| 10 | 月度新增/流失会员 | dws_member_*_summary daily |
| **MP-2 完整 daily salary** | board-coach 月度面板 | **需 ETL 团队配合**:新建 dws_assistant_daily_salary 表 + ETL Excel 上传 UI 改造 |
### Sprint 3 估算
5 指标 ~ 3-4h + MP-2 完整 5-6h(含 ETL 改造) = **L ~ 8-10h**
---
## 五、Sprint 4 范围(待启动)
### 5 个 P2 算法重算指标
| # | 指标 | 难点 |
|---|------|------|
| 11 | RS 关系指数 | 算法窗口期 + 衰减函数,daily 重算需重做累计 |
| 12 | 客户黏性指数 | 类似 RS,有时间衰减 |
| 13 | 任务完成率(coach_tasks) | 需补 dws daily 聚合视图 |
| 14 | Excel 修正(扣款/奖励/支出/收入) | 依赖 effective_date(F1-5b prep 已就位),后端 SQL 加截断 |
| 15 | 月度新增/流失会员(算法版) | dws_member_lifecycle 重算 |
### Sprint 4 估算
M-L ~ 6-8h
---
## 六、阶段 C(F1-7+ 远期)
详见 sandbox-replay-engine-spec.md §11.5:
- 3 个 P3 指标(门店等级 / 助教星级 / 累计 KPI)
- `biz.sandbox_audit_log` 用户行为审计表
- AI app8_consolidate prompt audit
---
## 七、关联
- 完整模块 spec:`docs/_overview/sandbox-replay-engine-spec.md`
- F1-5b 任务清单:`docs/_overview/wave1-findings/F1-5b-tasks.md`
- F1-5b MP-2 prep 审计:`docs/audit/changes/2026-05-05__wave1_f1_5b_mp2_prep.md`
- P20 SPEC §11.4 + §11.5(F1-6 待办登记):`docs/prd/specs/P20-runtime-context-sandbox.md`
- Sprint 1 审计:`docs/audit/changes/2026-05-05__f1_6_sprint1_sandbox_replay_kickoff.md`