# data-ana 对接契约

> 负责人：ai11
> 关联：[matrix.md](../matrix.md)、[coord-cross-review.md](../../coord-cross-review.md)、[analytics.proto](../../../packages/shared-proto/proto/analytics.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)
> 对齐文档：[01-understanding.md](../../../services/data-ana/docs/01-understanding.md)、[02-architecture-design.md](../../../services/data-ana/docs/02-architecture-design.md)、[objections/data-ana_issue.md](../objections/data-ana_issue.md)、[worklines/data-ana_workline.md](../worklines/data-ana_workline.md)
> 修订：v2（2026-07-10 by ai11）—— 修正 topic 命名 / gRPC 调用声明 / HTTP 端点声明 / 引用断裂，对齐 01/02 v2.1

---

## §1 我提供什么（对外接口）

### 1.1 gRPC 接口

| Service          | RPC                    | 请求                          | 响应                      | 端口  |
| ---------------- | ---------------------- | ----------------------------- | ------------------------- | ----- |
| AnalyticsService | GetClassPerformance    | GetClassPerformanceRequest    | ClassPerformance          | 50055 |
| AnalyticsService | GetStudentWeakness     | GetStudentWeaknessRequest     | StudentWeakness           | 50055 |
| AnalyticsService | GetLearningTrend       | GetLearningTrendRequest       | LearningTrend             | 50055 |
| AnalyticsService | GetTeacherDashboard    | GetTeacherDashboardRequest    | TeacherDashboard          | 50055 |
| AnalyticsService | GetStudentDashboard    | GetStudentDashboardRequest    | StudentDashboard          | 50055 |
| AnalyticsService | GetParentDashboard     | GetParentDashboardRequest     | ParentDashboard           | 50055 |
| AnalyticsService | GetAdminDashboard      | GetAdminDashboardRequest      | AdminDashboard            | 50055 |
| AnalyticsService | GetWarnings            | GetWarningsRequest            | WarningList               | 50055 |
| AnalyticsService | TriggerWarning         | TriggerWarningRequest         | TriggerWarningResponse    | 50055 |
| AnalyticsService | GetMasteryDistribution | GetMasteryDistributionRequest | MasteryDistribution       | 50055 |
| AnalyticsService | GetStudentMastery      | GetStudentMasteryRequest      | StudentMastery            | 50055 |
| AnalyticsService | SubscribeMasteryUpdate | SubscribeMasteryUpdateRequest | stream MasteryUpdateEvent | 50055 |

> **proto 状态**：analytics.proto 当前仅 3 RPC（GetClassPerformance / GetStudentWeakness / GetLearningTrend），扩展至 12 RPC 待 coord 补全或 ai11 在 P4 阶段自行补全（见 [ISSUE-003](../objections/data-ana_issue.md)）。完整 message 定义见 [02-architecture-design.md §4.2](../../../services/data-ana/docs/02-architecture-design.md)。
> **gRPC 启用阶段**：P4 启用（coord-cross-review.md §2.3 裁决），P2-P3 仅 HTTP。
> **响应信封**：所有 RPC 返回 ActionState[T]（coord-cross-review.md §5.3 P0 整改）。

### 1.2 HTTP 端点

data-ana 保留 HTTP :3006 端点作 Gateway 直连降级（gRPC 不可用时 BFF 可走 HTTP）。共 14 端点（3 基础 + 11 业务）：

