api-server/docs/architecture/m-ai-02-current-schema-audit.md
wangdl 800ce4a2b2 feat(M-AI-02): schema audit (02-01) + ADR freeze (02-02)
M-AI-02-01: Production DB audit of all 5 AI tables (9 rows, 0 orphans, 37 test refs)
M-AI-02-02: Frozen AiJob Expand ADR — 43-column schema, 6 indexes, state mapping

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-20 12:24:16 +08:00

36 KiB
Raw Blame History

M-AI-02-01现有 AI Job Schema 审计

审计日期2026-06-20 审计人wangdl半自动化审计代码扫描 + 生产 MySQL 只读查询 via SSH 隧道)


1. Schema 逐字段审计

1.1 AiAnalysisJob旧系统 A

物理表名:AiAnalysisJob | 创建于:prisma/migrations/20250516000000_init/migration.sql:300-319

# Prisma 字段名 物理列名 DB 类型 Nullable Default Index Unique FK onDelete onUpdate
1 id id VARCHAR(191) cuid() PK - - -
2 userId userId VARCHAR(191) - AiAnalysisJob_userId_idx User.id RESTRICT CASCADE
3 sessionId sessionId VARCHAR(191) - AiAnalysisJob_sessionId_idx - - -
4 answerId answerId VARCHAR(191) - - - -
5 jobType jobType VARCHAR(32) - - - -
6 status status VARCHAR(32) 'pending' AiAnalysisJob_status_idx - - -
7 progress progress INT 0 - - -
8 errorMessage errorMessage TEXT - - - -
9 queuedAt queuedAt DATETIME(3) - - - -
10 startedAt startedAt DATETIME(3) - - - -
11 completedAt completedAt DATETIME(3) - - - -
12 createdAt createdAt DATETIME(3) now() - - -
13 updatedAt updatedAt DATETIME(3) @updatedAt - - -

RelationresultsAiAnalysisResult[](一对多)


1.2 AiAnalysisResult旧系统 A 输出)

物理表名:AiAnalysisResult | 创建于:prisma/migrations/20250516000000_init/migration.sql:322-342

# Prisma 字段名 物理列名 DB 类型 Nullable Default Index Unique FK onDelete onUpdate
1 id id VARCHAR(191) cuid() PK - - -
2 userId userId VARCHAR(191) - AiAnalysisResult_userId_idx User.id RESTRICT CASCADE
3 jobId jobId VARCHAR(191) - AiAnalysisResult_jobId_idx AiAnalysisJob.id RESTRICT CASCADE
4 sessionId sessionId VARCHAR(191) - AiAnalysisResult_sessionId_idx - - -
5 answerId answerId VARCHAR(191) - - - -
6 summary summary TEXT - - - -
7 masteryScore masteryScore INT - - - -
8 strengths strengths JSON - - - -
9 weaknesses weaknesses JSON - - - -
10 suggestions suggestions JSON - - - -
11 nextActions nextActions JSON - - - -
12 rawResult rawResult JSON - - - -
13 createdAt createdAt DATETIME(3) now() - - -
14 updatedAt updatedAt DATETIME(3) @updatedAt - - -

RelationjobAiAnalysisJob(多对一)


1.3 AiRuntimeJob新系统 B

物理表名:AiRuntimeJob | 无独立 migration SQLFK 由 Prisma schema push 创建,生产已确认存在

