Files
Neo-ZQYY/docs/database/BD_Manual_runtime_context_sandbox.md
Neo caf179a5da feat: 2026-04-15~05-02 累积变更基线 — AI 重构 + Runtime Context + DWS 修复
涵盖(每条对应已存的审计记录):
- AI 模块拆分:apps/backend/app/ai/apps -> prompts/(8 个 APP + app2a 派生)
  audit: 2026-04-20__ai-module-complete.md
- admin-web AI 管理套件:AIDashboard / AIOperations / AIRunLogs / AITriggers / TriggerManager
  audit: 2026-04-21__admin-web-ai-management-suite.md
- App2 财务洞察 prompt v3 -> v5.1 + 小程序 AI 接入(chat / board-finance)
  audit: 2026-04-22__app2_prompt_v5_1_and_miniprogram_ai_insight.md
- App2 prewarm 全过滤器 + AI 触发器 cron reschedule
  audit: 2026-04-21__app2-finance-prewarm-all-filters.md
  migration: 20260420_ai_trigger_jobs_and_app2_prewarm.sql / 20260421_app2_prewarm_cron_reschedule.sql
- AppType 联合类型对齐 + adminAiAppTypes.test.ts
  audit: 2026-04-30__admin_web_ai_app_type_alignment.md
- DashScope tokens_used 提取修复
  audit: 2026-04-30__backend_dashscope_tokens_used_extraction.md
- App3 线索完整详情 prompt
  audit: 2026-05-01__backend_app3_full_detail_prompt.md
- Runtime Context 沙箱(5-1~5-2 主线):
  - 后端 schema/service + admin_runtime_context / xcx_runtime_clock 两个 router
  - admin-web RuntimeContext.tsx + miniprogram runtime-clock.ts
  - migration: 20260501__runtime_context_sandbox.sql
  - tools/db/verify_admin_web_sandbox.py + verify_sandbox_end_to_end.py
  - database/changes: 7 份 sandbox_* 验证报告
- 飞球 DWS 修复:finance_area_daily 区域汇总 + task_engine 调整
  + RLS 视图业务日上界(migration 20260502 + scripts/ops/gen_rls_business_date_migration.py)

合规:
- .gitignore 启用 tmp/ 排除
- 不入仓:apps/etl/connectors/feiqiu/.env(API_TOKEN secret,本地修改保留)

待验证清单:
- docs/audit/changes/2026-05-04__cumulative_baseline_pending_verification.md
  每个主题的功能完整性 / 上线验证几乎都未收口,按优先级 P0~P3 逐一处理
2026-05-04 02:30:19 +08:00

