# AI 数据权限、隐私授权与用户设置设计

## 1. 概述

定义 AI 分析可使用的数据范围、用户授权开关、隐私边界和字段发送规则。所有规则由 Snapshot Builder 执行，Runtime 无权绕过。

## 2. 用户授权项

存储在 `UserAiSettings` 中：

| 字段 | 默认值 | 说明 |
|------|--------|------|
| `allowAiAnalysis` | `true` | 总开关。关闭后禁止创建任何 AI Job |
| `allowUseLearningBehavior` | `true` | 是否允许使用学习行为数据（时长、频率、pattern） |
| `allowUseUserProfile` | `true` | 是否允许使用用户画像（目标、等级、偏好） |
| `allowUseDocumentContent` | `false` | 是否允许使用资料原文片段。默认关闭，需用户主动开启 |
| `allowStoreAiAnalysisHistory` | `true` | 是否允许保存 AI 分析历史 |
| `allowUserModelCredential` | `true` | 是否允许使用自己的 DeepSeek Key |
| `fallbackToPlatformKey` | `true` | 用户 Key 不可用时是否回退到平台 Key |

### 授权变更行为

| 用户操作 | HTTP | 立即影响 |
|---------|------|----------|
| 关闭 `allowAiAnalysis` | PUT /ai/settings | 禁止创建新 Job；pending Job → cancelled；running Job → cancelRequested |
| 关闭 `allowUseDocumentContent` | PUT /ai/settings | 新 Snapshot 不包含资料原文；已生成但未消费的 Snapshot 重用不受影响 |
| 开启 `allowUseDocumentContent` | PUT /ai/settings | 新 Snapshot 可包含资料原文 |
| 关闭 `allowUserModelCredential` | PUT /ai/settings | 已有 credential 不删除但不再用于新 Job；running Job 若使用用户 Key 继续完成 |

## 3. 用户设置项

存储在 `UserLearningProfile` 中：

| 字段 | 类型 | 说明 | 枚举值 |
|------|------|------|--------|
| `learningGoal` | string | 学习目标 | —（自由文本 + 预选项） |
| `currentLevel` | enum | 当前水平 | `beginner` / `basic` / `intermediate` / `advanced` / `expert` |
| `dailyAvailableMinutes` | int | 每日可用学习分钟 | 1-480 |
| `qualityPreference` | enum | 质量偏好 | `light` / `standard` / `deep` / `exam` |
| `ageRange` | enum | 年龄段（可选） | `unknown` / `under_18` / `age_18_24` / `age_25_34` / `age_35_44` / `age_45_54` / `age_55_plus` |
| `occupation` | string | 职业（可选） | 自由文本 |
| `aiAcceptanceLevel` | enum | AI 接受度 | `low` / `medium` / `high` |
| `digitalSkillLevel` | enum | 数字技能 | `low` / `medium` / `high` |
| `preferredQuestionTypes` | string[] | 偏好题型（可选） | 如 `["single_choice", "true_false"]` |
| `preferredLanguage` | string | 偏好语言 | `zh-CN` / `en-US` / `auto` |

## 4. 隐私边界

### 4.1 硬约束（不可被 AI 覆盖）

| 约束 | 来源 | 说明 |
|------|------|------|
| `dailyAvailableMinutes` | 用户设置 | AI 推荐的 estimatedMinutes 不得超过此值 |
| `allowAiAnalysis` | 用户授权 | 关闭时直接拒绝所有 Job |
| `allowUseDocumentContent` | 用户授权 | 关闭时不发送原文给模型 |
| `allowUseLearningBehavior` | 用户授权 | 关闭时不发送行为信号给模型 |

### 4.2 禁止收集的数据

- 精确年龄（只允许 ageRange，且为可选）
- 性别
- 收入
- 精确位置（只用 timezone offset）
- 设备标识符（只用 phone/tablet/desktop/web 类别）