# Prisma 字段名 物理列名 DB 类型 Nullable Default Index Unique FK onDelete onUpdate
1 id id VARCHAR(191) cuid() PK - - -
2 userId userId VARCHAR(191) - AiRuntimeJob_userId_idx User.id RESTRICT CASCADE
3 jobType jobType VARCHAR(64) - AiRuntimeJob_jobType_idx - - -
4 targetType targetType VARCHAR(32) - 复合 AiRuntimeJob_targetType_targetId_idx - - -
5 targetId targetId VARCHAR(255) - 同上 - - -
6 snapshotId snapshotId VARCHAR(191) - - - -
7 status status VARCHAR(32) 'pending' AiRuntimeJob_status_idx - - -
8 priority priority INT 0 - - -
9 idempotencyKey idempotencyKey VARCHAR(255) - - - -
10 apiKeyMode apiKeyMode VARCHAR(32) 'platform_key' - - -
11 credentialId credentialId VARCHAR(191) - - - -
12 modelProvider modelProvider VARCHAR(32) 'deepseek' - - -
13 modelName modelName VARCHAR(64) 'deepseek-chat' - - -
14 promptVersion promptVersion VARCHAR(100) - - - -
15 outputSchemaVersion outputSchemaVersion VARCHAR(100) - - - -
16 attemptNo attemptNo INT 0 - - -
17 retriedFromJobId retriedFromJobId VARCHAR(255) - - - -
18 lockedBy lockedBy VARCHAR(100) - - - -
19 lockedAt lockedAt DATETIME(3) - - - -
20 lockUntil lockUntil DATETIME(3) - AiRuntimeJob_lockUntil_idx - - -
21 startedAt startedAt DATETIME(3) - - - -
22 finishedAt finishedAt DATETIME(3) - - - -
23 retryCount retryCount INT 0 - - -
24 maxRetryCount maxRetryCount INT 3 - - -
25 timeoutSeconds timeoutSeconds INT 120 - - -
26 errorCode errorCode VARCHAR(100) - - - -
27 errorMessage errorMessage TEXT - - - -
28 cancelRequestedAt cancelRequestedAt DATETIME(3) - - - -
29 cancelledAt cancelledAt DATETIME(3) - - - -
30 createdAt createdAt DATETIME(3) now() - - -
31 updatedAt updatedAt DATETIME(3) @updatedAt - - -

RelationresultAiRuntimeResult?(一对一)


1.4 AiRuntimeResult新系统 B 输出)

物理表名:AiRuntimeResult | 无独立 migration SQLFK 由 Prisma schema push 创建,生产已确认存在

# Prisma 字段名 物理列名 DB 类型 Nullable Default Index Unique FK onDelete onUpdate
1 id id VARCHAR(191) cuid() PK - - -
2 jobId jobId VARCHAR(191) - AiRuntimeResult_jobId_idx AiRuntimeJob.id RESTRICT CASCADE
3 userId userId VARCHAR(191) - AiRuntimeResult_userId_idx User.id RESTRICT CASCADE
4 runtimeInstanceId runtimeInstanceId VARCHAR(100) - - - -
5 status status VARCHAR(32) - - - -
6 attemptNo attemptNo INT 0 - - -
7 resultIdempotencyKey resultIdempotencyKey VARCHAR(255) - AiRuntimeResult_resultIdempotencyKey_idx - - -
8 outputHash outputHash VARCHAR(255) - - - -
9 rawOutput rawOutput JSON - - - -
10 validatedOutput validatedOutput JSON - - - -
11 schemaVersion schemaVersion VARCHAR(100) - - - -
12 validationErrors validationErrors JSON - - - -
13 createdAt createdAt DATETIME(3) now() - - -
14 updatedAt updatedAt DATETIME(3) @updatedAt - - -

1.5 AiUsageLog共享 AI 用量日志)

物理表名:AiUsageLog | 创建于:prisma/migrations/20250518000000_add_objectkey_bucket_aiusage_waitlist/migration.sql:9-30FK 同文件 line 48

# Prisma 字段名 物理列名 DB 类型 Nullable Default Index Unique FK onDelete onUpdate
1 id id VARCHAR(191) cuid() PK - - -
2 userId userId VARCHAR(191) - AiUsageLog_userId_idx User.id RESTRICT CASCADE
3 feature feature VARCHAR(64) - AiUsageLog_feature_idx - - -
4 provider provider VARCHAR(32) - - - -
5 model model VARCHAR(100) - - - -
6 tier tier VARCHAR(32) - - - -
7 promptKey promptKey VARCHAR(128) - - - -
8 promptVersion promptVersion VARCHAR(32) - - - -
9 inputTokens inputTokens INT 0 - - -
10 outputTokens outputTokens INT 0 - - -
11 estimatedCost estimatedCost DOUBLE 0 - - -
12 latencyMs latencyMs INT 0 - - -
13 success success BOOLEAN true - - -
14 errorMessage errorMessage VARCHAR(500) - - - -
15 createdAt createdAt DATETIME(3) now() AiUsageLog_createdAt_idx - - -

1.6 FK 约束来源Prisma 隐式默认 vs 显式声明

全部 7 个 @relation 在 Prisma schema 中均未显式声明 onDelete / onUpdate

// 典型模式(无显式 onDelete/onUpdate
user User @relation(fields: [userId], references: [id])
job  AiAnalysisJob @relation(fields: [jobId], references: [id])

生产数据库 INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS 确认全部 7 个 FK 均为 RESTRICT / CASCADE,这与 Prisma 默认行为一致:

  • Prisma 默认 onDelete: Restrict
  • Prisma 默认 onUpdate: Cascade

