Inventory: 17 AI workflows cataloged. 6 migrated, 4 in M-AI-08 scope, 5 deferred, 2 legacy pending retirement. Confirmed M-AI-08 as final core business migration milestone. Contract: 4 Runtime job types (learning_state/weak_point/next_action/ flashcard) to be merged into unified learning_analysis Job. Data source matrix, snapshot schema, evidence schema, topology frozen. Co-Authored-By: Claude <noreply@anthropic.com>
463 lines
15 KiB
Markdown
463 lines
15 KiB
Markdown
# M-AI-08 学习综合分析迁移契约
|
||
|
||
> 审计日期:2026-06-22
|
||
> 契约状态:冻结(待 M-AI-08-01 验收)
|
||
> 对应里程碑:M-AI-08 学习综合分析、学习建议与 AI 迁移闭环
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [当前链路审计](#1-当前链路审计)
|
||
2. [目标链路](#2-目标链路)
|
||
3. [数据源矩阵](#3-数据源矩阵)
|
||
4. [聚合窗口](#4-聚合窗口)
|
||
5. [Snapshot Schema](#5-snapshot-schema)
|
||
6. [Output Schema](#6-output-schema)
|
||
7. [Evidence Schema](#7-evidence-schema)
|
||
8. [触发模型](#8-触发模型)
|
||
9. [幂等契约](#9-幂等契约)
|
||
10. [Artifact 矩阵](#10-artifact-矩阵)
|
||
11. [权限与隐私](#11-权限与隐私)
|
||
12. [Feature Flag](#12-feature-flag)
|
||
13. [回滚流程](#13-回滚流程)
|
||
14. [架构异常与不确定项](#14-架构异常与不确定项)
|
||
|
||
---
|
||
|
||
## 1. 当前链路审计
|
||
|
||
### 1.1 四种 Runtime 作业类型
|
||
|
||
| Job Type | 产出实体 | promptVersion | outputSchemaVersion | 现状 |
|
||
|----------|---------|---------------|---------------------|------|
|
||
| `learning_state_analysis` | AiLearningAnalysis | learning_state_v1 | analysis_output_v1 | Runtime 轮询 |
|
||
| `weak_point_analysis` | WeakPointCandidate | weak_point_v1 | weak_point_output_v1 | Runtime 轮询 |
|
||
| `next_action_planning` | NextActionRecommendation | next_action_v1 | next_action_output_v1 | Runtime 轮询 |
|
||
| `flashcard_generation` | Flashcard | flashcard_gen_v1 | flashcard_output_v1 | Runtime 轮询 |
|
||
|
||
### 1.2 当前时序(Runtime 轮询路径)
|
||
|
||
```text
|
||
POST /api/ai/jobs { jobType: "learning_state_analysis", targetType, targetId }
|
||
→ UserAiService.createAnalysisJob()
|
||
`src/modules/ai-runtime/user-ai.service.ts:199`
|
||
├─ settings 检查 (:201-206)
|
||
├─ jobType 验证 (:210-214)
|
||
├─ 幂等检查 (:225-233)
|
||
├─ 配额+预算 (:252-267)
|
||
├─ SnapshotBuilderService.buildSnapshot() (:270)
|
||
│ `src/modules/ai-runtime/snapshot-builder.service.ts:75`
|
||
│ ├─ fetchBehaviorData() — DailyLearningActivity, LearningSession
|
||
│ ├─ aggregateProgress() — MaterialReadingProgress
|
||
│ ├─ aggregateContent() — KnowledgeItem(可选)
|
||
│ ├─ calculateScores() — Quiz/Review/ActiveRecall/Feynman metrics
|
||
│ ├─ computeSignals() — engagement/consistency/streaks/patterns
|
||
│ └─ buildDeviceContext() — 平台/时区/设备
|
||
├─ PriorityRulesService.computeJobPriority() (:279)
|
||
└─ aiRuntimeJob.create (:282-299)
|
||
|
||
外部 Runtime 轮询
|
||
→ POST /internal/runtime/jobs/poll → pollJobs() (:22)
|
||
→ POST /internal/runtime/jobs/:id/lock → lockJob() (:85)
|
||
→ GET /internal/runtime/jobs/:id/snapshot → getSnapshot() (:153)
|
||
→ Runtime 执行 AI 调用(prompt/schema 外部管理)
|
||
→ POST /internal/runtime/jobs/:id/result → submitResult() (:220)
|
||
└─ persistResult() (:286)
|
||
├─ learning_state_analysis → AiLearningAnalysis.create (:295-311)
|
||
├─ weak_point_analysis → WeakPointCandidate.create (:313-337)
|
||
└─ next_action_planning → NextActionRecommendation.create (:339-365)
|
||
```
|
||
|
||
### 1.3 数据模型
|
||
|
||
**AiLearningAnalysis** (`schema.prisma:2173-2194`):
|
||
```text
|
||
id, userId, jobId, snapshotId, targetType, targetId,
|
||
learningState, summary, riskLevel, confidence, evidence (Json),
|
||
nextActionIds (Json), promptVersion, schemaVersion
|
||
```
|
||
|
||
**WeakPointCandidate** (`schema.prisma:2198-2216`):
|
||
```text
|
||
id, userId, jobId, snapshotId, targetType, targetId,
|
||
knowledgePointId, title, reason, confidence, evidence (Json),
|
||
status (active/resolved)
|
||
```
|
||
|
||
**NextActionRecommendation** (`schema.prisma:2220-2239`):
|
||
```text
|
||
id, userId, jobId, snapshotId, actionType, targetType, targetId,
|
||
title, reason, priority, estimatedMinutes, deviceSuitability,
|
||
status (active/resolved)
|
||
```
|
||
|
||
**LearningAnalysisSnapshot** (`schema.prisma:2058-2083`):
|
||
```text
|
||
id, userId, scopeType, scopeId, snapshotVersion, sourceDataVersion,
|
||
privacyScope, userProfile, aiSettings, deviceContext,
|
||
learningBehaviorSummary, materialProgressSummary, contentStructureSummary,
|
||
behaviorSignals, scoreSignals, constraints, allowedModelFields
|
||
```
|
||
|
||
### 1.4 拓扑(冻结)
|
||
|
||
审计确认:`learning_state_analysis`、`weak_point_analysis`、`next_action_planning` 三者**各自独立调用模型**,由外部 Runtime 分别执行。
|
||
|
||
**M-AI-08 迁移决策**:将三者合并为一个 `learning_analysis` Job,一次模型调用同时产出综合分析 + 薄弱点 + 建议。
|
||
|
||
```text
|
||
单一 learning_analysis Job
|
||
→ 一次 AiGateway 调用
|
||
→ LearningAnalysisProjector
|
||
→ AiLearningAnalysis(综合分析)
|
||
→ WeakPointCandidate × N(薄弱点)
|
||
→ NextActionRecommendation × N(建议)
|
||
→ AiJobArtifact × N
|
||
```
|
||
|
||
`flashcard_generation` 保持独立(不属于"学习分析"范畴)。
|
||
|
||
### 1.5 数据来源分类
|
||
|
||
| 数据 | 来源 | 可信度 | Snapshot 处理 |
|
||
|------|------|:---:|------|
|
||
| userId | JWT | 可信 | 直接包含 |
|
||
| learningGoal | UserLearningProfile | 可信 | 直接包含 |
|
||
| dailyAvailableMinutes | UserLearningProfile | 可信 | 聚合 |
|
||
| qualityPreference | UserAiSettings | 可信 | 直接包含 |
|
||
| 学习时长 | DailyLearningActivity | 可信 | **仅聚合后进入** |
|
||
| 活跃天数 | DailyLearningActivity | 可信 | 聚合指标 |
|
||
| 复习完成率 | ReviewCard + ReviewLog | 可信 | 聚合指标 |
|
||
| Quiz 正确率 | QuizAttempt | 可信 | 聚合指标 |
|
||
| Active Recall 表现 | AiAnalysisResult | 可信 | 聚合指标 |
|
||
| Feynman 弱点 | FocusItem + AiAnalysisResult | 可信 | 聚合指标 |
|
||
| 知识点进度 | KnowledgeItem + MaterialReadingProgress | 可信 | 列表(限量+截断) |
|
||
| 设备/时区 | DeviceContext(服务端推断) | 可信 | 直接包含 |
|
||
| 学习时长(客户端声明) | — | **不可信** | **禁止进入** |
|
||
| 掌握度(客户端声明) | — | **不可信** | **禁止进入** |
|
||
| JWT / API Key | Request Header | — | **禁止进入** |
|
||
| 完整对话历史 | — | — | **禁止进入** |
|
||
|
||
### 1.6 副作用矩阵
|
||
|
||
| 副作用 | 当前 | Unified |
|
||
|--------|------|---------|
|
||
| 配额消耗 | quota.incrementJobCount | 保持 |
|
||
| UsageLog | Runtime 记录 | Engine 记录 |
|
||
| 更新用户画像 | 否 | 否 |
|
||
| 更新知识点掌握度 | 否 | 否 |
|
||
| 创建通知 | 否 | 否 |
|
||
| 自动生成复习卡 | 否 | 否 |
|
||
| 自动执行建议 | 否 | 否(禁止) |
|
||
| Snapshot 创建 | SnapshotBuilderService | SnapshotBuilder(复用) |
|
||
|
||
---
|
||
|
||
## 2. 目标链路
|
||
|
||
```text
|
||
手动/定时触发
|
||
→ LearningAnalysisExecutionRouter
|
||
├─ [legacy] → UserAiService.createAnalysisJob()
|
||
└─ [unified] →
|
||
LearningAnalysisSnapshotBuilder.build()
|
||
→ AiJobCreationService.createJob()
|
||
→ Job + Snapshot + Outbox
|
||
→ BullMQ (ai-background)
|
||
→ Worker → AiJobExecutionEngine
|
||
EXECUTE: LearningAnalysisExecutor (AiGateway)
|
||
VALIDATE: SchemaValidator + EvidenceValidator + BusinessValidator
|
||
PROJECT: LearningAnalysisProjector
|
||
→ AiLearningAnalysis + WeakPointCandidate × N
|
||
+ NextActionRecommendation × N + Artifact
|
||
→ markSucceeded
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 数据源矩阵
|
||
|
||
| 数据源 | Prisma 模型 | Snapshot 包含方式 |
|
||
|--------|-----------|-----------------|
|
||
| 用户学习目标 | UserLearningProfile | 直接字段 |
|
||
| AI 设置 | UserAiSettings | 直接字段 |
|
||
| 每日学习活动 | DailyLearningActivity | 聚合(totalDuration/activeDays/sessionCount) |
|
||
| 学习会话 | LearningSession | 聚合(avgSessionDuration/completionRate) |
|
||
| 阅读进度 | MaterialReadingProgress | 列表(限量 50,仅标题+进度) |
|
||
| 知识项 | KnowledgeItem | 列表(限量 200,仅标题+摘要) |
|
||
| 复习记录 | ReviewCard + ReviewLog | 聚合(dueCount/completedCount/accuracy/overdue) |
|
||
| Quiz 结果 | QuizAttempt + QuizAnswer | 聚合(accuracy/byType/byKnowledgeItem) |
|
||
| Active Recall | AiAnalysisResult | 聚合(count/avgScore/weaknesses) |
|
||
| Feynman 评估 | AiAnalysisResult + FocusItem | 聚合(count/weaknesses/focusItems) |
|
||
| 设备上下文 | 服务端推断 | 直接字段(platform/timezone) |
|
||
| 连续学习 | StreakRecord | 聚合指标 |
|
||
|
||
---
|
||
|
||
## 4. 聚合窗口
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| windowStart | 请求时确定 | Snapshot 固定,不漂移 |
|
||
| windowEnd | 请求时确定 | 与 windowStart 配对 |
|
||
| timezone | 服务端推断 | 用户设备时区 |
|
||
| sourceCutoffAt | 请求时 | 数据截止时间 |
|
||
| aggregationVersion | 语义版本 | 算法版本,变化时重新分析 |
|
||
| 行为窗口 | 7 天 | 学习行为数据 |
|
||
| 成绩窗口 | 30 天 | Quiz/复习/Active Recall 成绩 |
|
||
|
||
---
|
||
|
||
## 5. Snapshot Schema
|
||
|
||
```typescript
|
||
interface LearningAnalysisSnapshot {
|
||
schemaVersion: string; // "learning-analysis-v1"
|
||
snapshot: {
|
||
userId: string;
|
||
triggerType: 'manual' | 'scheduled';
|
||
operationId: string;
|
||
windowStart: string; // ISO8601
|
||
windowEnd: string;
|
||
timezone: string; // IANA
|
||
aggregationVersion: string;
|
||
sourceCutoffAt: string;
|
||
|
||
learningGoal?: string;
|
||
qualityPreference?: string;
|
||
dailyAvailableMinutes?: number;
|
||
|
||
studyMetrics: {
|
||
totalStudyDuration: number;
|
||
activeDays: number;
|
||
sessionCount: number;
|
||
averageSessionDuration: number;
|
||
completionRate: number;
|
||
};
|
||
|
||
reviewMetrics: {
|
||
reviewDueCount: number;
|
||
reviewCompletedCount: number;
|
||
reviewAccuracy: number;
|
||
overdueCount: number;
|
||
retentionTrend: number; // -1 to 1
|
||
};
|
||
|
||
quizMetrics: {
|
||
quizCount: number;
|
||
attemptCount: number;
|
||
accuracy: number;
|
||
accuracyByQuestionType: Record<string, number>;
|
||
};
|
||
|
||
activeRecallMetrics: {
|
||
count: number;
|
||
avgScore: number;
|
||
weaknessCount: number;
|
||
};
|
||
|
||
feynmanMetrics: {
|
||
count: number;
|
||
weaknessCount: number;
|
||
focusItemCount: number;
|
||
};
|
||
|
||
knowledgeProgress: {
|
||
totalItems: number;
|
||
completedItems: number;
|
||
inProgressItems: number;
|
||
weakItems: Array<{ id: string; title: string }>;
|
||
};
|
||
|
||
dataQuality: {
|
||
availableSources: string[];
|
||
missingSources: string[];
|
||
sampleSize: number;
|
||
coverageStart?: string;
|
||
coverageEnd?: string;
|
||
insufficientDataReasons: string[];
|
||
};
|
||
|
||
promptKey: string;
|
||
promptVersion: string;
|
||
modelTier: string;
|
||
inputSchemaVersion: string;
|
||
outputSchemaVersion: string;
|
||
createdAt: string;
|
||
};
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Output Schema
|
||
|
||
```typescript
|
||
interface LearningAnalysisOutput {
|
||
summary: string;
|
||
strengths: Array<{
|
||
title: string;
|
||
description: string;
|
||
evidenceRefs: EvidenceRef[];
|
||
}>;
|
||
weaknesses: Array<{
|
||
title: string;
|
||
description: string;
|
||
knowledgePointId?: string;
|
||
evidenceRefs: EvidenceRef[];
|
||
}>;
|
||
trends: Array<{
|
||
metricKey: string;
|
||
direction: 'improving' | 'declining' | 'stable';
|
||
description: string;
|
||
evidenceRefs: EvidenceRef[];
|
||
}>;
|
||
risks: Array<{
|
||
title: string;
|
||
severity: 'low' | 'medium' | 'high';
|
||
description: string;
|
||
evidenceRefs: EvidenceRef[];
|
||
}>;
|
||
recommendations: Array<{
|
||
actionType: string;
|
||
title: string;
|
||
reason: string;
|
||
priority: number;
|
||
estimatedMinutes?: number;
|
||
targetType?: string;
|
||
targetId?: string;
|
||
evidenceRefs: EvidenceRef[];
|
||
}>;
|
||
confidence: number; // 0.0-1.0
|
||
dataQuality: {
|
||
overall: 'sufficient' | 'limited' | 'insufficient';
|
||
};
|
||
insufficientData: boolean;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Evidence Schema
|
||
|
||
每个结论必须附带 evidenceRefs:
|
||
|
||
```typescript
|
||
interface EvidenceRef {
|
||
sourceType: 'study_metric' | 'review_metric' | 'quiz_metric'
|
||
| 'active_recall' | 'feynman' | 'knowledge_progress';
|
||
metricKey: string; // e.g. "reviewAccuracy", "quizAccuracy"
|
||
entityId?: string; // knowledgeItemId / quizId / analysisResultId
|
||
windowStart: string;
|
||
windowEnd: string;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 触发模型
|
||
|
||
| 触发类型 | 幂等键 | 说明 |
|
||
|---------|--------|------|
|
||
| manual | `learning-analysis:manual:<operationId>` | 用户手动触发,同一操作重试相同 Job |
|
||
| scheduled | `learning-analysis:scheduled:<userId>:<windowStart>:<windowEnd>:<policyVersion>` | 定时触发,同一窗口唯一 |
|
||
|
||
---
|
||
|
||
## 9. 幂等契约
|
||
|
||
### 请求级
|
||
- 同一 operationId → 同一 Job
|
||
- 同一 scheduled window → 同一 Job
|
||
- 禁止 Date.now()/random 回退
|
||
|
||
### 投影级
|
||
- analysisId = deterministic(jobId)
|
||
- recommendationId = deterministic(jobId + ordinal)
|
||
- 入口 Artifact 检查 + P2002 catch
|
||
|
||
---
|
||
|
||
## 10. Artifact 矩阵
|
||
|
||
| 实体 | artifactType | artifactId |
|
||
|------|-------------|-----------|
|
||
| AiLearningAnalysis | `learning_analysis` | analysis.id |
|
||
| WeakPointCandidate | `weak_point` | candidate.id |
|
||
| NextActionRecommendation | `recommendation` | recommendation.id |
|
||
| Flashcard (单独) | `flashcard` | flashcard.id |
|
||
|
||
注:M-AI-02 已冻结 `learning_analysis`、`recommendation` 等 artifactType。
|
||
|
||
---
|
||
|
||
## 11. 权限与隐私
|
||
|
||
- 所有数据必须属于同一 userId
|
||
- 禁止其他用户数据进入 Snapshot
|
||
- 禁止跨用户 evidenceRefs
|
||
- 公开错误不泄漏 Snapshot/validatedOutput
|
||
- Admin 可查看所有(现有权限制)
|
||
|
||
---
|
||
|
||
## 12. Feature Flag
|
||
|
||
```text
|
||
Flag Name: LEARNING_ANALYSIS_ENGINE_MODE
|
||
Values: legacy | unified
|
||
Default: legacy
|
||
```
|
||
|
||
---
|
||
|
||
## 13. 回滚流程
|
||
|
||
```text
|
||
unified → legacy:
|
||
1. 修改 Flag → legacy
|
||
2. 新触发走 Legacy Runtime 轮询
|
||
3. 已创建 Unified Job 继续完成
|
||
4. 已生成分析保留
|
||
5. 无需数据库回滚
|
||
```
|
||
|
||
---
|
||
|
||
## 14. 架构异常与不确定项
|
||
|
||
### 阻塞项
|
||
|
||
| # | 异常 | 说明 | 处理 |
|
||
|---|------|------|------|
|
||
| A1 | 无 learning_state/weak_point/next_action Prompt 模板 | 当前由 Runtime 管理 | M-AI-08-03 需新增内联 Prompt |
|
||
| A2 | 无对应 Output Schema | 同上 | M-AI-08-03 需新增 Zod Schema |
|
||
| A3 | flashcard_generation prompt/schema 缺失 | 同 Quiz 模式 | 延期或并入 M-AI-08 |
|
||
|
||
### 不确定项
|
||
|
||
| 项 | 问题 | 建议 |
|
||
|----|------|------|
|
||
| 三个 Job Type 合并策略 | 当前三者独立,合并为一个 learning_analysis 需验证业务合理性 | M-AI-08-01 中确认后冻结 |
|
||
| flashcard_generation 归属 | 语义上更接近 Quiz 生成而非学习分析 | 建议纳入 M-AI-08 作为独立 Definition |
|
||
|
||
---
|
||
|
||
## 附录:关键文件索引
|
||
|
||
| 用途 | 路径 | 关键行号 |
|
||
|------|------|---------|
|
||
| 分析入口 | `src/modules/ai-runtime/user-ai.service.ts` | `:199` createAnalysisJob, `:11-17` JOB_TYPE_CONFIG |
|
||
| Runtime 持久化 | `src/modules/ai-runtime/internal/runtime-internal.service.ts` | `:286` persistResult, `:295-365` 四种类型分支 |
|
||
| Snapshot 构建 | `src/modules/ai-runtime/snapshot-builder.service.ts` | `:75` buildSnapshot, `:259` aggregateBehavior |
|
||
| Priority 规则 | `src/modules/ai-runtime/priority-rules.service.ts` | `:45` computePriorityRules |
|
||
| AiLearningAnalysis | `prisma/schema.prisma` | `:2173-2194` |
|
||
| WeakPointCandidate | `prisma/schema.prisma` | `:2198-2216` |
|
||
| NextActionRecommendation | `prisma/schema.prisma` | `:2220-2239` |
|
||
| LearningAnalysisSnapshot | `prisma/schema.prisma` | `:2058-2083` |
|
||
| UserLearningProfile | `prisma/schema.prisma` | `:1912-1932` |
|
||
| UserAiSettings | `prisma/schema.prisma` | `:1936-1953` |
|
||
| 学习趋势 Workflow | `src/modules/ai/workflows/learning-trend.workflow.ts` | `:30` execute |
|
||
| 学习趋势 Prompt | `src/modules/ai/prompts/learning-trend.prompt.ts` | `:1` system prompt |
|