# 成绩和学情诊断模块审计报告

> 审查日期：2026-06-22
> 审查范围：`src/modules/grades/**`（成绩模块）、`src/modules/diagnostic/**`（学情诊断模块）、`src/app/(dashboard)/{admin,teacher,student,parent}/grades/**`、`src/app/(dashboard)/{teacher,student}/diagnostic/**`、`src/app/(dashboard)/management/grade/**`、相关 i18n 翻译文件
> 架构图参考：`docs/architecture/004_architecture_impact_map.md` §2.6（grades）、§2.22（diagnostic）、`docs/architecture/005_architecture_data.json` L7362（grades）、L10927（diagnostic）

---

## 一、现有实现概要

### 1.1 文件分布

#### grades 模块（成绩分析）

| 层 | 路径 | 文件数 | 行数 | 说明 |
|----|------|--------|------|------|
| Actions | `src/modules/grades/actions.ts` | 1 | 312 | 10 个 Server Action（CRUD + 查询 + 导出） |
| Actions | `src/modules/grades/actions-analytics.ts` | 1 | 133 | 5 个分析 Server Action（趋势/对比/分布/排名） |
| Data-access | `src/modules/grades/data-access.ts` | 1 | 433 | 成绩 CRUD + 统计（含统计业务逻辑） |
| Data-access | `src/modules/grades/data-access-analytics.ts` | 1 | 337 | 趋势/对比/分布分析（含统计业务逻辑） |
| Data-access | `src/modules/grades/data-access-ranking.ts` | 1 | 119 | 排名查询（含 normalize 逻辑） |
| Export | `src/modules/grades/export.ts` | 1 | 200 | Excel 导出（明细 + 班级汇总） |
| Schema | `src/modules/grades/schema.ts` | 1 | 52 | 4 个 Zod schema |
| Types | `src/modules/grades/types.ts` | 1 | 186 | 14 个类型定义 |
| Components | `src/modules/grades/components/*` | 16 | 41~442 | 16 个组件（含 batch-grade-entry 442 行） |

#### diagnostic 模块（学情诊断）

| 层 | 路径 | 文件数 | 行数 | 说明 |
|----|------|--------|------|------|
| Actions | `src/modules/diagnostic/actions.ts` | 1 | 172 | 6 个 Server Action（生成/发布/删除/查询） |
| Data-access | `src/modules/diagnostic/data-access.ts` | 1 | 257 | 知识点掌握度查询 + 更新 |
| Data-access | `src/modules/diagnostic/data-access-reports.ts` | 1 | 203 | 诊断报告 CRUD（**直查 users 表**） |
| Schema | `src/modules/diagnostic/schema.ts` | 1 | 48 | 6 个 Zod schema |
| Types | `src/modules/diagnostic/types.ts` | 1 | 97 | 11 个类型定义 |
| Components | `src/modules/diagnostic/components/*` | 4 | 69~267 | 4 个组件（含 class-diagnostic-view 267 行） |

#### 路由层

| 角色 | 路由 | 文件数 | 说明 |
|------|------|--------|------|
| admin | `/admin/school/grades/`、`/admin/school/grades/insights/` | 4 | 含 loading.tsx + error.tsx |
| teacher | `/teacher/grades/`、`/teacher/grades/analytics/`、`/teacher/grades/entry/`、`/teacher/grades/stats/` | 4 | **无 loading.tsx / error.tsx** |
| teacher | `/teacher/diagnostic/`、`/teacher/diagnostic/class/[classId]/`、`/teacher/diagnostic/student/[studentId]/` | 3 | **无 loading.tsx / error.tsx** |
| student | `/student/grades/`、`/student/diagnostic/` | 4 | 含 loading.tsx，**无 error.tsx** |
| parent | `/parent/grades/` | 2 | 含 loading.tsx，**无 error.tsx** |
| management | `/management/grade/`、`/management/grade/classes/`、`/management/grade/insights/` | 5 | **`/management/grade/page.tsx` 缺失**（孤儿 loading/error） |

### 1.2 主要数据流

```
[成绩录入] teacher/grades/entry
   └─▶ grades/actions.batchCreateGradeRecordsAction
         ├─▶ requirePermission(GRADE_RECORD_MANAGE)
         └─▶ data-access.batchCreateGradeRecords → db.insert(gradeRecords)

[成绩查询] teacher/grades / student/grades / parent/grades
   └─▶ grades/actions.getGradeRecordsAction
         ├─▶ requirePermission(GRADE_RECORD_READ)
         ├─▶ data-access.getGradeRecords（含 scope 行级过滤）
         └─▶ 跨模块：classes/school/users data-access

[成绩分析] teacher/grades/analytics
   └─▶ grades/actions-analytics.getGradeTrendAction / getClassComparisonAction / ...
         ├─▶ requirePermission(GRADE_RECORD_READ)
         └─▶ data-access-analytics（含统计计算逻辑）

[学情诊断-学生] teacher/diagnostic/student/[id] / student/diagnostic
   └─▶ diagnostic/data-access.getStudentMasterySummary
         └─▶ 跨模块：users data-access（getUserNamesByIds）

[学情诊断-班级] teacher/diagnostic/class/[id]
   └─▶ diagnostic/data-access.getClassMasterySummary
         └─▶ 跨模块：classes/exams/questions/users data-access

[诊断报告生成] teacher/diagnostic
   └─▶ diagnostic/actions.generateStudentReportAction / generateClassReportAction
         ├─▶ requirePermission(DIAGNOSTIC_MANAGE)
         └─▶ data-access-reports.createDiagnosticReport
               └─▶ ⚠️ 直查 users 表（违反三层架构）
```

### 1.3 架构图记录情况

`004_architecture_impact_map.md` §2.6（grades）和 §2.22（diagnostic）已记录两个模块的导出函数、依赖关系、已知问题和文件清单。架构图信息基本完整，但存在以下遗漏：

