# core-edu 工作排期

> 负责人：ai08
> 关联：[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/core-edu_contract.md](../contracts/core-edu_contract.md)、[services/core-edu/docs/02-architecture-design.md](../../../services/core-edu/docs/02-architecture-design.md)
> 模式：全并行（各 AI 一口气完成 P2-P6 全部代码，最后统一集成测试）
> 批次归属：批次 2（P3 核心教学），关键路径

---

## §1 总览

core-edu 是教学核心服务，承载 D2 教学组织（classes）+ D3 教学核心（exams / homework / grades / attendance / schedule）两个限界上下文。

**全阶段目标**：

| 阶段 | 目标 | 就绪信号 |
| ---- | ---- | -------- |
| P2（已部分完成） | 服务骨架 + Outbox 模式 + REST CRUD + 三支柱可观测 | HTTP 3004 可访问 + /healthz + /readyz（DB 探针） |
| P3（核心） | gRPC 50053 启用 + 5 Service 全量 RPC + 状态机 + Outbox 事件全量 + 排课考勤 + 成绩计算配置化 + Temporal 试点 | gRPC 50053 + 27 RPC + HealthService SERVING |
| P4 | 持续优化 + 消费 data-ana mastery 事件 + content gRPC 调用（知识点关联） | — |
| P5 | 配合 msg 服务事件消费联调 + AI 辅助批改预留 | — |
| P6 | /readyz 硬化 + Outbox relay 迁独立 Go 服务评估 + 多租户行级隔离 | — |

**当前状态（2026-07-10）**：
- ✅ P2 骨架已就绪（exams/homework/grades 三域 REST CRUD + Outbox + 三支柱）
- ⏳ P3 待启动（依赖 ISSUE-001 ~ ISSUE-006 仲裁结果）

---

## §2 全阶段甘特图（P2-P6）

```mermaid
gantt
    title ai08 core-edu 全阶段排期（P2-P6）
    dateFormat YYYY-MM-DD
    axisFormat %m-%d

    section P2 骨架（已就绪）
    P2 服务骨架+Outbox+REST CRUD :done, p2, 2026-07-01, 7d

    section P3 核心教学（批次2 关键路径）
    P3.0 等待 coord 仲裁 ISSUE-001~006 :crit, p3a, 2026-07-10, 2d
    P3.1 黄金模板对齐（Drizzle getDb+Zod+kafka logger+/readyz探针） :crit, p3b, after p3a, 2d
    P3.2 TOPIC_MAP 重命名 edu.teaching.* + payload schema_version/event_id :crit, p3c, after p3b, 1d
    P3.3 考试状态机+作业状态机+成绩幂等 :crit, p3d, after p3c, 3d
    P3.4 gRPC server 50053 启用+5 Service 27 RPC :crit, p3e, after p3d, 3d
    P3.5 排课考勤数据模型+AttendanceService :p3f, after p3e, 2d
    P3.6 成绩计算配置化（grade_formulas+GradeCalculator） :p3g, after p3e, 2d
    P3.7 作业提交 Redis 分布式锁 :p3h, after p3e, 1d
    P3.8 DataScope 下推（Repository WHERE 注入） :p3i, after p3e, 1d
    P3.9 消费 IAM 事件（user.created/updated/deleted） :p3j, after p3e, 1d
    P3.10 Temporal 工作流试点（考试发布编排） :p3k, after p3e, 2d
    P3.11 classes 服务合并到 core-edu :p3l, after p3e, 2d
    P3.12 测试覆盖率≥80%+Dockerfile多阶段核对 :p3m, after p3l, 2d

    section P4 持续优化
    P4.1 消费 data-ana mastery.updated 事件 :p4a, after p3m, 2d
    P4.2 content gRPC 调用（知识点关联） :p4b, after p4a, 2d
    P4.3 读模型双轨读策略验证（MySQL+ClickHouse） :p4c, after p4b, 1d

    section P5 联调
    P5.1 msg 事件消费联调（通知触发） :p5a, after p4c, 2d
    P5.2 AI 辅助批改接口预留（GetExam/ListGradesByExam） :p5b, after p5a, 1d

    section P6 硬化
    P6.1 /readyz 硬化（Kafka+Redis+Temporal 探针） :p6a, after p5b, 1d
    P6.2 Outbox relay 迁独立 Go 服务评估 :p6b, after p6a, 2d
    P6.3 多租户行级隔离验证 :p6c, after p6b, 1d
```

**关键路径**：P3.0（仲裁）→ P3.1（模板对齐）→ P3.2（TOPIC_MAP）→ P3.3（状态机）→ P3.4（gRPC）→ P3.5/P3.6/P3.7/P3.8/P3.9/P3.10/P3.11（并行）→ P3.12（测试）

