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>
This commit is contained in:
Neo
2026-04-06 00:02:37 +08:00
parent 8228b3fa37
commit 70324d8542
185 changed files with 13595 additions and 1219 deletions

View File

@@ -1,22 +1,26 @@
# BD_Manualbiz Schema AI 表(对话记录 + 消息 + 缓存)
# 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 扩展)
> 关联 SPEC`05-miniapp-ai-integration`P5 AI 集成层)、`rns1-chat-integration`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. 变更说明
### 新增表(3 张P5 初始建表
### 新增表(5 张
| # | 表名 | 用途 | 字段数(初始→当前) |
|---|------|------|---------------------|
| 1 | `biz.ai_conversations` | AI 对话记录:每次 AI 调用(用户主动或系统自动)创建一条 | 8 → 13 |
| 2 | `biz.ai_messages` | AI 消息记录:对话中的每条消息(输入/输出/系统) | 6 → 7 |
| 3 | `biz.ai_cache` | AI 应用缓存:各应用的结构化输出结果 | 9 |
| # | 表名 | 用途 | 字段数(初始→当前) | 来源 |
|---|------|------|---------------------|------|
| 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
@@ -25,9 +29,26 @@
| 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_conversations13 字段)
#### biz.ai_conversations14 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
@@ -39,11 +60,12 @@
| `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 新增** — 关联上下文 IDtask 入口为 taskIdcustomer 入口为 customerIdcoach 入口为 coachIdgeneral 为 NULL |
| `title` | VARCHAR(200) | 可空 | **RNS1.4 新增** — 对话标题:自定义 > 上下文名称 > 首条消息前20字 |
| `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 新增** — 最后消息时间,用于历史列表排序和对话复用时限判断 |
| `last_message_at` | TIMESTAMPTZ | 可空 | **RNS1.4 新增** — 最后消息时间 |
| `session_id` | VARCHAR(100) | 可空 | **P14 新增** — 百炼 session_id格式 `conv_{conversation_id}_{created_timestamp}`,仅 App1 使用 |
#### biz.ai_messages7 字段)
@@ -57,7 +79,7 @@
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
| `reference_card` | JSONB | 可空 | **RNS1.4 新增** — 引用卡片 JSON`{type, title, summary, data}`,用于展示客户概览等结构化上下文数据 |
#### biz.ai_cache9 字段)
#### biz.ai_cache10 字段)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
@@ -70,6 +92,7 @@
| `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 约定
@@ -89,14 +112,62 @@
|----|-----------|------|------|
| `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)` — 清理超限记录ASC 排序便于删除最旧) |
| `ai_conversations` | `idx_ai_conv_context` | INDEX(条件) | **RNS1.4 新增**`(user_id, site_id, context_type, context_id, last_message_at DESC NULLS LAST) 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)` — 历史列表排序优化CHAT-1 倒序) |
| `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() | 创建时间 |
---
@@ -104,19 +175,56 @@
| 组件 | 影响 |
|------|------|
| ETL 任务 | 无影响。AI 表属于 `biz` Schema不参与 ETL 流程 |
| 后端 API | 直接依赖。P5 AI 模块(`apps/backend/app/ai/`基于这三张表实现对话持久化、缓存读写、SSE 流式对话等功能 |
| 后端 APIRNS1.4 | **直接依赖**。CHAT 模块`apps/backend/app/routers/xcx_chat.py``apps/backend/app/services/chat_service.py`)依赖 `ai_conversations` 的 5 个新字段(`context_type`/`context_id`/`title`/`last_message`/`last_message_at`)实现多入口对话复用、历史列表展示与排序;依赖 `ai_messages.reference_card` 存储引用卡片 JSON |
| 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 间接使用对话和缓存数据 |
| 小程序RNS1.4 | **接依赖**`pages/chat/chat.ts``pages/chat-history/chat-history.ts` 通过 CHAT-1/2/3/4 端点间接依赖新字段(`title`→对话标题、`lastMessage`→摘要、`timestamp`→排序、`referenceCard`→引用卡片渲染) |
| 管理后台 | 暂无影响。后续可能增加 AI 调用统计和缓存管理界面 |
| `member_retention_clue` | 间接关联。App8维客线索整理的结果同时写入 `ai_cache``member_retention_clue` 表 |
| 现有 `biz` Schema | 兼容。P5 新增 3 张表RNS1.4 仅在已有表上 ADD COLUMN / CREATE INDEX不修改已有字段或约束 |
| 管理后台 | **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 新增索引和字段:
@@ -185,7 +293,7 @@ SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'biz' AND table_name = 'ai_conversations'
ORDER BY ordinal_position;
-- 预期:返回 13P5 原始 8 字段 + RNS1.4 新增 5 字段
-- 预期:返回 14P5 原始 8 + RNS1.4 新增 5 + P14 session_id = 14
-- 3. 验证 ai_messages 的外键和 CHECK 约束
SELECT conname, contype, pg_get_constraintdef(oid) AS constraint_def
@@ -198,7 +306,7 @@ ORDER BY conname;
SELECT conname, pg_get_constraintdef(oid) AS constraint_def
FROM pg_constraint
WHERE conrelid = 'biz.ai_cache'::regclass AND contype = 'c';
-- 预期:返回 1 chk_ai_cache_type,包含 7 个枚举值
-- 预期:返回 2chk_ai_cache_type 含 7 个枚举值chk_ai_cache_status 含 4 个状态值)
-- 5. 验证 P5 初始索引全部存在5 个)
SELECT indexname
@@ -244,3 +352,101 @@ WHERE schemaname = 'biz' AND tablename = 'ai_conversations'
-- 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) |