- **grades 模块行数过时**：架构图 L681 标注 `data-access.ts` 419 行（实际 433 行）、L682 `data-access-analytics.ts` 293 行（实际 337 行）
- **diagnostic 模块 deps 过时**：`005_architecture_data.json` L10922/L10937-10941/L10954-10958/L10972-10975 仍记录 diagnostic 直查对方表，实际代码已通过 data-access 接口访问（P1-1 已修复但文档未同步）
- **diagnostic `data-access-reports.ts` 直查 users 表未记录**：架构图未标注此违规
- **grades 模块 actions-analytics.ts 的 5 个 Action 未完整列入 exports 清单**
- **`/management/grade/page.tsx` 缺失**未在路由清单中标注
- **teacher 端 grades/diagnostic 路由普遍缺少 loading.tsx/error.tsx** 未标注

---

## 二、现存问题与原因分析

### 2.1 安全性：权限校验缺失或不一致（P0）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [teacher/grades/entry/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/grades/entry/page.tsx) | **无任何权限校验**（既无 `requirePermission` 也无 `getAuthContext`） | "所有 Server Action 必须调用 `requirePermission()` 进行权限校验" |
| [teacher/grades/stats/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/grades/stats/page.tsx) | **无任何权限校验** | 同上 |
| [teacher/grades/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/grades/page.tsx) | 仅 `getAuthContext()`，无 `requirePermission(GRADE_RECORD_READ)` | 同上 |
| [teacher/grades/analytics/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/grades/analytics/page.tsx) | 仅 `getAuthContext()`，无 `requirePermission(GRADE_RECORD_READ)` | 同上 |
| [teacher/diagnostic/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/diagnostic/page.tsx) | 仅 `getAuthContext()`，无 `requirePermission(DIAGNOSTIC_READ)` | 同上 |
| [teacher/diagnostic/class/[classId]/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/diagnostic/class/[classId]/page.tsx) | 仅 `getAuthContext()`（有 dataScope 校验），无 `requirePermission(DIAGNOSTIC_READ)` | 同上 |
| [teacher/diagnostic/student/[studentId]/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/diagnostic/student/[studentId]/page.tsx) | 仅 `getAuthContext()`（有 dataScope 校验），无 `requirePermission(DIAGNOSTIC_READ)` | 同上 |
| [student/grades/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/grades/page.tsx) | 仅 `getAuthContext()`，无 `requirePermission(GRADE_RECORD_READ)` | 同上 |
| [student/diagnostic/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/diagnostic/page.tsx) | 仅 `getAuthContext()`，无 `requirePermission(DIAGNOSTIC_READ)` | 同上 |
| [parent/grades/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/parent/grades/page.tsx) | 仅 `getAuthContext()`（有 dataScope 校验），无 `requirePermission(GRADE_RECORD_READ)` | 同上 |

**后果**：成绩录入页面（`/teacher/grades/entry`）和成绩统计页面（`/teacher/grades/stats`）完全无权限校验，依赖路由中间件做粗粒度角色路由。若中间件配置错误或绕过，任意已登录用户可访问成绩录入页面并调用 `batchCreateGradeRecordsAction`（虽然 Action 层有 `requirePermission`，但页面层缺少二次校验不符合"Server Action 二次校验"要求）。