**预计工期**：
- P3：13 天（含 2 天等待仲裁）
- P4：5 天
- P5：3 天
- P6：4 天
- 合计：25 天（P3 后）

---

## §3 详细任务

### P3.0 等待 coord 仲裁 ISSUE-001 ~ ISSUE-006

- **负责人**：ai08（等待 coord）
- **依赖**：无
- **交付物**：coord 出具仲裁结论（见 [coord.md](../coord.md) 后续章节）
- **验收标准**：6 项 ISSUE 全部裁决，状态更新为"已裁决"
- **阻塞说明**：
  - ISSUE-001（proto 实际状态）：阻塞 P3.4 gRPC 实现
  - ISSUE-002（events.proto 未同步）：阻塞 P3.2 TOPIC_MAP 重命名
  - ISSUE-003（状态命名不一致）：阻塞 P3.3 状态机实现
  - ISSUE-004（class.transferred topic）：阻塞 P3.11 classes 合并
  - ISSUE-005（RPC 数量口径）：阻塞就绪信号声明
  - ISSUE-006（7 项设计决策）：阻塞 P3 全部实施

### P3.1 黄金模板对齐（P0）

- **负责人**：ai08
- **依赖**：无（可与其他 P3 任务并行）
- **交付物**：
  1. `src/config/database.ts` 改为 `getDb()` 函数式（对齐 classes 黄金模板）
  2. `src/config/kafka.ts` L20/L22 `console.log`/`console.warn` → `logger.info`/`logger.warn`
  3. 全部 Controller 接入 Zod ValidationPipe（exams/homework/grades schema 已存在，需接入到 Controller）
  4. `src/shared/health/health.controller.ts` /readyz 补 Redis ping + Kafka producer 连接探针
- **验收标准**：
  - `pnpm run lint` + `pnpm run typecheck` 零错误
  - /readyz 返回 3 项依赖状态（MySQL + Redis + Kafka）
  - kafka.ts 无 console.* 调用

### P3.2 TOPIC_MAP 重命名 + payload 补字段（P0）

- **负责人**：ai08
- **依赖**：ISSUE-002 仲裁（events.proto 同步）
- **交付物**：
  1. `src/shared/outbox/outbox.publisher.ts` TOPIC_MAP 改为 `edu.teaching.<aggregate>.<action>` 风格：
     - `edu.exam.events` → `edu.teaching.exam.created` / `.updated` / `.deleted` / `.published` / `.submitted`
     - `edu.homework.events` → `edu.teaching.homework.assigned` / `.submitted` / `.graded`
     - `edu.grade.events` → `edu.teaching.grade.recorded` / `.updated`
     - `edu.class.events` → 按 ISSUE-004 仲裁结果
     - 新增 `edu.teaching.attendance.recorded`
  2. outbox payload 补 `schema_version`（默认 `"v1"`）+ `event_id`（UUID）+ `occurred_at`（业务时间戳）+ `metadata: { traceId, userId }`
  3. `src/shared/outbox/outbox.schema.ts` 补 `event_id`、`occurred_at`、`next_retry_at` 字段 + `uniq_event_id` 唯一索引
- **验收标准**：
  - TOPIC_MAP 命名符合 coord §3.1 仲裁
  - outbox 表新增字段已迁移
  - payload JSON 含 schema_version + event_id + occurred_at + metadata

### P3.3 考试/作业状态机 + 成绩幂等（P1）

- **负责人**：ai08
- **依赖**：ISSUE-003 仲裁（状态命名统一）
- **交付物**：
  1. `src/exams/domain/exam-state-machine.ts`（纯函数：canTransition / transition）
  2. `src/homework/domain/homework-state-machine.ts`（纯函数）
  3. `src/exams/exams.service.ts` 接入状态机（PublishExam / StartExam / SubmitExam / GradeExam / ArchiveExam）
  4. `src/homework/homework.service.ts` 接入状态机（SubmitHomework / GradeHomework）
  5. `src/grades/grades.schema.ts` 补 `idempotency_key` 字段 + `uniq_student_exam` / `uniq_student_hw` / `uniq_idempotency` 唯一索引
  6. `src/grades/grades.service.ts` 实现幂等录入（先 SELECT 检查，再 INSERT）
- **验收标准**：
  - 状态机非法转换抛 `CORE_EDU_EXAM_INVALID_STATUS_TRANSITION`（409）
  - 同一学生同一考试/作业重复录入返回已存在成绩（幂等）
  - 状态命名按 ISSUE-003 仲裁结果统一