| method | path                                         | 权限                          | 响应                                |
| ------ | -------------------------------------------- | ----------------------------- | ----------------------------------- |
| GET    | `/healthz`                                   | —                             | `{status, service}`                 |
| GET    | `/readyz`                                    | —                             | `{status, ready, degraded, clickhouse, cdc_consumer, redis, iam_grpc}` |
| GET    | `/metrics`                                   | —                             | Prometheus 格式                     |
| GET    | `/analytics/class/{class_id}/performance`    | `ANALYTICS_CLASS_READ`        | `ActionState<ClassPerformanceData>` |
| GET    | `/analytics/student/{student_id}/weakness`   | `ANALYTICS_STUDENT_READ`      | `ActionState<StudentWeaknessData>`  |
| GET    | `/analytics/student/{student_id}/errorbook`  | `ANALYTICS_STUDENT_READ`      | `ActionState<StudentErrorBookData>` |
| GET    | `/analytics/student/{student_id}/trend`      | `ANALYTICS_STUDENT_READ`      | `ActionState<LearningTrendData>`    |
| GET    | `/analytics/student/{student_id}/attendance` | `ANALYTICS_STUDENT_READ`      | `ActionState<AttendanceData>`       |
| GET    | `/analytics/dashboard/teacher/{user_id}`     | `ANALYTICS_TEACHER_DASHBOARD` | `ActionState<TeacherDashboardData>` |
| GET    | `/analytics/dashboard/student/{user_id}`     | `ANALYTICS_STUDENT_DASHBOARD` | `ActionState<StudentDashboardData>` |
| GET    | `/analytics/dashboard/parent/{user_id}`      | `ANALYTICS_PARENT_DASHBOARD`  | `ActionState<ParentDashboardData>`  |
| GET    | `/analytics/dashboard/admin/{user_id}`       | `ANALYTICS_ADMIN_DASHBOARD`   | `ActionState<AdminDashboardData>`   |
| GET    | `/analytics/warnings`                        | `ANALYTICS_WARNING_READ`      | `ActionState<WarningListData>`      |
| GET    | `/analytics/mastery/distribution`            | `ANALYTICS_CLASS_READ`        | `ActionState<MasteryDistributionData>` |

> 完整端点设计见 [02-architecture-design.md §4.1](../../../services/data-ana/docs/02-architecture-design.md)。

### 1.3 GraphQL schema（如 BFF）

不适用。data-ana 是业务服务，不提供 GraphQL。BFF 层（teacher-bff / student-bff / parent-bff）聚合 data-ana gRPC 后对外暴露 GraphQL。

### 1.4 Kafka 事件发布

| Topic                            | Event           | 消费方                          | Outbox | 说明 |
| -------------------------------- | --------------- | ------------------------------- | ------ | ---- |
| `edu.insight.mastery.updated`    | MasteryUpdated  | core-edu（推荐个性化练习）/ msg | ❌ 豁免 | 掌握度计算完成触发 |
| `edu.insight.warning.triggered`  | WarningTriggered | msg（推送通知）/ core-edu（标记关注） | ❌ 豁免 | 预警阈值触发 |

> **Outbox 豁免**：派生数据事件（非业务事务写），豁免 Outbox 约束（[coord-cross-review.md §3.3](../../coord-cross-review.md) 已仲裁）。直接用 aiokafka AIOKafkaProducer 发布，`idempotent=true` + `transactional_id="data-ana-producer"`。
>
> **topic 命名说明**：按 004 §7.2 命名规范 `edu.<domain>.<aggregate>.<action>`，data-ana 属于 D6 智能洞察领域（domain=insight），故 `edu.insight.mastery.updated` 符合规范。matrix.md §4 中 `edu.analytics.mastery` 命名不符合规范（缺 action 层级，domain 用了服务名而非领域名），已提请 coord 统一（见 [ISSUE-005](../objections/data-ana_issue.md)）。

### 1.5 错误码前缀

`DATA_ANA_`（如 DATA_ANA_DASHBOARD_UNAVAILABLE、DATA_ANA_MASTERY_NOT_COMPUTED、DATA_ANA_CLICKHOUSE_UNAVAILABLE）

> 来源：[matrix.md §6](../matrix.md) 错误码前缀矩阵。

### 1.6 ClickHouse 宽表（供 coord 统一管理 DDL）

