"""沙箱时光机重算引擎 (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.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", ]