Files
Neo-ZQYY/docs/database/changes/2026-05-01__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

12 KiB
Raw Blame History

数据库变更:业务运行上下文与沙箱隔离

日期2026-05-01 迁移脚本:db/zqyy_app/migrations/20260501__runtime_context_sandbox.sql 关联代码:


1 · 变更说明

新增表

Schema.Table 字段数 用途
biz.site_runtime_context 9 单门店业务运行上下文:mode=live 使用真实日期,mode=sandbox 使用 sandbox_datesandbox_instance_id 隔离写入

biz.site_runtime_context 字段:

字段 类型 约束 说明
site_id bigint PK / FK → biz.sites(site_id) 门店 ID
mode varchar(20) NOT NULL DEFAULT liveCHECK mode IN ('live','sandbox') 运行模式
sandbox_date date 可空 sandbox 模式下系统假设的业务日期
sandbox_instance_id varchar(64) 可空 sandbox 模式写入隔离实例 ID
ai_mode varchar(20) NOT NULL DEFAULT liveCHECK ai_mode IN ('live') AI 调用模式(当前仅 live沙箱也真实调用 DashScope
status varchar(20) NOT NULL DEFAULT active 上下文状态
reason text 可空 切换原因,便于审计
updated_by bigint 可空 最近一次切换的操作人
created_at / updated_at timestamptz NOT NULL DEFAULT now() 创建/更新时间

复合 CHECKsite_runtime_context_sandbox_check

  • mode='live'sandbox_date IS NULLsandbox_instance_id IS NULL
  • mode='sandbox'sandbox_date IS NOT NULLsandbox_instance_id IS NOT NULL

新增列7 张表)

每张表新增两列:

类型 默认值 NULL 说明
runtime_mode varchar(20) 'live' NOT NULL 写入时所处模式(live / sandbox
sandbox_instance_id varchar(64) 'live' NOT NULL sandbox 写入隔离实例 IDlive 模式记录占位值 'live',便于唯一索引覆盖两种模式

涉及表:

Schema.Table 用途
biz.coach_tasks 助教任务
biz.coach_task_transfer_log 任务转移日志
biz.recall_events 召回事件
biz.coach_task_history 任务历史
biz.ai_cache AI 缓存
biz.ai_run_logs AI 运行日志
biz.ai_trigger_jobs AI 触发记录

索引变更

旧索引DROP

Schema.Index 原唯一性 原表
biz.idx_coach_tasks_site_assistant_member_type UNIQUE coach_tasks
biz.idx_recall_events_site_assistant_member_day UNIQUE recall_events

新索引CREATE

Schema.Index 类型 说明
biz.idx_coach_tasks_runtime_unique_active UNIQUE部分索引 WHERE status='active' 唯一键加入 runtime_mode + sandbox_instance_id,允许 sandbox/live 同时存在同一 (site/assistant/member/task_type) 的活跃任务
biz.idx_recall_events_runtime_site_assistant_member_day UNIQUE 召回事件唯一键加入 runtime 维度,按 pay_time 当日去重
biz.idx_coach_tasks_runtime_assistant_status INDEX 任务列表按 runtime + 助教 + 状态查询
biz.idx_ai_cache_runtime_lookup INDEX AI cache 按 cache_type + site + runtime + target 查询
biz.idx_ai_trigger_jobs_runtime_site INDEX AI 触发记录按 site + runtime + event_type + status 查询

数据回填

迁移在事务内执行,对 7 张表做:

UPDATE biz.<table>
   SET runtime_mode = 'live', sandbox_instance_id = 'live'
 WHERE sandbox_instance_id IS NULL;

迁移完成后所有历史行 runtime_mode='live'sandbox_instance_id='live',与新写入的 live 行保持一致,唯一索引继续生效。


2 · 兼容性影响

对后端

  • apps/backend/app/services/runtime_context.py 提供 get_runtime_contexttask_runtime_filterruntime_insert_columnsruntime_update_assignmentsas_runtime_now_paramas_runtime_today_paramnamespace_ai_target_id。当 biz.site_runtime_context 不存在或查询异常时降级为默认 live保证迁移前不破坏旧行为。
  • 已接入文件(写入或查询时考虑 runtime 维度):
    • apps/backend/app/services/task_manager.py
    • apps/backend/app/services/task_generator.py
    • apps/backend/app/services/task_expiry.py
    • apps/backend/app/services/recall_detector.py
    • apps/backend/app/services/board_service.py
    • apps/backend/app/ai/cache_service.py
    • apps/backend/app/ai/run_log_service.py
    • apps/backend/app/ai/prompts/app2_finance_prompt.py
    • apps/backend/app/ai/prompts/app2a_finance_area_prompt.py
  • 新增 admin API
    • GET /api/config/runtime-context:当前用户门店上下文(任意登录用户)。
    • GET /api/admin/runtime-context?site_id=...:按门店查询(仅 super_admin
    • GET /api/admin/runtime-context/sites:列出门店与运行上下文(仅 super_admin
    • PATCH /api/admin/runtime-context:切换 live/sandbox仅 super_admin

对 admin-web

  • 新增菜单「系统设置 → 业务运行上下文 / 沙箱」,路由 /settings/runtime-context,仅 super_admin 可见。
  • 切换 sandbox 时仅暂停/恢复 当前 site_id 下的 biz.trigger_jobs,不影响其他门店。

对小程序

  • 不直接读 site_runtime_context;通过后端 API 间接生效。
  • live 模式不改变现有行为sandbox 模式下看板/任务按 sandbox_datesandbox_instance_id 隔离。

对预算与监控

  • 真实预算、tokens 计数、审计仍按真实系统时间运行,不受沙箱影响。

3 · 回滚策略

前置条件

  • 确认无门店处于 mode='sandbox'

    SELECT site_id, mode, sandbox_date FROM biz.site_runtime_context WHERE mode='sandbox';
    
  • 后端 / admin-web 中 RuntimeContext 相关代码已经撤回或停止依赖(避免 schema DROP 后查询失败)。

回滚 SQL

迁移文件末尾提供完整回滚 SQL注释形式。简化版本

BEGIN;

DROP INDEX IF EXISTS biz.idx_ai_trigger_jobs_runtime_site;
DROP INDEX IF EXISTS biz.idx_ai_cache_runtime_lookup;
DROP INDEX IF EXISTS biz.idx_coach_tasks_runtime_assistant_status;
DROP INDEX IF EXISTS biz.idx_recall_events_runtime_site_assistant_member_day;
DROP INDEX IF EXISTS biz.idx_coach_tasks_runtime_unique_active;

CREATE UNIQUE INDEX idx_recall_events_site_assistant_member_day
  ON biz.recall_events
    USING btree (site_id, assistant_id, member_id,
                 (date_trunc('day', pay_time AT TIME ZONE 'Asia/Shanghai')));

CREATE UNIQUE INDEX idx_coach_tasks_site_assistant_member_type
  ON biz.coach_tasks
    USING btree (site_id, assistant_id, member_id, task_type)
    WHERE status = 'active';

ALTER TABLE biz.ai_trigger_jobs        DROP COLUMN IF EXISTS sandbox_instance_id, DROP COLUMN IF EXISTS runtime_mode;
ALTER TABLE biz.ai_run_logs            DROP COLUMN IF EXISTS sandbox_instance_id, DROP COLUMN IF EXISTS runtime_mode;
ALTER TABLE biz.ai_cache               DROP COLUMN IF EXISTS sandbox_instance_id, DROP COLUMN IF EXISTS runtime_mode;
ALTER TABLE biz.coach_task_history     DROP COLUMN IF EXISTS sandbox_instance_id, DROP COLUMN IF EXISTS runtime_mode;
ALTER TABLE biz.recall_events          DROP COLUMN IF EXISTS sandbox_instance_id, DROP COLUMN IF EXISTS runtime_mode;
ALTER TABLE biz.coach_task_transfer_log DROP COLUMN IF EXISTS sandbox_instance_id, DROP COLUMN IF EXISTS runtime_mode;
ALTER TABLE biz.coach_tasks            DROP COLUMN IF EXISTS sandbox_instance_id, DROP COLUMN IF EXISTS runtime_mode;

DROP TABLE IF EXISTS biz.site_runtime_context;

COMMIT;

数据保护

  • 旧唯一索引被替换为更宽的唯一索引(含 runtime 维度),不会因唯一冲突丢数据。
  • 回滚需先 DROP 新索引再重建旧索引;旧索引列子集仍唯一,回滚后历史 live 数据满足新约束。
  • 所有 7 张表中 sandbox 模式产生的“演练数据”应在切回 live 前清理或保留:迁移层默认不清理,由运维决定。

4 · 验证 SQL已在 test_zqyy_app 通过)

验证 1 · 新表与 CHECK 约束

SELECT conname FROM pg_constraint
 WHERE conrelid = 'biz.site_runtime_context'::regclass
   AND contype = 'c'
 ORDER BY conname;

-- 期望:
-- site_runtime_context_ai_mode_check
-- site_runtime_context_mode_check
-- site_runtime_context_sandbox_check

验证 2 · 7 张表的 runtime_mode / sandbox_instance_id 列存在

SELECT table_name, column_name
  FROM information_schema.columns
 WHERE table_schema = 'biz'
   AND column_name IN ('runtime_mode', 'sandbox_instance_id')
   AND table_name IN (
       'coach_tasks',
       'coach_task_transfer_log',
       'recall_events',
       'coach_task_history',
       'ai_cache',
       'ai_run_logs',
       'ai_trigger_jobs'
   )
 ORDER BY table_name, column_name;

-- 期望14 行7 张表 × 2 列)

验证 3 · 关键索引存在 / 旧索引已 DROP

SELECT indexname FROM pg_indexes
 WHERE schemaname = 'biz'
   AND indexname IN (
     'idx_coach_tasks_runtime_unique_active',
     'idx_recall_events_runtime_site_assistant_member_day',
     'idx_coach_tasks_runtime_assistant_status',
     'idx_ai_cache_runtime_lookup',
     'idx_ai_trigger_jobs_runtime_site'
   )
 ORDER BY indexname;
-- 期望5 行

SELECT indexname FROM pg_indexes
 WHERE schemaname = 'biz'
   AND indexname IN (
     'idx_coach_tasks_site_assistant_member_type',
     'idx_recall_events_site_assistant_member_day'
   );
-- 期望0 行

验证 4 · CHECK 约束生效

INSERT INTO biz.site_runtime_context
    (site_id, mode, sandbox_date, sandbox_instance_id)
VALUES (-1, 'sandbox', NULL, NULL);
-- 期望:失败,触发 site_runtime_context_sandbox_check

验证 5 · 历史数据回填

SELECT
    SUM(CASE WHEN runtime_mode = 'live' THEN 1 ELSE 0 END) AS live_cnt,
    SUM(CASE WHEN runtime_mode IS NULL THEN 1 ELSE 0 END) AS null_cnt
  FROM biz.ai_cache;
-- 期望null_cnt = 0

5 · 关联变更

关联项 状态 说明
后端 RuntimeContext 服务 / 路由 已实施 见上文文件清单
admin-web 沙箱设置页面 已实施 /settings/runtime-context(仅 super_admin
业务/AI 服务接入 runtime 过滤 已实施 任务、看板、AI cache、run logs 等
切换前停止 ETL/AI 队列 已实施 _stop_runtime_activity
暂停/恢复 biz.trigger_jobs 已实施 已按 site_id 隔离
主 DDL 同步 待执行 PYTHONUTF8=1 python tools/db/gen_consolidated_ddl.py 后同步 db/zqyy_app/schemas/biz.sql
表级 BD_Manual 已实施 BD_Manual_runtime_context_sandbox.md
生产库执行 上线前由运维按窗口执行

6 · 变更记录

日期 操作 执行人
2026-05-01 迁移脚本产出 Codex / Claude
2026-05-02 测试库 test_zqyy_app 执行 + 5 项验证通过 Cursor + Neo
2026-05-02 修复 trigger_jobs 暂停/恢复按 site_id 隔离admin-web 新增沙箱设置页面 Cursor + Neo
待定 生产库 zqyy_app 执行 Neo