| 宽表名                    | 用途                 | 引擎                          |
| ------------------------- | -------------------- | ----------------------------- |
| student_dashboard_view    | 学生学情宽表         | ReplacingMergeTree(last_updated) |
| student_errors            | 学生错题本           | ReplacingMergeTree(last_error_time) |
| mastery_snapshot          | 知识点掌握度历史快照 | MergeTree                     |
| attendance_logs           | 学生考勤记录         | ReplacingMergeTree(occurred_at) |
| ai_usage_log              | AI 用量计费记录      | ReplacingMergeTree(occurred_at) |

> DDL 由 coord 统一管理在 `infra/clickhouse/ddl/`（待 coord 建立），data-ana 提供内容。完整 DDL 见 [02-architecture-design.md §3](../../../services/data-ana/docs/02-architecture-design.md)。

---

## §2 我消费什么（依赖上游）

### 2.1 gRPC 调用（同步）

| 调用方   | 被调用方 | RPC                       | 用途                         | 端口  | 阶段 | 状态 |
| -------- | -------- | ------------------------- | ---------------------------- | ----- | ---- | ---- |
| data-ana | iam      | GetEffectiveDataScope     | DataScope 6 级过滤解析       | 50052 | P4   | ⚠️ iam.proto 当前未实现（ISSUE-001） |

> **降级兜底**：iam GetEffectiveDataScope 未就绪时，data-ana 按 role 映射默认 DataScope（教师=CLASS，学生=SELF，管理员=SCHOOL），标注 `details.degraded: true`。结果 Redis 缓存 5min（key: `data_ana:datascope:{user_id}`）。
>
> **裁决依据**：coord-cross-review.md §2.2 #3 已仲裁 iam P4 补全此 RPC。

### 2.2 Kafka 事件订阅（异步）

| Topic                                  | Event               | 发布方          | mock 策略                                         |
| -------------------------------------- | ------------------- | --------------- | ------------------------------------------------- |
| `edu.teaching.exam.published`          | ExamEvent           | core-edu (ai08) | core-edu 就绪前使用 CDC 模拟数据 + 本地 stub 事件 |
| `edu.teaching.homework.assigned`       | HomeworkEvent       | core-edu (ai08) | 同上                                              |
| `edu.teaching.grade.recorded`          | GradeEvent          | core-edu (ai08) | 同上                                              |
| `edu.teaching.class.transferred`       | ClassEvent          | core-edu (ai08) | 同上                                              |
| `edu.content.kp.events`                | KnowledgePointEvent | content (ai09)  | content 就绪前使用内置知识点维度表                |
| `edu.content.question.events`          | QuestionEvent       | content (ai09)  | content 就绪前忽略                                |
| `edu.insight.ai.usage`                 | AIUsageEvent        | ai (ai12)       | ai 就绪前忽略，AI 用量统计为空（P5）              |

> **topic 命名对齐**：core-edu 教学事件 topic 按 coord-cross-review.md §3.1 裁决统一为 `edu.teaching.<aggregate>.<action>` 风格。core-edu 代码实际发布 `edu.exam.events` 等，待 core-edu 整改后切换。
>
> **AIUsageEvent 缺失**：events.proto 当前缺 AIUsageEvent message（ISSUE-002），P5 前需 coord 补全。

### 2.3 HTTP 调用（如有）

无。data-ana 不通过 HTTP 调用上游服务。

### 2.4 CDC 数据源

| 数据源                                                | 用途                            | mock 策略                                                                   |
| ----------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------- |
| core-edu MySQL（exams/homework/grades/attendance 表） | Debezium CDC → Kafka 同步读模型 | core-edu 就绪前使用 ClickHouse 内置模拟数据集（30 学生 × 5 考试 × 10 作业） |
| content MySQL（knowledge_points 表）                  | Debezium CDC → 知识点元数据同步 | content 就绪前使用内置固定知识点表（数学 50 个知识点）                      |
| iam MySQL（users 表）                                 | Debezium CDC → 用户 dataScope 同步 | iam 就绪前使用硬编码 DataScope 降级                                         |

> **CDC topic 命名**：`edu-cdc.next_edu_cloud.<table>`（coord-cross-review.md §3.2 裁决补登 CDC topic 命名规范段，待 coord 在 004 §7.2 落实）。