对 M-AI-02 的影响

  1. M-AI-02-03/04 新增 FK 时应显式声明:避免依赖 Prisma 默认值(不同版本/方言可能不同),在 schema 中显式写出 onDelete: Restrict, onUpdate: Cascade 以保持全表一致
  2. AiUsageLog.jobId FKM-AI-02-08:新增 FK 应显式声明,但需评估 onDelete: SetNull vs Restrict——删除 Job 时 UsageLog 应保留以维持计费审计完整性
  3. OutboxEvent FKM-AI-02-07:新增 Outbox 表的 FK 应显式声明,通常为 onDelete: CascadeJob 删除时级联删 Outbox

2. 状态值枚举

2.1 AiAnalysisJob.status

无 enum 定义,使用任意 string。实际使用值

使用位置 说明
pending ai-analysis.repository.ts:15 创建时默认
processing ai-analysis.repository.ts:23, ai-analysis.worker.ts:48 Worker 开始处理
completed ai-analysis.repository.ts:24, ai-analysis.worker.ts:68 Worker 处理成功
failed ai-analysis.repository.ts:24, ai-analysis.worker.ts:102 Worker 处理失败
queued ai-analysis.service.ts:30,51 API 返回给客户端的虚拟状态(不写入 DB

2.2 AiRuntimeJob.status

无 enum 定义,使用任意 string。实际使用值

使用位置 说明
pending user-ai.service.ts:289,315,331, runtime-internal.service.ts:27,92,594 创建/重试回退
locked runtime-internal.service.ts:99,123 Runtime 获取锁
running runtime-internal.service.ts:129,131, job-reaper.service.ts:40,54, platform-budget.service.ts:55 执行中
succeeded runtime-internal.service.ts:268 执行成功
failed runtime-internal.service.ts:605, job-reaper.service.ts:98, submitFailure() 执行失败/超时终止
cancelled user-ai.service.ts:353, runtime-internal.service.ts:583 用户取消
expired docs/ai-job-state-machine.md:23 文档定义,代码中未显式赋此值

2.3 AiRuntimeResult.status

说明
succeeded 最终成功(代码中 runtime-internal.service.ts:249 检查)
其他值未明确定义 submitResult() 接受任意 dto.status

3. JobType 枚举

3.1 AiAnalysisJob.jobType旧系统 A

使用位置 说明
active-recall ai-analysis.service.ts:19 主动回忆分析
feynman-evaluation ai-analysis.service.ts:40 费曼评估分析

3.2 AiRuntimeJob.jobType新系统 B

使用位置 说明
learning_state_analysis user-ai.dto.ts:64 学习状态分析
weak_point_analysis user-ai.dto.ts:65 薄弱点分析
next_action_planning user-ai.dto.ts:66 下一步建议
quiz_generation user-ai.dto.ts:67 题目生成
flashcard_generation user-ai.dto.ts:68 卡片生成

关键发现:两套系统的 jobType 互不重叠,不存在同名冲突。


4. 生产数据审计

查询时间2026-06-20 03:14 UTC 数据库zhixi_prod @ 120.53.227.155:3306蜂驰云 8C 生产服务器SSH 隧道连接) MySQL 版本8.0.46 查询脚本:scripts/query-audit-data.ts

4.1 AiAnalysisJob

指标
总记录数 9
表大小 0.06 MB
最早记录 2026-06-19T14:34:05.815Z
最新记录 2026-06-19T14:48:10.489Z

状态分布

状态 数量 占比
completed 5 56%
failed 4 44%

jobType 分布

jobType 数量
feynman-evaluation 7
active-recall 2

NULL 字段分布

字段 NULL 数 NULL 率
sessionId 9 100%
answerId 7 78%
errorMessage 5 56%4 failed jobs 均有 errorMessage
queuedAt 0 0%
startedAt 0 0%
completedAt 0 0% — 正确。AiAnalysisRepository.updateJobStatus()completed/failed 均显式设置 completedAt = new Date()ai-analysis.repository.ts:23-24)。当前全部 9 行均为终态5 completed + 4 failed),无 pending/processing 滞留,故 0 NULL 符合预期。DateTime? 类型不生成 DEFAULT CURRENT_TIMESTAMPPrisma @updatedAt 也不影响此列。

最大字段长度

字段 最大长度
errorMessage 797 chars

4.2 AiAnalysisResult

指标
总记录数 5
表大小 0.06 MB
孤儿 FKjobId 不存在) 0
重复 (userId, jobId) 0