### P3.4 gRPC server 启用 + 5 Service 27 RPC（P1）

- **负责人**：ai08
- **依赖**：ISSUE-001 仲裁（proto 补全）、ISSUE-005 仲裁（RPC 统计口径）
- **交付物**：
  1. `src/main.ts` 启用 gRPC server（端口 50053，`@grpc/grpc-js` + `@bufbuild/protobuf`）
  2. `src/exams/exams.grpc-controller.ts`（ExamService 8 RPC：CreateExam / GetExam / ListExamsByClass / UpdateExam / DeleteExam / PublishExam / SubmitExam / GradeExam）
  3. `src/homework/homework.grpc-controller.ts`（HomeworkService 5 RPC：AssignHomework / GetHomework / ListHomeworkByClass / SubmitHomework / GradeHomework）
  4. `src/grades/grades.grpc-controller.ts`（GradeService 6 RPC：RecordGrade / GetGrade / ListGradesByStudent / ListGradesByExam / ListGradesByHomework / UpdateGrade）
  5. `src/attendance/attendance.grpc-controller.ts`（AttendanceService 4 RPC：RecordAttendance / GetAttendance / ListAttendanceByStudent / ListAttendanceByClass）
  6. `src/classes/classes.grpc-controller.ts`（ClassService 4 RPC：GetClass / GetClassesByTeacher / BatchGetClasses / ListStudentsByClass）
  7. `src/shared/health/health.grpc-controller.ts`（HealthService.Check 返回 SERVING）
- **验收标准**：
  - gRPC 50053 可连接，HealthService.Check 返回 SERVING
  - 27 RPC 全部可调用（含 P3 新增 5 RPC）
  - gRPC 调用走 AuthMiddleware + PermissionGuard（gRPC 上下文适配）

### P3.5 排课考勤数据模型 + AttendanceService（P2）

- **负责人**：ai08
- **依赖**：P3.4 gRPC 启用
- **交付物**：
  1. `src/schedule/schedule.schema.ts`（core_edu_courses / core_edu_lessons / core_edu_schedules 三表）
  2. `src/attendance/attendance.schema.ts`（core_edu_attendance 表）
  3. `src/schedule/schedule.service.ts`（含 ScheduleConflictChecker：教师/班级时间冲突检测）
  4. `src/attendance/attendance.service.ts`（含唯一索引幂等）
  5. `src/schedule/schedule.controller.ts` + `src/attendance/attendance.controller.ts`（REST + gRPC 双入口）
- **验收标准**：
  - 排课冲突检测正确（教师时间重叠抛 CORE_EDU_SCHEDULE_CONFLICT 409）
  - 考勤录入幂等（同一学生同一课时仅 1 条）

### P3.6 成绩计算配置化（P2）

- **负责人**：ai08
- **依赖**：P3.4 gRPC 启用、ISSUE-006 决策 #3（scope 优先级）
- **交付物**：
  1. `src/grades/grade-formulas.schema.ts`（core_edu_grade_formulas 表：scope / scope_id / formula_type / weights / custom_expression / effective_from / effective_to）
  2. `src/grades/domain/grade-calculator.ts`（纯函数：按 scope 优先级查询公式 → 加权/平均/自定义求值）
  3. `src/grades/grades.service.ts` 接入 GradeCalculator
  4. custom 公式安全求值（白名单正则 + Function 沙箱）
- **验收标准**：
  - 三种公式类型（weighted / average / custom）均正确计算
  - scope 优先级按 ISSUE-006 决策 #3 仲裁结果
  - custom 公式禁止函数调用 / 对象访问 / eval（白名单校验）

### P3.7 作业提交 Redis 分布式锁（P2）

- **负责人**：ai08
- **依赖**：P3.4 gRPC 启用、Redis 部署
- **交付物**：
  1. `src/shared/redis/redis.client.ts`（Redis 单例）
  2. `src/homework/homework.service.ts` submitHomework 接入 Redis 分布式锁
     - 锁 key：`lock:hw:submit:{homeworkId}:{studentId}`
     - 锁过期：30s
     - 重试：5 次 × 100ms 间隔
     - 超时：429 CORE_EDU_HOMEWORK_SUBMIT_LOCK_TIMEOUT
  3. 同步对 exam submit 也接入锁（锁 key：`lock:exam:submit:{examId}:{studentId}`）
- **验收标准**：
  - 50 并发提交同一作业，仅 1 条提交入库
  - 锁超时返回 429
  - DB 唯一索引兜底（锁失效时仍幂等）

### P3.8 DataScope 下推（P1）