---

## §3 就绪信号

### 3.1 我依赖的上游就绪标志

- [ ] core-edu gRPC 50053 启用（ai08）—— 业务事件 + CDC 数据源
- [ ] core-edu MySQL Debezium CDC 配置（ai08 + SRE）—— CDC 通道前提
- [ ] `edu.teaching.exam.published` / `edu.teaching.homework.assigned` / `edu.teaching.grade.recorded` / `edu.teaching.class.transferred` topic 有事件发布（ai08）
- [ ] content gRPC 50054 启用（ai09）—— 知识点维度 CDC
- [ ] `edu.content.kp.events` topic 有事件发布（ai09）
- [ ] iam.proto 补全 GetEffectiveDataScope RPC（ai06/coord，ISSUE-001）—— P4 阻塞项
- [ ] analytics.proto 扩展至 12 RPC（coord/ai11，ISSUE-003）—— gRPC stub 生成前提
- [ ] events.proto 补全 AIUsageEvent message（coord，ISSUE-002）—— P5 前补全
- [ ] ai gRPC 50057 启用（ai12）—— AI 用量统计（P5，可选）

### 3.2 我的就绪标志（供下游消费）

- [ ] **P4 就绪**：data-ana gRPC 50055 启用（HealthService.Check 返回 SERVING）
- [ ] **P4 就绪**：AnalyticsService 12 RPC 可调用（含 4 端 Dashboard + Warning + Mastery + Server Streaming SubscribeMasteryUpdate）
- [ ] **P4 就绪**：GetTeacherDashboard / GetStudentDashboard / GetParentDashboard / GetAdminDashboard 返回结构化数据
- [ ] **P4 就绪**：`edu.insight.mastery.updated` topic 可发布（mastery.updated / warning.triggered）
- [ ] **P5 就绪**：SubscribeMasteryUpdate server-streaming RPC 可订阅
- [ ] **P6 就绪**：CDC 多实例水平扩展 + ExamCache Redis 化完成

---

## §4 Mock 策略

### 4.1 我提供的 mock

在 data-ana 真实服务就绪前，为下游（teacher-bff / student-bff / parent-bff / admin-portal / msg）提供以下 mock：

- **gRPC mock**：使用 grpc-mock 拦截 50055 端口
  - GetTeacherDashboard 返回固定仪表盘（total_classes=3, class_avg_score=82.5, top_students 5 个, pending_homework_count=8）
  - GetStudentDashboard 返回固定仪表盘（avg_score=85.0, class_rank=5, weak_points 3 个）
  - GetParentDashboard 返回固定仪表盘（child_avg_score=85.0, child_class_rank=5）
  - GetAdminDashboard 返回固定仪表盘（total_teachers=50, total_students=1200, school_avg_score=80.0）
  - GetWarnings 返回固定 5 条预警（severity: warning/critical）
  - GetMasteryDistribution 返回固定分布（mastered=20, progressing=7, weak=3）
  - SubscribeMasteryUpdate 返回固定流（每 5 秒推 1 个 MasteryUpdateEvent）
- **Kafka mock**：data-ana 就绪前不发布真实 MasteryUpdated / WarningTriggered，msg 使用本地 stub 预警

### 4.2 我消费的 mock

在真实上游就绪前，data-ana 使用以下 mock：

- **业务数据**：ClickHouse 内置模拟数据集（30 学生 × 5 考试 × 10 作业 × 30 天出勤），不依赖 core-edu CDC
- **知识点维度**：内置固定知识点表（数学 50 个知识点），不依赖 content 事件
- **AI 用量**：AIUsageEvent 为空，仪表盘 AI 用量区块显示"暂无数据"
- **CDC 通道**：core-edu 就绪前 Debezium 不启动，使用 ClickHouse 批量导入模拟数据
- **DataScope**：iam GetEffectiveDataScope 未就绪前，按 role 映射默认 DataScope 降级（教师=CLASS，学生=SELF，管理员=SCHOOL），标注 `details.degraded: true`
