# 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 |