### 4.3 优先级体系

```
Level 0: 隐私授权 / 安全 / 合规 ← 不可覆盖
Level 1: 用户明确设置 ← AI 必须遵守
Level 2: 学习目标和任务类型
Level 3: 测评结果
Level 4: 学习行为信号
Level 5: 内容结构
Level 6: 设备与当前场景
Level 7: 用户画像辅助字段
Level 8: AI 自己的推断
```

### 4.4 设备信息限制

设备信息（phone / tablet / desktop / web）只能用于调整任务形式：

| 设备 | 适合任务 | 不适合任务 |
|------|---------|-----------|
| phone | 卡片、判断、单选、短复习 | 复杂题组、长文阅读 |
| tablet | 阅读、标注、笔记、轻量题组 | — |
| desktop/web | 深度学习、复杂题组、资料整理 | — |

**设备信息不得用于判断学习能力。**

### 4.5 年龄段限制

`ageRange` 只能作为辅助字段。不得根据年龄判断学习能力、智力水平或学习速度。`unknown` 是合法值。

## 5. Snapshot 过滤规则

Snapshot Builder 必须执行：

```typescript
function buildSnapshot(userId: string, targetType: string, targetId: string): Snapshot {
  const settings = getUserAiSettings(userId);
  const profile = getUserLearningProfile(userId);

  return {
    // 硬约束
    constraints: {
      dailyAvailableMinutes: profile?.dailyAvailableMinutes ?? null,
      qualityPreference: profile?.qualityPreference ?? 'standard',
      learningGoal: profile?.learningGoal ?? null,
    },

    // 授权过滤
    privacyScope: {
      allowDocumentContent: settings?.allowUseDocumentContent ?? false,
      allowLearningBehavior: settings?.allowUseLearningBehavior ?? true,
      allowUserProfile: settings?.allowUseUserProfile ?? true,
    },

    // 仅包含授权的数据
    userProfile: settings?.allowUseUserProfile ? pickSafeFields(profile) : undefined,
    learningBehaviorSummary: settings?.allowUseLearningBehavior ? aggregateBehavior(userId, targetType, targetId) : undefined,
    materialProgressSummary: aggregateProgress(userId, targetType, targetId),

    // 资料原文仅当授权
    contentStructureSummary: settings?.allowUseDocumentContent ? aggregateContent(targetType, targetId) : undefined,

    // 明确标注哪些字段可发送模型
    allowedModelFields: buildAllowedFields(settings, profile),

    // 设备上下文
    deviceContext: buildDeviceContext(),

    // 评分信号
    scoreSignals: calculateScores(userId, targetType, targetId),

    // 行为信号（仅当授权）
    behaviorSignals: settings?.allowUseLearningBehavior ? calculateBehaviorSignals(userId, targetType, targetId) : undefined,
  };
}
```

### `pickSafeFields`

从 UserLearningProfile 中只挑选安全字段：
- `learningGoal`, `currentLevel`, `qualityPreference`
- `ageRange`（仅辅助），`preferredLanguage`
- 排除：`dailyAvailableMinutes`（进 constraints，不直接进 profile）
- 排除：`occupation`（除非用户明确同意）

## 6. DeepSeek Prompt 发送规则

Prompt Render（Rust Runtime 侧）只发送 `Snapshot.allowedModelFields` 中列出的字段。未授权字段不在 Snapshot 中出现或标记为未授权。

## 7. 验收清单

- [x] 输出 AI 数据权限设计文档
- [x] 明确哪些字段必填、建议填写、可选
- [x] 明确哪些字段可发送给模型
- [x] 明确用户关闭 AI 授权后的 API 行为
- [x] 明确 Snapshot Builder 必须遵守授权
- [x] 明确 Runtime 无权绕过 API 读取用户隐私数据
- [x] 明确用户可删除自定义 key
- [x] 明确 Admin 不可查看用户 key 明文
