微信小程序页面迁移校验之前 P5任务处理之前
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:member_birthday_manual → member_retention_clue(维客线索重构)
|
||||
-- 日期:2026-02-26
|
||||
-- 目标库:zqyy_app / test_zqyy_app
|
||||
-- 说明:
|
||||
-- 1. 删除 member_birthday_manual 表(生日不再单独记录,归入维客线索"客户基础信息"大类)
|
||||
-- 2. 新建 member_retention_clue 表(维客线索:大类 + 摘要 + 详情)
|
||||
-- 影响:
|
||||
-- - 后端 API:/api/member-birthday 废弃,替换为 /api/retention-clue
|
||||
-- - ETL DWS:移除 FDW 读取 member_birthday_manual 的逻辑
|
||||
-- - FDW:fdw_app.member_birthday_manual 外部表需在 ETL 库侧同步删除
|
||||
-- 幂等:DROP IF EXISTS / CREATE IF NOT EXISTS,可重复执行
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 删除旧表 member_birthday_manual
|
||||
-- ============================================================
|
||||
DROP TABLE IF EXISTS member_birthday_manual CASCADE;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 创建 member_retention_clue 表
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS member_retention_clue (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
member_id BIGINT NOT NULL,
|
||||
category VARCHAR(20) NOT NULL
|
||||
CONSTRAINT chk_retention_clue_category CHECK (
|
||||
category IN ('客户基础信息', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
|
||||
),
|
||||
summary VARCHAR(200) NOT NULL,
|
||||
detail TEXT,
|
||||
recorded_by_assistant_id BIGINT,
|
||||
recorded_by_name VARCHAR(50),
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
site_id BIGINT NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE member_retention_clue
|
||||
IS '维客线索:助教为会员记录的销售/维护线索(大类 + 摘要 + 详情)';
|
||||
COMMENT ON COLUMN member_retention_clue.category
|
||||
IS '线索大类枚举:客户基础信息、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈';
|
||||
COMMENT ON COLUMN member_retention_clue.summary
|
||||
IS '摘要:重点信息';
|
||||
COMMENT ON COLUMN member_retention_clue.detail
|
||||
IS '详情:分析及扩展说明,可为空';
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 创建索引
|
||||
-- ============================================================
|
||||
CREATE INDEX IF NOT EXISTS idx_retention_clue_member
|
||||
ON member_retention_clue (member_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_retention_clue_site
|
||||
ON member_retention_clue (site_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_retention_clue_category
|
||||
ON member_retention_clue (member_id, category);
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 回滚(需手动执行,不在事务内)
|
||||
-- ============================================================
|
||||
-- BEGIN;
|
||||
-- DROP TABLE IF EXISTS member_retention_clue CASCADE;
|
||||
-- -- 如需恢复旧表,执行 2026-02-22__C2_member_birthday_manual.sql
|
||||
-- COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 验证 SQL
|
||||
-- ============================================================
|
||||
-- 1. 确认旧表已删除
|
||||
-- SELECT table_name FROM information_schema.tables
|
||||
-- WHERE table_schema = 'public' AND table_name = 'member_birthday_manual';
|
||||
-- 预期:0 行
|
||||
|
||||
-- 2. 确认新表存在
|
||||
-- SELECT table_name FROM information_schema.tables
|
||||
-- WHERE table_schema = 'public' AND table_name = 'member_retention_clue';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 3. 确认列结构完整(9 列)
|
||||
-- SELECT column_name, data_type, is_nullable
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'public' AND table_name = 'member_retention_clue'
|
||||
-- ORDER BY ordinal_position;
|
||||
-- 预期:id, member_id, category, summary, detail,
|
||||
-- recorded_by_assistant_id, recorded_by_name, recorded_at, site_id
|
||||
|
||||
-- 4. 确认 CHECK 约束存在
|
||||
-- SELECT conname, consrc FROM pg_constraint
|
||||
-- WHERE conrelid = 'member_retention_clue'::regclass AND contype = 'c';
|
||||
-- 预期:1 行,chk_retention_clue_category
|
||||
|
||||
-- 5. 确认索引存在
|
||||
-- SELECT indexname FROM pg_indexes
|
||||
-- WHERE tablename = 'member_retention_clue';
|
||||
-- 预期:3 行(idx_retention_clue_member, idx_retention_clue_site, idx_retention_clue_category)
|
||||
-- + 1 行主键索引
|
||||
@@ -0,0 +1,21 @@
|
||||
-- 迁移:为 auth.users.status 的 CHECK 约束添加 'new' 值
|
||||
-- 原因:新用户注册后初始状态为 'new'(尚未提交申请),提交申请后变为 'pending'
|
||||
-- 影响:后端 wx_login 新用户创建、dev_login mock 登录
|
||||
-- 回滚:见文件末尾
|
||||
|
||||
-- 1. 删除旧约束
|
||||
ALTER TABLE auth.users DROP CONSTRAINT IF EXISTS users_status_check;
|
||||
|
||||
-- 2. 添加包含 'new' 的新约束
|
||||
ALTER TABLE auth.users ADD CONSTRAINT users_status_check
|
||||
CHECK (status IN ('new', 'pending', 'approved', 'rejected', 'disabled'));
|
||||
|
||||
-- 3. 修改默认值:新用户初始状态为 'new'(尚未提交申请)
|
||||
ALTER TABLE auth.users ALTER COLUMN status SET DEFAULT 'new';
|
||||
|
||||
-- ── 回滚 SQL ──
|
||||
-- ALTER TABLE auth.users ALTER COLUMN status SET DEFAULT 'pending';
|
||||
-- ALTER TABLE auth.users DROP CONSTRAINT IF EXISTS users_status_check;
|
||||
-- ALTER TABLE auth.users ADD CONSTRAINT users_status_check
|
||||
-- CHECK (status IN ('pending', 'approved', 'rejected', 'disabled'));
|
||||
-- UPDATE auth.users SET status = 'pending' WHERE status = 'new';
|
||||
@@ -0,0 +1,52 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:member_retention_clue 新增 source 字段
|
||||
-- 日期:2026-02-27
|
||||
-- 目标库:zqyy_app / test_zqyy_app
|
||||
-- 说明:
|
||||
-- 区分维客线索来源:manual(助教手动)/ ai_consumption(应用3)/ ai_note(应用6)
|
||||
-- 影响:
|
||||
-- - 后端 API:POST /api/retention-clue 已引用 source 列,本次补齐 DDL
|
||||
-- - 已有数据自动填充 DEFAULT 'manual'
|
||||
-- 幂等:IF NOT EXISTS 检查列是否存在,可重复执行
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 仅在列不存在时添加(PostgreSQL 11+ 支持 IF NOT EXISTS)
|
||||
ALTER TABLE member_retention_clue
|
||||
ADD COLUMN IF NOT EXISTS source VARCHAR(20) NOT NULL DEFAULT 'manual';
|
||||
|
||||
COMMENT ON COLUMN member_retention_clue.source
|
||||
IS '线索来源:manual(助教手动)/ ai_consumption(应用3 消费分析)/ ai_note(应用6 备注分析)';
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 回滚(需手动执行)
|
||||
-- ============================================================
|
||||
-- BEGIN;
|
||||
-- ALTER TABLE member_retention_clue DROP COLUMN IF EXISTS source;
|
||||
-- COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 验证 SQL
|
||||
-- ============================================================
|
||||
-- 1. 确认 source 列存在
|
||||
-- SELECT column_name, data_type, column_default, is_nullable
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'public'
|
||||
-- AND table_name = 'member_retention_clue'
|
||||
-- AND column_name = 'source';
|
||||
-- 预期:1 行,varchar, 'manual'::character varying, NO
|
||||
|
||||
-- 2. 确认已有数据的 source 值
|
||||
-- SELECT source, COUNT(*) FROM member_retention_clue GROUP BY source;
|
||||
-- 预期:全部为 'manual'(或空表)
|
||||
|
||||
-- 3. 确认列注释
|
||||
-- SELECT col_description(
|
||||
-- (SELECT oid FROM pg_class WHERE relname = 'member_retention_clue'),
|
||||
-- (SELECT ordinal_position FROM information_schema.columns
|
||||
-- WHERE table_name = 'member_retention_clue' AND column_name = 'source')
|
||||
-- );
|
||||
-- 预期:包含 'manual' / 'ai_consumption' / 'ai_note'
|
||||
107
db/zqyy_app/migrations/2026-02-27__p4_create_biz_tables.sql
Normal file
107
db/zqyy_app/migrations/2026-02-27__p4_create_biz_tables.sql
Normal file
@@ -0,0 +1,107 @@
|
||||
-- =============================================================================
|
||||
-- 迁移脚本:创建 biz Schema 业务表(coach_tasks / coach_task_history / notes / trigger_jobs)
|
||||
-- 日期:2026-02-27
|
||||
-- 目标库:test_zqyy_app(通过 APP_DB_DSN 连接)
|
||||
-- 说明:在 biz Schema 下创建助教任务系统和备注系统所需的 4 张业务表,
|
||||
-- 包含字段定义、约束、CHECK 约束、外键、部分唯一索引和查询索引。
|
||||
-- 前提:biz Schema 已由 2026-02-24__p1_create_auth_biz_schemas.sql 创建
|
||||
-- 需求:1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9
|
||||
-- =============================================================================
|
||||
|
||||
-- 确保 biz Schema 存在(幂等)
|
||||
CREATE SCHEMA IF NOT EXISTS biz;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 1. biz.coach_tasks — 助教任务表
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS biz.coach_tasks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
assistant_id BIGINT NOT NULL,
|
||||
member_id BIGINT NOT NULL,
|
||||
task_type VARCHAR(50) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
||||
priority_score NUMERIC(5,2),
|
||||
expires_at TIMESTAMPTZ,
|
||||
is_pinned BOOLEAN DEFAULT FALSE,
|
||||
abandon_reason TEXT,
|
||||
completed_at TIMESTAMPTZ,
|
||||
completed_task_type VARCHAR(50),
|
||||
parent_task_id BIGINT REFERENCES biz.coach_tasks(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 部分唯一索引:同一 (site_id, assistant_id, member_id, task_type) 下 active 任务最多一条
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_coach_tasks_site_assistant_member_type
|
||||
ON biz.coach_tasks (site_id, assistant_id, member_id, task_type)
|
||||
WHERE status = 'active';
|
||||
|
||||
-- 助教任务列表查询索引
|
||||
CREATE INDEX IF NOT EXISTS idx_coach_tasks_assistant_status
|
||||
ON biz.coach_tasks (site_id, assistant_id, status);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 2. biz.coach_task_history — 任务变更历史表
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS biz.coach_task_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
task_id BIGINT NOT NULL REFERENCES biz.coach_tasks(id),
|
||||
action VARCHAR(50) NOT NULL,
|
||||
old_status VARCHAR(20),
|
||||
new_status VARCHAR(20),
|
||||
old_task_type VARCHAR(50),
|
||||
new_task_type VARCHAR(50),
|
||||
detail JSONB,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 3. biz.notes — 统一备注表
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS biz.notes (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
target_type VARCHAR(50) NOT NULL,
|
||||
target_id BIGINT NOT NULL,
|
||||
type VARCHAR(20) NOT NULL DEFAULT 'normal',
|
||||
content TEXT NOT NULL,
|
||||
rating_service_willingness SMALLINT CHECK (rating_service_willingness BETWEEN 1 AND 5),
|
||||
rating_revisit_likelihood SMALLINT CHECK (rating_revisit_likelihood BETWEEN 1 AND 5),
|
||||
task_id BIGINT REFERENCES biz.coach_tasks(id),
|
||||
ai_score SMALLINT,
|
||||
ai_analysis TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 按目标查询备注索引
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_target
|
||||
ON biz.notes (site_id, target_type, target_id);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 4. biz.trigger_jobs — 触发器配置表
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS biz.trigger_jobs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
job_type VARCHAR(100) NOT NULL,
|
||||
job_name VARCHAR(100) NOT NULL UNIQUE,
|
||||
trigger_condition VARCHAR(20) NOT NULL,
|
||||
trigger_config JSONB NOT NULL,
|
||||
last_run_at TIMESTAMPTZ,
|
||||
next_run_at TIMESTAMPTZ,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'enabled',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- 回滚脚本(按逆序执行)
|
||||
-- =============================================================================
|
||||
-- DROP INDEX IF EXISTS biz.idx_notes_target;
|
||||
-- DROP INDEX IF EXISTS biz.idx_coach_tasks_assistant_status;
|
||||
-- DROP INDEX IF EXISTS biz.idx_coach_tasks_site_assistant_member_type;
|
||||
-- DROP TABLE IF EXISTS biz.trigger_jobs;
|
||||
-- DROP TABLE IF EXISTS biz.notes;
|
||||
-- DROP TABLE IF EXISTS biz.coach_task_history;
|
||||
-- DROP TABLE IF EXISTS biz.coach_tasks;
|
||||
43
db/zqyy_app/migrations/2026-02-27__p4_seed_trigger_jobs.sql
Normal file
43
db/zqyy_app/migrations/2026-02-27__p4_seed_trigger_jobs.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
-- =============================================================================
|
||||
-- 迁移脚本:预置触发器配置种子数据
|
||||
-- 日期:2026-02-27
|
||||
-- 目标库:test_zqyy_app(通过 APP_DB_DSN 连接)
|
||||
-- 说明:在 biz.trigger_jobs 表中插入 4 条核心触发器配置,
|
||||
-- 驱动任务生成、有效期检查、召回完成检测、备注回溯重分类。
|
||||
-- 前提:biz.trigger_jobs 表已由 2026-02-27__p4_create_biz_tables.sql 创建
|
||||
-- 需求:2.1, 2.2, 2.3, 2.4, 2.5
|
||||
-- =============================================================================
|
||||
|
||||
INSERT INTO biz.trigger_jobs (job_type, job_name, trigger_condition, trigger_config, next_run_at)
|
||||
VALUES
|
||||
-- 1. task_generator:每日凌晨 4:00 运行,基于 WBI/NCI/RS 指数为助教生成任务
|
||||
('task_generator', 'task_generator', 'cron',
|
||||
'{"cron_expression": "0 4 * * *"}',
|
||||
(CURRENT_DATE + 1) + INTERVAL '4 hours'),
|
||||
|
||||
-- 2. task_expiry_check:每小时运行,检查 expires_at 到期的任务并标记 inactive
|
||||
('task_expiry_check', 'task_expiry_check', 'interval',
|
||||
'{"interval_seconds": 3600}',
|
||||
NOW() + INTERVAL '1 hour'),
|
||||
|
||||
-- 3. recall_completion_check:ETL 数据更新后触发,检测助教服务记录匹配活跃任务
|
||||
('recall_completion_check', 'recall_completion_check', 'event',
|
||||
'{"event_name": "etl_data_updated"}',
|
||||
NULL),
|
||||
|
||||
-- 4. note_reclassify_backfill:召回完成后触发,回溯重分类普通备注为回访备注
|
||||
('note_reclassify_backfill', 'note_reclassify_backfill', 'event',
|
||||
'{"event_name": "recall_completed"}',
|
||||
NULL)
|
||||
ON CONFLICT (job_name) DO NOTHING;
|
||||
|
||||
-- =============================================================================
|
||||
-- 回滚脚本
|
||||
-- =============================================================================
|
||||
-- DELETE FROM biz.trigger_jobs
|
||||
-- WHERE job_name IN (
|
||||
-- 'task_generator',
|
||||
-- 'task_expiry_check',
|
||||
-- 'recall_completion_check',
|
||||
-- 'note_reclassify_backfill'
|
||||
-- );
|
||||
@@ -0,0 +1,50 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:auth.site_code_mapping.tenant_id INTEGER → BIGINT
|
||||
-- 原因:飞球 tenant_id 值域(如 2790683160709957)远超 int4 上限(~21 亿)
|
||||
-- 日期:2026-03-03
|
||||
-- 目标库:zqyy_app / test_zqyy_app
|
||||
-- 注意:该列无外键、无索引依赖,可直接 ALTER
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. ALTER 列类型
|
||||
ALTER TABLE auth.site_code_mapping
|
||||
ALTER COLUMN tenant_id TYPE bigint;
|
||||
|
||||
-- 2. 刷新 FDW 外部表(ETL 库 tenant_id 变更后需重新导入)
|
||||
-- 注意:此步骤需在 ETL 库迁移完成后执行
|
||||
-- 如果 FDW 外部表类型不匹配,需要重新 IMPORT FOREIGN SCHEMA
|
||||
-- DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_assistant_order_contribution;
|
||||
-- IMPORT FOREIGN SCHEMA app
|
||||
-- LIMIT TO (v_dws_assistant_order_contribution)
|
||||
-- FROM SERVER test_etl_feiqiu_server
|
||||
-- INTO fdw_etl;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- =============================================================================
|
||||
-- 回滚脚本
|
||||
-- =============================================================================
|
||||
-- BEGIN;
|
||||
-- ALTER TABLE auth.site_code_mapping ALTER COLUMN tenant_id TYPE integer;
|
||||
-- COMMIT;
|
||||
|
||||
-- =============================================================================
|
||||
-- 验证 SQL
|
||||
-- =============================================================================
|
||||
-- 1. 确认列类型已变更
|
||||
-- SELECT column_name, data_type, udt_name
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'auth'
|
||||
-- AND table_name = 'site_code_mapping'
|
||||
-- AND column_name = 'tenant_id';
|
||||
-- 预期:data_type = 'bigint', udt_name = 'int8'
|
||||
|
||||
-- 2. 确认现有数据完整
|
||||
-- SELECT count(*) FROM auth.site_code_mapping;
|
||||
-- 预期:行数与迁移前一致
|
||||
|
||||
-- 3. 确认可插入大值 tenant_id
|
||||
-- SELECT 2790683160709957::bigint;
|
||||
-- 预期:返回 2790683160709957(不报溢出错误)
|
||||
@@ -0,0 +1,11 @@
|
||||
-- 2026-03-07 | 多实例任务隔离:task_queue 增加 enqueued_by 列
|
||||
-- 背景:发现有另一台机器(宿主机 D 盘部署)的后端也在消费同一个 task_queue,
|
||||
-- 导致任务被错误实例执行(路径不匹配 → UTF-8 解码失败)。
|
||||
-- 通过 enqueued_by 列记录入队实例的 hostname,process_loop 只消费自己入队的任务。
|
||||
|
||||
ALTER TABLE task_queue ADD COLUMN IF NOT EXISTS enqueued_by VARCHAR(255) DEFAULT NULL;
|
||||
|
||||
COMMENT ON COLUMN task_queue.enqueued_by IS '入队实例的 hostname(platform.node()),用于多实例任务隔离';
|
||||
|
||||
-- 回滚:
|
||||
-- ALTER TABLE task_queue DROP COLUMN IF EXISTS enqueued_by;
|
||||
@@ -0,0 +1,82 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:member_retention_clue.category CHECK 约束枚举对齐
|
||||
-- 日期:2026-03-08
|
||||
-- 目标库:zqyy_app / test_zqyy_app
|
||||
-- 说明:
|
||||
-- 将 category CHECK 约束中 '客户基础信息' 改为 '客户基础',
|
||||
-- 与 AI 应用 Prompt 枚举值统一(P5 spec 2026-03-08 评审决定)。
|
||||
-- '促销偏好' 保持不变(原 Prompt 中的 '促销接受' 已在 spec 侧对齐为 '促销偏好')。
|
||||
-- 统一后 6 个枚举值:客户基础、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈
|
||||
-- 影响:
|
||||
-- - 已有数据中 category='客户基础信息' 的行需更新为 '客户基础'
|
||||
-- - 后端 API:POST /api/retention-clue 的 category 校验需同步
|
||||
-- - AI 应用 3/6/8 的 Prompt 枚举已在 P5 spec 中统一
|
||||
-- 幂等:先检查约束是否存在再操作,可重复执行
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 更新已有数据(先于约束变更)
|
||||
-- ============================================================
|
||||
UPDATE member_retention_clue
|
||||
SET category = '客户基础'
|
||||
WHERE category = '客户基础信息';
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 删除旧 CHECK 约束
|
||||
-- ============================================================
|
||||
ALTER TABLE member_retention_clue
|
||||
DROP CONSTRAINT IF EXISTS chk_retention_clue_category;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 创建新 CHECK 约束('客户基础信息' → '客户基础')
|
||||
-- ============================================================
|
||||
ALTER TABLE member_retention_clue
|
||||
ADD CONSTRAINT chk_retention_clue_category CHECK (
|
||||
category IN ('客户基础', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 4. 更新列注释
|
||||
-- ============================================================
|
||||
COMMENT ON COLUMN member_retention_clue.category
|
||||
IS '线索大类枚举:客户基础、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈';
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 回滚(需手动执行)
|
||||
-- ============================================================
|
||||
-- BEGIN;
|
||||
-- UPDATE member_retention_clue SET category = '客户基础信息' WHERE category = '客户基础';
|
||||
-- ALTER TABLE member_retention_clue DROP CONSTRAINT IF EXISTS chk_retention_clue_category;
|
||||
-- ALTER TABLE member_retention_clue ADD CONSTRAINT chk_retention_clue_category CHECK (
|
||||
-- category IN ('客户基础信息', '消费习惯', '玩法偏好', '促销偏好', '社交关系', '重要反馈')
|
||||
-- );
|
||||
-- COMMENT ON COLUMN member_retention_clue.category
|
||||
-- IS '线索大类枚举:客户基础信息、消费习惯、玩法偏好、促销偏好、社交关系、重要反馈';
|
||||
-- COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- 验证 SQL
|
||||
-- ============================================================
|
||||
-- 1. 确认无 '客户基础信息' 残留
|
||||
-- SELECT COUNT(*) FROM member_retention_clue WHERE category = '客户基础信息';
|
||||
-- 预期:0
|
||||
|
||||
-- 2. 确认 CHECK 约束内容
|
||||
-- SELECT conname, pg_get_constraintdef(oid) FROM pg_constraint
|
||||
-- WHERE conrelid = 'member_retention_clue'::regclass AND contype = 'c';
|
||||
-- 预期:chk_retention_clue_category,包含 '客户基础'(非 '客户基础信息')
|
||||
|
||||
-- 3. 测试新枚举值可写入
|
||||
-- INSERT INTO member_retention_clue (member_id, category, summary, site_id)
|
||||
-- VALUES (0, '客户基础', '测试', 0);
|
||||
-- 预期:成功
|
||||
-- DELETE FROM member_retention_clue WHERE member_id = 0 AND summary = '测试';
|
||||
|
||||
-- 4. 测试旧枚举值被拒绝
|
||||
-- INSERT INTO member_retention_clue (member_id, category, summary, site_id)
|
||||
-- VALUES (0, '客户基础信息', '测试', 0);
|
||||
-- 预期:CHECK 约束违反错误
|
||||
65
db/zqyy_app/migrations/2026-03-08__create_ai_tables.sql
Normal file
65
db/zqyy_app/migrations/2026-03-08__create_ai_tables.sql
Normal file
@@ -0,0 +1,65 @@
|
||||
-- ============================================================
|
||||
-- P5 AI 集成层:创建 AI 对话、消息、缓存三张表
|
||||
-- 需求: 1.1, 1.2, 1.3, 1.4, 1.5
|
||||
-- ============================================================
|
||||
|
||||
-- 1. biz.ai_conversations —— AI 对话记录
|
||||
CREATE TABLE IF NOT EXISTS biz.ai_conversations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id VARCHAR(50) NOT NULL,
|
||||
nickname VARCHAR(100) NOT NULL DEFAULT '',
|
||||
app_id VARCHAR(30) NOT NULL,
|
||||
site_id BIGINT NOT NULL,
|
||||
source_page VARCHAR(100),
|
||||
source_context JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE biz.ai_conversations IS 'AI 对话记录:每次 AI 调用(用户主动或系统自动)创建一条';
|
||||
COMMENT ON COLUMN biz.ai_conversations.app_id IS '应用标识:app1_chat / app2_finance / app3_clue / app4_analysis / app5_tactics / app6_note / app7_customer / app8_consolidation';
|
||||
COMMENT ON COLUMN biz.ai_conversations.user_id IS '用户 ID,系统自动调用时为 system';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_conv_user_site ON biz.ai_conversations (user_id, site_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_conv_app_site ON biz.ai_conversations (app_id, site_id, created_at DESC);
|
||||
|
||||
-- 2. biz.ai_messages —— AI 消息记录
|
||||
CREATE TABLE IF NOT EXISTS biz.ai_messages (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
conversation_id BIGINT NOT NULL REFERENCES biz.ai_conversations(id) ON DELETE CASCADE,
|
||||
role VARCHAR(10) NOT NULL
|
||||
CONSTRAINT chk_ai_msg_role CHECK (role IN ('user', 'assistant', 'system')),
|
||||
content TEXT NOT NULL,
|
||||
tokens_used INTEGER,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE biz.ai_messages IS 'AI 消息记录:对话中的每条消息(输入/输出/系统)';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_msg_conv ON biz.ai_messages (conversation_id, created_at);
|
||||
|
||||
-- 3. biz.ai_cache —— AI 应用缓存
|
||||
CREATE TABLE IF NOT EXISTS biz.ai_cache (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
cache_type VARCHAR(30) NOT NULL
|
||||
CONSTRAINT chk_ai_cache_type CHECK (
|
||||
cache_type IN (
|
||||
'app2_finance', 'app3_clue', 'app4_analysis',
|
||||
'app5_tactics', 'app6_note_analysis',
|
||||
'app7_customer_analysis', 'app8_clue_consolidated'
|
||||
)
|
||||
),
|
||||
site_id BIGINT NOT NULL,
|
||||
target_id VARCHAR(100) NOT NULL,
|
||||
result_json JSONB NOT NULL,
|
||||
score INTEGER,
|
||||
triggered_by VARCHAR(100),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
COMMENT ON TABLE biz.ai_cache IS 'AI 应用缓存:各应用的结构化输出结果';
|
||||
COMMENT ON COLUMN biz.ai_cache.target_id IS '目标 ID:App2=时间维度编码 / App3,6,7,8=member_id / App4,5={assistant_id}_{member_id}';
|
||||
COMMENT ON COLUMN biz.ai_cache.score IS '评分:仅应用 6 使用(1-10 分)';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_cache_lookup ON biz.ai_cache (cache_type, site_id, target_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_cache_cleanup ON biz.ai_cache (cache_type, site_id, target_id, created_at);
|
||||
@@ -0,0 +1,13 @@
|
||||
-- 给 task_execution_log 添加 schedule_id 字段,关联调度任务
|
||||
-- 用于查询某个调度任务的执行历史
|
||||
ALTER TABLE task_execution_log
|
||||
ADD COLUMN IF NOT EXISTS schedule_id UUID REFERENCES scheduled_tasks(id) ON DELETE SET NULL;
|
||||
|
||||
-- 按 schedule_id 查询执行历史的部分索引
|
||||
CREATE INDEX IF NOT EXISTS idx_execution_log_schedule_id
|
||||
ON task_execution_log(schedule_id)
|
||||
WHERE schedule_id IS NOT NULL;
|
||||
|
||||
-- 给 task_queue 添加 schedule_id 字段,追溯调度来源
|
||||
ALTER TABLE task_queue
|
||||
ADD COLUMN IF NOT EXISTS schedule_id UUID REFERENCES scheduled_tasks(id) ON DELETE SET NULL;
|
||||
@@ -0,0 +1,29 @@
|
||||
-- 触发器:INSERT task_execution_log 时自动从 task_queue 回填 schedule_id
|
||||
-- 背景:队列处理循环中 schedule_id 可能未通过代码传递,用触发器兜底
|
||||
|
||||
CREATE OR REPLACE FUNCTION fn_backfill_schedule_id()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.schedule_id IS NULL AND NEW.queue_id IS NOT NULL THEN
|
||||
SELECT schedule_id INTO NEW.schedule_id
|
||||
FROM task_queue
|
||||
WHERE id = NEW.queue_id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_backfill_schedule_id ON task_execution_log;
|
||||
|
||||
CREATE TRIGGER trg_backfill_schedule_id
|
||||
BEFORE INSERT ON task_execution_log
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION fn_backfill_schedule_id();
|
||||
|
||||
-- 回填历史数据
|
||||
UPDATE task_execution_log el
|
||||
SET schedule_id = tq.schedule_id
|
||||
FROM task_queue tq
|
||||
WHERE el.queue_id = tq.id
|
||||
AND tq.schedule_id IS NOT NULL
|
||||
AND el.schedule_id IS NULL;
|
||||
Reference in New Issue
Block a user