Files
Neo-ZQYY/docs/database/changes/2026-05-06__ai_closure_schema_fixes.md
Neo 2dfc926f96 feat(ai): W1-AI-CLOSURE 超级 Sprint — 9 APP 全链路收口 + chat 上下文真激活
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>
2026-05-06 16:39:07 +08:00

7.7 KiB

2026-05-06 · W1-AI-CLOSURE Schema 修复 + 命名统一

关联迁移:db/zqyy_app/migrations/20260506__ai_closure_schema_fixes.sql

完整审计:docs/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_analysisapp7_customer(42 行)
    • app8_clue_consolidatedapp8_consolidation(72 行)
    • app6_note_analysisapp6_note(测试库 0 行,生产可能有)
  • biz.ai_run_logs.app_type:
    • app8_consolidateapp8_consolidation(123 行)
    • app8_clue_consolidatedapp8_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:测试断言不再固化"双名共存",改为统一命名

回滚策略

迁移文件末尾有完整"回滚参考"块,按以下顺序执行:

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 <commit_hash>

验证 SQL(已在测试库 PASS)

-- 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