### 2.2 架构分层：跨模块直接查询 users 表（P0）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [diagnostic/data-access-reports.ts](file:///e:/Desktop/CICD/src/modules/diagnostic/data-access-reports.ts) L8 | `import { learningDiagnosticReports, users } from "@/shared/db/schema"` | "modules/ 之间通过对方 data-access 通信，不直接查询对方 DB 表" |
| [diagnostic/data-access-reports.ts](file:///e:/Desktop/CICD/src/modules/diagnostic/data-access-reports.ts) L137-140 | `getDiagnosticReports` 直接 `leftJoin(users, ...)` 查询学生姓名 | 同上 |
| [diagnostic/data-access-reports.ts](file:///e:/Desktop/CICD/src/modules/diagnostic/data-access-reports.ts) L149-153 | 直接 `db.select({ id: users.id, name: users.name }).from(users)` 查询生成者姓名 | 同上 |
| [diagnostic/data-access-reports.ts](file:///e:/Desktop/CICD/src/modules/diagnostic/data-access-reports.ts) L168-170 | `getDiagnosticReportById` 直接 `leftJoin(users, ...)` | 同上 |
| [diagnostic/data-access-reports.ts](file:///e:/Desktop/CICD/src/modules/diagnostic/data-access-reports.ts) L177-182 | 直接 `db.select({ name: users.name }).from(users)` | 同上 |

**后果**：`diagnostic` 模块绕过 `users` 模块的 data-access 层直接查询 `users` 表，破坏模块封装性。`users` 表 schema 变更将直接影响 diagnostic 模块。同模块的 `data-access.ts` 已正确通过 `getUserNamesByIds` 访问，但 `data-access-reports.ts` 却绕过，存在不一致。

### 2.3 架构分层：统计业务逻辑混入 data-access（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [grades/data-access.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access.ts) L217-270 | `getClassGradeStats` 包含 average/median/max/min/variance/stdDev/passRate/excellentRate 计算（53 行统计逻辑） | "严格三层架构，依赖方向单向" — 统计计算属业务逻辑层 |
| [grades/data-access.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access.ts) L272-337 | `getStudentGradeSummary` 包含 averageScore 计算 | 同上 |
| [grades/data-access.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access.ts) L339-373 | `getClassRanking` 包含 rank 计算 | 同上 |
| [grades/data-access-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-analytics.ts) L59-119 | `getGradeTrend` 包含 normalized/avg 计算 | 同上 |
| [grades/data-access-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-analytics.ts) L128-218 | `getClassComparison` 包含 normalized/median/avg/passCount/excellentCount 计算（90 行） | 同上 |
| [grades/data-access-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-analytics.ts) L226-289 | `getSubjectComparison` 包含 median/avg/passRate/excellentRate 计算 | 同上 |
| [grades/data-access-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-analytics.ts) L299-336 | `getGradeDistribution` 包含 bucket 分类逻辑 | 同上 |
| [grades/data-access-ranking.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-ranking.ts) L31-118 | `getRankingTrend` 包含 normalize/rank 计算逻辑 | 同上 |

**后果**：data-access 层职责混乱，既负责数据读取又负责业务计算，难以单独测试统计逻辑。架构图 L671 已标记此 P2 问题。应抽取到独立的 `stats-service.ts`（参考 homework 模块的 `stats-service.ts` 范例）。

### 2.4 重复代码：工具函数多处重复（P1）

| 重复函数 | 出现位置 | 违反规则 |
|----------|----------|----------|
| `buildScopeClassFilter` | [grades/data-access.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access.ts) L57-75、[grades/data-access-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-analytics.ts) L34-48 | "工具函数：建议 ≤ 40 行" + DRY 原则 |
| `toNumber` | [grades/data-access.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access.ts) L34-37、[grades/data-access-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-analytics.ts) L24-27、[grades/data-access-ranking.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-ranking.ts) L16-19 | 同上 |
| `normalize` | [grades/data-access.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access.ts)、[grades/data-access-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-analytics.ts) L29-32、[grades/data-access-ranking.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-ranking.ts) L21-24 | 同上 |

**后果**：3 个文件重复实现相同工具函数，修改时需同步多处，易遗漏导致行为不一致。应抽取到 `grades/lib/stats-utils.ts` 或 `shared/lib/grade-utils.ts`。

### 2.5 国际化：完全缺失（P0）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/modules/grades/components/*`（16 个文件） | 全部使用硬编码英文字符串，0 处 `useTranslations` 调用 | "所有用户可见文本必须适配 i18n（使用 next-intl），提取翻译键" |
| `src/modules/diagnostic/components/*`（4 个文件） | 全部使用硬编码英文字符串，0 处 `useTranslations` 调用 | 同上 |
| [grades/export.ts](file:///e:/Desktop/CICD/src/modules/grades/export.ts) L12-17, L54-61, L68-80, L287, L295 | Excel 导出表头、指标名、文件名硬编码中文 | 同上 |
| `src/shared/i18n/messages/{zh-CN,en}/` | **不存在 `grades.json` 和 `diagnostic.json` 翻译文件** | 同上 |
| [i18n/request.ts](file:///e:/Desktop/CICD/src/i18n/request.ts) L22-28 | 仅加载 5 个命名空间（common/auth/onboarding/classes/errors），未加载 grades/diagnostic | 同上 |
| `src/modules/grade-management/components/*`（7 个文件，12 处） | 调用 `useTranslations("grade")` 但 `grade.json` 翻译文件不存在，**运行时会报 `MISSING_MESSAGE` 错误** | 同上 |

**后果**：
1. grades 和 diagnostic 模块完全无法国际化，所有用户可见文本固定为英文（部分中文混合），无法支持多语言。
2. grade-management 模块（年级管理，与成绩模块不同）调用未加载的 `grade` 命名空间，访问 `/management/grade/`、`/admin/school/grades/insights` 等页面会因找不到翻译键而**运行时报错**。

### 2.6 前端规范：Error Boundary 和 Suspense 缺失（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/modules/grades/components/*`（16 个文件） | 全部无 Error Boundary | "每个独立的数据区块必须用 React Error Boundary 包裹" |
| `src/modules/diagnostic/components/*`（4 个文件） | 全部无 Error Boundary | 同上 |
| `src/modules/grades/components/*`（16 个文件） | 全部无 Suspense + 骨架屏 | "异步数据使用 React Suspense + 骨架屏" |
| `src/modules/diagnostic/components/*`（4 个文件） | 全部无 Suspense + 骨架屏 | 同上 |
| `src/app/(dashboard)/teacher/grades/` | **无 loading.tsx / error.tsx** | 路由级错误边界和加载态缺失 |
| `src/app/(dashboard)/teacher/diagnostic/` | **无 loading.tsx / error.tsx** | 同上 |

**后果**：单个组件抛错会导致整个页面崩溃；异步加载无骨架屏过渡，用户体验差（白屏等待）。

### 2.7 前端规范：a11y 无障碍缺失（P2）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/modules/grades/components/*`（15/16 个文件） | 无 ARIA 属性（仅 batch-grade-entry.tsx 有 `aria-hidden` 和 `aria-invalid`） | "可访问性（a11y）：语义化标签、ARIA 属性、键盘导航" |
| `src/modules/diagnostic/components/*`（4 个文件） | 无 ARIA 属性 | 同上 |
| [grades/components/grade-record-list.tsx](file:///e:/Desktop/CICD/src/modules/grades/components/grade-record-list.tsx) L93-100 | 删除按钮无 `aria-label` | 同上 |
| [diagnostic/components/class-diagnostic-view.tsx](file:///e:/Desktop/CICD/src/modules/diagnostic/components/class-diagnostic-view.tsx) L128-139 | 热力图色块仅靠 `title` 属性，无 `role="img"` 和 `aria-label` | 同上 |
| [diagnostic/components/mastery-radar-chart.tsx](file:///e:/Desktop/CICD/src/modules/diagnostic/components/mastery-radar-chart.tsx) L38-66 | 雷达图无 `aria-label` / `role="img"` 描述 | 同上 |
| [diagnostic/components/report-list.tsx](file:///e:/Desktop/CICD/src/modules/diagnostic/components/report-list.tsx) L192-200, L202-210 | 发布/删除按钮仅 `title`，无 `aria-label` | 同上 |

**后果**：屏幕阅读器用户无法识别图表内容、按钮用途，不符合 WCAG 2.1 AA 标准。

### 2.8 TypeScript 规范：`as` 断言违规（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [grades/components/batch-grade-entry.tsx](file:///e:/Desktop/CICD/src/modules/grades/components/batch-grade-entry.tsx) L221 | `remark: undefined as string \| undefined` | "禁止 `as` 断言（除非从 `unknown` 转换或测试中，需注释原因）" |
| [grades/components/batch-grade-entry.tsx](file:///e:/Desktop/CICD/src/modules/grades/components/batch-grade-entry.tsx) L312 | `setType(v as typeof type)` | 同上 |
| [grades/components/grade-record-form.tsx](file:///e:/Desktop/CICD/src/modules/grades/components/grade-record-form.tsx) L142 | `setType(v as typeof type)` | 同上 |
| [grades/components/grade-distribution-chart.tsx](file:///e:/Desktop/CICD/src/modules/grades/components/grade-distribution-chart.tsx) L66-67 | `payload as { payload?: {...} }`（从 unknown 转换但未使用类型守卫） | 同上 |

**后果**：`as` 断言绕过 TypeScript 类型检查，可能隐藏运行时类型错误。应使用类型守卫或 Zod 运行时校验。

### 2.9 Tailwind 规范：任意值违规（P2）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [diagnostic/components/class-diagnostic-view.tsx](file:///e:/Desktop/CICD/src/modules/diagnostic/components/class-diagnostic-view.tsx) L255 | `className="w-[180px]"` | "禁止使用任意值（`w-[137px]`），除非有充分理由并注释" |
| [diagnostic/components/mastery-radar-chart.tsx](file:///e:/Desktop/CICD/src/modules/diagnostic/components/mastery-radar-chart.tsx) L45 | `className="mx-auto h-[360px] w-full max-w-[520px]"` | 同上 |

**后果**：绕过设计令牌系统，无法统一调整尺寸主题。

### 2.10 数据模型缺陷：班级报告 studentId 字段语义错误（P2）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [diagnostic/data-access.ts](file:///e:/Desktop/CICD/src/modules/diagnostic/data-access.ts) L111-114 | 班级报告 `studentId: generatedBy` 将生成者 ID 写入 studentId 字段 | "数据模型设计应语义清晰" |
| [diagnostic/data-access-reports.ts](file:///e:/Desktop/CICD/src/modules/diagnostic/data-access-reports.ts) L111 | 同上 | 同上 |

**后果**：`report-list.tsx` L178 显示 `r.studentName` 时，班级报告会显示生成者（教师）姓名而非学生姓名，存在数据语义错误。架构图 L1245 已标记此 P2 问题。

### 2.11 Server Action 规范：Zod 校验缺失（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [grades/actions.ts](file:///e:/Desktop/CICD/src/modules/grades/actions.ts) L154-170 | `deleteGradeRecordAction` 无 Zod 校验（仅 id 字符串） | "输入使用 Zod 验证，验证失败返回结构化错误" |
| [grades/actions.ts](file:///e:/Desktop/CICD/src/modules/grades/actions.ts) L171-190 | `getGradeRecordsAction` 无 Zod 校验（使用 `GradeQueryParams` 类型） | 同上 |
| [grades/actions.ts](file:///e:/Desktop/CICD/src/modules/grades/actions.ts) L191-208 | `getClassGradeStatsAction` 无 Zod 校验 | 同上 |
| [grades/actions.ts](file:///e:/Desktop/CICD/src/modules/grades/actions.ts) L209-232 | `getStudentGradeSummaryAction` 无 Zod 校验 | 同上 |
| [grades/actions.ts](file:///e:/Desktop/CICD/src/modules/grades/actions.ts) L233-250 | `getClassRankingAction` 无 Zod 校验 | 同上 |
| [grades/actions.ts](file:///e:/Desktop/CICD/src/modules/grades/actions.ts) L251-269 | `getGradeRecordByIdAction` 无 Zod 校验 | 同上 |
| [grades/actions.ts](file:///e:/Desktop/CICD/src/modules/grades/actions.ts) L270-312 | `exportGradesAction` 无 Zod 校验（params 为内联对象类型） | 同上 |
| [grades/actions-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/actions-analytics.ts) L26-45 | `getGradeTrendAction` 无 Zod 校验 | 同上 |
| [grades/actions-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/actions-analytics.ts) L46-64 | `getClassComparisonAction` 无 Zod 校验 | 同上 |
| [grades/actions-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/actions-analytics.ts) L65-83 | `getSubjectComparisonAction` 无 Zod 校验 | 同上 |
| [grades/actions-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/actions-analytics.ts) L84-103 | `getGradeDistributionAction` 无 Zod 校验 | 同上 |
| [grades/actions-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/actions-analytics.ts) L104-133 | `getRankingTrendAction` 无 Zod 校验 | 同上 |

**后果**：12 个 Action 缺失 Zod 校验，客户端可传入任意类型参数，可能导致运行时错误或 SQL 注入风险。diagnostic 模块的 6 个 Action 全部使用 Zod 校验，是标杆范例。

### 2.12 业务逻辑漏洞：grade_managed scope 返回空数据（P2）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [grades/data-access.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access.ts) L62-64 | `grade_managed` scope 返回 `sql\`1=0\``（始终无数据） | "权限过滤应正确反映角色数据范围" |
| [grades/data-access-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access-analytics.ts) L39 | 同上 | 同上 |

**后果**：年级管理员（grade_managed scope）无法查看任何成绩数据，可能是业务逻辑漏洞。年级管理员应能查看所管年级的所有班级成绩。

### 2.13 路由缺陷：page.tsx 缺失（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/app/(dashboard)/management/grade/page.tsx` | **文件缺失**，但有 loading.tsx/error.tsx 孤儿文件 | "路由页面应完整" |

**后果**：访问 `/management/grade` 会 404，但 loading.tsx 和 error.tsx 仍存在，造成混乱。

### 2.14 角色覆盖不一致：admin/parent 无 diagnostic UI（P2）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `005_architecture_data.json` L174-175, L214-215 | admin 和 parent 都有 `DIAGNOSTIC_MANAGE`/`DIAGNOSTIC_READ` 权限 | "权限点应有对应 UI" |
| `src/app/(dashboard)/admin/` | **无 diagnostic 页面** | 同上 |
| `src/app/(dashboard)/parent/` | **无 diagnostic 页面** | 同上 |

**后果**：admin 和 parent 拥有 diagnostic 权限但无对应 UI，权限与 UI 覆盖不一致。家长无法查看子女的学情诊断报告。

### 2.15 SearchParams 工具未统一（P3）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [student/grades/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/grades/page.tsx) | 自定义 `SearchParams` 类型和 `getParam` 函数 | "最大化复用" |
| [management/grade/insights/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/management/grade/insights/page.tsx) | 自定义 `SearchParams` 类型和 `getParam` 函数 | 同上 |

**后果**：与 teacher 端 grades 页面已复用 `@/shared/lib/search-params` 的做法不一致，存在重复代码。

---

## 三、行业差距对比

### 3.1 成绩模块（grades）行业对标

| 功能维度 | 行业优秀实践（K12 成绩管理系统） | 当前实现 | 差距影响 |
|----------|-------------------------------|----------|----------|
| **成绩录入** | 支持Excel批量导入、扫码录入、语音录入；录入时实时校验分数范围；自动计算总分、平均分 | 仅支持单条录入 + 批量录入（表单式）；有分数范围校验；无 Excel 导入 | 教师录入效率低，大班级成绩录入耗时 |
| **成绩分析** | 多维度分析（班级/年级/个人/科目）；支持自定义分析维度；提供归因分析（哪些题目失分多） | 5 种分析（趋势/班级对比/科目对比/分布/排名）；无归因分析；无自定义维度 | 教师无法定位失分原因，难以针对性教学 |
| **可视化** | 交互式图表（hover 显示详情、点击下钻）；支持图表下载为图片；支持自定义图表配置 | 静态图表（TrendLineChart/SimpleBarChart）；无 hover 详情；无下载功能 | 数据呈现不够直观，教师难以深入分析 |
| **报告导出** | 支持 PDF/Excel/CSV 多格式；支持自定义报告模板；支持批量导出（按班级/年级） | 仅 Excel 导出（明细 + 班级汇总）；无 PDF；无自定义模板 | 无法满足学校正式报告需求（如家长会报告需 PDF） |
| **预警机制** | 成绩异常预警（突然下降/持续低迷）；及格率预警；班级对比异常预警 | 无预警机制 | 教师无法及时发现学生成绩异常 |
| **多角色视图** | 学生看自己 + 班级平均；家长看子女 + 班级排名；教师看所教班级；管理员看全校 | 4 角色都有基本视图；但 parent 无 diagnostic；admin 无 diagnostic | 家长无法全面了解子女学情 |
| **空状态/加载态** | 完善的空状态插画 + 引导操作；骨架屏过渡 | 仅部分页面有 loading.tsx；组件无 Suspense | 用户体验差，白屏等待 |
| **数据联动** | 成绩 → 学情诊断 → 推荐练习；成绩 → 作业 → 知识点掌握度 | grades 与 diagnostic 无数据联动；无推荐练习 | 无法形成"诊断-练习-反馈"闭环 |

### 3.2 学情诊断模块（diagnostic）行业对标

| 功能维度 | 行业优秀实践（K12 学情诊断系统） | 当前实现 | 差距影响 |
|----------|-------------------------------|----------|----------|
| **知识点掌握度** | 基于IRT（项目反应理论）计算；支持知识点权重；支持时间衰减（近期表现权重更高） | 基于正确率简单计算；无权重；无时间衰减 | 掌握度计算不够精准 |
| **诊断报告** | 自动生成 PDF 报告；支持自定义模板；含学习建议、练习推荐、进步轨迹 | 生成 draft 报告（JSON 存储）；无 PDF；建议为静态文本 | 报告不够专业，无法直接发给家长 |
| **可视化** | 雷达图 + 热力图 + 知识图谱；支持知识点下钻；支持时间对比 | 雷达图 + 热力图；无知识图谱；无下钻 | 知识结构呈现不够清晰 |
| **个性化推荐** | 基于弱项推荐练习题/微课；支持难度自适应；支持学习路径规划 | 仅列出弱项知识点 + "Practice" 链接（跳转到作业列表） | 无法精准推荐练习内容 |
| **班级诊断** | 班级整体掌握度 + 重点关注学生列表 + 教学建议；支持按知识点筛选学生 | 班级掌握度摘要 + 需关注学生列表；无教学建议 | 教师难以根据诊断调整教学 |
| **历史趋势** | 掌握度随时间变化曲线；支持对比多个时间段 | 无历史趋势（仅当前快照） | 无法评估学习进步情况 |
| **多角色覆盖** | 学生/家长/教师/管理员都能查看；家长看子女诊断报告 | 仅 teacher + student 有 UI；parent/admin 无 UI | 家长无法了解子女学情 |

### 3.3 关键差距总结

1. **数据孤岛**：grades 和 diagnostic 模块无数据联动，无法形成"成绩 → 诊断 → 练习 → 反馈"闭环。行业优秀产品（如猿题库、作业帮）已实现完整学习闭环。
2. **家长端缺失**：parent 无 diagnostic UI，家长无法查看子女学情诊断报告。K12 场景下家长是重要决策者，缺失影响家校沟通。
3. **报告专业度不足**：diagnostic 报告为 JSON 存储，无 PDF 导出，无法直接用于家长会。行业产品普遍支持专业 PDF 报告。
4. **预警机制空白**：成绩异常、掌握度低迷无预警，教师无法主动干预。
5. **可视化深度不足**：无知识图谱、无下钻分析、无时间对比，数据呈现停留在表层。

---

## 四、改进优先级建议

### P0（紧急 — 安全与合规）

| # | 问题 | 改进方向 |
|---|------|----------|
| P0-1 | 权限校验缺失（10 个页面） | 所有页面调用 `requirePermission()`：teacher/grades 用 `GRADE_RECORD_READ`/`GRADE_RECORD_MANAGE`，teacher/diagnostic 用 `DIAGNOSTIC_READ`/`DIAGNOSTIC_MANAGE`，student/parent 用对应 READ 权限 |
| P0-2 | diagnostic/data-access-reports.ts 直查 users 表 | 改为调用 `@/modules/users/data-access` 的 `getUserNamesByIds`，删除 `users` 表 import |
| P0-3 | i18n 完全缺失 + grade-management 运行时报错 | 创建 `grades.json` 和 `diagnostic.json` 翻译文件（zh-CN + en）；修复 `grade-management` 模块的 `grade` 命名空间（创建 `grade.json` 或改用 `gradeManagement`）；在 `i18n/request.ts` 注册新命名空间 |
| P0-4 | `/management/grade/page.tsx` 缺失 | 补齐 page.tsx 或删除孤儿 loading.tsx/error.tsx |

### P1（较严重 — 架构与质量）

| # | 问题 | 改进方向 | 状态 |
|---|------|----------|------|
| P1-1 | 统计业务逻辑混入 data-access | 抽取 `grades/stats-service.ts`，将 `getClassGradeStats`/`getClassComparison`/`getSubjectComparison`/`getGradeDistribution`/`getRankingTrend` 的统计计算迁移至纯函数（参考 homework/stats-service.ts 范例） | ✅ 已完成 |
| P1-2 | 重复工具函数 | 抽取 `grades/lib/scope-filter.ts`（`buildScopeClassFilter`）和 `grades/lib/stats-utils.ts`（`toNumber`/`normalize`） | ✅ 已完成 |
| P1-3 | 12 个 Action 缺失 Zod 校验 | 为 `deleteGradeRecordAction`/`getGradeRecordsAction`/`getClassGradeStatsAction`/`getStudentGradeSummaryAction`/`getClassRankingAction`/`getGradeRecordByIdAction`/`exportGradesAction` + 5 个 analytics Action 创建对应 Zod schema | ✅ 已完成 |
| P1-4 | `as` 断言违规（4 处） | 使用类型守卫或 Zod 运行时校验替代 | ✅ 已完成 |
| P1-5 | Error Boundary 和 Suspense 缺失 | 创建 `grades/components/widget-boundary.tsx`（Error Boundary + Suspense + Skeleton 组合）；每个数据区块独立包裹；teacher/grades 和 teacher/diagnostic 路由补齐 loading.tsx/error.tsx | ✅ 已完成 |
| P1-6 | 架构图同步 | 更新 `004` 和 `005` 文档：grades 行数、diagnostic deps、新增 stats-service.ts、新增 lib/、补齐 actions-analytics exports | ✅ 已完成 |

### P2（优化 — 体验与扩展）

| # | 问题 | 改进方向 | 状态 |
|---|------|----------|------|
| P2-1 | a11y 无障碍缺失 | 补充 ARIA 属性：图表 `role="img"` + `aria-label`；按钮 `aria-label`；表格 `caption`；列表 `role="list"` | ✅ 已完成 |
| P2-2 | Tailwind 任意值 | 移除 `w-[180px]`/`h-[360px]`/`max-w-[520px]`，改用设计令牌或注释说明 | ✅ 已完成 |
| P2-3 | 班级报告 studentId 字段语义错误 | 修改 `learningDiagnosticReports` schema，将 `studentId` 改为可空，或增加 `classId`/`generatedBy` 字段 | ✅ 已完成 |
| P2-4 | grade_managed scope 返回空数据 | 修复 `buildScopeClassFilter`，grade_managed scope 应返回所管年级的班级过滤条件 | ✅ 已完成 |
| P2-5 | admin/parent 无 diagnostic UI | 新增 `/parent/diagnostic/` 页面（家长查看子女诊断报告）；admin 可复用 teacher 视图 | ✅ 已完成 |
| P2-6 | SearchParams 工具未统一 | student/grades 和 management/grade/insights 改用 `@/shared/lib/search-params` | ✅ 已完成 |

### P3（长期 — 行业对标）

| # | 问题 | 改进方向 |
|---|------|----------|
| P3-1 | grades 与 diagnostic 无数据联动 | 设计联动接口：成绩录入后触发掌握度更新；诊断报告含成绩趋势 |
| P3-2 | 无预警机制 | 新增 `grades/alerts-service.ts`：成绩下降预警、及格率预警、掌握度低迷预警 |
| P3-3 | 诊断报告无 PDF 导出 | 集成 PDF 生成库（如 @react-pdf/renderer），支持专业报告模板 |
| P3-4 | 无知识图谱可视化 | 引入知识图谱组件（如 react-flow），展示知识点关系与掌握度 |
| P3-5 | 无个性化练习推荐 | 基于弱项推荐练习题，对接 questions 模块 |
| P3-6 | Widget 配置系统 | 设计 `GradesWidgetConfig`/`DiagnosticWidgetConfig` 类型，按角色配置渲染哪些 Widget |

---

## 五、架构图同步说明

本次审计发现架构图存在以下遗漏或不一致，需在实现后同步更新：

### 5.1 `004_architecture_impact_map.md` 需补充

1. **§2.6 grades 模块**：
   - 更新文件清单行数：`data-access.ts` 419→433、`data-access-analytics.ts` 293→337
   - 补充 `actions-analytics.ts` 的 5 个 Action 到 exports 清单（当前仅列 11 个，实际 15 个）
   - 新增 `stats-service.ts`（P1-1 抽取后）
   - 新增 `lib/scope-filter.ts`、`lib/stats-utils.ts`（P1-2 抽取后）
   - 新增 `components/widget-boundary.tsx`（P1-5 新增）

2. **§2.22 diagnostic 模块**：
   - 更新已知问题：标注 `data-access-reports.ts` 直查 users 表（P0-2 修复前）
   - 更新文件清单行数（如有变化）

3. **路由清单**：
   - 标注 `/management/grade/page.tsx` 缺失（P0-4 修复前）
   - 标注 teacher/grades 和 teacher/diagnostic 路由缺少 loading.tsx/error.tsx
   - 新增 `/parent/diagnostic/` 路由（P2-5 实现后）

### 5.2 `005_architecture_data.json` 需修改

1. `modules.grades` 节点（L7362）：
   - 更新 `dataAccess` 中各函数的 `deps`：移除直查 `classes`/`classEnrollments`/`subjects`/`users`，改为 `classes/data-access.*`/`school/data-access.*`/`users/data-access.*`
   - 新增 `stats-service.ts` 的 exports
   - 新增 `lib/scope-filter.ts`、`lib/stats-utils.ts` 的 exports
   - 补充 `actions-analytics.ts` 的 5 个 Action 到 `actions` 数组

2. `modules.diagnostic` 节点（L10927）：
   - 更新 `dataAccess` 中各函数的 `deps`：移除直查 `users`/`classes`/`classEnrollments`/`examSubmissions`/`submissionAnswers`/`questionsToKnowledgePoints`，改为对应模块 data-access
   - 标注 `data-access-reports.ts` 的 `getDiagnosticReports`/`getDiagnosticReportById` 依赖 `users/data-access.getUserNamesByIds`（P0-2 修复后）

3. `permissions` 节点：
   - 确认 `GRADE_RECORD_READ`/`GRADE_RECORD_MANAGE`/`DIAGNOSTIC_READ`/`DIAGNOSTIC_MANAGE` 权限点已定义（已存在 ✓）

4. `routes` 节点：
   - 补充 teacher/grades/entry、teacher/grades/stats、teacher/diagnostic/class/[classId]、teacher/diagnostic/student/[studentId] 路由
   - 标注 `/management/grade/page.tsx` 缺失
   - 新增 `/parent/diagnostic/` 路由（P2-5 实现后）

5. `dependencyMatrix`：
   - 更新 grades → classes/school/users 的依赖关系（通过 data-access，已正确）
   - 更新 diagnostic → classes/exams/questions/users 的依赖关系（通过 data-access，P0-2 修复后完全正确）

### 5.3 翻译文件结构示例

```
src/shared/i18n/messages/
├─ zh-CN/
│  ├─ grades.json       # 新增（成绩模块）
│  ├─ diagnostic.json   # 新增（学情诊断模块）
│  └─ grade.json        # 新增（grade-management 模块，修复运行时报错）
└─ en/
   ├─ grades.json       # 新增
   ├─ diagnostic.json   # 新增
   └─ grade.json        # 新增
```

`grades.json` 结构示例（zh-CN）：

```json
{
  "title": {
    "list": "成绩查询",
    "entry": "成绩录入",
    "analytics": "成绩分析",
    "stats": "成绩统计"
  },
  "filters": {
    "class": "班级",
    "subject": "科目",
    "type": "类型",
    "semester": "学期",
    "allClasses": "全部班级",
    "allSubjects": "全部科目",
    "allTypes": "全部类型",
    "allSemesters": "全部学期",
    "searchPlaceholder": "按标题搜索..."
  },
  "type": {
    "exam": "考试",
    "quiz": "测验",
    "homework": "作业",
    "other": "其他"
  },
  "semester": {
    "s1": "第一学期",
    "s2": "第二学期"
  },
  "list": {
    "empty": "暂无成绩记录",
    "columns": {
      "student": "学生",
      "class": "班级",
      "subject": "科目",
      "title": "标题",
      "score": "分数",
      "type": "类型",
      "semester": "学期",
      "recordedBy": "录入人",
      "date": "日期"
    }
  },
  "form": {
    "title": "录入成绩",
    "save": "保存",
    "saving": "保存中...",
    "cancel": "取消",
    "selectClass": "选择班级",
    "selectSubject": "选择科目",
    "selectStudent": "选择学生",
    "titlePlaceholder": "如期中考试",
    "score": "分数",
    "fullScore": "满分",
    "remark": "备注（可选）",
    "remarkPlaceholder": "关于此成绩的备注..."
  },
  "delete": {
    "title": "删除成绩记录",
    "confirmation": "确定要删除此成绩记录吗？此操作不可撤销。",
    "confirm": "删除",
    "cancel": "取消",
    "deleting": "删除中..."
  },
  "export": {
    "detail": "导出成绩明细",
    "classReport": "导出班级成绩总表",
    "success": "导出成功",
    "failed": "导出失败"
  },
  "stats": {
    "title": "统计",
    "average": "平均分",
    "median": "中位数",
    "max": "最高分",
    "min": "最低分",
    "stdDev": "标准差",
    "variance": "方差",
    "passRate": "及格率",
    "excellentRate": "优秀率",
    "count": "人数"
  },
  "analytics": {
    "trend": "成绩趋势",
    "classComparison": "班级对比",
    "subjectComparison": "科目对比",
    "distribution": "分数分布",
    "ranking": "排名",
    "rankingTrend": "排名趋势"
  },
  "batch": {
    "title": "批量录入",
    "saving": "保存中...",
    "restored": "已恢复未保存的成绩草稿",
    "invalidScores": "存在无效分数",
    "fullScoreRequired": "满分必填",
    "saved": "已录入"
  },
  "empty": {
    "noRecords": "暂无成绩记录",
    "noData": "暂无数据"
  },
  "error": {
    "loadFailed": "加载失败",
    "saveFailed": "保存失败",
    "deleteFailed": "删除失败",
    "retry": "重试"
  }
}
```

`diagnostic.json` 结构示例（zh-CN）：

```json
{
  "title": {
    "student": "学生学情诊断",
    "class": "班级学情诊断",
    "reportList": "诊断报告"
  },
  "type": {
    "individual": "个人",
    "class": "班级",
    "grade": "年级"
  },
  "status": {
    "draft": "草稿",
    "published": "已发布",
    "archived": "已归档"
  },
  "filters": {
    "reportType": "报告类型",
    "status": "状态",
    "allTypes": "全部类型",
    "allStatuses": "全部状态"
  },
  "summary": {
    "overallMastery": "总体掌握度",
    "strengths": "强项",
    "weaknesses": "弱项",
    "students": "学生数",
    "avgMastery": "平均掌握度",
    "needAttention": "需重点关注"
  },
  "chart": {
    "radarTitle": "知识点掌握度",
    "radarDescription": "掌握度雷达图",
    "heatmapTitle": "知识点掌握度热力图",
    "rankingTitle": "知识点排名"
  },
  "report": {
    "generate": "生成诊断报告",
    "generateStudent": "生成学生诊断报告",
    "generateClass": "生成班级诊断报告",
    "publish": "发布",
    "delete": "删除",
    "publishTitle": "发布报告",
    "deleteTitle": "删除报告",
    "recommendations": "学习建议",
    "history": "报告历史"
  },
  "strengths": {
    "title": "强项（≥80%）",
    "practice": "练习"
  },
  "weaknesses": {
    "title": "弱项（<60%）",
    "practice": "练习"
  },
  "empty": {
    "noData": "暂无诊断数据",
    "noClassData": "无法加载班级掌握度摘要",
    "noMastery": "暂无知识点掌握度记录",
    "noReports": "暂无诊断报告"
  },
  "error": {
    "generateFailed": "生成报告失败",
    "publishFailed": "发布失败",
    "deleteFailed": "删除失败",
    "loadFailed": "加载失败",
    "retry": "重试"
  }
}
```

---

## 六、合规项确认

以下条目**已通过审计**：

- ✅ **grades 模块跨模块依赖全部通过 data-access**：所有跨模块访问（classes/school/users）均通过对方 data-access 函数
- ✅ **diagnostic 模块 data-access.ts 跨模块依赖通过 data-access**（仅 data-access-reports.ts 违规）
- ✅ **所有 Server Action 调用 `requirePermission()`**：grades 15 个 + diagnostic 6 个 = 21 个 Action 全部合规
- ✅ **所有 Server Action 返回 `ActionState<T>`**
- ✅ **所有 Server Action 使用 `revalidatePath` 精确刷新**
- ✅ **无 `role === "xxx"` 硬编码**：全模块无
- ✅ **diagnostic 组件使用 `usePermission().hasPermission()`**：class-diagnostic-view.tsx 和 report-list.tsx 已使用
- ✅ **无 `dangerouslySetInnerHTML`**
- ✅ **无 `any` 类型**
- ✅ **文件行数全部合规**：最大为 grades/components/batch-grade-entry.tsx 442 行 < 500 行组件建议上限
- ✅ **`"use client"` / `"use server"` / `"server-only"` 正确放置**
- ✅ **`import type` 使用规范**
- ✅ **diagnostic schema.ts 枚举与 types.ts 联合类型一致**
- ✅ **接口命名规范**（无 I 前缀，PascalCase）

---

## 七、重构方案设计要点（供后续实现参考）

### 7.1 完全解耦

- 定义 `GradesDataService` 接口抽象数据依赖，使用 React Context 注入
- 模块内部组件绝不直接 import 其他业务模块的 actions 或 data-access
- 不同角色差异通过接口不同实现隔离（如 `TeacherGradesService`/`StudentGradesService`/`ParentGradesService`）

### 7.2 组合优先

- 所有 UI 通过组件组合（children、slots、render props）实现灵活性
- 逻辑复用抽取为自定义 hooks（如 `useGradeRecords`/`useGradeTrend`/`useMasterySummary`）
- 严禁继承或深层嵌套 HOC

### 7.3 最大化复用

- 识别四角色共用 UI 块：`GradeTrendChart`/`GradeStatsCard`/`MasteryRadarChart`/`WidgetBoundary`
- 抽象泛型组件：`<DataTable<T>>`/`<FilterBar>`/`<EmptyState>`/`<ErrorState>`
- 各角色模块仅组合复用单元，可配置化显示内容

### 7.4 配置驱动

- 设计 `GradesWidgetConfig` 类型，按角色配置渲染哪些 Widget
- 示例：teacher 看 [录入, 查询, 分析, 统计]，student 看 [我的成绩, 趋势]，parent 看 [子女成绩, 趋势]

### 7.5 错误与边界处理

- 每个独立数据区块用 `<WidgetBoundary>`（Error Boundary + Suspense + Skeleton 组合）包裹
- 明确处理空数据、无权限、网络异常等边界状态
- 支持流式渲染（React Server Components 获取初始数据）

### 7.6 可测试性

- 数据获取、计算、格式化等纯逻辑放入 `stats-service.ts` 或 hooks
- 导出清晰接口类型以便 mock
- 统计函数为纯函数，易于单测

### 7.7 监控埋点

- 预留关键操作埋点接口：成绩录入、报告生成、报告发布、导出操作
- 通过 `shared/lib/analytics` 统一上报
