# 2026-05-06 · W1-AI-CLOSURE Schema 修复 + 命名统一 > 关联迁移:[`db/zqyy_app/migrations/20260506__ai_closure_schema_fixes.sql`](../../../db/zqyy_app/migrations/20260506__ai_closure_schema_fixes.sql) > > 完整审计:[`docs/audit/changes/2026-05-06__w1_ai_closure_super_sprint.md`](../../audit/changes/2026-05-06__w1_ai_closure_super_sprint.md) ## 变更说明 ### 新增列 | 表 | 列 | 类型 | 默认值 | 用途 | |---|---|---|---|---| | `public.member_retention_clue` | `emoji` | `varchar(8)` | `''` | 维客线索独立 emoji 字段(App8 prompt 输出 emoji 直接写入,不再嵌 summary 字符串) | | `public.member_retention_clue` | `runtime_mode` | `varchar(20)` | `'live'` | 运行模式 live/sandbox(与其他 7 张 ai_* 表对齐) | | `public.member_retention_clue` | `sandbox_instance_id` | `varchar(64)` | `'live'` | sandbox 模式写入隔离实例 ID | | `biz.ai_run_logs` | `assistant_id` | `bigint NULL` | NULL | App4/App5 这类 (assistant, member) 二元任务的助教 ID,便于失败定位 | ### 新增索引 - `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` ### CHECK 约束更新 - `chk_ai_cache_type` 重建:8 类应用名(`app2_finance` / `app2a_finance_area` / `app3_clue` / `app4_analysis` / `app5_tactics` / `app6_note` / `app7_customer` / `app8_consolidation`),与 prompt 文件名 + CacheTypeEnum 完全对齐 ### 数据 UPDATE(命名统一) - `biz.ai_cache.cache_type`: - `app7_customer_analysis` → `app7_customer`(42 行) - `app8_clue_consolidated` → `app8_consolidation`(72 行) - `app6_note_analysis` → `app6_note`(测试库 0 行,生产可能有) - `biz.ai_run_logs.app_type`: - `app8_consolidate` → `app8_consolidation`(123 行) - `app8_clue_consolidated` → `app8_consolidation`(测试库 0 行) - `app6_note_analysis` / `app7_customer_analysis` → 应用名(测试库 0 行) - `public.member_retention_clue.runtime_mode`:全部 NULL 填 `'live'`(44 行) ### 历史 emoji 回填(独立脚本) `scripts/ops/backfill_retention_clue_emoji.py` — 把 summary 嵌入的首 emoji 抽到 emoji 列(测试库 44/44 行成功,可重入)。 ## 兼容性影响 ### ETL 影响 - 无直接影响。ETL 不写 ai_cache / ai_run_logs / member_retention_clue,只是注释里"客户基础信息" → "客户基础" 的字面量调整(`apps/etl/connectors/feiqiu/tasks/dws/member_consumption_task.py` + `member_visit_task.py`)。 ### 后端 API 影响 - `customer_service._build_ai_insight`:cache_type 从 `app4_analysis`(错位)改为正确的 `app7_customer`,加 `site_id` 过滤 + 字段对齐 App7Result schema(strategies[].title/content) - `customer_service._build_retention_clues`:加 `site_id` 过滤 + 字段补齐(detail/source/recorded_by_name/emoji) - `task_manager.py` aiSuggestion:取 `one_line_summary` 替代不存在的 `app4.summary` - `task_manager.py` talkingPoints:cache_type `app5_talking_points`(不存在)→ `app5_tactics` + 字段 `tactics[].scenario/script` - `cleanup_service.py`:`WHERE app_type=%s` BUG 修(应是 `cache_type`)— 90 天清理 + 20K 上限重新生效 - `cache_service.CACHE_EXPIRY_DAYS`:补 `app2a_finance_area: 0`(64 区域组合不再永不过期) - `dispatcher._write_retention_clue`:emoji 独立写入(不再拼 summary)+ 加 runtime_mode/sandbox_instance_id 五元组隔离 - `xcx_chat`:`ReferenceCard` schema 补 `link/source_page` 字段;`get_messages` SQL 加 `AND role IN ('user','assistant')` 过滤 system 行;`build_reference_card` KPI 富卡接入 SSE 路径 - WS `/ws/ai-cache/{site_id}` + `/ws/ai-alerts/{site_id}`:加 `?token=` query 参数 + JWT 校验 + site_id 一致性校验 ### 小程序影响 - `customer-detail.ts`:clues 类型 4 字段 → 6 字段(tag/tagColor/emoji/text/source/desc);`_loadAIInsight` cache_type 改 `app7_customer` + 字段 `s.title/s.content` - `customer-detail.wxml`:clue-card props 字段对齐 + `wx:if` 空态隐藏 + ai-float-button 补 sourcePage - `task-detail.ts/wxml/json`:retentionClues tagColor 6 类;talkingPoints 类型 string[] → TacticItem[];整页补 ai-float-button + json 注册组件 - `chat.ts`:三分支(task/customer/coach)补 `sourcePage` + `pageFilters.contextId`,Phase 2.3 链路真正激活 - 14 处 wxml ai-float-button 全部补 sourcePage ### admin-web 影响 - `AIOperations.tsx` `CACHE_TYPE_OPTIONS` 8 类对齐(去除 _analysis / _consolidated 后缀) - `AIRunLogs.tsx` `RUN_LOG_APP_TYPE_OPTIONS` 删除旧 `app8_consolidate`(数据已 UPDATE 到 `app8_consolidation`) - `__tests__/adminAiAppTypes.test.ts`:测试断言不再固化"双名共存",改为统一命名 ## 回滚策略 迁移文件末尾有完整"回滚参考"块,按以下顺序执行: ```sql BEGIN; -- 1. DROP 新约束 ALTER TABLE biz.ai_cache DROP CONSTRAINT IF EXISTS chk_ai_cache_type; -- 2. UPDATE 命名回滚(注意:旧名 app8_consolidate vs app8_clue_consolidated 已合并, -- 回滚无法精确还原,只能选其一,以下示例选 ai_cache 旧描述名) 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'; -- 3. ADD 旧约束 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')); -- 4. DROP 索引 + 列 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; -- 5. emoji 反向回填(若需要)— 把 emoji 列拼回 summary UPDATE public.member_retention_clue SET summary = emoji || ' ' || summary, emoji = '' WHERE emoji != ''; COMMIT; ``` 代码侧:`git revert `。 ## 验证 SQL(已在测试库 PASS) ```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. assistant_id 列存在 SELECT column_name FROM information_schema.columns WHERE table_schema='biz' AND table_name='ai_run_logs' AND column_name='assistant_id'; -- 预期 1 行 -- 3. 旧 cache_type 0 残留 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. 旧 app_type 0 残留 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. emoji 回填覆盖率(回填脚本跑后) SELECT count(*) FILTER (WHERE emoji != '') AS filled, count(*) FILTER (WHERE emoji = '') AS empty, count(*) AS total FROM public.member_retention_clue; -- 预期 filled=44, empty=0, total=44(测试库) ``` ## 关联 - 完整审计:`docs/audit/changes/2026-05-06__w1_ai_closure_super_sprint.md` - backlog 登记:`docs/_overview/architecture-evolution-backlog.md` §七 #14 主体收口 + #15-#28 残余子任务 - 关联表 RLS 迁移(P1-1 public → biz schema):§七 #21 后续 sprint