Phase 2.3 chat 上下文捕获链路从未真正激活到完整工作: - 14 处 ai-float-button 补 sourcePage,chat.ts 三分支同步设 pageFilters.contextId - 后端 page_context 4 层 BUG 修(列名错位 + RLS site_id 未重设) - xcx_chat filters.pop 破坏 body.page_context 引用 — dict() 浅拷贝隔离 - chat 流式 markdown 实时解析(表格/标题/列表/加粗 + KPI 富卡) - reference_card KPI 富卡接入 SSE 路径,db 真写入 - 维客线索 source 显示规则:AI 来源用机器人 icon 替代长文字 数据库: - public.member_retention_clue 加 emoji + runtime_mode + sandbox_instance_id - biz.ai_run_logs 加 assistant_id + 复合索引 - chk_ai_cache_type CHECK 约束 8 类应用名 - cache_type / app_type 命名统一(app6_note / app7_customer / app8_consolidation) - 历史 emoji 抽取脚本 44/44 成功 后端 silent failure 修: - cleanup_service WHERE app_type → cache_type(90 天清理 + 20K 上限重新生效) - _build_ai_insight 字段错位修复(app4 → app7 + 字段对齐 prompt schema) - task_manager talkingPoints 改 app5_tactics + tactics 字段 - task_manager aiSuggestion 改取 one_line_summary - cache_service.CACHE_EXPIRY_DAYS 加 app2a_finance_area - WS /ws/ai-cache 加 token + JWT + site_id 校验(P0 信息泄露漏洞) - internal_ai token 改 hmac.compare_digest 工具/文档: - main.py 加 RotatingFileHandler logs/backend.log + uvicorn /health 过滤 - 新建 utils/clue_category.py(VI 6 类配色 + emoji fallback + source 显示规则) - 新建 utils/markdown.ts(轻量 md 转 rich-text 解析 + streaming 容错) - audit + 数据库变更说明 + backlog §七 #14 收口 + #15-#38 残余子任务 - backlog 追加 §十一 App1 参数/MCP/沙箱审计 + §十二 百炼/SQL MCP 主任务线 实地 MCP 走查:14 入口数据层 + 5 代表入口 sourcePage 注入 + customer-detail 全模块 + chat md 渲染 + reference_card 富卡 都已验证。9 项预先 BUG/UX 登记 §七 #29-#38 后续修复。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
173 lines
8.1 KiB
PL/PgSQL
173 lines
8.1 KiB
PL/PgSQL
-- 2026-05-06
|
|
-- W1-AI-CLOSURE 超级 Sprint 组 1 — Schema 修复 + 命名统一
|
|
--
|
|
-- 背景:
|
|
-- AI 9 APP 全链路调研发现以下劣化:
|
|
-- 1. emoji 嵌入 summary 字符串(dispatcher.py:582-584),数据库 member_retention_clue
|
|
-- 表无独立 emoji 列,违反"字段独立性"哲学
|
|
-- 2. member_retention_clue 表无 runtime_mode / sandbox_instance_id,沙箱模式下 App8
|
|
-- 写入会污染 prod 视图(其他 7 张 ai_* 表都有这两列,本表是唯一例外)
|
|
-- 3. ai_run_logs 缺 assistant_id 列,App4/App5 这种 (assistant, member) 二元任务
|
|
-- 失败定位困难
|
|
-- 4. cache_type / app_type 双名长期共存:
|
|
-- ai_cache.cache_type = app7_customer_analysis / app8_clue_consolidated
|
|
-- ai_run_logs.app_type = app7_customer / app8_consolidate
|
|
-- 违反"schema 一致性"哲学,统一为应用名(与 prompt 文件名一致):
|
|
-- app7_customer / app8_consolidation
|
|
--
|
|
-- 影响范围:
|
|
-- - public.member_retention_clue:加 3 列(emoji + runtime_mode + sandbox_instance_id)
|
|
-- - biz.ai_run_logs:加 assistant_id 列 + 复合索引补建
|
|
-- - biz.ai_cache + biz.ai_run_logs:cache_type / app_type 命名统一
|
|
-- - 后端 dispatcher / cleanup_service / cache_service 代码相应修改(组 2-5)
|
|
--
|
|
-- 兼容性:
|
|
-- - emoji 列默认空字符串,新写入由 dispatcher 移除拼字符串后独立写入(组 3)
|
|
-- - runtime_mode / sandbox_instance_id 默认 'live',与其他 ai_* 表一致
|
|
-- - 命名 UPDATE 后,旧字符串 'app7_customer_analysis' / 'app8_clue_consolidated' /
|
|
-- 'app8_consolidate' 在数据库中绝迹,代码侧必须同步更新
|
|
-- - 回填脚本 scripts/ops/backfill_retention_clue_emoji.py 抽取 summary 嵌入的 emoji
|
|
-- 到 emoji 列,本迁移不做该回填(脚本走 dry-run + 实跑两步)
|
|
--
|
|
-- 回滚策略:见末尾"回滚参考"块。
|
|
--
|
|
-- 验证 SQL(执行后跑):
|
|
-- 1. SELECT column_name FROM information_schema.columns
|
|
-- WHERE table_schema='public' AND table_name='member_retention_clue'
|
|
-- AND column_name IN ('emoji','runtime_mode','sandbox_instance_id');
|
|
-- 预期 3 行
|
|
-- 2. SELECT column_name FROM information_schema.columns
|
|
-- WHERE table_schema='biz' AND table_name='ai_run_logs'
|
|
-- AND column_name='assistant_id';
|
|
-- 预期 1 行
|
|
-- 3. SELECT cache_type, count(*) FROM biz.ai_cache
|
|
-- WHERE cache_type IN ('app6_note_analysis','app7_customer_analysis',
|
|
-- 'app8_clue_consolidated','app8_consolidate')
|
|
-- GROUP BY 1;
|
|
-- 预期 0 行
|
|
-- 4. SELECT app_type, count(*) FROM biz.ai_run_logs
|
|
-- WHERE app_type IN ('app6_note_analysis','app7_customer_analysis',
|
|
-- 'app8_consolidate','app8_clue_consolidated')
|
|
-- GROUP BY 1;
|
|
-- 预期 0 行
|
|
-- 5. SELECT runtime_mode, count(*) FROM public.member_retention_clue GROUP BY 1;
|
|
-- 预期 'live' 一行覆盖全部历史
|
|
|
|
BEGIN;
|
|
|
|
-- ── 1) public.member_retention_clue: 加 emoji + runtime_mode + sandbox_instance_id ──
|
|
|
|
ALTER TABLE public.member_retention_clue
|
|
ADD COLUMN IF NOT EXISTS emoji character varying(8) NOT NULL DEFAULT '';
|
|
|
|
ALTER TABLE public.member_retention_clue
|
|
ADD COLUMN IF NOT EXISTS runtime_mode character varying(20) NOT NULL DEFAULT 'live',
|
|
ADD COLUMN IF NOT EXISTS sandbox_instance_id character varying(64) NOT NULL DEFAULT 'live';
|
|
|
|
UPDATE public.member_retention_clue
|
|
SET runtime_mode = 'live', sandbox_instance_id = 'live'
|
|
WHERE runtime_mode IS NULL OR sandbox_instance_id IS NULL;
|
|
|
|
COMMENT ON COLUMN public.member_retention_clue.emoji IS
|
|
'维客线索独立 emoji 字段(由 App8 prompt 输出 emoji 字段直接写入,不嵌 summary);本字段于 W1-AI-CLOSURE 引入,历史数据由 backfill_retention_clue_emoji.py 回填。';
|
|
COMMENT ON COLUMN public.member_retention_clue.runtime_mode IS
|
|
'运行模式:live / sandbox;sandbox 模式写入隔离实例 ID,live 与其他门店共享 prod 视图。';
|
|
COMMENT ON COLUMN public.member_retention_clue.sandbox_instance_id IS
|
|
'sandbox 模式写入隔离实例 ID;live 模式固定为 live。';
|
|
|
|
-- ── 2) biz.ai_run_logs: 加 assistant_id 列 + 复合索引 ──
|
|
|
|
ALTER TABLE biz.ai_run_logs
|
|
ADD COLUMN IF NOT EXISTS assistant_id bigint;
|
|
|
|
COMMENT ON COLUMN biz.ai_run_logs.assistant_id IS
|
|
'App4/App5 这类 (assistant, member) 二元关系任务的助教 ID,便于失败定位;App2/App3/App6/App7/App8 类任务为 NULL。';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_ai_run_logs_assistant_member
|
|
ON biz.ai_run_logs (site_id, assistant_id, member_id, created_at DESC)
|
|
WHERE assistant_id IS NOT NULL;
|
|
|
|
-- ── 3) cache_type / app_type 命名统一(app6 + app7 + app8) ──
|
|
-- 双名长期共存违反 schema 一致性,统一为与 prompt 文件名一致的应用名:
|
|
-- app6_note_analysis -> app6_note
|
|
-- app7_customer_analysis -> app7_customer
|
|
-- app8_clue_consolidated / app8_consolidate -> app8_consolidation
|
|
-- 注意:cache_type 有 chk_ai_cache_type CHECK 约束,需先 DROP 再 UPDATE 再 ADD 新约束。
|
|
|
|
ALTER TABLE biz.ai_cache DROP CONSTRAINT IF EXISTS chk_ai_cache_type;
|
|
|
|
UPDATE biz.ai_cache
|
|
SET cache_type = 'app6_note'
|
|
WHERE cache_type = 'app6_note_analysis';
|
|
|
|
UPDATE biz.ai_cache
|
|
SET cache_type = 'app7_customer'
|
|
WHERE cache_type = 'app7_customer_analysis';
|
|
|
|
UPDATE biz.ai_cache
|
|
SET cache_type = 'app8_consolidation'
|
|
WHERE cache_type IN ('app8_clue_consolidated', 'app8_consolidate');
|
|
|
|
UPDATE biz.ai_run_logs
|
|
SET app_type = 'app8_consolidation'
|
|
WHERE app_type IN ('app8_consolidate', 'app8_clue_consolidated');
|
|
|
|
UPDATE biz.ai_run_logs
|
|
SET app_type = 'app6_note'
|
|
WHERE app_type = 'app6_note_analysis';
|
|
|
|
UPDATE biz.ai_run_logs
|
|
SET app_type = 'app7_customer'
|
|
WHERE app_type = 'app7_customer_analysis';
|
|
|
|
-- 注意:ai_run_logs 中 app7 测试库已经是 'app7_customer'(102 行),app6 在测试库
|
|
-- 无数据;UPDATE 旧名字若不存在则 0 行影响,幂等安全。
|
|
|
|
ALTER TABLE biz.ai_cache
|
|
ADD CONSTRAINT chk_ai_cache_type
|
|
CHECK (cache_type IN (
|
|
'app2_finance',
|
|
'app2a_finance_area',
|
|
'app3_clue',
|
|
'app4_analysis',
|
|
'app5_tactics',
|
|
'app6_note',
|
|
'app7_customer',
|
|
'app8_consolidation'
|
|
));
|
|
|
|
COMMENT ON CONSTRAINT chk_ai_cache_type ON biz.ai_cache IS
|
|
'AI 8 个写缓存的应用类型(app1_chat 走 ai_messages 不进缓存);命名与 prompt 文件名一致。';
|
|
|
|
-- ── 4) 索引收尾(若旧索引引用旧 cache_type 字符串,无影响 — 索引按当前值重建) ──
|
|
|
|
COMMIT;
|
|
|
|
-- =============================================================================
|
|
-- 回滚参考(测试库回滚先跑此块,正式库回滚需评估业务影响):
|
|
-- =============================================================================
|
|
-- BEGIN;
|
|
--
|
|
-- -- 命名 UPDATE 回滚(注意:旧名字 app8_consolidate vs app8_clue_consolidated 已合并,
|
|
-- -- 回滚无法精确还原,只能选其一;以下示例选 ai_cache 的旧描述名)
|
|
-- -- ALTER TABLE biz.ai_cache DROP CONSTRAINT IF EXISTS chk_ai_cache_type;
|
|
-- -- UPDATE biz.ai_run_logs SET app_type = 'app8_consolidate' WHERE app_type = 'app8_consolidation';
|
|
-- -- UPDATE biz.ai_run_logs SET app_type = 'app7_customer_analysis' WHERE app_type = 'app7_customer';
|
|
-- -- UPDATE biz.ai_run_logs SET app_type = 'app6_note_analysis' WHERE app_type = 'app6_note';
|
|
-- -- UPDATE biz.ai_cache SET cache_type = 'app8_clue_consolidated' WHERE cache_type = 'app8_consolidation';
|
|
-- -- UPDATE biz.ai_cache SET cache_type = 'app7_customer_analysis' WHERE cache_type = 'app7_customer';
|
|
-- -- UPDATE biz.ai_cache SET cache_type = 'app6_note_analysis' WHERE cache_type = 'app6_note';
|
|
-- -- ALTER TABLE biz.ai_cache ADD CONSTRAINT chk_ai_cache_type CHECK (cache_type IN
|
|
-- -- ('app2_finance','app2a_finance_area','app3_clue','app4_analysis','app5_tactics',
|
|
-- -- 'app6_note_analysis','app7_customer_analysis','app8_clue_consolidated'));
|
|
--
|
|
-- DROP INDEX IF EXISTS biz.idx_ai_run_logs_assistant_member;
|
|
-- ALTER TABLE biz.ai_run_logs DROP COLUMN IF EXISTS assistant_id;
|
|
--
|
|
-- ALTER TABLE public.member_retention_clue
|
|
-- DROP COLUMN IF EXISTS sandbox_instance_id,
|
|
-- DROP COLUMN IF EXISTS runtime_mode,
|
|
-- DROP COLUMN IF EXISTS emoji;
|
|
--
|
|
-- COMMIT;
|