Files
Neo-ZQYY/docs/database/BD_Manual_ai_tables.md
Neo 70324d8542 chore: 文档与 IDE 配置整理
- .kiro/specs/ → docs/specs/(41 个历史需求 spec 迁移,移除 .config.kiro)
- CLAUDE.md 三层拆分:根文件精简 + apps/backend/CLAUDE.md + .claude/commands/
- 新增 /spec-close、/pre-change 两个工作流命令
- DDL 基线刷新(从测试库重新导出 11 个文件,dws 35→38 表,biz 18→21 表)
- BD_Manual → BD_manual 命名统一(48 个文件)
- 修复 3 处文档与数据库不一致(auth.users.status 默认值、scheduled_tasks 字段、RLS 视图数)
- 新增 BD_manual_public_rbac_tables.md(public schema 8 张 RBAC/工作流表)
- 合并 biz.trigger_jobs 文档(10→12 字段,归档独立文档)
- docs/database/README.md 索引更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:02:37 +08:00

453 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BD_Manualbiz 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_conversations14 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `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_messages7 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `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_cache10 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `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_logs15 字段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_jobs13 字段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 流式对话等功能 |
| 后端 APIRNS1.4 | **直接依赖**。CHAT 模块依赖 `ai_conversations` 的 5 个 RNS1.4 字段实现多入口对话复用 |
| 后端 APIP14 | **直接依赖**`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 迁移)
按逆序 DROPCASCADE 处理外键依赖):
```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_roleCHECK role IN ...)和 ai_messages_conversation_id_fkeyFK → 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) |