- **负责人**：ai08
- **依赖**：无
- **交付物**：
  1. `src/shared/datascope/datascope-injector.ts`（根据 x-user-data-scope 头注入 WHERE 条件）
  2. `src/exams/exams.repository.ts` 接入 DataScopeInjector（按 class_id / school_id 过滤）
  3. `src/grades/grades.repository.ts` 接入 DataScopeInjector（按 student_id / class_id / school_id 过滤）
  4. `src/middleware/permission.guard.ts` 增强：解析 dataScope 注入到 request
- **验收标准**：
  - 教师仅能查自己班级的考试/成绩
  - 学生仅能查自己的成绩
  - 校管理员可查全校

### P3.9 消费 IAM 事件（P1）

- **负责人**：ai08
- **依赖**：IAM gRPC 50052 就绪（ai06）
- **交付物**：
  1. `src/shared/kafka/kafka.consumer.ts`（Kafka consumer 单例）
  2. `src/iam-events/iam-user.consumer.ts`（订阅 edu.identity.user.created / .updated / .deleted）
  3. `src/iam-events/teacher-associations.schema.ts`（core_edu_teacher_associations 表 + uniq_teacher_class_subject 唯一索引）
  4. 消费幂等（基于 user_id + class_id + subject_id 唯一索引）
- **验收标准**：
  - IAM 发布 user.created 事件后，core-edu 写入 teacher_associations
  - 重复消费同一事件不重复写入（幂等）
  - user.deleted 事件软删除教师关联（保留历史成绩归属）

### P3.10 Temporal 工作流试点（P3）

- **负责人**：ai08
- **依赖**：Temporal server 部署（infra）、P3.4 gRPC 启用
- **交付物**：
  1. `src/workflows/exam-publish.workflow.ts`（考试发布编排工作流）
  2. `src/workflows/exam-publish.activities.ts`（5 个 Activity：创建提交骨架 / 通知 msg / 等待作答窗口 / 自动提交未答 / 通知教师）
  3. `src/exams/exams.service.ts` publishExam 调用 `workflowClient.start(examPublishWorkflow, ...)`
  4. `src/shared/temporal/temporal.client.ts`（Temporal client 单例）
- **验收标准**：
  - 考试发布后 Temporal UI 可见工作流实例
  - 工作流完成 5 个 Activity
  - 失败可重试

### P3.11 classes 服务合并到 core-edu（P3）

- **负责人**：ai08
- **依赖**：ISSUE-006 决策 #1（合并时机）仲裁、P3.4 gRPC 启用
- **交付物**：
  1. `services/classes/src/` 代码迁入 `services/core-edu/src/classes/`
  2. 删除独立 `services/classes/` 目录
  3. `src/classes/classes.module.ts` 接入 core-edu AppModule
  4. `src/classes/classes.grpc-controller.ts`（ClassService 4 RPC）
  5. classes 错误码 `CLASSES_*` 保留（coord §5.5 仲裁，黄金模板历史遗留）
- **验收标准**：
  - classes 服务代码完全迁入 core-edu
  - ClassService 4 RPC 可调用
  - 原 classes 服务端口 3001 不再存在
  - `pnpm run arch:scan` 确认 arch.db 已更新

### P3.12 测试覆盖率 + Dockerfile 核对（P3）

- **负责人**：ai08
- **依赖**：P3.1 ~ P3.11 全部完成
- **交付物**：
  1. 单元测试：状态机 / GradeCalculator / ScheduleConflictChecker / DataScopeInjector（纯函数优先）
  2. 集成测试：ExamsService / HomeworkService / GradesService / AttendanceService / ScheduleService
  3. Outbox relay 测试（mock Kafka）
  4. `Dockerfile` 多阶段构建核对（builder → runner，最终镜像无 devDependencies）
- **验收标准**：
  - `pnpm run test` 通过
  - 覆盖率 ≥ 80%
  - Dockerfile 多阶段构建，最终镜像 ≤ 200MB

### P4.1 消费 data-ana mastery.updated 事件

- **负责人**：ai08
- **依赖**：data-ana gRPC 50055 就绪（ai11）
- **交付物**：`src/data-ana-events/mastery.consumer.ts`（订阅 edu.insight.mastery.updated，基于 mastery_score_id 幂等）
- **验收标准**：重复消费不重复写入

### P4.2 content gRPC 调用（知识点关联）

- **负责人**：ai08
- **依赖**：content gRPC 50054 就绪（ai09）
- **交付物**：`src/content/content.client.ts`（调用 ContentService.GetKnowledgePoints，排课关联知识点）
- **验收标准**：lessons.knowledge_point_ids 引用的知识点在 content 服务可查

### P4.3 读模型双轨读策略验证

