新增指标(无 fdw_queries 原查询 + 0 现有调用方 + 无 thin wrapper),沿用 sprint 1/sprint2 #1 模式 @trace_service + @runtime_aware decorator + 显式 stat_date <= ctx.business_date 上界 + dws_member_consumption_summary .total_consume_amount 字段 items_sum 口径。 双口径数值验证 PASS(member=2799207087163141 黄先生,直接 Python 调用): - 4a live(today=2026-05-05): get_total_consume_amount=1252.65 - 4b sandbox=2026-04-20: get_total_consume_amount=999.99(walkthrough 测试快照) unit test sprint1+sprint2 累计 19/19 PASS,无回归。 记录 thin wrapper 决策原则到 spec §5.5(迁移辅助层,非常态架构; fdw_queries 长远退化纯 ETL 物理访问层,清理放收尾 sprint)。 注:#3 累计交易笔数因 spec §4 字段未明确(dws_order_summary vs total_visit_count)暂停,等 Neo 决断后继续。 详见 docs/audit/changes/2026-05-06__f1_6_sprint2_total_consume_amount.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
198 lines
10 KiB
Markdown
198 lines
10 KiB
Markdown
# 沙箱时光机引擎 (Sandbox Replay Engine) — 模块 Spec
|
|
|
|
> 版本:v1.0 · 创建日期:2026-05-05
|
|
> 决策来源:F1-5b Wave B MP-2 调研收尾(Neo 同意分阶段实现,加入主干任务排期)
|
|
> 主要落地依赖:F1-5a runtime_context 框架 + F1-5b 业务日上界裁剪基础设施
|
|
>
|
|
> **状态**:Spec 阶段(未启动实施)
|
|
|
|
## 一、设计意图
|
|
|
|
P20 沙箱(`runtime_mode='sandbox' + sandbox_business_date`)的核心承诺是 **"设定某个历史日期 X 后,系统所有数据/统计/AI 生成内容呈现的状态都是 X 那天的样子"**,即"时光机"语义。
|
|
|
|
当前 F1-5a/F1-5b 已建立基础设施,但**业务读取层只覆盖了"daily 累计型"指标**(财务流水 / 服务记录),**未覆盖"月度结算型 / 状态算法型 / Excel 修正型"指标**。沙箱设到 4-20 时,部分页面仍能"看见未来"或显示已知最终结果,违反时光机语义。
|
|
|
|
本模块旨在**统一所有业务读取按 sandbox_business_date 重算**,达成完整时光机体验。
|
|
|
|
## 二、当前已就位的基础设施(继承)
|
|
|
|
| 层 | 已做 | Wave / Commit |
|
|
|----|------|--------------|
|
|
| **RuntimeContext 框架** | site_runtime_context 表 + apply_runtime_session_vars() | F1-5a 421e193 |
|
|
| **数据写入隔离** | ai_run_logs / coach_tasks / 各业务写入表带 runtime_mode + sandbox_instance_id | F1-5a + F1-5b A1 af02446 |
|
|
| **app 视图业务日上界裁剪** | 39 个 app.v_* 视图加 `WHERE stat_date <= business_date_now()`(2026-05-02 迁移) | 2026-05-02 ETL 迁移 |
|
|
| **后端读取**(daily 累计型指标) | board-finance / customer-detail / customer-records / coach-service-records 等 | F1-5b MP-1/3/5 |
|
|
| **后端读取**(权限路由) | manager 角色权限隔离 | F1-5b BE-1 |
|
|
| **admin-web sandbox 透出** | UI-1/2/3/4/5 全套 runtime 字段 + 提示条 + 全局徽章 | F1-5b Wave A/B |
|
|
| **Excel 修正表 schema 准备** | salary_adjustments / stg_finance_expense / stg_platform_income 加 effective_date NOT NULL | F1-5b MP-2 prep(本次) |
|
|
|
|
## 三、缺失能力清单(本模块覆盖范围)
|
|
|
|
22 个指标按复杂度 + 优先级分:
|
|
|
|
### P1 — Daily 视图已有,后端 service 切换即可(14 项,S 复杂度)
|
|
|
|
| # | 指标 | 当前数据源 | 目标 daily 视图 | 重算逻辑 |
|
|
|---|------|-----------|----------------|---------|
|
|
| 1 | 会员储值卡余额 | dws_member_balance_snapshot(状态) | dws_member_balance_change daily 累计 | SUM(收入-支出) WHERE date<=B |
|
|
| 2 | 60 天消费 | dws_member_consumption_summary | dws_member_consumption_daily | SUM 60 day window 终点为 B |
|
|
| 3 | 累计消费总额 | 同上 | 同上 | SUM all WHERE date<=B |
|
|
| 4 | 距上次到店天数 | 状态字段 | dwd 消费记录 | B - MAX(visit_date WHERE<=B) |
|
|
| 5 | 累计服务客户数(助教) | dws_assistant_customer_stats | daily 累计 | COUNT DISTINCT member WHERE date<=B |
|
|
| 6 | 助教等级 | dws_assistant_daily_detail.assistant_level_code | 同表 | SELECT WHERE stat_date=B |
|
|
| 7 | 月度课时(助教) | dws_assistant_daily_detail.base_hours 等 | 同表月度聚合 | SUM WHERE month_of(B) AND stat_date<=B |
|
|
| 8 | 月度计费金额(助教) | dws_assistant_daily_detail.total_ledger_amount | 同上 | SUM WHERE month_of(B) AND stat_date<=B |
|
|
| 9 | 门店月度财务 | dws_finance_daily_summary | 同表(已实现 MP-1) | ✓ 已完成 |
|
|
| 10 | 月度新增会员 | dws_member_*_summary | dws_member_daily | COUNT WHERE join_date<=B AND month_of(B) |
|
|
| 11 | 月度流失会员 | 同上 | 同上 | COUNT WHERE last_visit<B-30 AND month_of(B) |
|
|
| 12 | 累计交易笔数 | dws_order_summary | daily | COUNT WHERE date<=B |
|
|
| 13 | 累计 GMV | dws_finance_daily_summary.gross_amount | 同上 | SUM WHERE date<=B |
|
|
| 14 | AI 缓存命中率 | sandbox_instance_id 隔离 | F1-5a 已覆盖 | ✓ 已完成 |
|
|
|
|
### P2 — 算法重算(5 项,M 复杂度)
|
|
|
|
| # | 指标 | 复杂度 | 难点 |
|
|
|---|------|------|------|
|
|
| 15 | 关系指数 RS | M | RS 涉及窗口期 + 衰减函数,daily 重算需要重做累计逻辑 |
|
|
| 16 | 客户黏性指数 | M | 类似 RS,有时间衰减 |
|
|
| 17 | 助教月薪(完整 daily salary 含罚分) | M-L | **MP-2 真正实施在此**,需要新建 dws_assistant_daily_salary 表 |
|
|
| 18 | 任务完成率(coach_tasks 累计) | M | 需 coach_tasks 加 dws 层 daily 聚合视图 |
|
|
| 19 | Excel 修正(扣款/奖励/支出/收入) | M | 依赖 effective_date(F1-5b prep 已做),后端 SQL 加截断 |
|
|
|
|
### P3 — 状态算法依赖累计(3 项,L 复杂度)
|
|
|
|
| # | 指标 | 难点 |
|
|
|---|------|------|
|
|
| 20 | 门店等级评级 | 评级算法依赖累计 KPI 反推,需要重新建模 |
|
|
| 21 | 助教星级 | 类似门店等级,涉及多月 KPI 综合 |
|
|
| 22 | 用户操作行为日志 | **完全未覆盖**(F1-5a 仅写入隔离,没有"用户行为审计表");需新建 sandbox_audit_log |
|
|
|
|
## 四、实施分阶段(主干任务排期)
|
|
|
|
| 阶段 | 内容 | 工作量 | 时机 |
|
|
|------|------|------|------|
|
|
| **阶段 0(F1-5b prep,本次)** | Excel 修正表加 effective_date schema + 沙箱时光机 spec 文档 | 1.5h | **本次 F1-5b Wave B 完成** |
|
|
| **阶段 A(F1-5b 已完成)** | RuntimeContext 框架 + app 视图 business_date 上界 + daily 累计型指标 9 项 | — | F1-5a + F1-5b 已完成 |
|
|
| **阶段 B(F1-6)** | 14 个 P1 指标 service 层切换 daily 累计 + 5 个 P2 指标(含 MP-2 完整 daily salary) | 2-3 周 | F1-6 |
|
|
| **阶段 C(F1-7+)** | 3 个 P3 指标 + sandbox_audit_log 用户行为审计 | 1-2 周 | F1-7 长期 |
|
|
|
|
## 五、阶段 B 实施模式建议
|
|
|
|
### 5.1 sandbox_replay 模块结构
|
|
|
|
```
|
|
apps/backend/app/services/sandbox_replay/
|
|
├── __init__.py # runtime_aware decorator
|
|
├── balance_replay.py # 会员余额(P1-1)
|
|
├── consumption_replay.py # 消费累计(P1-2/3/4/12/13)
|
|
├── assistant_metrics_replay.py # 助教课时/收入/客户(P1-5/6/7/8)
|
|
├── member_lifecycle_replay.py # 月度新增/流失(P1-10/11)
|
|
├── salary_replay.py # MP-2 完整 daily salary(P2-17,需新 dws 表)
|
|
├── adjustments_replay.py # Excel 修正截断(P2-19)
|
|
├── tasks_replay.py # 任务完成率(P2-18)
|
|
├── rs_replay.py # RS 算法重算(P2-15)
|
|
└── intimacy_replay.py # 客户黏性(P2-16)
|
|
```
|
|
|
|
### 5.2 接口契约(runtime_aware decorator)
|
|
|
|
```python
|
|
@runtime_aware(metric='member_balance')
|
|
def get_member_balance(site_id: int, member_id: int) -> Decimal:
|
|
"""根据 RuntimeContext 自动选 live / sandbox 路径。"""
|
|
# 实现内会判断:
|
|
# - if runtime_ctx.is_sandbox: 调 balance_replay.partial(business_date)
|
|
# - else: 调 dws_query.live_balance()
|
|
pass
|
|
```
|
|
|
|
### 5.3 测试模式
|
|
|
|
每个 replay 模块配套 unit test(BE-3 / T3 模式):
|
|
- mock get_runtime_context 返回 live / sandbox 两路
|
|
- 断言 SQL 包含 daily 累计 + business_date 截断
|
|
- 部分 integration test 用真实测试库验证数据一致性
|
|
|
|
### 5.4 性能考虑
|
|
|
|
- daily 累计 SQL 比 monthly snapshot 慢(SUM 多行 vs 单行查)
|
|
- 建议:sandbox 模式下接受性能折衷;live 模式仍走原 dws 月度路径
|
|
- 必要时可做 in-memory cache(business_date 不变时缓存命中)
|
|
|
|
### 5.5 thin wrapper 使用原则(2026-05-06 决策)
|
|
|
|
`fdw_queries.*` 中的 thin wrapper(转发到 `sandbox_replay.*`)是**迁移辅助层,不是常态架构**。决策树:
|
|
|
|
```
|
|
Q1: fdw_queries 已有同名函数?
|
|
└─ 没有 → 不写 thin wrapper(直接在 sandbox_replay 加新函数)
|
|
└─ 有 → Q2
|
|
|
|
Q2: 业务方已经在调用 fdw_queries.X?
|
|
└─ 没有 → 不写 thin wrapper(老函数直接删)
|
|
└─ 有 → Q3
|
|
|
|
Q3: 改这些调用方的 import 风险大?
|
|
└─ 改起来便宜(≤3 处) → 直接改 import,不写 thin wrapper
|
|
└─ 改起来贵(多处 / 容易漏改) → 写 thin wrapper
|
|
```
|
|
|
|
**对 Sprint 2 应用**:
|
|
- #1 60d 消费(原有 + 2 处调用)→ 写 thin wrapper(已完成)
|
|
- #2 累计消费总额 / #3 累计交易笔数 / #5 累计 GMV(原无 + 0 调用)→ 不写
|
|
- #4 储值卡余额(原有 + 多处调用)→ 写 thin wrapper
|
|
|
|
**长远走向**:F1-6 全部迁完后,所有老调用方逐步改成 `from sandbox_replay import ...`,
|
|
`fdw_queries.py` 退化成纯 ETL 物理访问层(`_fdw_context` + 直接 SQL helper),不再承载业务指标接口。
|
|
thin wrapper 一次性清理可放到"sandbox_replay 收尾 sprint"(F1-7+ 或更晚,本 spec §11.5 待定)。
|
|
|
|
**新功能/新接口判断**:不要因为"风格一致"在 fdw_queries 加 0 调用方的空 thin wrapper(churn,违反改动最小化原则)。
|
|
|
|
## 六、阶段 B 前置依赖清单
|
|
|
|
| 依赖 | 来源 | 状态 |
|
|
|------|------|------|
|
|
| RuntimeContext + business_date | F1-5a | ✓ 已完成 |
|
|
| app 视图 daily 上界裁剪 | 2026-05-02 迁移 | ✓ 已完成 |
|
|
| Excel 修正表 effective_date 字段 | F1-5b MP-2 prep(本次) | ✓ 本次完成 |
|
|
| ETL Excel 上传 UI 支持 effective_date 列 | F1-6 阶段 B 内 | ⏳ 待做 |
|
|
| dws_assistant_daily_salary 表/视图 | F1-6 阶段 B 内(MP-2 真正实施) | ⏳ 待做 |
|
|
| dws_member_daily 系列视图(部分缺失) | F1-6 阶段 B 内 | ⏳ 待做 |
|
|
|
|
## 七、阶段 C 远期目标
|
|
|
|
### sandbox_audit_log(用户行为审计)
|
|
|
|
**业务目标**:沙箱模式下用户的所有操作都记录,切回 live 后可追溯,支持"沙箱演练复盘"场景。
|
|
|
|
**表设计草案**:
|
|
```sql
|
|
CREATE TABLE biz.sandbox_audit_log (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
site_id BIGINT NOT NULL,
|
|
sandbox_instance_id VARCHAR(100) NOT NULL,
|
|
user_id BIGINT NOT NULL,
|
|
action_type VARCHAR(50) NOT NULL, -- 'view' / 'trigger_ai' / 'modify' / ...
|
|
page_path VARCHAR(200),
|
|
payload JSONB,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
```
|
|
|
|
**写入入口**:统一 middleware 或 decorator 拦截 sandbox 模式请求。
|
|
|
|
## 八、关联
|
|
|
|
- F1-5a 主体审计:`docs/audit/changes/2026-05-05__wave1_f1_5a_sandbox_batch_run.md`
|
|
- F1-5a 走查报告:`docs/audit/changes/2026-05-05__wave1_f1_5a_backend_walkthrough.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:`docs/prd/specs/P20-runtime-context-sandbox.md`
|
|
- 业务日上界 ETL 迁移:`db/etl_feiqiu/migrations/20260502__rls_views_business_date_upper_bound.sql`
|
|
|
|
## 九、决策路径(Owner Approval)
|
|
|
|
- **2026-05-05 Neo 决策**:
|
|
> "沙箱'全数据时光机'模块可行性 — 同意,分阶段实现,往主干任务排期中增加。"
|
|
- **本 spec 状态**:已 commit,等待 F1-6 启动。F1-6 启动时本 spec 作为阶段 B 实施依据。
|