# BD_Manual:biz Schema AI 表(对话记录 + 消息 + 缓存 + 运行日志 + 调度记录) > 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接) > 迁移脚本: > - `db/zqyy_app/migrations/2026-03-08__create_ai_tables.sql`(初始建表) > - `db/zqyy_app/migrations/2026-03-20__rns14_chat_module_extend.sql`(RNS1.4 CHAT 扩展) > - `db/zqyy_app/migrations/2026-03-22__p14_ai_module.sql`(P14 DashScope 迁移 + 调度器完善) > - `db/zqyy_app/migrations/2026-03-23__p15_ai_monitoring.sql`(P15 AI 监控后台 — alert_status + BRIN 索引) > 关联 SPEC:`05-miniapp-ai-integration`(P5 AI 集成层)、`rns1-chat-integration`(RNS1.4 CHAT 对齐与联调收尾)、`P14-ai-dashscope-migration`(P14 DashScope 迁移)、`ai-monitoring-testing`(P15 AI 监控后台) --- ## 1. 变更说明 ### 新增表(5 张) | # | 表名 | 用途 | 字段数(初始→当前) | 来源 | |---|------|------|---------------------|------| | 1 | `biz.ai_conversations` | AI 对话记录:每次 AI 调用(用户主动或系统自动)创建一条 | 8 → 14 | P5 | | 2 | `biz.ai_messages` | AI 消息记录:对话中的每条消息(输入/输出/系统) | 6 → 7 | P5 | | 3 | `biz.ai_cache` | AI 应用缓存:各应用的结构化输出结果 | 9 → 10 | P5 | | 4 | `biz.ai_run_logs` | **P14 新增** — AI 运行记录:每次 DashScope API 调用的详细日志 | 14 → 15 | P14, P15 | | 5 | `biz.ai_trigger_jobs` | **P14 新增** — 调度运行记录:每次 AI 事件触发的编排执行记录 | 13 | P14 | ### RNS1.4 CHAT 模块扩展字段(2026-03-20) | # | 表名 | 新增字段 | 用途 | |---|------|---------|------| | 1 | `biz.ai_conversations` | `context_type`, `context_id`, `title`, `last_message`, `last_message_at` | 多入口对话复用 + 历史列表展示与排序 | | 2 | `biz.ai_messages` | `reference_card` | 引用卡片 JSON(客户概览等结构化上下文数据) | ### P14 DashScope 迁移扩展字段(2026-03-22) | # | 表名 | 新增字段 | 用途 | |---|------|---------|------| | 1 | `biz.ai_conversations` | `session_id` | 百炼 session_id(格式 `conv_{id}_{ts}`),仅 App1 使用 | | 2 | `biz.ai_cache` | `status` | 缓存状态:valid / expired / invalidated / generating | ### P15 AI 监控后台扩展(2026-03-23) | # | 表名 | 变更类型 | 说明 | |---|------|---------|------| | 1 | `biz.ai_run_logs` | 新增字段 `alert_status` | 告警处理状态:NULL / pending / acknowledged / ignored | | 2 | `biz.ai_run_logs` | 新增约束 `chk_ai_run_logs_alert_status` | CHECK (alert_status IS NULL OR alert_status IN ('pending', 'acknowledged', 'ignored')) | | 3 | `biz.ai_run_logs` | 新增部分索引 `idx_ai_run_logs_alert` | (alert_status, created_at DESC) WHERE status IN ('failed', 'timeout', 'circuit_open') — 告警列表查询 | | 4 | `biz.ai_run_logs` | 新增 BRIN 索引 `idx_ai_run_logs_created_brin` | BRIN (created_at) WITH (pages_per_range = 32) — Dashboard 聚合优化 | | 5 | `biz.ai_run_logs` | 回填 | 已有 status IN ('failed','timeout','circuit_open') 的记录 alert_status 设为 'pending' | ### 表字段明细 #### biz.ai_conversations(14 字段) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | `id` | BIGSERIAL | PK | 自增主键 | | `user_id` | VARCHAR(50) | NOT NULL | 用户 ID,系统自动调用时为 `system` | | `nickname` | VARCHAR(100) | NOT NULL DEFAULT '' | 用户昵称 | | `app_id` | VARCHAR(30) | NOT NULL | 应用标识:app1_chat / app2_finance / app3_clue / app4_analysis / app5_tactics / app6_note / app7_customer / app8_consolidation | | `site_id` | BIGINT | NOT NULL | 门店 ID(多门店隔离) | | `source_page` | VARCHAR(100) | 可空 | 来源页面标识 | | `source_context` | JSONB | 可空 | 页面上下文 JSON | | `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 | | `context_type` | VARCHAR(20) | 可空 | **RNS1.4 新增** — 对话关联上下文类型:task / customer / coach / general | | `context_id` | VARCHAR(50) | 可空 | **RNS1.4 新增** — 关联上下文 ID | | `title` | VARCHAR(200) | 可空 | **RNS1.4 新增** — 对话标题 | | `last_message` | TEXT | 可空 | **RNS1.4 新增** — 最后一条消息内容摘要(截断至100字) | | `last_message_at` | TIMESTAMPTZ | 可空 | **RNS1.4 新增** — 最后消息时间 | | `session_id` | VARCHAR(100) | 可空 | **P14 新增** — 百炼 session_id,格式 `conv_{conversation_id}_{created_timestamp}`,仅 App1 使用 | #### biz.ai_messages(7 字段) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | `id` | BIGSERIAL | PK | 自增主键 | | `conversation_id` | BIGINT | NOT NULL, FK → `biz.ai_conversations(id)` ON DELETE CASCADE | 关联对话 | | `role` | VARCHAR(10) | NOT NULL, CHECK IN ('user', 'assistant', 'system') | 消息角色 | | `content` | TEXT | NOT NULL | 消息内容 | | `tokens_used` | INTEGER | 可空 | 本条消息消耗的 token 数 | | `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 | | `reference_card` | JSONB | 可空 | **RNS1.4 新增** — 引用卡片 JSON:`{type, title, summary, data}`,用于展示客户概览等结构化上下文数据 | #### biz.ai_cache(10 字段) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | `id` | BIGSERIAL | PK | 自增主键 | | `cache_type` | VARCHAR(30) | NOT NULL, CHECK 7 个枚举值 | 缓存类型(见下方枚举) | | `site_id` | BIGINT | NOT NULL | 门店 ID(多门店隔离) | | `target_id` | VARCHAR(100) | NOT NULL | 目标 ID,含义因 cache_type 而异 | | `result_json` | JSONB | NOT NULL | 结构化输出结果 | | `score` | INTEGER | 可空 | 评分:仅应用 6 使用(1-10 分) | | `triggered_by` | VARCHAR(100) | 可空 | 触发来源标识 | | `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 | | `expires_at` | TIMESTAMPTZ | 可空 | 可选过期时间 | | `status` | VARCHAR(20) | DEFAULT 'valid', CHECK | **P14 新增** — 缓存状态:valid(有效)/ expired(已过期)/ invalidated(手动失效)/ generating(生成中) | ### cache_type 枚举值与 target_id 约定 | cache_type | target_id 格式 | 示例 | |---|---|---| | `app2_finance` | 时间维度编码 | `this_month`, `last_week` | | `app3_clue` | member_id | `12345` | | `app4_analysis` | `{assistant_id}_{member_id}` | `100_12345` | | `app5_tactics` | `{assistant_id}_{member_id}` | `100_12345` | | `app6_note_analysis` | member_id | `12345` | | `app7_customer_analysis` | member_id | `12345` | | `app8_clue_consolidated` | member_id | `12345` | ### 约束与索引 | 表 | 约束/索引名 | 类型 | 说明 | |----|-----------|------|------| | `ai_conversations` | `idx_ai_conv_user_site` | INDEX | `(user_id, site_id, created_at DESC)` — 用户历史对话列表查询 | | `ai_conversations` | `idx_ai_conv_app_site` | INDEX | `(app_id, site_id, created_at DESC)` — 按应用查询对话 | | `ai_conversations` | `idx_ai_conv_context` | INDEX(条件) | **RNS1.4** — `(user_id, site_id, context_type, context_id, last_message_at DESC) WHERE context_type IS NOT NULL` | | `ai_conversations` | `idx_ai_conv_last_msg` | INDEX | **RNS1.4** — `(user_id, site_id, last_message_at DESC NULLS LAST)` | | `ai_messages` | FK `conversation_id` | FK | → `biz.ai_conversations(id)` ON DELETE CASCADE | | `ai_messages` | `chk_ai_msg_role` | CHECK | `role IN ('user', 'assistant', 'system')` | | `ai_messages` | `idx_ai_msg_conv` | INDEX | `(conversation_id, created_at)` — 对话消息列表 | | `ai_cache` | `chk_ai_cache_type` | CHECK | 7 个枚举值 | | `ai_cache` | `chk_ai_cache_status` | CHECK | **P14** — `status IN ('valid', 'expired', 'invalidated', 'generating')` | | `ai_cache` | `idx_ai_cache_lookup` | INDEX | `(cache_type, site_id, target_id, created_at DESC)` — 查询最新缓存 | | `ai_cache` | `idx_ai_cache_cleanup` | INDEX | `(cache_type, site_id, target_id, created_at)` — 清理超限记录 | | `ai_run_logs` | `idx_ai_run_logs_site_app` | INDEX | **P14** — `(site_id, app_type)` | | `ai_run_logs` | `idx_ai_run_logs_created` | INDEX | **P14** — `(created_at)` — Token 预算聚合 | | `ai_run_logs` | `idx_ai_run_logs_status` | INDEX | **P14** — `(status)` | | `ai_run_logs` | `chk_ai_run_logs_alert_status` | CHECK | **P15** — `alert_status IS NULL OR alert_status IN ('pending', 'acknowledged', 'ignored')` | | `ai_run_logs` | `idx_ai_run_logs_alert` | INDEX(部分) | **P15** — `(alert_status, created_at DESC) WHERE status IN ('failed', 'timeout', 'circuit_open')` — 告警列表查询优化 | | `ai_run_logs` | `idx_ai_run_logs_created_brin` | BRIN INDEX | **P15** — `BRIN (created_at) WITH (pages_per_range = 32)` — Dashboard 聚合查询优化,适合按时间顺序插入的表 | | `ai_trigger_jobs` | `idx_ai_trigger_jobs_site` | INDEX | **P14** — `(site_id, event_type)` | | `ai_trigger_jobs` | `idx_ai_trigger_jobs_dedup` | INDEX(条件) | **P14** — `(event_type, member_id, site_id, created_at) WHERE status NOT IN ('skipped_duplicate')` — 去重 | | `ai_trigger_jobs` | `idx_ai_trigger_jobs_status` | INDEX | **P14** — `(status)` | #### biz.ai_run_logs(15 字段,P14 新增 + P15 扩展) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | `id` | BIGSERIAL | PK | 自增主键 | | `site_id` | BIGINT | NOT NULL | 门店 ID | | `app_type` | VARCHAR(30) | NOT NULL | 应用类型:app1_chat / app2_finance / ... / app8_consolidate | | `trigger_type` | VARCHAR(20) | NOT NULL | 触发类型:user / scheduled / event / forced | | `member_id` | BIGINT | 可空 | 会员 ID | | `request_prompt` | TEXT | 可空 | 请求 prompt(截断前 2000 字符) | | `response_text` | TEXT | 可空 | 响应文本 | | `tokens_used` | INTEGER | DEFAULT 0 | 消耗 token 数 | | `latency_ms` | INTEGER | 可空 | 调用延迟(毫秒) | | `status` | VARCHAR(20) | NOT NULL DEFAULT 'pending' | 状态:pending / running / success / failed / timeout / budget_exceeded | | `error_message` | TEXT | 可空 | 错误信息 | | `session_id` | VARCHAR(100) | 可空 | 百炼 session_id(仅 App1) | | `alert_status` | VARCHAR(20) | DEFAULT NULL, CHECK | **P15 新增** — 告警处理状态:NULL(非告警记录)/ pending(待处理)/ acknowledged(已确认)/ ignored(已忽略)。仅 status IN ('failed','timeout','circuit_open') 的记录才设置此字段 | | `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT now() | 创建时间 | | `finished_at` | TIMESTAMPTZ | 可空 | 完成时间 | #### biz.ai_trigger_jobs(13 字段,P14 新增) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | `id` | BIGSERIAL | PK | 自增主键 | | `site_id` | BIGINT | NOT NULL | 门店 ID | | `event_type` | VARCHAR(30) | NOT NULL | 事件类型:consumption / dws_completed / note_created / task_assigned | | `connector_type` | VARCHAR(30) | DEFAULT 'feiqiu' | 连接器类型 | | `member_id` | BIGINT | 可空 | 会员 ID | | `payload` | JSONB | 可空 | 附加数据 | | `status` | VARCHAR(20) | NOT NULL DEFAULT 'pending' | 状态:pending / running / completed / failed / skipped_duplicate / budget_exceeded | | `is_forced` | BOOLEAN | DEFAULT false | 是否强制执行(跳过去重检查) | | `app_chain` | VARCHAR(100) | 可空 | 调用链描述,如 `app3→app8→app7` | | `started_at` | TIMESTAMPTZ | 可空 | 开始时间 | | `finished_at` | TIMESTAMPTZ | 可空 | 完成时间 | | `error_message` | TEXT | 可空 | 错误信息 | | `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT now() | 创建时间 | --- ## 2. 兼容性影响 | 组件 | 影响 | |------|------| | ETL 任务 | **P14 新增**:DWS 任务完成后通过 `utils/ai_trigger.py` 发送 HTTP 触发事件到后端 `ai_trigger_jobs`,失败不中断 ETL 流程 | | 后端 API | 直接依赖。P5 AI 模块(`apps/backend/app/ai/`)基于这些表实现对话持久化、缓存读写、SSE 流式对话等功能 | | 后端 API(RNS1.4) | **直接依赖**。CHAT 模块依赖 `ai_conversations` 的 5 个 RNS1.4 字段实现多入口对话复用 | | 后端 API(P14) | **直接依赖**。`ai_run_logs` 用于 Token 预算聚合(BudgetTracker);`ai_trigger_jobs` 用于事件去重和调度记录;`ai_conversations.session_id` 用于百炼会话管理;`ai_cache.status` 用于缓存状态控制 | | 小程序 | 间接依赖。小程序通过后端 AI API 间接使用对话和缓存数据 | | 管理后台 | **P15 直接依赖**。admin-web AI 监控后台(4 个页面)依赖 `ai_run_logs.alert_status` 实现告警管理(确认/忽略),依赖 BRIN 索引优化 Dashboard 聚合查询性能 | | `member_retention_clue` | 间接关联。App8 结果同时写入 `ai_cache` 和 `member_retention_clue` 表(P14 实现幂等 DELETE+INSERT) | --- ## 3. 回滚策略 ### 3d. 回滚 P15 AI 监控后台(2026-03-23 迁移) 按逆序 DROP 新增索引、约束和字段: ```sql BEGIN; DROP INDEX IF EXISTS biz.idx_ai_run_logs_created_brin; DROP INDEX IF EXISTS biz.idx_ai_run_logs_alert; ALTER TABLE biz.ai_run_logs DROP CONSTRAINT IF EXISTS chk_ai_run_logs_alert_status; ALTER TABLE biz.ai_run_logs DROP COLUMN IF EXISTS alert_status; COMMIT; ``` 注意: - 回滚后 admin-web AI 监控后台的告警管理功能将不可用 - Dashboard 聚合查询性能可能下降(失去 BRIN 索引) - 回滚不影响 P14 的核心功能(调度器、预算追踪等) ### 3c. 回滚 P14 DashScope 迁移(2026-03-22 迁移) 按逆序 DROP 新增表和字段: ```sql -- 删除 P14 新增约束和字段 ALTER TABLE biz.ai_cache DROP CONSTRAINT IF EXISTS chk_ai_cache_status; ALTER TABLE biz.ai_cache DROP COLUMN IF EXISTS status; ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS session_id; -- 删除 P14 新增表 DROP TABLE IF EXISTS biz.ai_trigger_jobs; DROP TABLE IF EXISTS biz.ai_run_logs; ``` 注意: - 回滚后 P14 AI 调度器(dispatcher)、Token 预算追踪(BudgetTracker)将无法正常工作 - `ai_run_logs` 和 `ai_trigger_jobs` 中的数据将丢失,需先备份 - 回滚不影响 P5 和 RNS1.4 的功能 ### 3a. 回滚 RNS1.4 CHAT 扩展(2026-03-20 迁移) 按逆序 DROP 新增索引和字段: ```sql -- 删除 RNS1.4 新增索引 DROP INDEX IF EXISTS biz.idx_ai_conv_context; DROP INDEX IF EXISTS biz.idx_ai_conv_last_msg; -- 回退 ai_messages 新增字段 ALTER TABLE biz.ai_messages DROP COLUMN IF EXISTS reference_card; -- 回退 ai_conversations 新增字段(逆序) ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS last_message_at; ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS last_message; ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS title; ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS context_id; ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS context_type; ``` 注意: - 回滚后 CHAT 模块(xcx_chat 路由、chat_service)将无法正常工作 - 如果新字段中已有数据,需先备份再执行回滚 - 回滚不影响 P5 初始建表的 8 个原始字段 ### 3b. 回滚 P5 初始建表(2026-03-08 迁移) 按逆序 DROP(CASCADE 处理外键依赖): ```sql -- 删除索引 DROP INDEX IF EXISTS biz.idx_ai_cache_cleanup; DROP INDEX IF EXISTS biz.idx_ai_cache_lookup; DROP INDEX IF EXISTS biz.idx_ai_msg_conv; DROP INDEX IF EXISTS biz.idx_ai_conv_app_site; DROP INDEX IF EXISTS biz.idx_ai_conv_user_site; -- 删除表(ai_messages 依赖 ai_conversations,需先删或用 CASCADE) DROP TABLE IF EXISTS biz.ai_cache CASCADE; DROP TABLE IF EXISTS biz.ai_messages CASCADE; DROP TABLE IF EXISTS biz.ai_conversations CASCADE; ``` 注意: - `ai_messages` 通过 FK 依赖 `ai_conversations`,CASCADE 会级联删除消息 - 如果表中已有数据,需先备份再执行回滚 - 回滚不会删除 `biz` Schema 本身 --- ## 4. 验证 SQL ### 4a. P5 初始建表验证 ```sql -- 1. 验证 3 张 AI 表全部存在 SELECT table_name FROM information_schema.tables WHERE table_schema = 'biz' AND table_name IN ('ai_conversations', 'ai_messages', 'ai_cache') ORDER BY table_name; -- 预期:返回 3 行(ai_cache, ai_conversations, ai_messages) -- 2. 验证 ai_conversations 字段数量和关键字段 SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_conversations' ORDER BY ordinal_position; -- 预期:返回 14 行(P5 原始 8 + RNS1.4 新增 5 + P14 session_id = 14) -- 3. 验证 ai_messages 的外键和 CHECK 约束 SELECT conname, contype, pg_get_constraintdef(oid) AS constraint_def FROM pg_constraint WHERE conrelid = 'biz.ai_messages'::regclass ORDER BY conname; -- 预期:包含 chk_ai_msg_role(CHECK role IN ...)和 ai_messages_conversation_id_fkey(FK → ai_conversations) -- 4. 验证 ai_cache 的 CHECK 约束(7 个枚举值) SELECT conname, pg_get_constraintdef(oid) AS constraint_def FROM pg_constraint WHERE conrelid = 'biz.ai_cache'::regclass AND contype = 'c'; -- 预期:返回 2 行(chk_ai_cache_type 含 7 个枚举值,chk_ai_cache_status 含 4 个状态值) -- 5. 验证 P5 初始索引全部存在(5 个) SELECT indexname FROM pg_indexes WHERE schemaname = 'biz' AND indexname IN ( 'idx_ai_conv_user_site', 'idx_ai_conv_app_site', 'idx_ai_msg_conv', 'idx_ai_cache_lookup', 'idx_ai_cache_cleanup' ) ORDER BY indexname; -- 预期:返回 5 行 ``` ### 4b. RNS1.4 CHAT 扩展验证 ```sql -- 6. 验证 ai_conversations 新增 5 个字段存在 SELECT column_name, data_type, character_maximum_length FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_conversations' AND column_name IN ('context_type', 'context_id', 'title', 'last_message', 'last_message_at'); -- 预期:返回 5 行 -- context_type | character varying | 20 -- context_id | character varying | 50 -- title | character varying | 200 -- last_message | text | NULL -- last_message_at | timestamp with time zone | NULL -- 7. 验证 ai_messages 新增 reference_card 字段存在 SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_messages' AND column_name = 'reference_card'; -- 预期:返回 1 行,data_type = 'jsonb' -- 8. 验证 RNS1.4 新增 2 个索引存在 SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'biz' AND tablename = 'ai_conversations' AND indexname IN ('idx_ai_conv_context', 'idx_ai_conv_last_msg'); -- 预期:返回 2 行 -- idx_ai_conv_context — 含 WHERE context_type IS NOT NULL 条件 -- idx_ai_conv_last_msg — (user_id, site_id, last_message_at DESC NULLS LAST) ``` ### 4c. P14 DashScope 迁移验证 ```sql -- 9. 验证 ai_run_logs 表存在且字段正确 SELECT column_name, data_type, character_maximum_length FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_run_logs' ORDER BY ordinal_position; -- 预期:返回 15 行 -- 10. 验证 ai_trigger_jobs 表存在且字段正确 SELECT column_name, data_type, character_maximum_length FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_trigger_jobs' ORDER BY ordinal_position; -- 预期:返回 13 行 -- 11. 验证 ai_run_logs 索引(含 PK) SELECT indexname FROM pg_indexes WHERE schemaname = 'biz' AND tablename = 'ai_run_logs'; -- 预期:6 行(PK + 3 P14 索引 + 2 P15 索引) -- 12. 验证 ai_trigger_jobs 索引(含去重部分索引) SELECT indexname FROM pg_indexes WHERE schemaname = 'biz' AND tablename = 'ai_trigger_jobs'; -- 预期:4 行(PK + 3 个索引) -- 13. 验证 ai_conversations.session_id 字段 SELECT column_name, data_type, character_maximum_length FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_conversations' AND column_name = 'session_id'; -- 预期:1 行,varchar(100) -- 14. 验证 ai_cache.status 字段 + CHECK 约束 SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_cache' AND column_name = 'status'; -- 预期:1 行,varchar(20),default 'valid' SELECT conname FROM pg_constraint WHERE conrelid = 'biz.ai_cache'::regclass AND conname = 'chk_ai_cache_status'; -- 预期:1 行 ``` ### 4d. P15 AI 监控后台验证 ```sql -- 15. 验证 ai_run_logs.alert_status 字段存在 SELECT column_name, data_type, character_maximum_length, column_default FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_run_logs' AND column_name = 'alert_status'; -- 预期:1 行,varchar(20),default NULL -- 16. 验证 alert_status CHECK 约束 SELECT conname, pg_get_constraintdef(oid) AS constraint_def FROM pg_constraint WHERE conrelid = 'biz.ai_run_logs'::regclass AND conname = 'chk_ai_run_logs_alert_status'; -- 预期:1 行,CHECK (alert_status IS NULL OR alert_status IN ('pending', 'acknowledged', 'ignored')) -- 17. 验证 P15 新增索引(部分索引 + BRIN 索引) SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'biz' AND tablename = 'ai_run_logs' AND indexname IN ('idx_ai_run_logs_alert', 'idx_ai_run_logs_created_brin'); -- 预期:2 行 -- idx_ai_run_logs_alert — 含 WHERE status IN ('failed', 'timeout', 'circuit_open') -- idx_ai_run_logs_created_brin — USING brin -- 18. 验证 ai_run_logs 总索引数(P14 3个 + P15 2个 + PK = 6) SELECT indexname FROM pg_indexes WHERE schemaname = 'biz' AND tablename = 'ai_run_logs'; -- 预期:6 行 -- 19. 验证回填结果:失败/超时/熔断记录的 alert_status 应为 'pending' SELECT COUNT(*) AS unset_alerts FROM biz.ai_run_logs WHERE status IN ('failed', 'timeout', 'circuit_open') AND alert_status IS NULL; -- 预期:0(所有失败记录已回填为 'pending') ``` ### P15 admin API 查询模式说明 P15 admin-web AI 监控后台引入以下典型查询模式: | 查询场景 | SQL 模式 | 使用索引 | |---------|---------|---------| | Dashboard 今日统计 | `SELECT COUNT(*), AVG(latency_ms) FROM ai_run_logs WHERE created_at >= 今日零点` | `idx_ai_run_logs_created_brin` | | Dashboard 7天趋势 | `SELECT date_trunc('day', created_at), COUNT(*) FROM ai_run_logs WHERE created_at >= 7天前 GROUP BY 1` | `idx_ai_run_logs_created_brin` | | 告警列表 | `SELECT * FROM ai_run_logs WHERE status IN ('failed','timeout','circuit_open') AND alert_status = 'pending' ORDER BY created_at DESC` | `idx_ai_run_logs_alert` | | 告警确认/忽略 | `UPDATE ai_run_logs SET alert_status = 'acknowledged' WHERE id = ?` | PK | | 调用记录分页 | `SELECT * FROM ai_run_logs WHERE site_id = ? AND app_type = ? ORDER BY created_at DESC LIMIT ? OFFSET ?` | `idx_ai_run_logs_site_app` | | Token 预算聚合 | `SELECT SUM(tokens_used) FROM ai_run_logs WHERE created_at >= 今日零点 AND status = 'success'` | `idx_ai_run_logs_created` (B-tree) |