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