Files
Neo-ZQYY/apps/backend/app/services/sandbox_replay/__init__.py
Neo 7b1cfadc2e feat(backend): F1-6 sprint2 #4 储值卡余额迁移 sandbox_replay (SCD2 时光机)
新建 sandbox_replay/balance_replay.py 模块,迁移 fdw_queries.get_member_balance,
fdw_queries 改 thin wrapper 保持 5 处现有调用(chat/coach/customer x2/task_manager)
透明兼容。

数据源 dim_member_card_account 是 SCD2 维度表(原生支持时光机),sandbox 改造
关键是替换 scd2_is_current=1 过滤为 scd2_start_time + scd2_end_time 时间过滤
(ref_date+1day 边界 = 当天结束时仍 active 的版本,timestamptz 比较稳定)。

双口径 UI 走查 PASS(member=2799207363643141 葛先生,SCD2 历史余额变化样本):
- 4a live(today=2026-05-05): 储值余额 ¥6,602
- 4b sandbox=2026-04-20: 储值余额 ¥18,080(差异 1.1w+,时光机效果显著)

unit test sprint1+sprint2 累计 24/24 PASS,无回归。

附带本次 sprint 2 触发的架构级登记:
- 新建 docs/_overview/architecture-evolution-backlog.md(DWD 孤立 + Core 中间件 +
  库重组,长远架构演进 backlog)
- F1-6-tasks.md 登记 #3 累计交易笔数推迟 Sprint 3(ETL 配合新增
  total_open_table_count,因现有 total_visit_count 实算 COUNT(settle_type IN (1,3))
  含商城订单,不符 Neo "开台次数"业务语义)
- sandbox-replay-engine-spec §5.5 thin wrapper 决策原则(已在 #2 commit)

详见 docs/audit/changes/2026-05-06__f1_6_sprint2_member_balance.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 01:26:18 +08:00

51 lines
2.2 KiB
Python

"""沙箱时光机重算引擎 (Sandbox Replay Engine)
F1-6 沙箱时光机阶段 B 启动模块。
设计意图:
P20 沙箱(`runtime_mode='sandbox' + sandbox_business_date`)的"时光机"语义
要求所有业务指标按 sandbox_business_date 重新累计/截断,而非展示 ETL
跑批的最新累计。F1-5a/F1-5b 已建立基础设施(RuntimeContext + app 视图
业务日上界),本模块统一所有"runtime-aware"业务读取路径。
完整 spec:
docs/_overview/sandbox-replay-engine-spec.md
模块结构(随 sprint 推进逐步填充):
sandbox_replay/
├── __init__.py # 本文件,re-export 主要 API
├── _decorator.py # @runtime_aware decorator
├── consumption_replay.py # 消费/到店/累计交易(P1-2/3/4/12/13)
├── balance_replay.py # 会员余额(P1-1)— sprint 2
├── assistant_metrics_replay.py # 助教课时/收入/客户(P1-5/6/7/8)— sprint 3
├── salary_replay.py # MP-2 完整 daily salary(P2-17)— sprint 3
├── adjustments_replay.py # Excel 修正截断(P2-19)— sprint 3
├── tasks_replay.py # 任务完成率(P2-18)— sprint 4
├── rs_replay.py # RS 算法重算(P2-15)— sprint 4
└── intimacy_replay.py # 客户黏性(P2-16)— sprint 4
接口契约(sprint 1):
@runtime_aware(metric='last_visit_days')
def get_last_visit_days(conn, site_id: int, member_ids: list[int]) -> dict[int, int | None]:
'''函数体内自动接收 RuntimeContext,可读 ctx.is_sandbox / ctx.business_date。'''
...
"""
from app.services.sandbox_replay._decorator import runtime_aware
from app.services.sandbox_replay.balance_replay import (
get_member_balance as get_member_balance_replay,
)
from app.services.sandbox_replay.consumption_replay import (
get_consumption_60d as get_consumption_60d_replay,
get_last_visit_days as get_last_visit_days_replay,
get_total_consume_amount as get_total_consume_amount_replay,
)
__all__ = [
"runtime_aware",
"get_last_visit_days_replay",
"get_consumption_60d_replay",
"get_total_consume_amount_replay",
"get_member_balance_replay",
]