最大 JSON 字段长度

字段 最大长度
summary 162 chars
strengths 61 chars
weaknesses 184 chars
suggestions 367 chars
nextActions 185 chars
rawResult 1,048 chars

父 Job 状态分布

父 Job 状态 Result 数量
completed 5

结论:所有 5 个 Result 均属于 completed Job无 failed Job 产生 Result符合代码逻辑

NULL 字段

字段 NULL 数
sessionId 5 (100%)
answerId 5 (100%)
summary / masteryScore / strengths / etc. 0

4.3 AiRuntimeJob

指标
总记录数 0
表大小 0.11 MB空表空间为 initial extent 分配)

确认ADR "唯一创建 AiRuntimeJob 的入口无人调用" 的断言正确。该表在生产中为零记录。

4.4 AiRuntimeResult

指标
总记录数 0
表大小 0.08 MB空表

4.5 AiUsageLog

指标
总记录数 110
表大小 0.11 MB
最早记录 2026-05-28T13:02:12.261Z
最新记录 2026-06-19T14:52:40.199Z
success=true 30 (27.3%)
success=false 80 (72.7%)
errorMessage NULL 30仅 success=true 的日志无错误消息)

feature 分布

feature 总数 成功 失败 成功率
learning-trend 70 11 59 15.7%
knowledge-import 15 1 14 6.7%
rag-chat 13 12 1 92.3%
active-recall-analysis 7 1 6 14.3%
feynman-evaluation 4 4 0 100%
review-card-generation 1 1 0 100%

provider 分布

provider 数量
minimax 59
deepseek 51

model 分布

model 数量
minimax-m2.7 59
deepseek-v4-pro 35
deepseek-v4-flash 16

AiUsageLog 成功率根因分析

107/110 的失败率72.7%)由以下因素叠加导致:

  1. MiniMax M2.7 API Key 已到期2026-06-06 到期,见 devops 轻量云服务器凭据.md
    • minimax-m2.7 共 59 次调用,失败 59 次100% 失败率)
    • 影响 featurelearning-trend59 次全部 minimaxknowledge-import14/15 次失败含大量 minimax
  2. learning-trend70 次,成功率 16%)是后台定时任务 → 高调用量 × minimax 到期 = 大量失败
  3. rag-chat13 次,成功率 92%)使用 deepseek-v4-pro → 几乎全部成功
  4. feynman-evaluation4 次,成功率 100%)使用 deepseek-v4-pro → 全部成功

结论:排除 Minimax 到期影响后DeepSeek 成功率接近 100%。2026-06-06 后所有调用已切换至 DeepSeek V4 Pro/V4 Flash蜂驰云生产凭据确认M-AI-02 无需为 Minimax 保留兼容。

孤儿外键

检测 SQL 结果
AiAnalysisResult.jobIdAiAnalysisJob.id SELECT COUNT(*) FROM AiAnalysisResult r LEFT JOIN AiAnalysisJob j ON r.jobId=j.id WHERE j.id IS NULL 0
AiAnalysisResult.userIdUser.id 同上模式 0
AiAnalysisJob.userIdUser.id 同上模式 0
AiUsageLog.userIdUser.id 同上模式(有 FK RESTRICT不可能存在 0

重复业务记录

定义:重复 = 同一用户对同一 jobType 在 1 秒内 创建 ≥2 条记录(并发创建)。

检测模式 SQL 结果
(userId, jobType) 1秒并发 SELECT a.id,b.id FROM AiAnalysisJob a JOIN AiAnalysisJob b ON a.userId=b.userId AND a.jobType=b.jobType WHERE a.id<b.id AND ABS(TIMESTAMPDIFF(SECOND,a.createdAt,b.createdAt))<=1 1 对
(userId, sessionId, jobType) 并发 GROUP BY userId,sessionId,jobType HAVING cnt>1 WHERE sessionId IS NOT NULL 0sessionId 100% NULL 导致)
(userId, jobId) 在 AiAnalysisResult GROUP BY userId,jobId HAVING cnt>1 0
AiUsageLog 同秒双写 GROUP BY userId,feature,provider,model,createdAt HAVING cnt>1 0

发现的并发重复详情

id1=cmql14u46007nwfzktt0ttsn0  id2=cmql14uey007rwfzkl9uf0ssz
jobType=feynman-evaluation  status=completed/completed
createdAt=2026-06-19T14:34:05Z / 14:34:06Z  diff=0s