- **负责人**：ai08
- **依赖**：data-ana CDC 链路就绪
- **交付物**：验证刚提交成绩查 MySQL（强一致），聚合统计查 ClickHouse（最终一致 < 5s）
- **验收标准**：双轨读策略符合 02 文档 §3.3

### P5.1 msg 事件消费联调

- **负责人**：ai08
- **依赖**：msg gRPC 50056 就绪（ai10）
- **交付物**：验证 msg 消费 core-edu 事件触发通知（edu.teaching.exam.created / homework.assigned / grade.recorded）
- **验收标准**：端到端事件链路通

### P5.2 AI 辅助批改接口预留

- **负责人**：ai08
- **依赖**：无
- **交付物**：确认 ExamService.GetExam / GradeService.ListGradesByExam 接口对 ai 服务可用
- **验收标准**：ai 服务可调用上述 RPC

### P6.1 /readyz 硬化

- **负责人**：ai08
- **依赖**：无
- **交付物**：/readyz 补 Temporal client 连接探针
- **验收标准**：/readyz 返回 4 项依赖状态（MySQL + Redis + Kafka + Temporal）

### P6.2 Outbox relay 迁独立 Go 服务评估

- **负责人**：ai08（评估，不实施）
- **依赖**：无
- **交付物**：评估报告（是否迁出进程内 relay）
- **验收标准**：产出评估结论

### P6.3 多租户行级隔离验证

- **负责人**：ai08
- **依赖**：无
- **交付物**：验证 school_id 行级隔离（不同学校数据互不可见）
- **验收标准**：跨学校查询返回空

---

## §4 依赖与就绪信号

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

| 依赖项 | 提供方 | 就绪信号 | 状态 | 影响 |
| ------ | ------ | -------- | ---- | ---- |
| coord 仲裁 ISSUE-001 ~ ISSUE-006 | coord | coord.md 仲裁章节 | ⏳ 待仲裁 | 阻塞 P3 全部 |
| iam gRPC 50052 | ai06 | HealthService.Check = SERVING | ⏳ | 阻塞 P3.9 消费 IAM 事件 |
| events.proto 同步 | coord | events.proto 含 AttendanceEvent + schema_version | ⏳ | 阻塞 P3.2 TOPIC_MAP |
| core_edu.proto 补全 | coord 或 ai08 | 5 service 27 RPC 定义 | ⏳ | 阻塞 P3.4 gRPC 实现 |
| Redis 部署 | infra | redis:6379 可连接 | ⏳ | 阻塞 P3.7 分布式锁 + P3.1 /readyz |
| Temporal server 部署 | infra | temporal:7233 可连接 | ⏳ | 阻塞 P3.10 工作流试点 |
| buf.gen.yaml gRPC 插件 | coord | buf generate 产出 TS gRPC 代码 | ⏳ | 阻塞 P3.4 gRPC 实现 |
| data-ana gRPC 50055（P4） | ai11 | HealthService.Check = SERVING | ⏳ | 阻塞 P4.1 mastery 消费 |
| content gRPC 50054（P4） | ai09 | HealthService.Check = SERVING | ⏳ | 阻塞 P4.2 知识点关联 |
| msg gRPC 50056（P5） | ai10 | HealthService.Check = SERVING | ⏳ | 阻塞 P5.1 事件联调 |

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

| 阶段 | 就绪信号 | 消费方 | 状态 |
| ---- | -------- | ------ | ---- |
| P2（已就绪） | HTTP 3004 可访问 + /healthz + /readyz（DB 探针） | teacher-bff（REST 调用） | ✅ |
| P3（核心） | gRPC 50053 + 27 RPC + HealthService SERVING | teacher-bff / student-bff / parent-bff / ai | ⏳ |
| P3 子信号 1 | ClassService 4 RPC 可调用 | teacher-bff（班级列表） | ⏳ |
| P3 子信号 2 | ExamService 8 RPC 可调用 | teacher-bff / student-bff / ai | ⏳ |
| P3 子信号 3 | HomeworkService 5 RPC 可调用 | teacher-bff / student-bff | ⏳ |
| P3 子信号 4 | GradeService 6 RPC 可调用 | teacher-bff / student-bff / parent-bff | ⏳ |
| P3 子信号 5 | AttendanceService 4 RPC 可调用 | parent-bff | ⏳ |
| P3 子信号 6 | edu.teaching.* topic 可发布（含 attendance.recorded） | msg / data-ana / push-gateway | ⏳ |

### 4.3 Mock 策略（全并行开发期间）

详见 [contracts/core-edu_contract.md §4](../contracts/core-edu_contract.md)。
