Sprint 2 收尾指标:门店级累计 GMV(与 #1-#4 会员级粒度不同),新建
sandbox_replay/finance_replay.py 模块。无原 fdw_queries.get_total_gmv
函数(0 现有调用方),不写 thin wrapper(spec §5.5 决策原则)。
数据源 dws_finance_daily_summary.gross_amount(门店日度财务汇总,daily 累计)。
SQL 模式 SUM(gross_amount) WHERE stat_date <= ctx.business_date,与 #1-#4
取最新单行不同,是多行累计 SUM。SQL 层 COALESCE(SUM(...), 0) 兜底,无数据
返回 Decimal('0')(开店前累计 GMV = 0,业务语义)。
口径 gross_amount = table_fee + goods + assistant_pd + assistant_cx,**不含
electricity_money**(与会员级 items_sum 略有差异,docstring 明确防止交叉验证)。
双口径数值验证 PASS(直接 Python,site=2790685415443269 朗朗桌球):
- 4a live(today=2026-05-05): ¥5,725,837.51
- 4b sandbox=2026-04-20: ¥5,653,063.37(差异 ¥72,774,即 4-21~4-27 七天合计)
新增防御性回归测试 test_get_total_gmv_no_member_ids_param 阻断未来误加
member_id 参数(门店级粒度强约束)。
unit test sprint1+sprint2 累计 28/28 PASS,无回归。
Sprint 2 收尾(4 项迁移 + #3 推迟 Sprint 3 等 ETL 配合)。
详见 docs/audit/changes/2026-05-06__f1_6_sprint2_total_gmv.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
156 lines
7.6 KiB
Markdown
156 lines
7.6 KiB
Markdown
# 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 | ✅ 完成(2026-05-06,4 项迁移 + #3 推迟 Sprint 3) |
|
||
| Sprint 3 | 5 个助教/门店 P1 + **MP-2 完整**(daily salary 含 ETL 改造)+ Sprint 2 推迟的 #3(ETL 新增 `total_open_table_count`)| L ~ 9-11h | ⏳ 待启动 |
|
||
| 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 范围(进行中)
|
||
|
||
启动顺序按方案 A:从已有 `consumption_replay.py` 模块扩展开始,新模块靠后。
|
||
|
||
### 5 个会员相关 P1 指标
|
||
|
||
| # | 指标 | 当前实现位置 | 目标迁移到 | 复杂度 | 状态 |
|
||
|---|------|-------------|----------|------|------|
|
||
| 1 | 60 天消费 | `fdw_queries.get_consumption_60d` | `sandbox_replay/consumption_replay.py`(扩展) | S | ✅ 2026-05-06(thin wrapper)|
|
||
| 2 | 累计消费总额 | (无,新增) | `sandbox_replay/consumption_replay.py`(扩展) | S | ✅ 2026-05-06(无 wrapper,0 调用方)|
|
||
| 3 | 累计交易笔数 | DWS 现有 `total_visit_count` 实算 `COUNT(settle_type IN (1,3))` 不符开台次数语义,需 ETL 新增 `total_open_table_count` | `sandbox_replay/consumption_replay.py`(扩展) | S | ⏸️ **推迟 Sprint 3**(ETL 配合,详见 [架构演进 backlog](../architecture-evolution-backlog.md))|
|
||
| 4 | 会员储值卡余额 | `fdw_queries.get_member_balance` | `sandbox_replay/balance_replay.py`(新建) | S | ✅ 2026-05-06(thin wrapper,SCD2 时光机)|
|
||
| 5 | 累计 GMV | `dws_finance_daily_summary.gross_amount`(门店级,与现有"区间 GMV"语义不同) | `sandbox_replay/finance_replay.py`(新建) | S | ✅ 2026-05-06(无 wrapper,门店级 SUM)|
|
||
|
||
### Sprint 2 实施模式
|
||
所有指标走 `@runtime_aware` decorator + `app.v_dws_*` 视图查询。每个指标:
|
||
- 1 个新函数 to sandbox_replay/
|
||
- 1 个 thin wrapper in fdw_queries.py(兼容)
|
||
- 单元测试(本地不入仓)+ MCP 双口径走查(navigate_to + snapshot + screenshot,**含 UI 实地验证**)
|
||
- 审计 + 单独 commit
|
||
|
||
### Sprint 2 commit
|
||
- #1 60d 消费 — commit `d418621`(2026-05-06)
|
||
- #2 累计消费总额 — commit `32716bc`(2026-05-06)
|
||
- #3 累计交易笔数 — **推迟 Sprint 3**(ETL 配合新增 `total_open_table_count`,详见 [架构演进 backlog](../architecture-evolution-backlog.md) 第 3 项)
|
||
- #4 储值卡余额 — commit `7b1cfad`(2026-05-06,SCD2 时光机)
|
||
- #5 累计 GMV — `feat(backend): F1-6 sprint2 #5 累计 GMV 加入 sandbox_replay (门店级)`(待提交)
|
||
|
||
### 估算
|
||
5 指标 × 30-50min = 3-4h(#1 实际 ~ 40min)
|
||
|
||
---
|
||
|
||
## 四、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`
|