评估:两个 Job 均在 1 秒内创建jobType 相同(feynman-evaluation),最终状态均为 completed。这是 AiAnalysisService.evaluateFeynman() 的并发调用导致。AiAnalysisJob 表无 (userId, sessionId, jobType) 唯一约束无法在数据库层防御并发重复。M-AI-02-03 引入 idempotencyKey 后可通过业务层幂等解决。

JSON/TEXT 字段最大容量

检测方法SELECT MAX(LENGTH(column)) FROM table

字段 类型 当前最大 检测 SQL
AiAnalysisJob errorMessage TEXT 797 chars SELECT MAX(LENGTH(errorMessage)) FROM AiAnalysisJob
AiAnalysisResult rawResult JSON 1,048 chars SELECT MAX(LENGTH(rawResult)) FROM AiAnalysisResult
AiAnalysisResult strengths JSON 61 同上
AiAnalysisResult weaknesses JSON 184 同上
AiAnalysisResult suggestions JSON 367 chars 同上
AiAnalysisResult nextActions JSON 185 同上
AiAnalysisResult summary TEXT 162 同上
AiRuntimeJob errorMessage TEXT 0零记录 SELECT MAX(LENGTH(errorMessage)) FROM AiRuntimeJob
AiRuntimeResult rawOutput JSON NULL零记录 SELECT MAX(LENGTH(rawOutput)) FROM AiRuntimeResult
AiRuntimeResult validatedOutput JSON NULL零记录 同上

M-AI-02-04 容量建议

  • 当前 JSON/TEXT 字段均在 1KB 级别,远低于 MEDIUMTEXT16MB上限
  • rawResult 最大 1KB → M-AI-02-04 新增的 AiJobSnapshot 需要容纳完整的 snapshot JSON约 5-10KB建议 MEDIUMTEXTJSONMySQL JSON 列上限 1GB但索引友好
  • AiAnalysisJob.errorMessage 最大 797 chars → 新建 AiJob.errorMessage 保持 TEXT64KB即可无需 MEDIUMTEXT

5. 调用方完整清单

5.1 prisma.aiAnalysisJob 直接访问

文件 行号 操作 说明
src/modules/ai-analysis/ai-analysis.repository.ts 9 create 创建 Job
src/modules/ai-analysis/ai-analysis.repository.ts 26 update 更新 Job 状态
src/modules/ai-analysis/ai-analysis.repository.ts 30 findUnique 查询 Jobinclude results

5.2 prisma.aiAnalysisResult 直接访问

文件 行号 操作 说明
src/modules/ai-analysis/ai-analysis.repository.ts 37 create 创建分析结果
src/modules/ai-analysis/ai-analysis.repository.ts 53 findUnique 查询分析结果
src/modules/learning-session/admin-learning.service.ts 229 findMany Admin 分页查询
src/modules/learning-session/admin-learning.service.ts 233 count Admin 总数

5.3 prisma.aiRuntimeJob 直接访问

文件 行号 操作 说明
src/modules/ai-runtime/user-ai.service.ts 226 findUnique 幂等检查
src/modules/ai-runtime/user-ai.service.ts 282 create 创建 Job
src/modules/ai-runtime/user-ai.service.ts 344 findFirst 取消查询
src/modules/ai-runtime/user-ai.service.ts 351,359 update 取消更新
src/modules/ai-runtime/user-ai.service.ts 429 findFirst 状态查询
src/modules/ai-runtime/user-ai.service.ts 448 findMany 列表查询
src/modules/ai-runtime/internal/runtime-internal.service.ts 36 findMany Poll 查询
src/modules/ai-runtime/internal/runtime-internal.service.ts 89 updateMany Lock
src/modules/ai-runtime/internal/runtime-internal.service.ts 123,129 updateMany Heartbeat
src/modules/ai-runtime/internal/runtime-internal.service.ts 139,154,202,225,576,661 findUnique Snapshot/Result/Fail 查询
src/modules/ai-runtime/internal/runtime-internal.service.ts 192,268,583,594,605 update Snapshot/Result/Fail 更新
src/modules/ai-runtime/job-reaper.service.ts 28 updateMany 清扫过期锁
src/modules/ai-runtime/job-reaper.service.ts 39,69 findMany 查询 stuck/expired
src/modules/ai-runtime/job-reaper.service.ts 53,82,96 updateMany 清扫失败/过期
src/modules/ai-runtime/platform-budget.service.ts 53 count 平台预算检查

5.4 prisma.aiUsageLog 直接访问