153 lines
8.0 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.
# BD_Manual业务运行上下文biz.site_runtime_context+ 沙箱隔离列
> 目标库:`test_zqyy_app`(通过 `TEST_APP_DB_DSN` 连接);生产库 `zqyy_app` 待上线
> 迁移脚本:[db/zqyy_app/migrations/20260501__runtime_context_sandbox.sql](../../db/zqyy_app/migrations/20260501__runtime_context_sandbox.sql)
> 关联变更说明:[changes/2026-05-01__runtime_context_sandbox.md](changes/2026-05-01__runtime_context_sandbox.md)
> 关联设计:球房停业后,希望按门店把后台 / 小程序切到“假设当前是历史日期”的状态做开发与演示,而真实预算与系统时间不受影响。
---
## 1. 设计目标
- 多门店隔离:每个 `site_id` 独立选择 live 或 sandbox互不影响。
- 业务时钟统一后端任务、看板、AI 调用都通过 `RuntimeContext.business_date` 取“今天”,不再直接调用 `date.today()` / `NOW()`
- 写入隔离sandbox 模式下任务、AI cache、run logs 写入时携带 `runtime_mode='sandbox'` + `sandbox_instance_id=sbx_*`,与 live 数据共存但不污染。
- 安全降级:表缺失或异常时退回 live确保迁移前线上行为不变。
---
## 2. 表结构
### 2.1 `biz.site_runtime_context`9 字段)
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| `site_id` | bigint | PKFK → `biz.sites(site_id)` | 门店 ID |
| `mode` | varchar(20) | NOT NULL DEFAULT `'live'`CHECK `IN ('live','sandbox')` | 运行模式 |
| `sandbox_date` | date | 可空 | sandbox 模式下系统假设的业务日期 |
| `sandbox_instance_id` | varchar(64) | 可空 | sandbox 模式写入隔离实例 ID格式 `sbx_<24hex>` |
| `ai_mode` | varchar(20) | NOT NULL DEFAULT `'live'`CHECK `IN ('live')` | AI 调用模式;当前仅 live沙箱也真实调用 DashScope |
| `status` | varchar(20) | NOT NULL DEFAULT `'active'` | 上下文状态 |
| `reason` | text | 可空 | 切换原因(运维/演示备注) |
| `updated_by` | bigint | 可空 | 最近一次切换的 admin user_id |
| `created_at` | timestamptz | NOT NULL DEFAULT now() | 创建时间 |
| `updated_at` | timestamptz | NOT NULL DEFAULT now() | 更新时间API 切换时手动写入 NOW() |
约束:
| 约束名 | 类型 | 说明 |
|---|---|---|
| `site_runtime_context_pkey` | PK | `(site_id)` |
| `site_runtime_context_site_id_fkey` | FK | `site_id → biz.sites(site_id)` |
| `site_runtime_context_mode_check` | CHECK | `mode IN ('live','sandbox')` |
| `site_runtime_context_ai_mode_check` | CHECK | `ai_mode IN ('live')` |
| `site_runtime_context_sandbox_check` | CHECK | `live` 模式 `sandbox_*` 必为 NULL`sandbox` 模式 `sandbox_*` 必非 NULL |
### 2.2 7 张表新增 runtime 维度列
每张表新增两列,默认值 `'live'` / `'live'`NOT NULL
| Schema.Table | 主要用途 |
|---|---|
| `biz.coach_tasks` | 助教任务(召回/回访/关系建设) |
| `biz.coach_task_transfer_log` | 任务转移日志 |
| `biz.coach_task_history` | 任务历史归档 |
| `biz.recall_events` | 消费引发的召回事件 |
| `biz.ai_cache` | AI 应用缓存 |
| `biz.ai_run_logs` | AI 调用明细 |
| `biz.ai_trigger_jobs` | AI 调度记录 |
| 列 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `runtime_mode` | varchar(20) | `'live'` | 写入时所处模式 |
| `sandbox_instance_id` | varchar(64) | `'live'` | sandbox 实例 IDlive 模式占位 `'live'` |
### 2.3 索引
替换的旧索引:
| Schema.Index | 状态 |
|---|---|
| `biz.idx_coach_tasks_site_assistant_member_type` | DROP |
| `biz.idx_recall_events_site_assistant_member_day` | DROP |
新建索引:
| Schema.Index | 类型 | 列 | 说明 |
|---|---|---|---|
| `idx_coach_tasks_runtime_unique_active` | UNIQUE部分 `WHERE status='active'` | `(site_id, assistant_id, member_id, task_type, runtime_mode, sandbox_instance_id)` | 同一 (site/assistant/member/task) 在 live 与 sandbox 之间互不冲突,可同时存在活跃任务 |
| `idx_recall_events_runtime_site_assistant_member_day` | UNIQUE | `(site_id, assistant_id, member_id, runtime_mode, sandbox_instance_id, date_trunc('day', pay_time AT TIME ZONE 'Asia/Shanghai'))` | 召回事件按当日去重,加入 runtime 维度 |
| `idx_coach_tasks_runtime_assistant_status` | INDEX | `(site_id, runtime_mode, sandbox_instance_id, assistant_id, status)` | 任务列表查询 |
| `idx_ai_cache_runtime_lookup` | INDEX | `(cache_type, site_id, runtime_mode, sandbox_instance_id, target_id, created_at DESC)` | AI cache 查询 |
| `idx_ai_trigger_jobs_runtime_site` | INDEX | `(site_id, runtime_mode, sandbox_instance_id, event_type, status)` | AI 调度记录查询 |
---
## 3. 写入与查询约定
### 3.1 写入
写入新行时统一使用 `app.services.runtime_context.runtime_insert_columns(site_id)` 取出列、占位符与值live 行得到 `('live','live')`sandbox 行得到 `('sandbox', sbx_<id>)`
`biz.ai_cache.target_id` 在 sandbox 模式下会被 `namespace_ai_target_id` 加上前缀,例如 `sbx_xxx:this_month__all`
### 3.2 查询
业务查询统一使用 `task_runtime_filter(site_id, alias=...)`
- live 模式 `AND COALESCE(t.runtime_mode,'live')='live' AND COALESCE(t.sandbox_instance_id,'live')='live'`
- sandbox 模式 `AND t.runtime_mode='sandbox' AND t.sandbox_instance_id=%s`
### 3.3 切换 API
| 接口 | 权限 | 说明 |
|---|---|---|
| `GET /api/config/runtime-context` | 任意登录用户 | 返回当前用户门店的 RuntimeContext |
| `GET /api/admin/runtime-context?site_id=...` | super_admin | 按 site_id 查询 |
| `GET /api/admin/runtime-context/sites` | super_admin | 列出门店与运行上下文 |
| `PATCH /api/admin/runtime-context` | super_admin | 切换 live/sandbox |
切换 sandbox 时按 `site_id` 暂停 `biz.trigger_jobs``status='enabled'` 的记录为 `paused_by_sandbox`;切回 live 时恢复同一 site 的 `paused_by_sandbox``enabled`
---
## 4. 真实数据 vs 沙箱数据
| 主体 | live 行 | sandbox 行 |
|---|---|---|
| 业务时钟 | 真实日期 | `sandbox_date` |
| 任务表 | 写入 `('live','live')` | 写入 `('sandbox', sbx_*)` |
| AI cache | `runtime_mode='live'``target_id` 不变 | `runtime_mode='sandbox'``target_id``sbx_*:` 前缀 |
| AI run logs / trigger jobs | live | sandbox + sbx_* |
| 真实预算 / DashScope tokens 计费 | 计入真实统计 | 同样真实计入(不为节流) |
| `biz.trigger_jobs` | sandbox 模式下当前 site 下 `enabled` 行被改为 `paused_by_sandbox`;其它 site 不动 |
---
## 5. 验证清单
- 列 / 索引 / 约束按 `changes/2026-05-01__runtime_context_sandbox.md` 第 4 节 5 项 SQL 验证。
- 切换流程:在 admin-web `/settings/runtime-context` 选择门店 → 切到 sandbox指定 `sandbox_date`)→ 验证 `biz.trigger_jobs` 仅当前 site 被暂停,其它 site 不受影响 → 切回 live 恢复。
---
## 6. 注意事项
- **不要直接清空 sandbox 数据**`coach_tasks``ai_cache` 等如有清理需求,须按 `runtime_mode='sandbox' AND sandbox_instance_id=...` 限定。
- **生产库上线**:执行迁移前先确认无门店启用 sandbox上线后再开放 admin-web 入口。
- **降级路径**:当 `biz.site_runtime_context` 表暂未上线(如逐步发布),后端 `get_runtime_context` 自动降级为 live避免业务中断。
## 7. 读取层「不看未来」改造路线
R1 初版只解决了**写入隔离**。读取层的看板 / 任务 / AI / 小程序仍存在「sandbox 模式下读到 sandbox_date 之后真实数据」的风险。完整改造方案与逐项文件清单见
[changes/2026-05-02__sandbox_no_future_data_plan.md](changes/2026-05-02__sandbox_no_future_data_plan.md)。
| 层 | 进度 |
|---|---|
| A 文档 / UI 警告 | 进行中admin-web Alert + 本章节) |
| B-1 后端 service / prompts / data_fetchers / fdw_queries 时间锚替换与 SQL 上界 | 计划中 |
| B-2 小程序去除本地年月 | 计划中 |
| C ETL RLS 视图层 `app.current_business_date` 业务日上界 | 计划中 |
完成顺序按 plan 文档执行;每个 PR 自带 sandbox 模式下「不返回 sandbox_date 之后数据」的最小验证。