文件 行号 操作 说明
src/modules/ai/usage/ai-usage-log.service.ts 28 create 写日志
src/modules/admin-costs/cost-aggregation.service.ts 31 findMany 成本聚合
src/modules/admin-metrics/admin-metrics.controller.ts 56 findMany AI 耗时统计
src/modules/admin-dashboard/admin-dashboard.service.ts 46 count 今日 AI 调用数
src/modules/admin-costs/admin-costs.service.ts 85 groupBy 成本统计
src/modules/learning-session/admin-learning.service.ts 247,251 findMany/count Admin 查询

5.5 间接调用方(通过 Repository/Service

调用方 文件 说明
AiAnalysisWorker.process() src/workers/ai-analysis.worker.ts:48-102 通过 AiAnalysisRepository 更新状态和结果
AiAnalysisService.analyze() src/modules/ai-analysis/ai-analysis.service.ts:12-31 创建 Job 并入队
AiAnalysisService.evaluateFeynman() src/modules/ai-analysis/ai-analysis.service.ts:33-52 创建 Job 并入队
AiAnalysisService.getJobStatus() src/modules/ai-analysis/ai-analysis.service.ts:54-67 查询状态(序列化给客户端)
AdminLearningService.getAnalysis() src/modules/learning-session/admin-learning.service.ts:223-236 Admin 分页查询
AdminLearningService.getAiUsage() src/modules/learning-session/admin-learning.service.ts:240-254 Admin 分页查询
AiUsageLogService.log() src/modules/ai/usage/ai-usage-log.service.ts:26-32 写用量日志
CostAggregationService.aggregateToday() src/modules/admin-costs/cost-aggregation.service.ts:27-59 成本聚合
AdminMetricsController.aiMetrics() src/modules/admin-metrics/admin-metrics.controller.ts:54-89 AI 耗时统计
AdminDashboardService.getStats() src/modules/admin-dashboard/admin-dashboard.service.ts:46 仪表盘统计

6. 测试文件引用

文件 引用方式 影响
test/helpers/integration-harness.ts:228 硬编码表名 'AiAnalysisResult', 'AiAnalysisJob' 重命名 Prisma model 后需同步
src/modules/ai-runtime/user-ai.service.spec.ts Mock prisma.aiRuntimeJob.* 与 Prisma model 名无关mock
src/modules/ai-runtime/job-reaper.service.spec.ts Mock prisma.aiRuntimeJob.* 与 Prisma model 名无关mock
src/modules/ai-runtime/platform-budget.service.spec.ts Mock prisma.aiRuntimeJob.* 与 Prisma model 名无关mock
src/modules/ai-runtime/internal/runtime-internal.service.spec.ts Mock prisma.aiRuntimeJob.* 与 Prisma model 名无关mock
test/api-e2e.worker-int-spec.ts Raw SQL 8 处 + 字符串引用 8 处 = 16 处引用物理表名 AiAnalysisJob / AiAnalysisResult M-AI-02-09 重命名后需全部同步
test/worker-integration.worker-int-spec.ts Raw SQL 13 处 + 字符串引用 8 处 = 21 处引用物理表名 M-AI-02-09 重命名后需全部同步

7. 兼容风险分析

7.1 Model 名依赖

风险等级:中等

以下代码依赖 Prisma model 名 AiAnalysisJobAiAnalysisResult

依赖点 文件 行号 影响
prisma.aiAnalysisJob ai-analysis.repository.ts 9,26,30 重命名 model 会导致编译错误
prisma.aiAnalysisResult ai-analysis.repository.ts 37,53 同上
prisma.aiAnalysisResult admin-learning.service.ts 229,233 同上
硬编码表名字符串 test/helpers/integration-harness.ts 228 清理表时使用物理表名 'AiAnalysisJob''AiAnalysisResult'

注意prisma.aiRuntimeJobprisma.aiUsageLog 不会被 M-AI-02 重命名(当前范围仅统一 AiAnalysisJob → AiJob)。

7.2 状态 enum 依赖

风险等级:低

  • 所有状态值均为 raw string无 enum 定义
  • AiAnalysisJob 使用 pending/processing/completed/failed
  • AiRuntimeJob 使用 pending/locked/running/succeeded/failed/cancelled
  • 两套状态值语义不同M-AI-02-04 需要统一
  • 当前代码中没有任何 AiAnalysisJobStatus type/enum 定义

7.3 API 直接序列化数据库状态

风险等级:中等

API 文件 行号 序列化字段
AiAnalysisService.getJobStatus() ai-analysis.service.ts:54-67 直接透传 job.statusjob.jobTypejob.queuedAtjob.startedAtjob.completedAt 字段名变更会影响 iOS
UserAiService.getJob() user-ai.service.ts:428-443 选择字段直接序列化 jobTypestatuserrorCodeerrorMessage 字段名变更会影响 iOS
UserAiService.listJobs() user-ai.service.ts:445-459 同上 同上
UserAiService.createAnalysisJob() 返回值 user-ai.service.ts:340 { jobId, status, createdAt } 不受影响(仅返回 ID+状态)

7.4 Relation 重命名影响

风险等级:低

  • AiAnalysisJob.resultsAiAnalysisResult[](系统 A
  • AiAnalysisResult.jobAiAnalysisJob(系统 A
  • AiRuntimeJob.resultAiRuntimeResult?(系统 B
  • AiRuntimeResult.jobAiRuntimeJob(系统 B
  • M-AI-02-09 将 AiAnalysisJob 重命名为 AiJob 时,AiAnalysisResult.job 的 relation 类型需要同时更新

7.5 Raw SQL / 手写表名

风险等级:中高

  • 源文件(src/)中无 $queryRaw/$executeRaw 引用 AI 分析相关表
  • worker.main.ts:24system.controller.ts:35 仅使用 SELECT 1 做健康检查
  • 重要发现E2E 测试文件使用 Raw SQL 直接查询物理表:
    • test/api-e2e.worker-int-spec.ts8 处 Raw SQL + 8 处字符串引用it/console.log= 16 处硬编码表名 'AiAnalysisJob''AiAnalysisResult'
    • test/worker-integration.worker-int-spec.ts13 处 Raw SQL + 8 处字符串引用 = 21 处同上
    • 合计 37 处引用,其中 21 处为 Raw SQLSELECT ... FROM AiAnalysisJob16 处为字符串引用
  • M-AI-02-09 重命名物理表后,这 37 处引用必须全部同步更新

7.6 报表/Admin 直接查询物理表

风险等级:中等

所有 Admin 查询通过 Prisma Client不会直接访问物理表。但以下聚合逻辑依赖现有字段

Admin 功能 文件 依赖字段
Dashboard 今日 AI 调用数 admin-dashboard.service.ts:46 AiUsageLog.createdAt
AI 耗时统计 admin-metrics.controller.ts:56-86 AiUsageLog.providermodellatencyMssuccessestimatedCost
成本聚合 cost-aggregation.service.ts:31-56 AiUsageLog.*
Admin 分析结果 admin-learning.service.ts:229-233 AiAnalysisResult.*

7.7 跨表数据关系

源表 目标表 关系 当前状态 M-AI-02 影响
AiAnalysisJob AiAnalysisResult 1:N FK 生产运行中 需保留
AiAnalysisJob FocusItem 逻辑关联Worker 进程内直接调用 FocusItemsService.create() 生产运行中 不直接受影响
AiAnalysisJob ReviewCard 逻辑关联EventEmitter → Subscriber → AI Workflow → ReviewCard 表) 生产运行中 M-AI-05 Outbox 替代时必须保持此链
AiRuntimeJob AiRuntimeResult 1:1 FK 闲置(无生产者) M-AI-03 规划使用
AiRuntimeJob LearningAnalysisSnapshot 逻辑关联snapshotId 闲置 M-AI-03 规划使用
AiRuntimeJob AiLearningAnalysis 逻辑关联jobId 闲置 M-AI-04 规划使用
AiRuntimeJob QuestionGenerationPlan 逻辑关联jobId 闲置 M-AI-06 规划使用
AiRuntimeJob FlashcardGenerationPlan 逻辑关联jobId 闲置 M-AI-06 规划使用
AiUsageLog 无 FK 到 Job 表 无关联 - M-AI-02-08 需新增

ai.analysis.completed 事件订阅链完整追踪

对 M-AI-05Outbox 替代 EventEmitter至关重要。

AiAnalysisWorker.process()                         // src/workers/ai-analysis.worker.ts:72
  └─ eventBus.publish(new AIAnalysisCompleted(...)) // 发布事件
       │
       └─ ReviewCardSubscriber.handleAIAnalysisCompleted()  // src/modules/review/review-card.subscriber.ts:11
            │  @OnEvent('ai.analysis.completed')
            │  订阅 payload: { userId, jobId, sessionId?, answerId?, type, score?, analysis? }
            │
            └─ ReviewService.generateCards(userId, input)   // src/modules/review/review.service.ts:68
                 │  从 payload.analysis 中提取 weaknesses/strengths/summary
                 │  拼接为 prompt 文本 → CardGenerationWorkflow.execute()
                 │
                 └─ ReviewRepository.insertCard(data)       // src/modules/review/review.repository.ts:38
                      │  this.prisma.reviewCard.create({ data })
                      │
                      └─ 写入 ReviewCard 表 (prisma/schema.prisma:642-668)
                          字段: userId, frontText, backText, difficulty, status,
                                intervalDays, easeFactor, repetitionCount, lapseCount,
                                scheduleState, nextReviewAt

关键发现

  1. ReviewCard jobId FK — 无法溯源哪次 AI 分析生成了哪张卡片
  2. Subscriber 依赖进程内 EventEmitter — M-AI-05 改为 Outbox 后,此链必须保持
  3. FocusItem 不走事件订阅 — Worker 进程内直接调 focusItems.create()ai-analysis.worker.ts:88),不存在可靠投递保证
  4. Subscriber 声明的 payload 接口(review-card.subscriber.ts:12-20)与实际 AIAnalysisCompleted event 类(ai-analysis.worker.ts:13-16)完全耦合 — 任何 event schema 变更需同步修改 subscriber

8. 关键发现总结

  1. 生产数据量极小AiAnalysisJob 仅 9 行AiAnalysisResult 仅 5 行AiUsageLog 110 行。所有表 < 0.12 MB。Migration 锁表风险极低(毫秒级 ALTER TABLE
  2. AiRuntimeJob/AiRuntimeResult 零记录:确认 ADR 断言正确——系统 B 无生产者活动。M-AI-02 Expand 无需回填历史 RuntimeJob 数据。
  3. 两套系统并存AiAnalysisJob生产运行中active-recall + feynmanAiRuntimeJob闲置5 种分析类型)是两套完全独立的表,无任何 FK/逻辑关联
  4. AiUsageLog 与 Job 表无关联:当前 AiUsageLogjobId 字段,无法追踪单次 AI 调用归属。M-AI-02-08 需要新增 jobId FK
  5. 无 Outbox/事件溯源:系统 A 通过 NestJS EventEmitter 发布事件(ai.analysis.completed),系统 B 在 API 进程内同步写 Notification——无持久化事件表
  6. 状态值非标准化:无 enum 定义,两套状态语义不兼容(processing vs locked/runningcompleted vs succeeded
  7. AiRuntimeJob 字段远多于 AiAnalysisJob31 vs 13 字段M-AI-02 Expand 后应保持一致
  8. 无 raw SQL 引用 AI 表AI 表仅通过 Prisma Client 访问
  9. 37 处测试文件硬编码表名test/api-e2e.worker-int-spec.ts(16) 和 test/worker-integration.worker-int-spec.ts(21) 中 Raw SQL 及字符串引用物理表名 'AiAnalysisJob'/'AiAnalysisResult'M-AI-02-09 重命名后必须全部同步。另有 test/helpers/integration-harness.ts:228 的清理列表
  10. sessionId/answerId 高 NULL 率AiAnalysisJob 中 sessionId 100% NULLanswerId 78% NULL——M-AI-02-04 可评估是否保留这两个字段
  11. 无孤儿 FK:所有 FK 引用一致,数据完整性良好
  12. 全部 FK 来自 Prisma 默认值Prisma schema 中 7 个 @relation 均未显式声明 onDelete/onUpdate,生产实际值为 RESTRICT/CASCADEPrisma 默认。M-AI-02-03/04 新增 FK 应显式声明以避免跨版本/跨方言歧义(见 1.6 节)
  13. ReviewCard 无 jobId 溯源ai.analysis.completedReviewCardSubscriber → AI Workflow → ReviewCard 的完整链路已追踪(见 7.7 节),但 ReviewCard 表无 jobId FK无法溯源卡片生成来源
  14. FocusItem 不走事件系统Worker 进程内直接同步调用 focusItems.create()无可靠投递保证——M-AI-05 Outbox 化时需考虑
  15. AiUsageLog 低成功率根因已定位72.7% 失败由 MiniMax M2.7 API Key 到期2026-06-06导致 — learning-trend 全部使用 minimax 故失败率 84%。DeepSeek 调用近乎 100% 成功。生产已全量切换至 DeepSeek V4 Pro/FlashM-AI-02 无需保留 Minimax 兼容