{
  "_meta": {
    "project": "Next_Edu",
    "description": "K12 智慧教务管理系统",
    "generatedAt": "2026-06-17",
    "formatVersion": "1.1",
    "rule": "每次文件修改后须同步更新本文件",
    "lastUpdate": "grades 模块 v3/v4 P3 长期问题修复（2026-06-23）：(v3-P3-1) batch-grade-entry.tsx 新增下载模板按钮，客户端生成 CSV 模板（含学生姓名/分数/备注列头 + BOM 支持 Excel UTF-8）。(v3-P3-2) grade-record-list.tsx 新增多选复选框（全选/单选）+ 批量删除工具栏 + 批量删除确认对话框；data-access.ts 新增 bulkDeleteGradeRecords（使用 inArray 一次性删除避免 N+1）；actions.ts 新增 bulkDeleteGradeRecordsAction（GRADE_RECORD_MANAGE 权限 + 限制单次最多 500 条）。(v4-P3-2) batch-grade-entry.tsx 顶部新增可折叠新手引导提示框（4 步使用说明），使用 localStorage 记住用户关闭状态。i18n zh-CN/en grades.json 同步新增 batch.downloadTemplate/templateAriaLabel/templateStudentName/templateScore/templateRemark/templateFilename + batch.guide.* + list.bulkDelete/bulkDeleteConfirmation/bulkDeleteSelected/bulkDeleteSuccess/bulkDeleteFailed/selectAll/selectRow/clearSelection 键。--- 之前更新：V4 AI 模块深度增强（ai-module-v2 对标 Khanmigo/Duolingo Max/Squirrel AI/Century Tech）：(V4-1) SSE 流式响应：shared/lib/ai/client.ts 新增 createAiChatCompletionStream（AsyncGenerator 逐 token 产出），新增 API 路由 /api/ai/chat/stream（POST，requirePermission(AI_CHAT)+checkDailyLimit+filterUserInput+学生苏格拉底系统提示+ReadableStream 流式输出+filterAiOutput+incrementDailyUsage+trackEvent），新增 hook useAiChatStream（fetch+ReadableStream reader+SSE 解析+AbortController 停止生成+localStorage 持久化最近 20 条）。(V4-2) Markdown 渲染：新增组件 AiMarkdownRenderer（react-markdown+remark-gfm，代码块/表格/列表+hover 复制按钮+memo 优化），AiChatPanel 全面重写（流式渲染+停止按钮+清空确认+建议提示词空状态+aria-live+错误展示）。(V4-3) 全局 AI 助手：新增组件 AiAssistantWidget（fixed 浮动按钮+Sheet 侧抽屉+usePathname 路由推断上下文+inferContextFromPath 映射 7 类场景系统提示：教师批改/备课/考试/学生错题本/学生作业/家长/管理员+useAiClientOptional 无 Provider 时隐藏+pulsing 绿色指示器），dashboard layout 全局注入 AiClientProvider+AiAssistantWidget。(V4-4) 内容安全：新增 services/content-safety.ts（filterUserInput 阻断暴力/自残/色情/毒品/黑客/PII 索取；filterAiOutput 输出二次过滤+学生场景阻断直接答案；checkDailyLimit 学生 50/教师 200/家长 30/管理员 500；incrementDailyUsage；getDailyLimit），COPPA/FERPA K12 合规。(V4-5) 多角色 AI 覆盖：types.ts 新增 ChildSummaryInput/Result、StudyPathInput/Result、AiUsageStats 类型 + AiService/AiClientService 接口扩展；schema.ts 新增 4 个 Zod schema；prompt-templates.ts 新增 CHILD_SUMMARY_SYSTEM_PROMPT（家庭教育顾问，家长友好语言）+ STUDY_PATH_SYSTEM_PROMPT（自适应学习路径设计师，3-7 步骤）；ai-service.ts 新增 generateChildSummary（聚合成绩/出勤/错题本数据）+ recommendStudyPath（基于掌握度数据）；actions.ts 新增 generateChildSummaryAction/recommendStudyPathAction（AI_CHAT 权限）+ getAiUsageStatsAction（AI_CONFIGURE 权限）；新增组件 AiChildSummary（家长端：整体评估 Markdown+优势绿勾+改进橙警+家庭辅导建议+下一步徽章）、AiUsageDashboard（管理员端：4 统计卡片+按能力进度条+按角色徽章+Top 用户列表+最近活动日志）、AiStudyPath（学生端：当前等级横幅+学习路径步骤+连接线+状态图标+预估时间+激励消息）。(V4-6) i18n 修复与扩展：修复 AiGradingAssist/AiLessonContentGenerator/AiQuestionVariantGenerator 共 8 处错误 i18n 键引用（CardDescription 重复 title、label/placeholder/button 复用 generateContent、3 个变体类型标签全部显示「生成」）；zh-CN/en ai.json 全面重写新增 chat.streaming/stopGeneration/copy/clearConfirm/suggestedPrompts、grading.description/batch*、lessonPrep.description/additionalContext/insertContent、exam.variantType.*/targetDifficulty/addVariant、parent.*、admin.*、studyPath.*、widget.*、safety.* 等键。架构文档 004/005 同步更新。"
  },
  "architectureOverview": {
    "layers": [
      {
        "name": "app",
        "description": "路由层,Next.js App Router,按角色分组(admin/teacher/student/parent/management)",
        "path": "src/app/"
      },
      {
        "name": "modules",
        "description": "业务模块层,按业务领域划分(25 个模块)",
        "path": "src/modules/"
      },
      {
        "name": "shared",
        "description": "基础设施层,提供共享能力(db/lib/hooks/components/types)",
        "path": "src/shared/"
      }
    ],
    "dependencyRule": "app → modules → shared (单向依赖,上层可依赖下层,下层不可依赖上层)",
    "violations": [
      "✅ shared/lib ↔ auth 循环依赖已修复(audit-logger/change-logger/auth-guard 改为通过 shared/lib/session.ts 单一入口获取 session，session.ts 内部 dynamic import @/auth 打破静态循环)",
      "✅ dashboard 跨模块直查 11 张表已修复(改为并行调用各模块 dashboard stats 函数: getUsersDashboardStats/getClassesDashboardStats/getTextbooksDashboardStats/getQuestionsDashboardStats/getExamsDashboardStats/getHomeworkDashboardStats)",
      "✅ messaging 绕过 notifications dispatcher 已修复(通知 CRUD 和偏好迁移至 notifications 模块，messaging 通过 dispatcher 发送通知)",
      "✅ classes/data-access.ts 混入 homework/scheduling/grades 跨模块逻辑已修复(拆分为 5 个文件，classes 通过 homework/data-access-classes 获取作业数据)",
      "✅ classSchedule 表三处写入口已统一(写函数从 classes 迁移至 scheduling/data-access-class-schedule.ts，classes 仅保留读函数)",
      "✅ P1-1 跨模块直查已修复(homework/grades/parent/diagnostic/elective/proctoring/notifications/scheduling/classes 等模块的直查改为通过对方 data-access 接口)"
    ]
  },
  "techStack": {
    "framework": "Next.js 16 (App Router)",
    "language": "TypeScript (strict)",
    "ui": "React 19 + Tailwind CSS v4 + shadcn/ui",
    "state": [
      "Zustand",
      "nuqs",
      "React Hook Form"
    ],
    "database": "MySQL + Drizzle ORM 0.45",
    "auth": "NextAuth v5 (JWT strategy)",
    "validation": "Zod 4",
    "ai": "OpenAI SDK (multi-provider)",
    "testing": [
      "Vitest",
      "Playwright"
    ]
  },
  "roles": [
    "admin",
    "teacher",
    "student",
    "parent",
    "grade_head",
    "teaching_head"
  ],
  "permissions": {
    "EXAM_CREATE": "exam:create",
    "EXAM_READ": "exam:read",
    "EXAM_UPDATE": "exam:update",
    "EXAM_DELETE": "exam:delete",
    "EXAM_DUPLICATE": "exam:duplicate",
    "EXAM_PUBLISH": "exam:publish",
    "EXAM_AI_GENERATE": "exam:ai_generate",
    "EXAM_SUBMIT": "exam:submit",
    "HOMEWORK_CREATE": "homework:create",
    "HOMEWORK_GRADE": "homework:grade",
    "HOMEWORK_SUBMIT": "homework:submit",
    "QUESTION_CREATE": "question:create",
    "QUESTION_READ": "question:read",
    "QUESTION_UPDATE": "question:update",
    "QUESTION_DELETE": "question:delete",
    "TEXTBOOK_CREATE": "textbook:create",
    "TEXTBOOK_READ": "textbook:read",
    "TEXTBOOK_UPDATE": "textbook:update",
    "TEXTBOOK_DELETE": "textbook:delete",
    "CLASS_CREATE": "class:create",
    "CLASS_READ": "class:read",
    "CLASS_UPDATE": "class:update",
    "CLASS_DELETE": "class:delete",
    "CLASS_ENROLL": "class:enroll",
    "CLASS_SCHEDULE": "class:schedule",
    "SCHOOL_MANAGE": "school:manage",
    "GRADE_MANAGE": "grade:manage",
    "USER_MANAGE": "user:manage",
    "AI_CHAT": "ai:chat",
    "AI_CONFIGURE": "ai:configure",
    "SETTINGS_ADMIN": "settings:admin",
    "AUDIT_LOG_READ": "audit_log:read",
    "ANNOUNCEMENT_MANAGE": "announcement:manage",
    "ANNOUNCEMENT_READ": "announcement:read",
    "GRADE_RECORD_MANAGE": "grade_record:manage",
    "GRADE_RECORD_READ": "grade_record:read",
    "FILE_UPLOAD": "file:upload",
    "FILE_READ": "file:read",
    "FILE_DELETE": "file:delete",
    "COURSE_PLAN_MANAGE": "course_plan:manage",
    "COURSE_PLAN_READ": "course_plan:read",
    "ATTENDANCE_MANAGE": "attendance:manage",
    "ATTENDANCE_READ": "attendance:read",
    "MESSAGE_SEND": "message:send",
    "MESSAGE_READ": "message:read",
    "MESSAGE_DELETE": "message:delete",
    "SCHEDULE_AUTO": "schedule:auto",
    "SCHEDULE_ADJUST": "schedule:adjust",
    "DIAGNOSTIC_MANAGE": "diagnostic:manage",
    "DIAGNOSTIC_READ": "diagnostic:read",
    "ELECTIVE_MANAGE": "elective:manage",
    "ELECTIVE_READ": "elective:read",
    "ELECTIVE_SELECT": "elective:select",
    "EXAM_PROCTOR": "exam:proctor",
    "EXAM_PROCTOR_READ": "exam:proctor:read",
    "LESSON_PLAN_CREATE": "lesson_plan:create",
    "LESSON_PLAN_READ": "lesson_plan:read",
    "LESSON_PLAN_UPDATE": "lesson_plan:update",
    "LESSON_PLAN_DELETE": "lesson_plan:delete",
    "LESSON_PLAN_PUBLISH": "lesson_plan:publish",
    "DASHBOARD_ADMIN_READ": "dashboard:admin_read",
    "DASHBOARD_TEACHER_READ": "dashboard:teacher_read",
    "DASHBOARD_STUDENT_READ": "dashboard:student_read",
    "DASHBOARD_PARENT_READ": "dashboard:parent_read",
    "ERROR_BOOK_READ": "error_book:read",
    "ERROR_BOOK_MANAGE": "error_book:manage",
    "ERROR_BOOK_ANALYTICS_READ": "error_book:analytics_read"
  },
  "rolePermissions": {
    "admin": [
      "EXAM_CREATE",
      "EXAM_READ",
      "EXAM_UPDATE",
      "EXAM_DELETE",
      "EXAM_DUPLICATE",
      "EXAM_PUBLISH",
      "EXAM_AI_GENERATE",
      "HOMEWORK_CREATE",
      "HOMEWORK_GRADE",
      "QUESTION_CREATE",
      "QUESTION_READ",
      "QUESTION_UPDATE",
      "QUESTION_DELETE",
      "TEXTBOOK_CREATE",
      "TEXTBOOK_READ",
      "TEXTBOOK_UPDATE",
      "TEXTBOOK_DELETE",
      "CLASS_CREATE",
      "CLASS_READ",
      "CLASS_UPDATE",
      "CLASS_DELETE",
      "CLASS_ENROLL",
      "CLASS_SCHEDULE",
      "SCHOOL_MANAGE",
      "GRADE_MANAGE",
      "USER_MANAGE",
      "AI_CHAT",
      "AI_CONFIGURE",
      "SETTINGS_ADMIN",
      "AUDIT_LOG_READ",
      "ANNOUNCEMENT_MANAGE",
      "ANNOUNCEMENT_READ",
      "GRADE_RECORD_MANAGE",
      "GRADE_RECORD_READ",
      "FILE_UPLOAD",
      "FILE_READ",
      "FILE_DELETE",
      "COURSE_PLAN_MANAGE",
      "COURSE_PLAN_READ",
      "ATTENDANCE_MANAGE",
      "ATTENDANCE_READ",
      "MESSAGE_SEND",
      "MESSAGE_READ",
      "MESSAGE_DELETE",
      "SCHEDULE_AUTO",
      "SCHEDULE_ADJUST",
      "DIAGNOSTIC_MANAGE",
      "DIAGNOSTIC_READ",
      "ELECTIVE_MANAGE",
      "ELECTIVE_READ",
      "EXAM_PROCTOR",
      "EXAM_PROCTOR_READ",
      "DASHBOARD_ADMIN_READ",
      "ERROR_BOOK_ANALYTICS_READ"
    ],
    "teacher": [
      "EXAM_CREATE",
      "EXAM_READ",
      "EXAM_UPDATE",
      "EXAM_DELETE",
      "EXAM_DUPLICATE",
      "EXAM_PUBLISH",
      "EXAM_AI_GENERATE",
      "HOMEWORK_CREATE",
      "HOMEWORK_GRADE",
      "QUESTION_CREATE",
      "QUESTION_READ",
      "QUESTION_UPDATE",
      "QUESTION_DELETE",
      "TEXTBOOK_CREATE",
      "TEXTBOOK_READ",
      "TEXTBOOK_UPDATE",
      "CLASS_READ",
      "CLASS_ENROLL",
      "CLASS_SCHEDULE",
      "AI_CHAT",
      "ANNOUNCEMENT_READ",
      "GRADE_RECORD_MANAGE",
      "GRADE_RECORD_READ",
      "FILE_UPLOAD",
      "FILE_READ",
      "FILE_DELETE",
      "COURSE_PLAN_READ",
      "ATTENDANCE_MANAGE",
      "ATTENDANCE_READ",
      "MESSAGE_SEND",
      "MESSAGE_READ",
      "MESSAGE_DELETE",
      "DIAGNOSTIC_MANAGE",
      "DIAGNOSTIC_READ",
      "ELECTIVE_MANAGE",
      "ELECTIVE_READ",
      "EXAM_PROCTOR",
      "EXAM_PROCTOR_READ",
      "DASHBOARD_TEACHER_READ",
      "ERROR_BOOK_ANALYTICS_READ"
    ],
    "student": [
      "EXAM_READ",
      "EXAM_SUBMIT",
      "HOMEWORK_SUBMIT",
      "QUESTION_READ",
      "TEXTBOOK_READ",
      "CLASS_READ",
      "AI_CHAT",
      "ANNOUNCEMENT_READ",
      "GRADE_RECORD_READ",
      "FILE_READ",
      "COURSE_PLAN_READ",
      "ATTENDANCE_READ",
      "MESSAGE_SEND",
      "MESSAGE_READ",
      "MESSAGE_DELETE",
      "DIAGNOSTIC_READ",
      "ELECTIVE_SELECT",
      "ELECTIVE_READ",
      "DASHBOARD_STUDENT_READ",
      "ERROR_BOOK_READ",
      "ERROR_BOOK_MANAGE"
    ],
    "parent": [
      "EXAM_READ",
      "TEXTBOOK_READ",
      "CLASS_READ",
      "ANNOUNCEMENT_READ",
      "GRADE_RECORD_READ",
      "FILE_READ",
      "ATTENDANCE_READ",
      "MESSAGE_SEND",
      "MESSAGE_READ",
      "MESSAGE_DELETE",
      "DASHBOARD_PARENT_READ",
      "ERROR_BOOK_READ"
    ],
    "grade_head": [
      "EXAM_CREATE",
      "EXAM_READ",
      "EXAM_UPDATE",
      "EXAM_DELETE",
      "EXAM_DUPLICATE",
      "EXAM_PUBLISH",
      "EXAM_AI_GENERATE",
      "HOMEWORK_CREATE",
      "HOMEWORK_GRADE",
      "QUESTION_CREATE",
      "QUESTION_READ",
      "QUESTION_UPDATE",
      "QUESTION_DELETE",
      "TEXTBOOK_CREATE",
      "TEXTBOOK_READ",
      "TEXTBOOK_UPDATE",
      "CLASS_CREATE",
      "CLASS_READ",
      "CLASS_UPDATE",
      "CLASS_ENROLL",
      "CLASS_SCHEDULE",
      "GRADE_MANAGE",
      "AI_CHAT",
      "ANNOUNCEMENT_READ",
      "GRADE_RECORD_READ",
      "COURSE_PLAN_READ",
      "ATTENDANCE_READ",
      "MESSAGE_SEND",
      "MESSAGE_READ",
      "MESSAGE_DELETE",
      "DIAGNOSTIC_MANAGE",
      "DIAGNOSTIC_READ",
      "ELECTIVE_READ",
      "EXAM_PROCTOR_READ",
      "ERROR_BOOK_ANALYTICS_READ"
    ],
    "teaching_head": [
      "EXAM_CREATE",
      "EXAM_READ",
      "EXAM_UPDATE",
      "EXAM_DELETE",
      "EXAM_DUPLICATE",
      "EXAM_PUBLISH",
      "EXAM_AI_GENERATE",
      "HOMEWORK_CREATE",
      "HOMEWORK_GRADE",
      "QUESTION_CREATE",
      "QUESTION_READ",
      "QUESTION_UPDATE",
      "QUESTION_DELETE",
      "TEXTBOOK_CREATE",
      "TEXTBOOK_READ",
      "TEXTBOOK_UPDATE",
      "CLASS_READ",
      "GRADE_MANAGE",
      "AI_CHAT",
      "ANNOUNCEMENT_READ",
      "GRADE_RECORD_READ",
      "COURSE_PLAN_READ",
      "ATTENDANCE_READ",
      "MESSAGE_SEND",
      "MESSAGE_READ",
      "MESSAGE_DELETE",
      "DIAGNOSTIC_READ",
      "ELECTIVE_READ",
      "ERROR_BOOK_ANALYTICS_READ"
    ]
  },
  "dataScopeTypes": {
    "all": "管理员：无过滤",
    "owned": "仅自己创建的资源，含 userId 字段",
    "class_taught": "教师：所教班级，含 classIds[] 和可选 subjectIds[]",
    "grade_managed": "年级主任：所管年级，含 gradeIds[]",
    "class_members": "学生：所在班级的成员数据",
    "children": "家长：子女数据，含 childrenIds[]"
  },
  "modules": {
    "shared": {
      "path": "src/shared",
      "description": "全项目共享基础设施：数据库、工具函数、权限系统、UI组件、Hooks",
      "exports": {
        "functions": [
          {
            "name": "cn",
            "file": "lib/utils.ts",
            "signature": "cn(...inputs: ClassValue[]): string",
            "purpose": "合并CSS类名并解决Tailwind冲突",
            "deps": [
              "clsx",
              "tailwind-merge"
            ],
            "usedBy": [
              "*所有模块组件"
            ]
          },
          {
            "name": "formatDate",
            "file": "lib/utils.ts",
            "signature": "formatDate(date: string | Date, locale?: string): string",
            "params": {
              "date": "日期值",
              "locale": "Intl locale，默认zh-CN"
            },
            "purpose": "国际化日期格式化",
            "deps": [],
            "usedBy": [
              "exams",
              "homework",
              "dashboard",
              "textbooks"
            ]
          },
          {
            "name": "formatDateTime",
            "file": "lib/utils.ts",
            "signature": "formatDateTime(date: string | Date, locale?: string): string",
            "params": {
              "date": "日期值",
              "locale": "Intl locale，默认zh-CN"
            },
            "purpose": "国际化日期+时间格式化（含小时、分钟），P1-3 重构从 lesson-plan-card、version-history-drawer、proctoring-dashboard、exam-ai-generator 等处重复的 new Date(x).toLocaleString(...) 抽取",
            "deps": [],
            "usedBy": [
              "lesson-preparation/components/lesson-plan-card.tsx",
              "lesson-preparation/components/version-history-drawer.tsx",
              "proctoring/components/proctoring-dashboard.tsx",
              "exams/components/exam-ai-generator.tsx"
            ]
          },
          {
            "name": "formatLongDate",
            "file": "lib/utils.ts",
            "signature": "formatLongDate(date: string | Date, locale?: string): string",
            "params": {
              "date": "日期值",
              "locale": "Intl locale，默认zh-CN"
            },
            "purpose": "国际化长日期格式化（含星期、完整月份名），P1-3 重构从 teacher-dashboard-header 中重复的 new Date().toLocaleDateString('en-US', { weekday, year, month, day }) 抽取；teacher_bug_v4 P2-1 默认 locale 改为 zh-CN，weekday 改为 short，输出形如「2026年6月20日周一」",
            "deps": [],
            "usedBy": [
              "dashboard/components/teacher-dashboard/teacher-dashboard-header.tsx"
            ]
          },
          {
            "name": "getParam",
            "file": "lib/search-params.ts",
            "signature": "getParam(params: SearchParams, key: string): string | undefined",
            "params": {
              "params": "Next.js searchParams 对象",
              "key": "参数键名"
            },
            "purpose": "规范化 Next.js 15+ searchParams 访问（string | string[] | undefined → string | undefined），re-export 自 utils.ts 的 getSearchParam",
            "deps": [
              "shared/lib/utils.getSearchParam"
            ],
            "usedBy": [
              "teacher/attendance/page.tsx",
              "teacher/attendance/sheet/page.tsx",
              "teacher/attendance/stats/page.tsx",
              "teacher/classes/schedule/page.tsx",
              "teacher/classes/students/page.tsx",
              "teacher/course-plans/page.tsx",
              "teacher/diagnostic/page.tsx",
              "teacher/elective/page.tsx",
              "teacher/exams/all/page.tsx",
              "teacher/grades/page.tsx",
              "teacher/grades/analytics/page.tsx",
              "teacher/grades/entry/page.tsx",
              "teacher/grades/stats/page.tsx",
              "teacher/homework/assignments/page.tsx",
              "teacher/questions/page.tsx",
              "teacher/textbooks/page.tsx"
            ]
          },
          {
            "name": "parseAiChatPayload",
            "file": "lib/ai/payload-parser.ts",
            "signature": "parseAiChatPayload(body: unknown): AiChatRequest",
            "purpose": "解析并校验AI聊天请求负载",
            "deps": [
              "zod"
            ],
            "usedBy": [
              "app/api/ai/chat/route.ts"
            ]
          },
          {
            "name": "encryptAiApiKey",
            "file": "lib/ai/api-key-crypto.ts",
            "signature": "encryptAiApiKey(value: string): string",
            "purpose": "AES加密AI Provider API Key",
            "deps": [
              "crypto"
            ],
            "usedBy": [
              "settings/actions.ts"
            ]
          },
          {
            "name": "decryptAiApiKey",
            "file": "lib/ai/api-key-crypto.ts",
            "signature": "decryptAiApiKey(value: string): string",
            "purpose": "AES解密AI Provider API Key",
            "deps": [
              "crypto"
            ],
            "usedBy": [
              "settings/actions.ts",
              "ai/api-key-crypto.ts内部"
            ]
          },
          {
            "name": "testAiProviderConfig",
            "file": "lib/ai/client.ts",
            "signature": "testAiProviderConfig(input: { apiKey: string; baseUrl?: string; model: string }): Promise<boolean>",
            "purpose": "测试AI Provider连通性(直接配置)",
            "deps": [
              "createAiChatCompletion"
            ],
            "usedBy": [
              "settings/actions.ts"
            ]
          },
          {
            "name": "testAiProviderById",
            "file": "lib/ai/client.ts",
            "signature": "testAiProviderById(providerId: string, overrides?: { baseUrl?: string; model?: string }): Promise<boolean>",
            "purpose": "测试AI Provider连通性(按ID)",
            "deps": [
              "shared/db",
              "createAiChatCompletion"
            ],
            "usedBy": [
              "settings/actions.ts"
            ]
          },
          {
            "name": "createAiChatCompletion",
            "file": "lib/ai/client.ts",
            "signature": "createAiChatCompletion(input: AiChatRequest): Promise<{ content: string; usage: unknown }>",
            "purpose": "调用AI模型生成聊天回复",
            "deps": [
              "openai",
              "shared/db",
              "ai/provider-config.ts"
            ],
            "usedBy": [
              "exams/ai-pipeline.ts",
              "app/api/ai/chat/route.ts"
            ]
          },
          {
            "name": "getAiErrorMessage",
            "file": "lib/ai/errors.ts",
            "signature": "getAiErrorMessage(v: unknown): string",
            "purpose": "从AI错误中提取可读消息",
            "deps": [],
            "usedBy": [
              "exams/ai-pipeline.ts"
            ]
          },
          {
            "name": "getAuthContext",
            "file": "lib/auth-guard.ts",
            "signature": "getAuthContext(): Promise<AuthContext>",
            "returns": "AuthContext { userId, roles, permissions, dataScope }",
            "purpose": "获取当前用户完整认证上下文",
            "deps": [
              "shared/lib/session.getSession",
              "shared/db"
            ],
            "usedBy": [
              "所有业务模块的Server Actions"
            ]
          },
          {
            "name": "requirePermission",
            "file": "lib/auth-guard.ts",
            "signature": "requirePermission(permission: Permission): Promise<AuthContext>",
            "throws": "PermissionDeniedError",
            "purpose": "断言当前用户拥有指定权限",
            "deps": [
              "getAuthContext"
            ],
            "usedBy": [
              "所有业务模块的Server Actions"
            ]
          },
          {
            "name": "checkPermission",
            "file": "lib/auth-guard.ts",
            "signature": "checkPermission(permission: Permission): Promise<{ allowed: boolean; ctx: AuthContext }>",
            "purpose": "非抛出版权限检查",
            "deps": [
              "getAuthContext"
            ],
            "usedBy": []
          },
          {
            "name": "requireAuth",
            "file": "lib/auth-guard.ts",
            "signature": "requireAuth(): Promise<AuthContext>",
            "purpose": "仅断言用户已登录",
            "deps": [
              "getAuthContext"
            ],
            "usedBy": []
          },
          {
            "name": "resolvePermissions",
            "file": "lib/permissions.ts",
            "signature": "resolvePermissions(roleNames: string[]): Permission[]",
            "purpose": "合并多角色的权限列表(去重)",
            "deps": [
              "ROLE_PERMISSIONS"
            ],
            "usedBy": [
              "auth.ts (JWT callback)"
            ]
          },
          {
            "name": "logAudit",
            "file": "lib/audit-logger.ts",
            "signature": "logAudit(params: LogAuditParams): Promise<void>",
            "purpose": "记录操作日志（静默失败）",
            "deps": [
              "shared/lib/session.getSession",
              "shared/lib/http-utils.resolveClientIp",
              "shared/lib/http-utils.getUserAgent",
              "shared.db",
              "shared.db.schema.auditLogs",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "school/actions.ts",
              "其他Server Actions"
            ]
          },
          {
            "name": "logLoginEvent",
            "file": "lib/login-logger.ts",
            "signature": "logLoginEvent(params: LogLoginEventParams): Promise<void>",
            "purpose": "记录登录日志（signin/signout/signup，静默失败）",
            "deps": [
              "shared/lib/http-utils.resolveClientIp",
              "shared/lib/http-utils.getUserAgent",
              "shared.db",
              "shared.db.schema.loginLogs",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "auth.ts (events.signIn, events.signOut)"
            ]
          },
          {
            "name": "normalizeRole",
            "file": "lib/role-utils.ts",
            "signature": "normalizeRole(value: unknown): NormalizedRole",
            "purpose": "将角色值规范化为 admin/teacher/student/parent 之一（纯函数，legacy 别名 grade_head/teaching_head→teacher）",
            "deps": [],
            "usedBy": [
              "auth.ts (jwt/session callbacks)",
              "lib/role-utils.resolvePrimaryRole"
            ]
          },
          {
            "name": "resolvePrimaryRole",
            "file": "lib/role-utils.ts",
            "signature": "resolvePrimaryRole(roleNames: string[]): NormalizedRole",
            "purpose": "从多角色列表解析主角色（优先级 admin>teacher>parent>student，纯函数）",
            "deps": [
              "lib/role-utils.normalizeRole"
            ],
            "usedBy": [
              "auth.ts (authorize, jwt callback)"
            ]
          },
          {
            "name": "normalizeBcryptHash",
            "file": "lib/bcrypt-utils.ts",
            "signature": "normalizeBcryptHash(value: string): string",
            "purpose": "将存储的 bcrypt 哈希规范化为 $2b$ 前缀形式（纯函数，兼容 legacy 无前缀存储）",
            "deps": [],
            "usedBy": [
              "auth.ts (authorize)"
            ]
          },
          {
            "name": "resolveClientIp",
            "file": "lib/http-utils.ts",
            "signature": "resolveClientIp(): Promise<string>",
            "purpose": "从请求头解析客户端 IP（x-forwarded-for 第一段/x-real-ip，best-effort，失败返回 unknown）",
            "deps": [
              "next/headers"
            ],
            "usedBy": [
              "auth.ts (authorize 速率限制键)",
              "lib/audit-logger.logAudit",
              "lib/change-logger.logDataChange",
              "lib/login-logger.logLoginEvent"
            ]
          },
          {
            "name": "getUserAgent",
            "file": "lib/http-utils.ts",
            "signature": "getUserAgent(): Promise<string>",
            "purpose": "从请求头解析 User-Agent（best-effort，失败返回 unknown）",
            "deps": [
              "next/headers"
            ],
            "usedBy": [
              "lib/audit-logger.logAudit",
              "lib/login-logger.logLoginEvent"
            ]
          },
          {
            "name": "getSession",
            "file": "lib/session.ts",
            "signature": "getSession(): Promise<AppSession>",
            "purpose": "session 获取单一入口（server-only，内部 dynamic import @/auth 打破 shared/lib ↔ auth 静态循环依赖）",
            "deps": [
              "@/auth (dynamic import)"
            ],
            "usedBy": [
              "lib/auth-guard.getAuthContext",
              "lib/audit-logger.logAudit",
              "lib/change-logger.logDataChange"
            ]
          },
          {
            "name": "getOrCreatePasswordSecurity",
            "file": "lib/password-security-service.ts",
            "signature": "getOrCreatePasswordSecurity(db, passwordSecurity, userId: string): Promise<PasswordSecurityRow>",
            "purpose": "获取或创建用户的 password_security 行（server-only）",
            "deps": [
              "drizzle-orm.eq",
              "@paralleldrive/cuid2",
              "shared.db",
              "shared.db.schema.passwordSecurity"
            ],
            "usedBy": [
              "auth.ts (authorize)",
              "lib/password-security-service.recordFailedLogin",
              "lib/password-security-service.resetFailedLogin"
            ]
          },
          {
            "name": "recordFailedLogin",
            "file": "lib/password-security-service.ts",
            "signature": "recordFailedLogin(db, passwordSecurity, userId: string): Promise<{ locked: boolean; lockedUntil: Date | null }>",
            "purpose": "递增失败登录计数，达到阈值则锁定账户（server-only）",
            "deps": [
              "lib/password-security-service.getOrCreatePasswordSecurity",
              "lib/password-policy.PASSWORD_RULES",
              "shared.db",
              "shared.db.schema.passwordSecurity"
            ],
            "usedBy": [
              "auth.ts (authorize 密码校验失败分支)"
            ]
          },
          {
            "name": "resetFailedLogin",
            "file": "lib/password-security-service.ts",
            "signature": "resetFailedLogin(db, passwordSecurity, userId: string): Promise<void>",
            "purpose": "登录成功后重置失败计数与锁定状态（server-only）",
            "deps": [
              "lib/password-security-service.getOrCreatePasswordSecurity",
              "shared.db",
              "shared.db.schema.passwordSecurity"
            ],
            "usedBy": [
              "auth.ts (authorize 登录成功分支)"
            ]
          },
          {
            "name": "isAllowedMimeType",
            "file": "lib/file-storage.ts",
            "signature": "isAllowedMimeType(mimeType: string): boolean",
            "purpose": "判断MIME类型是否在允许上传的白名单中",
            "deps": [
              "ALLOWED_MIME_TYPES 常量"
            ],
            "usedBy": [
              "app/api/upload/route.ts",
              "files/components/file-upload.tsx"
            ]
          },
          {
            "name": "generateStoragePath",
            "file": "lib/file-storage.ts",
            "signature": "generateStoragePath(originalName: string): string",
            "purpose": "根据原始文件名生成存储路径 uploads/YYYY-MM/cuid.ext（相对于public/）",
            "deps": [
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "app/api/upload/route.ts"
            ]
          },
          {
            "name": "getFileExtension",
            "file": "lib/file-storage.ts",
            "signature": "getFileExtension(filename: string): string",
            "purpose": "从文件名中提取小写扩展名（不含点）",
            "deps": [],
            "usedBy": [
              "shared/lib/file-storage 内部"
            ]
          },
          {
            "name": "formatFileSize",
            "file": "lib/file-storage.ts",
            "signature": "formatFileSize(bytes: number): string",
            "purpose": "将字节数格式化为人类可读字符串（如 1.5 MB、800 KB）",
            "deps": [],
            "usedBy": [
              "files/components/file-upload.tsx",
              "files/components/file-list.tsx",
              "files/components/file-preview.tsx"
            ]
          },
          {
            "name": "exportToExcel",
            "file": "lib/excel.ts",
            "signature": "exportToExcel(params: { sheets: ExcelSheet[] }): Promise<Buffer>",
            "params": {
              "sheets": "ExcelSheet[]，每个含 name/columns/rows"
            },
            "purpose": "将多 sheet 数据导出为 Excel Buffer（表头加粗+冻结首行+自动筛选）",
            "deps": [
              "exceljs"
            ],
            "usedBy": [
              "users/import-export.exportUsersToExcel",
              "grades/export.exportGradeRecordsToExcel",
              "grades/export.exportClassGradeReportToExcel"
            ]
          },
          {
            "name": "parseExcel",
            "file": "lib/excel.ts",
            "signature": "parseExcel(buffer: Buffer): Promise<ParsedSheet[]>",
            "returns": "ParsedSheet[]，每个含 sheetName/rows",
            "purpose": "从 Buffer 解析 Excel 文件，首行作为表头，返回每 sheet 的行记录数组",
            "deps": [
              "exceljs"
            ],
            "usedBy": [
              "users/actions.importUsersAction",
              "app/api/import/route.ts"
            ]
          },
          {
            "name": "generateTemplate",
            "file": "lib/excel.ts",
            "signature": "generateTemplate(params: { sheets: TemplateSheet[] }): Promise<Buffer>",
            "params": {
              "sheets": "TemplateSheet[]，每个含 name/columns(含 note)/sampleRows?"
            },
            "purpose": "生成导入模板 Buffer（表头加粗+第二行填写说明+示例行）",
            "deps": [
              "exceljs"
            ],
            "usedBy": [
              "users/import-export.generateUserImportTemplate"
            ]
          },
          {
            "name": "useA11yId",
            "file": "lib/a11y.ts",
            "signature": "useA11yId(prefix: string): string",
            "purpose": "基于React.useId生成SSR安全的唯一ID，用于aria-describedby、aria-labelledby等",
            "deps": [
              "react"
            ],
            "usedBy": [
              "待扩展（表单组件、a11y组件）"
            ]
          },
          {
            "name": "mergeA11yProps",
            "file": "lib/a11y.ts",
            "signature": "mergeA11yProps<T extends Record<string, unknown>>(...props: (T | undefined | null | false)[]): T",
            "purpose": "合并多组aria/data属性，普通属性后者覆盖前者，aria-*/data-*字符串属性以空格拼接",
            "deps": [],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "describeInput",
            "file": "lib/a11y.ts",
            "signature": "describeInput(describedBy?: string, error?: string, hint?: string): { ariaDescribedBy?: string; ariaInvalid?: boolean }",
            "purpose": "计算输入框的aria-describedby（合并多个ID）与aria-invalid（error存在则为true）",
            "deps": [],
            "usedBy": [
              "待扩展（表单组件）"
            ]
          },
          {
            "name": "loadingAria",
            "file": "lib/a11y.ts",
            "signature": "loadingAria(isLoading: boolean): { ariaBusy: boolean; ariaLive: 'polite' | 'assertive' }",
            "purpose": "提供加载状态的aria-busy与aria-live=polite属性",
            "deps": [],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getInitials",
            "file": "lib/utils.ts",
            "signature": "getInitials(name: string | null | undefined): string",
            "purpose": "从用户姓名提取首字母缩写（最多2字符，用于头像 fallback）",
            "deps": [],
            "usedBy": [
              "shared/components/ui/avatar.tsx",
              "modules/dashboard/components/*-dashboard/*-header.tsx",
              "modules/parent/components/child-card.tsx"
            ]
          },
          {
            "name": "formatDateForFile",
            "file": "lib/utils.ts",
            "signature": "formatDateForFile(d?: Date): string",
            "purpose": "格式化日期为 YYYY-MM-DD 用于文件名（P1-c/P2-c 重构：从 grades/export.ts、audit/actions.ts、api/export/route.ts、users/actions.ts 四处重复实现抽取）",
            "deps": [],
            "usedBy": [
              "grades/actions.exportGradesAction",
              "audit/actions.exportAuditLogsAction",
              "api/export/route",
              "users/actions.exportUsersAction"
            ]
          },
          {
            "name": "downloadBase64File",
            "file": "lib/download.ts",
            "signature": "downloadBase64File(base64: string, filename: string, mimeType?: string): void",
            "purpose": "客户端下载 Base64 编码文件（默认 MIME 为 Excel xlsx），P1-c 重构从 grades/export-button、users/user-import-dialog 两处重复实现抽取",
            "deps": [
              "shared/lib/download.downloadBlob"
            ],
            "usedBy": [
              "grades/components/export-button.tsx",
              "users/components/user-import-dialog.tsx"
            ]
          },
          {
            "name": "downloadBlob",
            "file": "lib/download.ts",
            "signature": "downloadBlob(blob: Blob, filename: string): void",
            "purpose": "客户端下载 Blob 对象（创建临时 URL + a 标签点击 + revoke），P1-c 重构从 audit/audit-log-export-button 抽取",
            "deps": [],
            "usedBy": [
              "shared/lib/download.downloadBase64File",
              "audit/components/audit-log-export-button.tsx"
            ]
          },
          {
            "name": "handleActionError",
            "file": "lib/action-utils.ts",
            "signature": "handleActionError(e: unknown): ActionState<never>",
            "purpose": "2026-06-23 审计修复新增：统一 Server Action catch 块错误处理。PermissionDeniedError/BusinessError 返回其 message（可安全暴露），其他 Error 返回通用消息并 console.error 记录到服务端日志，避免内部错误消息（如 SQL 错误、堆栈信息）暴露给客户端",
            "deps": [
              "shared/lib/auth-guard.PermissionDeniedError",
              "shared/types/action-state.ActionState"
            ],
            "usedBy": [
              "grades/actions",
              "grades/actions-analytics",
              "homework/actions",
              "lesson-preparation/actions",
              "lesson-preparation/actions-publish",
              "lesson-preparation/actions-ai",
              "lesson-preparation/actions-kp",
              "其他模块 Server Actions"
            ]
          },
          {
            "name": "safeActionCall",
            "file": "lib/action-utils.ts",
            "signature": "safeActionCall<T>(action: () => Promise<ActionState<T>>, options?: { onError?: (error: unknown) => void; onFinally?: () => void }): Promise<ActionState<T> | null>",
            "purpose": "2026-06-23 审计修复新增：客户端调用 Server Action 的 try/catch/finally 包装器。Action 抛出异常时执行 onError 回调，无论成功失败执行 onFinally 回调（用于重置 loading 状态），防止 UI 永久卡 loading",
            "deps": [
              "shared/types/action-state.ActionState"
            ],
            "usedBy": [
              "grades/components/grade-record-list",
              "grades/components/grade-record-form",
              "grades/components/batch-grade-entry",
              "grades/components/export-button",
              "其他模块客户端组件（20+ 处）"
            ]
          },
          {
            "name": "safeJsonParse",
            "file": "lib/action-utils.ts",
            "signature": "safeJsonParse<T>(json: string, errorMessage: string): T",
            "purpose": "2026-06-23 审计修复新增：安全 JSON.parse，失败时抛出 ValidationError（替代裸 JSON.parse，避免 SyntaxError 被外层 catch 捕获后暴露解析细节给客户端）",
            "deps": [
              "shared/lib/action-utils.ValidationError"
            ],
            "usedBy": [
              "grades/actions.batchCreateGradeRecordsAction",
              "homework/actions.batchAutoGradeSubmissionsAction",
              "其他模块 Server Actions（8+ 处）"
            ]
          },
          {
            "name": "safeParseDate",
            "file": "lib/action-utils.ts",
            "signature": "safeParseDate(value: string, fieldName: string): Date",
            "purpose": "2026-06-23 审计修复新增：校验日期字符串有效性，无效则抛出 ValidationError。用于 Server Action 中包装 new Date() 调用，避免 Invalid Date 传播到下游逻辑",
            "deps": [
              "shared/lib/action-utils.ValidationError"
            ],
            "usedBy": [
              "lesson-preparation/actions-publish",
              "其他模块 Server Actions（15+ 处）"
            ]
          },
          {
            "name": "safeParseNumber",
            "file": "lib/action-utils.ts",
            "signature": "safeParseNumber(value: string, fieldName: string): number",
            "purpose": "2026-06-23 审计修复新增：校验数字字符串，无效则抛出 ValidationError。用于 Server Action 中包装 Number() 调用",
            "deps": [
              "shared/lib/action-utils.ValidationError"
            ],
            "usedBy": [
              "其他模块 Server Actions"
            ]
          },
          {
            "name": "escapeLikePattern",
            "file": "lib/action-utils.ts",
            "signature": "escapeLikePattern(input: string): string",
            "purpose": "2026-06-23 审计修复新增：转义 SQL LIKE 通配符（% _ \\），防止用户输入干扰模糊查询。用于构建 sql`LIKE ${'%'+escapeLikePattern(q)+'%'}` 模式",
            "deps": [],
            "usedBy": [
              "其他模块 data-access 模糊查询"
            ]
          },
          {
            "name": "BusinessError",
            "file": "lib/action-utils.ts",
            "signature": "class BusinessError extends Error { constructor(message: string, code?: string) }",
            "purpose": "2026-06-23 审计修复新增：已知业务错误基类，message 可安全返回客户端（由 handleActionError 识别）。子类：NotFoundError（资源不存在）、ValidationError（输入校验错误）",
            "deps": [],
            "usedBy": [
              "grades/actions.assertClassInScope",
              "grades/data-access.updateGradeRecord",
              "grades/data-access.deleteGradeRecord",
              "shared/lib/action-utils.handleActionError"
            ]
          },
          {
            "name": "NotFoundError",
            "file": "lib/action-utils.ts",
            "signature": "class NotFoundError extends BusinessError { constructor(resource: string) }",
            "purpose": "2026-06-23 审计修复新增：资源不存在错误。自动生成 `${resource} 不存在` 消息，code: not_found。用于 data-access 层存在性检查失败时抛出",
            "deps": [
              "shared/lib/action-utils.BusinessError"
            ],
            "usedBy": [
              "grades/data-access.updateGradeRecord",
              "grades/data-access.deleteGradeRecord"
            ]
          },
          {
            "name": "ValidationError",
            "file": "lib/action-utils.ts",
            "signature": "class ValidationError extends BusinessError { constructor(message: string) }",
            "purpose": "2026-06-23 审计修复新增：输入校验错误，code: validation_error。用于 safeJsonParse/safeParseDate/safeParseNumber 解析失败时抛出",
            "deps": [
              "shared/lib/action-utils.BusinessError"
            ],
            "usedBy": [
              "shared/lib/action-utils.safeJsonParse",
              "shared/lib/action-utils.safeParseDate",
              "shared/lib/action-utils.safeParseNumber"
            ]
          }
        ],
        "hooks": [
          {
            "name": "useActionWithToast",
            "file": "hooks/use-action-with-toast.ts",
            "signature": "useActionWithToast<T>(): { isPending: boolean; execute: (action: () => Promise<ActionState<T>>) => void }",
            "purpose": "包装Server Action + toast反馈"
          },
          {
            "name": "useActionMutation",
            "file": "hooks/use-action-mutation.ts",
            "signature": "useActionMutation<T>(options?: { successMessage?, errorMessage?, onSuccess?, onError? }): { isWorking: boolean; mutate: (action: () => Promise<ActionState<T>>) => Promise<ActionState<T> | undefined> }",
            "purpose": "通用 Server Action mutation Hook，P1-4 重构从 50+ 个文件中重复的 setIsWorking(true) + try/catch/finally + toast 模式抽取。支持 successMessage/errorMessage/onSuccess/onError 配置",
            "usedBy": [
              "school/components/school-form-dialog.tsx",
              "school/components/school-delete-dialog.tsx"
            ]
          },
          {
            "name": "useActionQuery",
            "file": "hooks/use-action-query.ts",
            "signature": "useActionQuery<T>(action: () => Promise<ActionState<T>>, options?: { deps?, enabled?, errorMessage? }): { data, loading, error, refetch }",
            "purpose": "通用 Server Action 查询 Hook，P1-4 重构从 11 个文件中重复的 useEffect + useState(loading) + Action().then().catch().finally() 模式抽取。内置竞态防护（cancelled flag）",
            "usedBy": [
              "questions/components/create-question-dialog.tsx"
            ]
          },
          {
            "name": "useDebounce",
            "file": "hooks/use-debounce.ts",
            "signature": "useDebounce<T>(value: T, delay?: number): T",
            "purpose": "防抖Hook"
          },
          {
            "name": "useMediaQuery",
            "file": "hooks/use-media-query.ts",
            "signature": "useMediaQuery(query: string): boolean",
            "purpose": "响应式媒体查询Hook"
          },
          {
            "name": "useLocalStorage",
            "file": "hooks/use-local-storage.ts",
            "signature": "useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void]",
            "purpose": "localStorage持久化Hook"
          },
          {
            "name": "useCurrentTime",
            "file": "hooks/use-current-time.ts",
            "signature": "useCurrentTime(intervalMs?: number): Date",
            "purpose": "V4（P2-5）新增：返回当前时间并按指定间隔自动更新的 Hook，用于需要随时间刷新的 UI（如课表'进行中'徽章），避免 useMemo 依赖 [items] 导致过时"
          },
          {
            "name": "usePermission",
            "file": "hooks/use-permission.ts",
            "signature": "usePermission(): { permissions: Permission[]; roles: string[]; hasPermission: (p: Permission) => boolean; hasAnyPermission: (...p: Permission[]) => boolean; hasAllPermissions: (...p: Permission[]) => boolean; hasRole: (r: string) => boolean }",
            "purpose": "客户端权限检查Hook",
            "usedBy": [
              "layout/app-sidebar.tsx",
              "exams/components",
              "homework/components"
            ]
          },
          {
            "name": "logDataChange",
            "file": "lib/change-logger.ts",
            "signature": "logDataChange(params: LogDataChangeParams): Promise<void>",
            "purpose": "记录数据变更日志（写入 dataChangeLogs 表，自动获取 changedBy/changedByName/ipAddress；静默失败）",
            "deps": [
              "shared/lib/session.getSession",
              "shared/lib/http-utils.resolveClientIp",
              "shared/db (dataChangeLogs)",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "待扩展（数据变更场景）"
            ]
          },
          {
            "name": "StorageProvider",
            "file": "lib/storage-provider.ts",
            "signature": "interface StorageProvider { save(file: Buffer, storagePath: string): Promise<string>; read(storagePath: string): Promise<Buffer>; delete(storagePath: string): Promise<void>; exists(storagePath: string): Promise<boolean>; getUrl(storagePath: string): string }",
            "purpose": "文件存储抽象接口，便于未来切换到 OSS/S3",
            "usedBy": [
              "app/api/files/batch-delete/route.ts"
            ]
          },
          {
            "name": "LocalStorageProvider",
            "file": "lib/storage-provider.ts",
            "signature": "class LocalStorageProvider implements StorageProvider",
            "purpose": "本地磁盘存储实现，文件持久化到 public/uploads/...，URL 为 /uploads/...",
            "deps": [
              "fs/promises",
              "path"
            ],
            "usedBy": [
              "通过 storageProvider 实例使用"
            ]
          },
          {
            "name": "storageProvider",
            "file": "lib/storage-provider.ts",
            "signature": "const storageProvider: StorageProvider",
            "purpose": "默认存储 Provider 单例（LocalStorageProvider 实例），替换此实例可迁移到 OSS/S3",
            "usedBy": [
              "app/api/files/batch-delete/route.ts"
            ]
          }
        ],
        "components": [
          {
            "name": "AuthSessionProvider",
            "file": "components/auth-session-provider.tsx",
            "purpose": "NextAuth SessionProvider包装",
            "usedBy": [
              "app/layout.tsx"
            ]
          },
          {
            "name": "OnboardingGate",
            "file": "components/onboarding-gate.tsx",
            "purpose": "新用户引导流程（已废弃，逻辑迁移至 modules/onboarding/）",
            "deprecated": true,
            "usedBy": []
          },
          {
            "name": "ThemeProvider",
            "file": "components/theme-provider.tsx",
            "purpose": "next-themes主题切换",
            "usedBy": [
              "app/layout.tsx"
            ]
          },
          {
            "name": "EmptyState",
            "file": "components/ui/empty-state.tsx",
            "purpose": "列表空状态展示",
            "usedBy": [
              "exams",
              "homework",
              "questions",
              "textbooks"
            ]
          },
          {
            "name": "GlobalSearch",
            "file": "components/global-search.tsx",
            "props": "{ className?, placeholder? }",
            "purpose": "全局搜索组件（防抖300ms调用 GET /api/search，Cmd/Ctrl+K快捷键聚焦，Escape关闭，↑/↓键盘导航，Enter跳转；下拉展示 question/textbook/exam/announcement 四类结果）",
            "internalDeps": [
              "useDebounce",
              "Input",
              "Link",
              "useRouter"
            ],
            "usedBy": [
              "layout/components/site-header.tsx"
            ]
          },
          {
            "name": "Switch",
            "file": "components/ui/switch.tsx",
            "basedOn": "@radix-ui/react-switch",
            "props": "Radix Switch Root props (checked, onCheckedChange, disabled, id, aria-label)",
            "purpose": "开关切换UI组件（shadcn风格，checked/unchecked两态）",
            "usedBy": [
              "settings/components/notification-preferences-form.tsx"
            ]
          },
          {
            "name": "StatCard",
            "file": "components/ui/stat-card.tsx",
            "props": "{ title, value, icon?, description?, color?, highlight?, href?, isLoading?, valueClassName? }",
            "purpose": "统计卡片（标题+数值+图标+描述+跳转+骨架屏），P1-a 重构从 8 处重复实现抽取（teacher/student/admin dashboard stats、class-overview-stats、grade insights 等）",
            "internalDeps": [
              "Card",
              "CardHeader",
              "CardTitle",
              "CardContent",
              "Link",
              "cn",
              "Skeleton"
            ],
            "usedBy": [
              "dashboard/components/teacher-dashboard/teacher-stats.tsx",
              "dashboard/components/student-dashboard/student-stats-grid.tsx",
              "dashboard/components/admin-dashboard/admin-dashboard.tsx",
              "classes/components/class-detail/class-overview-stats.tsx",
              "app/(dashboard)/admin/school/grades/insights/page.tsx",
              "app/(dashboard)/management/grade/insights/page.tsx"
            ]
          },
          {
            "name": "StatItem",
            "file": "components/ui/stat-item.tsx",
            "props": "{ label, value, icon?, hint?, valueClassName? }",
            "purpose": "紧凑统计项（label+icon+value+hint，用于统计面板网格），P1-a 重构从 attendance-stats-card、grade-stats-card 两处重复实现抽取",
            "internalDeps": [
              "cn"
            ],
            "usedBy": [
              "attendance/components/attendance-stats-card.tsx",
              "grades/components/grade-stats-card.tsx"
            ]
          },
          {
            "name": "ChipNav",
            "file": "components/ui/chip-nav.tsx",
            "props": "{ options: ChipNavOption[], currentId, buildHref: (id) => string, size?: 'sm'|'xs', allOption?, className? }",
            "purpose": "芯片导航组（通过 URL search params 切换筛选维度，Link 跳转），P1-b 重构从 stats-class-selector、attendance-stats-class-selector、analytics-filters 三处重复实现抽取",
            "internalDeps": [
              "Link",
              "cn"
            ],
            "usedBy": [
              "grades/components/stats-class-selector.tsx",
              "attendance/components/attendance-stats-class-selector.tsx",
              "grades/components/analytics-filters.tsx"
            ]
          },
          {
            "name": "PageHeader",
            "file": "components/ui/page-header.tsx",
            "props": "{ title, description?, icon?: ComponentType, actions?: ReactNode, className? }",
            "purpose": "页面头部（标题+描述+icon+actions，响应式布局：移动端纵向，桌面端横向），P2-b 重构从 admin-dashboard、profile/page、settings/security/page 三处内联头部抽取",
            "internalDeps": [
              "cn"
            ],
            "usedBy": [
              "dashboard/components/admin-dashboard/admin-dashboard.tsx",
              "app/(dashboard)/profile/page.tsx",
              "app/(dashboard)/settings/security/page.tsx",
              "modules/settings/components/settings-view.tsx"
            ]
          },
          {
            "name": "FilterBar",
            "file": "components/ui/filter-bar.tsx",
            "props": "{ children, hasFilters?, onReset?, layout?: 'default'|'wrap'|'between', gapClassName?, className?, resetClassName? }",
            "purpose": "筛选栏容器（统一布局壳 + Reset 按钮），P3-b 重构从 exam/textbook/question/audit-log/login-log filters 五处重复布局抽取。URL 状态管理方式（nuqs/router/callback）由各模块自行处理",
            "internalDeps": [
              "Button",
              "cn"
            ],
            "usedBy": [
              "exams/components/exam-filters.tsx",
              "textbooks/components/textbook-filters.tsx",
              "questions/components/question-filters.tsx",
              "audit/components/audit-log-filters.tsx",
              "audit/components/login-log-filters.tsx"
            ]
          },
          {
            "name": "FilterSearchInput",
            "file": "components/ui/filter-bar.tsx",
            "props": "{ value, onChange, placeholder?, className?, inputClassName? }",
            "purpose": "筛选栏搜索框（带 Search 图标的 Input），P3-b 重构从 exam/textbook/question filters 三处重复搜索框抽取",
            "internalDeps": [
              "Input",
              "Search (lucide-react)",
              "cn"
            ],
            "usedBy": [
              "exams/components/exam-filters.tsx",
              "textbooks/components/textbook-filters.tsx",
              "questions/components/question-filters.tsx"
            ]
          },
          {
            "name": "FilterResetButton",
            "file": "components/ui/filter-bar.tsx",
            "props": "{ onClick, className? }",
            "purpose": "筛选栏重置按钮（Reset + X 图标），P3-b 重构从 6 个 filter 文件中重复的 Reset 按钮抽取",
            "internalDeps": [
              "Button",
              "X (lucide-react)",
              "cn"
            ],
            "usedBy": [
              "FilterBar（内部使用）"
            ]
          },
          {
            "name": "ChartCardShell",
            "file": "components/charts/chart-card-shell.tsx",
            "props": "{ title, description?, icon?, iconClassName?, titleClassName?, isEmpty?, emptyTitle?, emptyDescription?, emptyIcon?, emptyClassName?, children, className?, contentClassName? }",
            "purpose": "图表卡片外壳（Card + CardHeader + EmptyState + CardContent 统一结构），P3-c 重构从 8 个图表文件重复的 Card 包装抽取",
            "internalDeps": [
              "Card",
              "CardHeader",
              "CardTitle",
              "CardDescription",
              "CardContent",
              "EmptyState",
              "cn"
            ],
            "usedBy": [
              "grades/components/grade-trend-chart.tsx",
              "grades/components/grade-distribution-chart.tsx",
              "grades/components/class-comparison-chart.tsx",
              "grades/components/subject-comparison-chart.tsx",
              "dashboard/components/teacher-dashboard/teacher-grade-trends.tsx",
              "dashboard/components/student-dashboard/student-grades-card.tsx",
              "parent/components/child-grade-summary.tsx",
              "diagnostic/components/mastery-radar-chart.tsx"
            ]
          },
          {
            "name": "TrendLineChart",
            "file": "components/charts/trend-line-chart.tsx",
            "props": "{ data, series: TrendLineSeries[], xKey?, yDomain?, yTickFormatter?, xTickFormatter?, heightClassName?, margin?, yWidth?, tooltipClassName?, tooltipLabelKey?, className? }",
            "purpose": "趋势折线图（LineChart 统一配置：CartesianGrid + XAxis + YAxis + ChartTooltip + Line），P3-c 重构从 4 个 LineChart 文件（grade-trend-chart、teacher-grade-trends、student-grades-card、child-grade-summary）几乎逐行相同的配置抽取",
            "internalDeps": [
              "ChartContainer",
              "ChartTooltip",
              "ChartTooltipContent",
              "CartesianGrid",
              "Line",
              "LineChart",
              "XAxis",
              "YAxis",
              "cn"
            ],
            "usedBy": [
              "grades/components/grade-trend-chart.tsx",
              "dashboard/components/teacher-dashboard/teacher-grade-trends.tsx",
              "dashboard/components/student-dashboard/student-grades-card.tsx",
              "parent/components/child-grade-summary.tsx"
            ]
          },
          {
            "name": "SimpleBarChart",
            "file": "components/charts/simple-bar-chart.tsx",
            "props": "{ data, bars: BarSeries[], xKey, yDomain?, yAllowDecimals?, yTickFormatter?, xTickFormatter?, xTruncateLength?, yWidth?, heightClassName?, margin?, showLegend?, tooltipClassName?, tooltipFormatter?, cellColors?, className? }",
            "purpose": "柱状图（BarChart 统一配置：CartesianGrid + XAxis + YAxis + ChartTooltip + Bar），P3-c 重构从 grade-distribution-chart（单 Bar + Cell 分桶着色）和 class-comparison-chart（多 Bar + Legend）抽取",
            "internalDeps": [
              "ChartContainer",
              "ChartTooltip",
              "ChartTooltipContent",
              "Bar",
              "BarChart",
              "CartesianGrid",
              "Legend",
              "XAxis",
              "YAxis",
              "Cell",
              "cn"
            ],
            "usedBy": [
              "grades/components/grade-distribution-chart.tsx",
              "grades/components/class-comparison-chart.tsx"
            ]
          },
          {
            "name": "ComparisonRadarChart",
            "file": "components/charts/comparison-radar-chart.tsx",
            "props": "{ data, series: RadarSeries[], angleKey, angleTickFormatter?, angleTickFontSize?, domain?, tickCount?, showLegend?, heightClassName?, tooltipClassName?, className?, gridStrokeDasharray?, gridStrokeOpacity? }",
            "purpose": "对比雷达图（RadarChart 统一配置：PolarGrid + PolarAngleAxis + PolarRadiusAxis + ChartTooltip + Radar），P3-c 重构从 subject-comparison-chart（双 Radar：averageScore + passRate）和 mastery-radar-chart（双 Radar：student + classAverage，含条件 Legend）抽取",
            "internalDeps": [
              "ChartContainer",
              "ChartTooltip",
              "ChartTooltipContent",
              "PolarAngleAxis",
              "PolarGrid",
              "PolarRadiusAxis",
              "Radar",
              "RadarChart",
              "Legend",
              "cn"
            ],
            "usedBy": [
              "grades/components/subject-comparison-chart.tsx",
              "diagnostic/components/mastery-radar-chart.tsx"
            ]
          },
          {
            "name": "ScheduleList",
            "file": "components/schedule/schedule-list.tsx",
            "props": "{ items: ScheduleListItemData[], variant?: 'separator'|'card', spacingClassName?, renderTrailing?, className? }",
            "purpose": "课表列表（课程+时间+地点+班级徽章），P3-a 重构从 student-today-schedule-card、child-schedule-card、student-schedule-view 三处逐行复制的列表项渲染抽取。支持 separator（分隔线）和 card（卡片）两种变体",
            "internalDeps": [
              "ScheduleListItem",
              "cn"
            ],
            "usedBy": [
              "dashboard/components/student-dashboard/student-today-schedule-card.tsx",
              "parent/components/child-schedule-card.tsx",
              "student/components/student-schedule-view.tsx"
            ]
          },
          {
            "name": "ScheduleListItem",
            "file": "components/schedule/schedule-list.tsx",
            "props": "{ item: ScheduleListItemData, variant?: 'separator'|'card', trailing?, className? }",
            "purpose": "课表列表项（单条课程渲染：course + Clock + MapPin + Badge），P3-a 重构从 3 个课表文件中重复的列表项抽取",
            "internalDeps": [
              "Badge",
              "Clock (lucide-react)",
              "MapPin (lucide-react)",
              "cn"
            ],
            "usedBy": [
              "ScheduleList（内部使用）"
            ]
          },
          {
            "name": "QuestionBankFilters",
            "file": "components/question/question-bank-filters.tsx",
            "props": "{ search, onSearchChange, type, onTypeChange, difficulty, onDifficultyChange, layout?: 'default'|'compact', className? }",
            "purpose": "题库筛选栏（搜索+题型+难度），P3-d 重构从 exam-assembly（compact 布局）和 question-bank-picker（default 布局，同时将原生 HTML input/select 迁移到 shadcn Input/Select）两处重复筛选栏抽取。状态管理方式由调用方自行处理",
            "internalDeps": [
              "Select",
              "SelectContent",
              "SelectItem",
              "SelectTrigger",
              "SelectValue",
              "FilterSearchInput",
              "cn"
            ],
            "usedBy": [
              "exams/components/exam-assembly.tsx",
              "lesson-preparation/components/question-bank-picker.tsx"
            ]
          },
          {
            "name": "ConfirmDeleteDialog",
            "file": "components/ui/confirm-delete-dialog.tsx",
            "props": "{ open, onOpenChange, title, description, onConfirm, isWorking?, confirmText?, cancelText?, destructive? }",
            "purpose": "通用删除确认对话框（AlertDialog 包装），P0-1 重构从 announcement-detail、message-detail、course-plan-detail、grade-classes-view、students-table 五处重复的 AlertDialog 删除确认块抽取",
            "internalDeps": [
              "AlertDialog",
              "AlertDialogContent",
              "AlertDialogHeader",
              "AlertDialogTitle",
              "AlertDialogDescription",
              "AlertDialogFooter",
              "AlertDialogCancel",
              "AlertDialogAction",
              "cn"
            ],
            "usedBy": [
              "announcements/components/announcement-detail.tsx",
              "messaging/components/message-detail.tsx",
              "course-plans/components/course-plan-detail.tsx",
              "classes/components/grade-classes-view.tsx",
              "classes/components/students-table.tsx"
            ]
          },
          {
            "name": "Pagination",
            "file": "components/ui/pagination.tsx",
            "props": "{ page, pageSize, total, totalPages, onPageChange, itemLabel? }",
            "purpose": "通用分页 UI（Showing X-Y of Z + Page X of Y + 上一页/下一页按钮），P0-2 重构从 audit-log-table、login-log-table、data-change-log-table 三处重复的分页块抽取",
            "internalDeps": [
              "Button",
              "ChevronLeft (lucide-react)",
              "ChevronRight (lucide-react)",
              "cn"
            ],
            "usedBy": [
              "audit/components/audit-log-table.tsx",
              "audit/components/login-log-table.tsx",
              "audit/components/data-change-log-table.tsx"
            ]
          },
          {
            "name": "EmptyTableRow",
            "file": "components/ui/empty-table-row.tsx",
            "props": "{ colSpan, message? }",
            "purpose": "表格空状态行（TableRow + TableCell 居中显示空状态文案），P0-3 重构从 audit-log-table、login-log-table、data-change-log-table 三处重复的空表格行抽取",
            "internalDeps": [
              "TableRow",
              "TableCell"
            ],
            "usedBy": [
              "audit/components/audit-log-table.tsx",
              "audit/components/login-log-table.tsx",
              "audit/components/data-change-log-table.tsx"
            ]
          },
          {
            "name": "StatusBadge",
            "file": "components/ui/status-badge.tsx",
            "props": "{ status, variantMap, labelMap?, classNameMap?, className?, capitalize? }",
            "purpose": "通用状态徽章（Badge + 状态→variant/label/className 映射表），P1-1 重构从 audit StatusBadge/ActionBadge（2 处 100% 相同）、grades typeColors（2 处 100% 相同）、StudentHomeworkProgressStatus（3 处不一致映射）、QuestionType（switch+label）等 9+ 处重复抽取。同时修复 in_progress 在不同文件中颜色不一致的 bug",
            "internalDeps": [
              "Badge",
              "cn"
            ],
            "usedBy": [
              "audit/components/audit-log-table.tsx",
              "audit/components/login-log-table.tsx",
              "audit/components/data-change-log-table.tsx",
              "grades/components/grade-record-list.tsx",
              "grades/components/student-grade-summary.tsx",
              "app/(dashboard)/student/learning/assignments/page.tsx",
              "parent/components/child-homework-summary.tsx",
              "dashboard/components/student-dashboard/student-upcoming-assignments-card.tsx",
              "questions/components/question-columns.tsx"
            ]
          },
          {
            "name": "TextField",
            "file": "components/form-fields/text-field.tsx",
            "props": "{ control, name, label, placeholder?, description?, type?, disabled?, itemClassName?, inputClassName?, inputProps?, toInputValue?, fromInputValue? }",
            "purpose": "通用文本字段（FormField + FormItem + FormLabel + FormControl + Input + FormMessage 包装），P1-2 重构从 profile-settings-form（6 处）、exam-basic-info-form（4 处）、ai-provider-settings-card（4 处）等 16 处重复的 Input FormField 抽取",
            "internalDeps": [
              "FormField",
              "FormItem",
              "FormLabel",
              "FormControl",
              "FormDescription",
              "FormMessage",
              "Input"
            ],
            "usedBy": [
              "settings/components/profile-settings-form.tsx",
              "settings/components/ai-provider-settings-card.tsx",
              "exams/components/exam-basic-info-form.tsx"
            ]
          },
          {
            "name": "SelectField",
            "file": "components/form-fields/select-field.tsx",
            "props": "{ control, name, label, placeholder?, description?, options: SelectOption[], disabled?, itemClassName?, labelSlot?, toSelectValue?, fromSelectValue? }",
            "purpose": "通用选择字段（FormField + FormItem + FormLabel + Select + SelectContent + SelectItem + FormMessage 包装），P1-2 重构从 exam-basic-info-form（3 处）、ai-provider-settings-card（1 处）、create-question-dialog（2 处）、profile-settings-form（1 处）等 8 处重复的 Select FormField 抽取。支持 toSelectValue/fromSelectValue 转换器处理 number↔string",
            "internalDeps": [
              "FormField",
              "FormItem",
              "FormLabel",
              "FormControl",
              "FormDescription",
              "FormMessage",
              "Select",
              "SelectContent",
              "SelectItem",
              "SelectTrigger",
              "SelectValue"
            ],
            "usedBy": [
              "settings/components/profile-settings-form.tsx",
              "settings/components/ai-provider-settings-card.tsx",
              "exams/components/exam-basic-info-form.tsx",
              "questions/components/create-question-dialog.tsx"
            ]
          },
          {
            "name": "TextareaField",
            "file": "components/form-fields/textarea-field.tsx",
            "props": "{ control, name, label, placeholder?, description?, disabled?, itemClassName?, textareaClassName?, textareaProps? }",
            "purpose": "通用多行文本字段（FormField + FormItem + FormLabel + FormControl + Textarea + FormMessage 包装），P1-2 重构从 exam-ai-generator、create-question-dialog 两处重复的 Textarea FormField 抽取",
            "internalDeps": [
              "FormField",
              "FormItem",
              "FormLabel",
              "FormControl",
              "FormDescription",
              "FormMessage",
              "Textarea"
            ],
            "usedBy": [
              "questions/components/create-question-dialog.tsx"
            ]
          }
        ],
        "constants": [
          {
            "name": "ROLE_PERMISSIONS",
            "file": "lib/permissions.ts",
            "type": "const",
            "description": "角色-权限映射表",
            "usedBy": [
              "resolvePermissions",
              "auth.ts JWT callback"
            ]
          },
          {
            "name": "Permissions",
            "file": "types/permissions.ts",
            "type": "const",
            "description": "38个权限点常量对象（含 FILE_UPLOAD/FILE_READ/FILE_DELETE）",
            "usedBy": [
              "auth-guard",
              "use-permission",
              "所有actions"
            ]
          },
          {
            "name": "db",
            "file": "db/index.ts",
            "type": "const",
            "description": "Drizzle ORM 实例",
            "usedBy": [
              "所有业务模块"
            ]
          },
          {
            "name": "questionTypeEnum",
            "file": "db/schema.ts",
            "type": "const",
            "description": "题目类型枚举",
            "usedBy": [
              "questions",
              "exams"
            ]
          },
          {
            "name": "classEnrollmentStatusEnum",
            "file": "db/schema.ts",
            "type": "const",
            "description": "选课状态枚举",
            "usedBy": [
              "classes"
            ]
          }
        ],
        "files": [
          {
            "path": "db/relations.ts",
            "description": "20+ 个 Drizzle relations 定义",
            "usedBy": [
              "所有业务模块的关联查询"
            ]
          },
          {
            "path": "next-auth.d.ts",
            "description": "NextAuth Session/JWT 类型扩展声明",
            "usedBy": [
              "auth.ts",
              "useSession"
            ]
          }
        ],
        "types": [
          {
            "name": "ActionState",
            "file": "types/action-state.ts",
            "definition": "ActionState<T = void> = { success: boolean; message?: string; errors?: Record<string, string[]>; data?: T }",
            "usedBy": [
              "所有模块Server Action"
            ]
          },
          {
            "name": "Permission",
            "file": "types/permissions.ts",
            "definition": "Permission = (typeof Permissions)[keyof typeof Permissions]",
            "usedBy": [
              "auth-guard",
              "use-permission",
              "所有actions"
            ]
          },
          {
            "name": "Role",
            "file": "types/permissions.ts",
            "definition": "Role = 'admin' | 'teacher' | 'student' | 'parent' | 'grade_head' | 'teaching_head'",
            "usedBy": [
              "auth-guard",
              "permissions",
              "proxy",
              "next-auth.d.ts",
              "use-permission"
            ]
          },
          {
            "name": "DataScope",
            "file": "types/permissions.ts",
            "definition": "DataScope = { type: 'all' } | { type: 'owned'; userId: string } | { type: 'class_members'; classIds: string[] } | { type: 'grade_managed'; gradeIds: string[] } | { type: 'class_taught'; classIds: string[]; subjectIds?: string[] } | { type: 'children'; childrenIds: string[] }",
            "usedBy": [
              "auth-guard",
              "exams/data-access",
              "homework/data-access",
              "dashboard/data-access",
              "grades/data-access",
              "parent/children/[studentId]/page.tsx"
            ]
          },
          {
            "name": "AuthContext",
            "file": "types/permissions.ts",
            "definition": "AuthContext = { userId: string; roles: Role[]; permissions: Permission[]; dataScope: DataScope }",
            "usedBy": [
              "auth-guard",
              "所有调用requirePermission的Server Action"
            ]
          },
          {
            "name": "PermissionDeniedError",
            "file": "lib/auth-guard.ts",
            "definition": "class PermissionDeniedError extends Error { constructor(permission: string) }",
            "usedBy": [
              "所有Server Action的try/catch"
            ]
          }
        ]
      },
      "dbTables": {
        "users": {
          "fields": [
            "id",
            "name",
            "email",
            "emailVerified",
            "image",
            "password",
            "phone",
            "address",
            "gender",
            "age",
            "birthDate",
            "guardianName",
            "guardianPhone",
            "guardianRelation",
            "consentAcceptedAt",
            "gradeId",
            "departmentId",
            "onboardedAt",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "auth",
            "users",
            "dashboard",
            "classes"
          ]
        },
        "accounts": {
          "fields": [
            "userId",
            "type",
            "provider",
            "providerAccountId",
            "refresh_token",
            "access_token",
            "expires_at",
            "token_type",
            "scope",
            "id_token",
            "session_state"
          ],
          "usedBy": [
            "auth"
          ]
        },
        "sessions": {
          "fields": [
            "sessionToken",
            "userId",
            "expires"
          ],
          "usedBy": [
            "auth"
          ]
        },
        "verificationTokens": {
          "fields": [
            "identifier",
            "token",
            "expires"
          ],
          "usedBy": [
            "auth"
          ]
        },
        "roles": {
          "fields": [
            "id",
            "name",
            "description",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "auth",
            "auth-guard"
          ]
        },
        "usersToRoles": {
          "fields": [
            "userId",
            "roleId"
          ],
          "usedBy": [
            "auth",
            "auth-guard"
          ]
        },
        "rolePermissions": {
          "fields": [
            "roleId",
            "permission"
          ],
          "usedBy": [
            "auth (seed)"
          ]
        },
        "knowledgePoints": {
          "fields": [
            "id",
            "name",
            "description",
            "anchorText",
            "parentId",
            "chapterId",
            "level",
            "order",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "textbooks",
            "questions"
          ]
        },
        "knowledgePointPrerequisites": {
          "fields": [
            "knowledgePointId",
            "prerequisiteKpId",
            "createdAt"
          ],
          "usedBy": [
            "textbooks"
          ],
          "description": "知识点前置依赖（知识图谱）：复合主键 knowledgePointId+prerequisiteKpId，双外键 cascade 删除，无独立 id 字段"
        },
        "questions": {
          "fields": [
            "id",
            "content",
            "type",
            "difficulty",
            "authorId",
            "parentId",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "questions",
            "exams",
            "homework"
          ]
        },
        "questionsToKnowledgePoints": {
          "fields": [
            "questionId",
            "knowledgePointId"
          ],
          "usedBy": [
            "questions"
          ]
        },
        "subjects": {
          "fields": [
            "id",
            "name",
            "code",
            "order",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "exams",
            "textbooks"
          ]
        },
        "textbooks": {
          "fields": [
            "id",
            "title",
            "subject",
            "grade",
            "publisher",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "textbooks"
          ]
        },
        "chapters": {
          "fields": [
            "id",
            "textbookId",
            "title",
            "order",
            "parentId",
            "content",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "textbooks"
          ]
        },
        "departments": {
          "fields": [
            "id",
            "name",
            "description",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "school"
          ]
        },
        "classrooms": {
          "fields": [
            "id",
            "name",
            "building",
            "floor",
            "capacity",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "school"
          ]
        },
        "academicYears": {
          "fields": [
            "id",
            "name",
            "startDate",
            "endDate",
            "isActive",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "school"
          ]
        },
        "schools": {
          "fields": [
            "id",
            "name",
            "code",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "school",
            "classes"
          ]
        },
        "grades": {
          "fields": [
            "id",
            "schoolId",
            "name",
            "order",
            "gradeHeadId",
            "teachingHeadId",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "school",
            "classes",
            "exams",
            "auth-guard"
          ]
        },
        "classes": {
          "fields": [
            "id",
            "schoolId",
            "gradeId",
            "teacherId",
            "name",
            "homeroom",
            "room",
            "invitationCode",
            "schoolName",
            "grade",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "classes",
            "homework",
            "auth-guard"
          ]
        },
        "classSubjectTeachers": {
          "fields": [
            "classId",
            "teacherId",
            "subjectId",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "classes",
            "auth-guard"
          ]
        },
        "classEnrollments": {
          "fields": [
            "classId",
            "studentId",
            "status",
            "createdAt"
          ],
          "usedBy": [
            "classes",
            "homework"
          ]
        },
        "classSchedule": {
          "fields": [
            "id",
            "classId",
            "weekday",
            "startTime",
            "endTime",
            "course",
            "location",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "classes"
          ]
        },
        "exams": {
          "fields": [
            "id",
            "creatorId",
            "title",
            "description",
            "subjectId",
            "gradeId",
            "status",
            "structure",
            "startTime",
            "endTime",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "exams",
            "homework"
          ]
        },
        "examQuestions": {
          "fields": [
            "examId",
            "questionId",
            "score",
            "order"
          ],
          "usedBy": [
            "exams"
          ]
        },
        "examSubmissions": {
          "fields": [
            "id",
            "examId",
            "studentId",
            "score",
            "submittedAt",
            "status",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "exams"
          ]
        },
        "submissionAnswers": {
          "fields": [
            "id",
            "submissionId",
            "questionId",
            "answerContent",
            "score",
            "feedback",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "exams"
          ]
        },
        "homeworkAssignments": {
          "fields": [
            "id",
            "creatorId",
            "sourceExamId",
            "title",
            "description",
            "status",
            "availableAt",
            "dueAt",
            "allowLate",
            "lateDueAt",
            "maxAttempts",
            "structure",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "homework"
          ]
        },
        "homeworkAssignmentQuestions": {
          "fields": [
            "assignmentId",
            "questionId",
            "score",
            "order"
          ],
          "usedBy": [
            "homework"
          ]
        },
        "homeworkAssignmentTargets": {
          "fields": [
            "assignmentId",
            "studentId",
            "createdAt"
          ],
          "usedBy": [
            "homework"
          ]
        },
        "homeworkSubmissions": {
          "fields": [
            "id",
            "assignmentId",
            "studentId",
            "status",
            "attemptNo",
            "score",
            "submittedAt",
            "startedAt",
            "isLate",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "homework"
          ]
        },
        "homeworkAnswers": {
          "fields": [
            "id",
            "submissionId",
            "questionId",
            "answerContent",
            "score",
            "feedback",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "homework"
          ]
        },
        "aiProviders": {
          "fields": [
            "id",
            "provider",
            "baseUrl",
            "model",
            "apiKeyEncrypted",
            "apiKeyLast4",
            "isDefault",
            "createdBy",
            "updatedBy",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "settings",
            "ai"
          ]
        },
        "auditLogs": {
          "fields": [
            "id",
            "userId",
            "userName",
            "action",
            "module",
            "targetId",
            "targetType",
            "detail",
            "ipAddress",
            "userAgent",
            "status",
            "createdAt"
          ],
          "usedBy": [
            "audit",
            "shared/lib/audit-logger"
          ]
        },
        "loginLogs": {
          "fields": [
            "id",
            "userId",
            "userEmail",
            "action",
            "status",
            "ipAddress",
            "userAgent",
            "errorMessage",
            "createdAt"
          ],
          "usedBy": [
            "audit",
            "shared/lib/login-logger",
            "auth"
          ]
        },
        "dataChangeLogs": {
          "fields": [
            "id",
            "tableName",
            "recordId",
            "action",
            "oldValue",
            "newValue",
            "changedBy",
            "changedByName",
            "ipAddress",
            "createdAt"
          ],
          "usedBy": [
            "audit",
            "shared/lib/change-logger"
          ]
        },
        "announcements": {
          "fields": [
            "id",
            "title",
            "content",
            "type",
            "status",
            "targetGradeId",
            "targetClassId",
            "authorId",
            "publishedAt",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "announcements"
          ]
        },
        "fileAttachments": {
          "fields": [
            "id",
            "filename",
            "originalName",
            "mimeType",
            "size",
            "storagePath",
            "url",
            "uploaderId",
            "targetType",
            "targetId",
            "createdAt"
          ],
          "usedBy": [
            "files"
          ]
        },
        "gradeRecords": {
          "fields": [
            "id",
            "studentId",
            "classId",
            "subjectId",
            "examId",
            "academicYearId",
            "title",
            "score",
            "fullScore",
            "type",
            "semester",
            "recordedBy",
            "remark",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "grades"
          ]
        },
        "coursePlans": {
          "fields": [
            "id",
            "classId",
            "subjectId",
            "teacherId",
            "academicYearId",
            "semester",
            "totalHours",
            "completedHours",
            "weeklyHours",
            "startDate",
            "endDate",
            "syllabus",
            "objectives",
            "status",
            "createdBy",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "course-plans"
          ]
        },
        "coursePlanItems": {
          "fields": [
            "id",
            "planId",
            "week",
            "topic",
            "content",
            "hours",
            "textbookChapter",
            "notes",
            "isCompleted",
            "completedAt",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "course-plans"
          ]
        },
        "parentStudentRelations": {
          "fields": [
            "id",
            "parentId",
            "studentId",
            "relation",
            "createdAt"
          ],
          "usedBy": [
            "parent",
            "auth-guard"
          ]
        },
        "messages": {
          "fields": [
            "id",
            "senderId",
            "receiverId",
            "subject",
            "content",
            "isRead",
            "readAt",
            "parentMessageId",
            "senderDeletedAt",
            "receiverDeletedAt",
            "createdAt"
          ],
          "usedBy": [
            "messaging"
          ]
        },
        "messageNotifications": {
          "fields": [
            "id",
            "userId",
            "type",
            "title",
            "content",
            "link",
            "isRead",
            "priority",
            "isArchived",
            "createdAt"
          ],
          "usedBy": [
            "notifications",
            "messaging (via re-export)"
          ]
        },
        "notificationPreferences": {
          "fields": [
            "id",
            "userId",
            "emailEnabled",
            "smsEnabled",
            "pushEnabled",
            "homeworkNotifications",
            "gradeNotifications",
            "announcementNotifications",
            "messageNotifications",
            "attendanceNotifications",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "notifications",
            "messaging (via re-export)",
            "settings"
          ]
        },
        "attendanceRecords": {
          "fields": [
            "id",
            "studentId",
            "classId",
            "scheduleId",
            "date",
            "status",
            "remark",
            "recordedBy",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "attendance"
          ]
        },
        "attendanceRules": {
          "fields": [
            "id",
            "classId",
            "lateThresholdMinutes",
            "earlyLeaveThresholdMinutes",
            "enableAutoMark",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "attendance"
          ]
        },
        "schedulingRules": {
          "fields": [
            "id",
            "classId",
            "maxDailyHours",
            "maxContinuousHours",
            "lunchBreakStart",
            "lunchBreakEnd",
            "morningStart",
            "afternoonEnd",
            "avoidBackToBack",
            "balancedSubjects",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "scheduling"
          ]
        },
        "scheduleChanges": {
          "fields": [
            "id",
            "originalScheduleId",
            "classId",
            "originalTeacherId",
            "substituteTeacherId",
            "originalDate",
            "newDate",
            "newStartTime",
            "newEndTime",
            "reason",
            "status",
            "requestedBy",
            "approvedBy",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "scheduling"
          ]
        },
        "passwordSecurity": {
          "fields": [
            "id",
            "userId",
            "failedLoginAttempts",
            "lockedUntil",
            "passwordChangedAt",
            "mustChangePassword",
            "lastPasswordChange",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "auth",
            "settings"
          ]
        },
        "knowledgePointMastery": {
          "fields": [
            "id",
            "studentId",
            "knowledgePointId",
            "masteryLevel",
            "totalQuestions",
            "correctQuestions",
            "lastAssessedAt",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "diagnostic"
          ],
          "description": "知识点掌握度记录（复合主键 studentId+knowledgePointId，onDuplicateKeyUpdate upsert）"
        },
        "learningDiagnosticReports": {
          "fields": [
            "id",
            "studentId",
            "generatedBy",
            "classId",
            "reportType",
            "period",
            "summary",
            "strengths",
            "weaknesses",
            "recommendations",
            "overallScore",
            "status",
            "createdAt",
            "updatedAt"
          ],
          "indexes": [
            "diagnostic_student_idx (studentId)",
            "diagnostic_generated_by_idx (generatedBy)",
            "diagnostic_status_idx (status)",
            "diagnostic_report_type_idx (reportType)",
            "diagnostic_class_idx (classId)"
          ],
          "usedBy": [
            "diagnostic"
          ],
          "description": "学情诊断报告（reportType: individual/class/grade；status: draft/published/archived；v4-P1-4 新增 classId 字段 varchar(128) references classes.id onDelete:set null，用于班级报告关联班级，发布时批量通知全班学生）"
        },
        "electiveCourses": {
          "fields": [
            "id",
            "name",
            "subjectId",
            "teacherId",
            "gradeId",
            "description",
            "capacity",
            "enrolledCount",
            "classroom",
            "schedule",
            "startDate",
            "endDate",
            "selectionStartAt",
            "selectionEndAt",
            "status",
            "selectionMode",
            "credit",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "elective"
          ],
          "description": "选修课程（status: draft/open/closed/cancelled；selectionMode: fcfs/lottery）"
        },
        "courseSelections": {
          "fields": [
            "id",
            "courseId",
            "studentId",
            "status",
            "priority",
            "selectedAt",
            "enrolledAt",
            "droppedAt",
            "lotteryRank",
            "createdAt",
            "updatedAt"
          ],
          "usedBy": [
            "elective"
          ],
          "description": "选课记录（复合主键 courseId+studentId；status: selected/enrolled/waitlist/dropped/rejected）"
        }
      }
    },
    "auth": {
      "path": "src/auth.ts",
      "description": "用户认证：NextAuth配置（handlers/auth/signIn/signOut）、JWT/Session callbacks、events回调(登录日志)。集成密码安全策略（账户锁定、失败登录追踪）和登录速率限制。P1-3 拆分后，辅助函数已迁移至 shared/lib/{role-utils,bcrypt-utils,http-utils,password-security-service}",
      "imports": [
        "shared/lib/permissions",
        "shared/lib/login-logger",
        "shared/lib/password-policy",
        "shared/lib/rate-limit",
        "shared/lib/role-utils",
        "shared/lib/bcrypt-utils",
        "shared/lib/http-utils",
        "shared/lib/password-security-service",
        "shared/db",
        "shared/db/schema"
      ],
      "exports": {
        "functions": [
          {
            "name": "auth",
            "signature": "auth(): Promise<Session | null>",
            "purpose": "获取当前用户Session",
            "usedBy": [
              "shared/lib/session.ts (dynamic import，再被 auth-guard/audit-logger/change-logger 间接使用)",
              "所有Server Component页面",
              "app/api 路由"
            ]
          },
          {
            "name": "handlers",
            "signature": "{ GET, POST }",
            "purpose": "NextAuth Route Handler",
            "usedBy": [
              "app/api/auth/[...nextauth]/route.ts"
            ]
          },
          {
            "name": "signIn",
            "signature": "signIn(provider, options?)",
            "purpose": "登录",
            "usedBy": [
              "login-form.tsx"
            ]
          },
          {
            "name": "signOut",
            "signature": "signOut(options?)",
            "purpose": "登出",
            "usedBy": [
              "site-header.tsx"
            ]
          }
        ],
        "events": [
          {
            "name": "signIn",
            "signature": "async signIn({ user }) => void",
            "purpose": "用户登录成功后记录登录日志",
            "deps": [
              "shared/lib/login-logger.logLoginEvent"
            ],
            "params": "{ userId: user.id, userEmail: user.email, action: 'signin', status: 'success' }"
          },
          {
            "name": "signOut",
            "signature": "async signOut(message) => void",
            "purpose": "用户登出后记录登录日志(处理NextAuth v5不同message形状)",
            "deps": [
              "shared/lib/login-logger.logLoginEvent"
            ],
            "params": "{ userId?, userEmail, action: 'signout', status: 'success' }"
          }
        ]
      },
      "middleware": {
        "file": "src/proxy.ts",
        "signature": "proxy(request: NextRequest): Promise<NextResponse>",
        "purpose": "基于权限点的路由守卫（Next.js 16 将 middleware 重命名为 proxy）",
        "routePermissions": {
          "/admin": "school:manage",
          "/teacher": "exam:create",
          "/student": "homework:submit",
          "/parent": "dashboard:parent:read",
          "/management": "grade:manage"
        },
        "dashboardRoutePermissions": {
          "/admin/dashboard": "dashboard:admin:read",
          "/teacher/dashboard": "dashboard:teacher:read",
          "/student/dashboard": "dashboard:student:read",
          "/parent/dashboard": "dashboard:parent:read"
        },
        "apiPermissions": {
          "/api/ai/chat": "ai:chat"
        },
        "securityNote": "P0 修复：原先 /teacher 和 /parent 都使用 EXAM_READ，但 student/parent 也有 EXAM_READ，导致跨角色访问漏洞。改为使用各角色独有的权限点。"
      }
    },
    "exams": {
      "path": "src/modules/exams",
      "description": "考试全生命周期：创建(手动/AI)、编辑、预览、发布、删除、复制",
      "exports": {
        "actions": [
          {
            "name": "createExamAction",
            "permission": "EXAM_CREATE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "手动模式创建考试草稿",
            "deps": [
              "requirePermission",
              "shared/db",
              "data-access.persistExamDraft"
            ],
            "usedBy": [
              "exam-form.tsx"
            ]
          },
          {
            "name": "createAiExamAction",
            "permission": "EXAM_AI_GENERATE",
            "signature": "同上",
            "purpose": "AI模式创建考试",
            "deps": [
              "requirePermission",
              "ai-pipeline.generateAiCreateDraftFromSource",
              "data-access.persistAiGeneratedExamDraft"
            ],
            "usedBy": [
              "exam-form.tsx"
            ]
          },
          {
            "name": "previewAiExamAction",
            "permission": "EXAM_AI_GENERATE",
            "signature": "(prevState: ActionState<AiPreviewData> | null, formData: FormData) => Promise<ActionState<AiPreviewData>>",
            "purpose": "AI预览试卷(不持久化)",
            "deps": [
              "requirePermission",
              "ai-pipeline.generateAiPreviewData"
            ],
            "usedBy": [
              "exam-ai-generator.tsx"
            ]
          },
          {
            "name": "regenerateAiQuestionAction",
            "permission": "EXAM_AI_GENERATE",
            "signature": "(prevState: ActionState<AiRewriteQuestionData> | null, formData: FormData) => Promise<ActionState<AiRewriteQuestionData>>",
            "purpose": "AI重写单个题目",
            "deps": [
              "requirePermission",
              "ai-pipeline.regenerateAiQuestionByInstruction"
            ],
            "usedBy": [
              "exam-preview-question-editor.tsx"
            ]
          },
          {
            "name": "updateExamAction",
            "permission": "EXAM_UPDATE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "更新考试(含资源归属校验)",
            "deps": [
              "requirePermission",
              "data-access.getExamCreatorId",
              "data-access.updateExamWithQuestions"
            ],
            "usedBy": [
              "exam-form.tsx"
            ]
          },
          {
            "name": "deleteExamAction",
            "permission": "EXAM_DELETE",
            "signature": "同上",
            "purpose": "删除考试(含资源归属校验)",
            "deps": [
              "requirePermission",
              "data-access.getExamCreatorId",
              "data-access.deleteExamById"
            ],
            "usedBy": [
              "exam-actions.tsx"
            ]
          },
          {
            "name": "duplicateExamAction",
            "permission": "EXAM_DUPLICATE",
            "signature": "同上",
            "purpose": "复制考试",
            "deps": [
              "requirePermission",
              "data-access.getExamCreatorId",
              "data-access.duplicateExam"
            ],
            "usedBy": [
              "exam-actions.tsx"
            ]
          },
          {
            "name": "getExamPreviewAction",
            "permission": "EXAM_READ",
            "signature": "(examId: string) => Promise<ActionState<{ structure: unknown; questions: Array<{ id: string }> }>>",
            "purpose": "获取考试预览数据",
            "deps": [
              "requirePermission",
              "data-access.getExamPreview"
            ],
            "usedBy": [
              "exam-viewer.tsx"
            ]
          },
          {
            "name": "getSubjectsAction",
            "permission": "EXAM_READ",
            "signature": "() => Promise<ActionState<{id:string;name:string}[]>>",
            "purpose": "获取科目列表",
            "deps": [
              "requirePermission",
              "data-access.getExamSubjects"
            ],
            "usedBy": [
              "exam-form.tsx"
            ]
          },
          {
            "name": "getGradesAction",
            "permission": "EXAM_READ",
            "signature": "同上",
            "purpose": "获取年级列表",
            "deps": [
              "requirePermission",
              "data-access.getExamGrades"
            ],
            "usedBy": [
              "exam-form.tsx"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getExams",
            "signature": "getExams(params: GetExamsParams & { scope: DataScope }): Promise<Exam[]>",
            "purpose": "查询考试列表(含数据权限过滤)",
            "usedBy": [
              "teacher/exams/all/page.tsx",
              "homework创建页面"
            ]
          },
          {
            "name": "getExamById",
            "signature": "getExamById(id: string, scope?: DataScope): Promise<Exam | null>",
            "purpose": "按ID获取考试详情",
            "usedBy": [
              "exam详情/编辑页面"
            ]
          },
          {
            "name": "persistExamDraft",
            "signature": "persistExamDraft(input: { examId, title, creatorId, subjectId, gradeId, scheduledAt?, description }): Promise<void>",
            "purpose": "持久化手动考试草稿",
            "usedBy": [
              "createExamAction"
            ]
          },
          {
            "name": "addExamQuestions",
            "signature": "addExamQuestions(examId: string, items: Array<{ questionId: string; score: number; order: number }>): Promise<void>",
            "purpose": "批量插入考试-题目关联（跨模块写接口，供 lesson-preparation/publish-service 调用，避免直查 examQuestions 表）",
            "usedBy": [
              "lesson-preparation/publish-service.publishLessonPlanHomework"
            ]
          },
          {
            "name": "persistAiGeneratedExamDraft",
            "signature": "persistAiGeneratedExamDraft(input: { examId, title, creatorId, subjectId, gradeId, scheduledAt?, description, structure, generated }): Promise<void>",
            "purpose": "持久化AI生成考试草稿（P0-1 已修复：通过 questions/data-access.createQuestionWithRelations 创建题目，不再直查 questions 表）",
            "usedBy": [
              "createAiExamAction"
            ]
          },
          {
            "name": "getExamsDashboardStats",
            "signature": "(scope?: DataScope) => Promise<ExamsDashboardStats>",
            "purpose": "获取考试仪表盘统计数据(考试总数,支持数据范围过滤)",
            "usedBy": [
              "dashboard/data-access.getAdminDashboardData"
            ]
          },
          {
            "name": "omitScheduledAtFromDescription",
            "signature": "(description: string | null) => string",
            "purpose": "从描述中移除scheduledAt信息",
            "usedBy": [
              "exams/data-access内部"
            ]
          },
          {
            "name": "resolveSubjectGradeNames",
            "signature": "(input: { subjectId?, gradeId? }) => Promise<{ subjectName?, gradeName? }>",
            "purpose": "解析科目与年级名称",
            "usedBy": [
              "exams/data-access内部"
            ]
          },
          {
            "name": "buildExamDescription",
            "signature": "(input: { subject, grade, difficulty, totalScore, durationMin, scheduledAt?, questionCount? }) => string",
            "purpose": "构建考试描述文本",
            "usedBy": [
              "exams/data-access内部"
            ]
          },
          {
            "name": "getExamCreatorId",
            "signature": "(examId: string) => Promise<string | null>",
            "purpose": "获取考试创建者ID（用于权限校验）",
            "usedBy": [
              "updateExamAction",
              "deleteExamAction",
              "duplicateExamAction"
            ]
          },
          {
            "name": "updateExamWithQuestions",
            "signature": "(input: { examId, title, subjectId, gradeId, difficulty, totalScore, durationMin, scheduledAt?, description?, structure }) => Promise<void>",
            "purpose": "更新考试及题目关联（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "updateExamAction"
            ]
          },
          {
            "name": "deleteExamById",
            "signature": "(examId: string) => Promise<void>",
            "purpose": "按ID删除考试（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "deleteExamAction"
            ]
          },
          {
            "name": "duplicateExam",
            "signature": "(examId: string, creatorId: string) => Promise<{ newExamId: string }>",
            "purpose": "复制考试（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "duplicateExamAction"
            ]
          },
          {
            "name": "getExamPreview",
            "signature": "(examId: string) => Promise<{ structure: unknown; questions: Array<{ id: string }> } | null>",
            "purpose": "获取考试预览数据（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "getExamPreviewAction"
            ]
          },
          {
            "name": "getExamSubjects",
            "signature": "() => Promise<Array<{ id: string; name: string }>>",
            "purpose": "获取科目列表（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "getSubjectsAction"
            ]
          },
          {
            "name": "getExamGrades",
            "signature": "() => Promise<Array<{ id: string; name: string }>>",
            "purpose": "获取年级列表（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "getGradesAction"
            ]
          },
          {
            "name": "GetExamsParams",
            "type": "type",
            "definition": "{ q?, status?, difficulty?, page?, pageSize? }",
            "usedBy": [
              "getExams",
              "getExamsAction"
            ]
          }
        ],
        "aiPipeline": [
          {
            "name": "generateAiPreviewData",
            "signature": "(input: { title, subject?, grade?, difficulty, totalScore, durationMin, questionCount?, sourceText, aiProviderId? }) => Promise<{ ok, data?, rawOutput?, message? }>",
            "purpose": "AI预览生成",
            "deps": [
              "shared/lib/ai.createAiChatCompletion"
            ],
            "usedBy": [
              "previewAiExamAction"
            ]
          },
          {
            "name": "generateAiCreateDraftFromSource",
            "signature": "同上",
            "purpose": "AI从源文本生成完整考试",
            "deps": [
              "shared/lib/ai.createAiChatCompletion"
            ],
            "usedBy": [
              "createAiExamAction"
            ]
          },
          {
            "name": "regenerateAiQuestionByInstruction",
            "signature": "(input: { instruction, originalQuestion, sourceText?, aiProviderId? }) => Promise<{ ok, data?, message? }>",
            "purpose": "AI按指令重写题目",
            "deps": [
              "shared/lib/ai.createAiChatCompletion"
            ],
            "usedBy": [
              "regenerateAiQuestionAction"
            ]
          },
          {
            "name": "AiQuestionSchema",
            "type": "const",
            "description": "zod schema 校验AI生成题目",
            "usedBy": [
              "ai-pipeline内部"
            ]
          },
          {
            "name": "AiInsertQuestionSchema",
            "type": "const",
            "description": "zod schema 校验AI插入题目",
            "usedBy": [
              "ai-pipeline内部"
            ]
          },
          {
            "name": "AiGeneratedQuestion",
            "type": "type",
            "definition": "{ id, type, difficulty, score, content }",
            "usedBy": [
              "ai-pipeline内部",
              "exams/components"
            ]
          },
          {
            "name": "AiGeneratedStructureNode",
            "type": "type",
            "definition": "{ id, type: \"group\"|\"question\", title?, questionId?, score?, children? }",
            "usedBy": [
              "ai-pipeline内部",
              "exams/components"
            ]
          },
          {
            "name": "AiGeneratedStructureNodeSchema",
            "type": "const",
            "description": "zod schema 校验AI生成结构节点(递归)",
            "usedBy": [
              "ai-pipeline内部"
            ]
          },
          {
            "name": "AiGeneratedStructureSchema",
            "type": "const",
            "description": "zod schema 校验AI生成结构",
            "usedBy": [
              "ai-pipeline内部"
            ]
          },
          {
            "name": "generateAiExamDraft",
            "type": "function",
            "signature": "(input) => Promise<AiDraftResult>",
            "purpose": "生成AI考试草稿",
            "deps": [
              "shared/lib/ai.createAiChatCompletion"
            ],
            "usedBy": [
              "ai-pipeline内部"
            ]
          }
        ],
        "types": [
          {
            "name": "Exam",
            "definition": "{ id, title, subject, grade, status, difficulty, totalScore, durationMin, questionCount, scheduledAt?, createdAt, updatedAt?, tags? }",
            "usedBy": [
              "exams/components",
              "homework/types",
              "dashboard/types"
            ]
          },
          {
            "name": "AiPreviewData",
            "definition": "{ title, rawOutput?, sections?, questions? }",
            "usedBy": [
              "exams/actions",
              "exams/components"
            ]
          },
          {
            "name": "AiRewriteQuestionData",
            "definition": "{ type, difficulty, score, content }",
            "usedBy": [
              "exams/actions",
              "exams/components"
            ]
          },
          {
            "name": "ExamStatus",
            "type": "type",
            "definition": "\"draft\" | \"published\" | \"archived\"",
            "usedBy": [
              "exams/components",
              "exams/data-access"
            ]
          },
          {
            "name": "ExamDifficulty",
            "type": "type",
            "definition": "1 | 2 | 3 | 4 | 5",
            "usedBy": [
              "exams/components",
              "exams/data-access"
            ]
          },
          {
            "name": "SubmissionStatus",
            "type": "type",
            "definition": "\"pending\" | \"graded\"",
            "usedBy": [
              "exams/components",
              "exams/data-access"
            ]
          },
          {
            "name": "ExamSubmission",
            "type": "interface",
            "definition": "{ id, examId, examTitle, studentName, submittedAt, score?, status }",
            "usedBy": [
              "exams/components"
            ]
          }
        ],
        "components": [
          {
            "name": "ExamPaperPreview",
            "file": "assembly/exam-paper-preview.tsx",
            "purpose": "试卷预览"
          },
          {
            "name": "QuestionBankList",
            "file": "assembly/question-bank-list.tsx",
            "purpose": "题库列表(组卷选择)"
          },
          {
            "name": "SelectedQuestionList",
            "file": "assembly/selected-question-list.tsx",
            "purpose": "已选题目列表",
            "types": [
              "ExamNode"
            ]
          },
          {
            "name": "StructureEditor",
            "file": "assembly/structure-editor.tsx",
            "purpose": "试卷结构编辑器"
          },
          {
            "name": "ExamActions",
            "file": "exam-actions.tsx",
            "purpose": "考试操作按钮(删除/复制等)"
          },
          {
            "name": "ExamAiGenerator",
            "file": "exam-ai-generator.tsx",
            "purpose": "AI生成考试面板"
          },
          {
            "name": "ExamAssembly",
            "file": "exam-assembly.tsx",
            "purpose": "组卷主界面"
          },
          {
            "name": "ExamBasicInfoForm",
            "file": "exam-basic-info-form.tsx",
            "purpose": "考试基本信息表单"
          },
          {
            "name": "ExamCard",
            "file": "exam-card.tsx",
            "purpose": "考试卡片"
          },
          {
            "name": "examColumns",
            "file": "exam-columns.tsx",
            "type": "ColumnDef[]",
            "purpose": "考试表格列定义"
          },
          {
            "name": "ExamDataTable",
            "file": "exam-data-table.tsx",
            "purpose": "考试数据表格"
          },
          {
            "name": "ExamFilters",
            "file": "exam-filters.tsx",
            "purpose": "考试筛选器"
          },
          {
            "name": "formSchema",
            "file": "exam-form-types.ts",
            "type": "const",
            "purpose": "表单zod schema"
          },
          {
            "name": "ExamFormValues",
            "file": "exam-form-types.ts",
            "type": "type",
            "purpose": "表单值类型"
          },
          {
            "name": "PreviewQuestion",
            "file": "exam-form-types.ts",
            "type": "type",
            "purpose": "预览题目类型"
          },
          {
            "name": "EditableQuestionContent",
            "file": "exam-form-types.ts",
            "type": "type",
            "purpose": "可编辑题目内容"
          },
          {
            "name": "PreviewSnapshotMeta",
            "file": "exam-form-types.ts",
            "type": "type",
            "purpose": "预览快照元数据"
          },
          {
            "name": "PreviewBackgroundTask",
            "file": "exam-form-types.ts",
            "type": "type",
            "purpose": "预览后台任务"
          },
          {
            "name": "aiProviderLabels",
            "file": "exam-form-types.ts",
            "type": "const",
            "purpose": "AI Provider标签映射"
          },
          {
            "name": "defaultValues",
            "file": "exam-form-types.ts",
            "type": "const",
            "purpose": "表单默认值"
          },
          {
            "name": "previewTaskStorageKey",
            "file": "exam-form-types.ts",
            "type": "const",
            "purpose": "预览任务localStorage key"
          },
          {
            "name": "ExamForm",
            "file": "exam-form.tsx",
            "purpose": "考试表单(创建/编辑)"
          },
          {
            "name": "ExamGrid",
            "file": "exam-grid.tsx",
            "purpose": "考试网格视图"
          },
          {
            "name": "ExamModeSelector",
            "file": "exam-mode-selector.tsx",
            "purpose": "考试模式选择(手动/AI)"
          },
          {
            "name": "ExamPreviewDialog",
            "file": "exam-preview-dialog.tsx",
            "purpose": "考试预览对话框"
          },
          {
            "name": "ExamPreviewQuestionEditor",
            "file": "exam-preview-question-editor.tsx",
            "purpose": "预览题目编辑器"
          },
          {
            "name": "buildPreviewNodes",
            "file": "exam-preview-utils.ts",
            "type": "function",
            "purpose": "构建预览节点"
          },
          {
            "name": "parseEditableContent",
            "file": "exam-preview-utils.ts",
            "type": "function",
            "purpose": "解析可编辑内容"
          },
          {
            "name": "flattenPreviewQuestions",
            "file": "exam-preview-utils.ts",
            "type": "function",
            "purpose": "扁平化预览题目"
          },
          {
            "name": "findPreviewQuestionNode",
            "file": "exam-preview-utils.ts",
            "type": "function",
            "purpose": "查找预览题目节点"
          },
          {
            "name": "updatePreviewQuestionNodeInList",
            "file": "exam-preview-utils.ts",
            "type": "function",
            "purpose": "更新预览题目节点"
          },
          {
            "name": "buildPreviewSignature",
            "file": "exam-preview-utils.ts",
            "type": "function",
            "purpose": "构建预览签名"
          },
          {
            "name": "buildPreviewPayload",
            "file": "exam-preview-utils.ts",
            "type": "function",
            "purpose": "构建预览payload"
          },
          {
            "name": "buildPreviewRequestData",
            "file": "exam-preview-utils.ts",
            "type": "function",
            "purpose": "构建预览请求数据"
          },
          {
            "name": "ExamViewer",
            "file": "exam-viewer.tsx",
            "purpose": "考试查看器"
          },
          {
            "name": "QuestionOptionsEditor",
            "file": "question-options-editor.tsx",
            "purpose": "题目选项编辑器"
          },
          {
            "name": "QuestionSubQuestionsEditor",
            "file": "question-sub-questions-editor.tsx",
            "purpose": "子题目编辑器"
          },
          {
            "name": "ExamAnalyticsDashboard",
            "file": "exam-analytics-dashboard.tsx",
            "purpose": "V3-8 新增：考试分析仪表盘（汇总卡片：应考人数/已批改份数/平均分/及格率 + 分数段分布 + 逐题分析表：错误率/难度等级），对标智学网考试分析功能",
            "types": [
              "ExamAnalyticsSummary"
            ]
          }
        ],
        "hooks": [
          {
            "name": "useExamPreview",
            "file": "use-exam-preview.ts",
            "signature": "(form: UseFormReturn<ExamFormValues>) => { previewOpen, setPreviewOpen, previewLoading, previewNodes, handlePreview, handleBackgroundPreview, handleOpenPreviewTask, handleRewriteSelectedQuestion }",
            "purpose": "考试预览Hook",
            "usedBy": [
              "exam-form.tsx"
            ]
          }
        ],
        "utils": [
          {
            "name": "normalizeStructure",
            "file": "utils/normalize-structure.ts",
            "type": "function",
            "signature": "(nodes: unknown) => ExamNode[]",
            "purpose": "将持久化的 exam.structure（unknown JSON）运行时校验并归一化为类型安全的 ExamNode[]（类型守卫模式，无 as 断言；递归处理 group children；保证 id 唯一）",
            "deps": [
              "@paralleldrive/cuid2.createId",
              "exams/components/assembly/selected-question-list.ExamNode"
            ],
            "usedBy": [
              "teacher/exams/[id]/build/page.tsx"
            ]
          }
        ],
        "statsService": [
          {
            "name": "getExamAnalytics",
            "file": "stats-service.ts",
            "type": "function",
            "signature": "(examId: string) => Promise<ExamAnalyticsSummary | null>",
            "purpose": "V3-8 新增：考试分析数据聚合（cache 包装）。聚合该考试所有作业的已批改提交数据，计算平均分、及格率、分数段分布、逐题错误率与难度等级（easy/medium/hard）。对标智学网考试分析功能。",
            "deps": [
              "shared.db",
              "shared.db.schema.exams",
              "shared.db.schema.examQuestions",
              "homework/data-access.getHomeworkAssignmentsByExamId",
              "homework/data-access.getGradedSubmissionsByExamId",
              "homework/lib/question-content-utils.getQuestionText",
              "react.cache"
            ],
            "usedBy": [
              "teacher/exams/[id]/analytics/page.tsx"
            ]
          },
          {
            "name": "ExamAnalyticsSummary",
            "file": "stats-service.ts",
            "type": "interface",
            "definition": "{ examId, examTitle, totalScore, assignmentCount, totalStudents, submittedCount, gradedCount, averageScore, maxScore, passRate, scoreDistribution: Array<{ range, count }>, questions: Array<{ questionId, questionType, questionText, maxScore, errorCount, errorRate, difficulty: 'easy'|'medium'|'hard' }> }",
            "usedBy": [
              "getExamAnalytics",
              "exams/components/exam-analytics-dashboard.tsx"
            ]
          }
        ]
      }
    },
    "homework": {
      "path": "src/modules/homework",
      "description": "作业全生命周期：创建(源自考试)、发布、学生作答、教师批改、数据分析",
      "exports": {
        "actions": [
          {
            "name": "createHomeworkAssignmentAction",
            "permission": "HOMEWORK_CREATE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "从已有考试创建作业",
            "deps": [
              "requirePermission",
              "data-access-write.createHomeworkAssignment",
              "exams/data-access.getExams"
            ],
            "usedBy": [
              "homework-assignment-form.tsx"
            ]
          },
          {
            "name": "startHomeworkSubmissionAction",
            "permission": "HOMEWORK_SUBMIT",
            "signature": "同上",
            "purpose": "学生开始作答",
            "deps": [
              "requirePermission",
              "data-access-write.startHomeworkSubmission"
            ],
            "usedBy": [
              "homework-take-view.tsx"
            ]
          },
          {
            "name": "saveHomeworkAnswerAction",
            "permission": "HOMEWORK_SUBMIT",
            "signature": "同上",
            "purpose": "保存单题答案",
            "deps": [
              "requirePermission",
              "data-access-write.saveHomeworkAnswer"
            ],
            "usedBy": [
              "homework-take-view.tsx"
            ]
          },
          {
            "name": "submitHomeworkAction",
            "permission": "HOMEWORK_SUBMIT",
            "signature": "同上",
            "purpose": "提交作业",
            "deps": [
              "requirePermission",
              "data-access-write.submitHomework"
            ],
            "usedBy": [
              "homework-take-view.tsx"
            ]
          },
          {
            "name": "gradeHomeworkSubmissionAction",
            "permission": "HOMEWORK_GRADE",
            "signature": "同上",
            "purpose": "教师批改作业",
            "deps": [
              "requirePermission",
              "data-access-write.gradeHomeworkSubmission"
            ],
            "usedBy": [
              "homework-grading-view.tsx"
            ]
          },
          {
            "name": "batchAutoGradeSubmissionsAction",
            "permission": "HOMEWORK_GRADE",
            "signature": "(prevState: ActionState<{ successCount: number; failedCount: number; fullyGradedCount: number }> | null, formData: FormData) => Promise<ActionState<{ successCount: number; failedCount: number; fullyGradedCount: number }>>",
            "purpose": "V3-7 新增：批量自动批改提交。教师在提交列表页勾选多份提交后一键自动批改所有客观题（选择题/判断题），主观题保持原分数。非管理员仅可批改自己创建的作业提交（通过 getHomeworkSubmissionForGrading 校验 creatorId===ctx.userId）。",
            "deps": [
              "requirePermission",
              "shared/lib/parse.safeJsonParse",
              "data-access-write.getHomeworkSubmissionForGrading",
              "data-access-write.batchAutoGradeSubmissions",
              "shared/lib/track-event.trackExamEvent",
              "revalidatePath"
            ],
            "usedBy": [
              "homework-batch-grading-view.tsx"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getHomeworkAssignments",
            "signature": "(params?: { creatorId?: string; ids?: string[]; classId?: string; scope?: DataScope }) => Promise<HomeworkAssignmentListItem[]>",
            "usedBy": [
              "teacher作业列表页",
              "homework-assignment-form.tsx"
            ]
          },
          {
            "name": "getHomeworkAssignmentReviewList",
            "signature": "(params: { creatorId: string; scope?: DataScope }) => Promise<HomeworkAssignmentReviewListItem[]>",
            "usedBy": [
              "teacher批改列表"
            ]
          },
          {
            "name": "getHomeworkSubmissions",
            "signature": "(params?: { assignmentId?: string; classId?: string; creatorId?: string; scope?: DataScope }) => Promise<HomeworkSubmissionListItem[]>",
            "usedBy": [
              "teacher提交列表"
            ]
          },
          {
            "name": "getStudentHomeworkAssignments",
            "signature": "(studentId: string) => Promise<StudentHomeworkAssignmentListItem[]>",
            "usedBy": [
              "student/dashboard"
            ]
          },
          {
            "name": "getHomeworkAssignmentById",
            "signature": "(id: string, scope?: DataScope) => Promise<...>",
            "purpose": "按ID获取作业详情",
            "usedBy": [
              "homework详情页"
            ]
          },
          {
            "name": "getHomeworkSubmissionDetails",
            "signature": "(submissionId: string) => Promise<HomeworkSubmissionDetails | null>",
            "purpose": "获取提交详情(含答案)",
            "usedBy": [
              "homework-grading-view.tsx"
            ]
          },
          {
            "name": "getDemoStudentUser",
            "signature": "() => Promise<{ id: string; name: string } | null>",
            "purpose": "获取演示学生用户（已迁移至 users 模块 getCurrentStudentUser，此处为 re-export 向后兼容）",
            "usedBy": [
              "homework内部"
            ]
          },
          {
            "name": "getStudentHomeworkTakeData",
            "signature": "(assignmentId: string, studentId: string) => Promise<StudentHomeworkTakeData | null>",
            "purpose": "获取学生作答数据",
            "usedBy": [
              "homework-take-view.tsx"
            ]
          },
          {
            "name": "getHomeworkAssignmentsByExamId",
            "signature": "(examId: string) => Promise<Array<{ id, title, status: string | null, targetCount, submittedCount, gradedCount, dueAt: string | null }>>",
            "purpose": "V3-8 新增：按考试 ID 查询关联作业列表（含目标/已提交/已批改学生计数，3 个聚合查询并行执行）。供 exams/stats-service.getExamAnalytics 跨模块调用。",
            "deps": [
              "shared.db",
              "shared.db.schema.homeworkAssignments",
              "shared.db.schema.homeworkAssignmentTargets",
              "shared.db.schema.homeworkSubmissions",
              "drizzle-orm.eq/inArray/and/count/sql",
              "react.cache"
            ],
            "usedBy": [
              "exams/stats-service.getExamAnalytics"
            ]
          },
          {
            "name": "getGradedSubmissionsByExamId",
            "signature": "(examId: string) => Promise<Array<{ submissionId, assignmentId, studentId, studentName, score, answers: Array<{ questionId, score, answerContent: unknown }> }>>",
            "purpose": "V3-8 新增：按考试 ID 查询所有已批改提交（按学生去重，保留最新一条；含答案列表）。供 exams/stats-service.getExamAnalytics 跨模块调用。",
            "deps": [
              "shared.db",
              "shared.db.schema.homeworkAssignments",
              "shared.db.schema.homeworkSubmissions",
              "drizzle-orm.eq/inArray/and",
              "react.cache"
            ],
            "usedBy": [
              "exams/stats-service.getExamAnalytics"
            ]
          },
          {
            "name": "getStudentSubmissionResult",
            "signature": "(assignmentId: string, studentId: string) => Promise<HomeworkSubmissionDetails | null>",
            "purpose": "V3-9 新增：获取学生在指定作业的最新提交结果（用于提交后反馈页）。查找学生最近一次已提交/已批改的 submission，返回完整详情含答案（复用 getHomeworkSubmissionDetails）。",
            "deps": [
              "shared.db",
              "shared.db.schema.homeworkSubmissions",
              "drizzle-orm.eq/and/inArray/desc",
              "data-access.getHomeworkSubmissionDetails",
              "react.cache"
            ],
            "usedBy": [
              "student/learning/assignments/[assignmentId]/result/page.tsx"
            ]
          },
          {
            "name": "getStudentExamResults",
            "signature": "(studentId: string) => Promise<Array<{ submissionId, examId, examTitle, assignmentId, assignmentTitle, score, maxScore, submittedAt: string | null, status }>>",
            "purpose": "V3-11 新增：获取学生的考试结果列表（供家长端展示）。查找学生所有已批改的、关联到考试的作业提交，按 examId 去重，limit 50。供 parent/data-access.getChildDashboardData 跨模块调用。",
            "deps": [
              "shared.db",
              "shared.db.schema.homeworkSubmissions",
              "drizzle-orm.eq/and/desc",
              "react.cache"
            ],
            "usedBy": [
              "parent/data-access.getChildDashboardData"
            ]
          }
        ],
        "dataAccessClasses": [
          {
            "name": "getAssignmentIdsForStudents",
            "file": "data-access-classes.ts",
            "signature": "(studentIds: string[]) => Promise<string[]>",
            "purpose": "返回目标学生涉及的作业ID列表（P0-7 新增，供 classes 模块跨模块调用）",
            "usedBy": [
              "classes/data-access-stats.getClassHomeworkInsights",
              "classes/data-access-stats.getGradeHomeworkInsights",
              "classes/data-access-students.getStudentsSubjectScores"
            ]
          },
          {
            "name": "getHomeworkAssignmentsWithSubject",
            "file": "data-access-classes.ts",
            "signature": "(params: { assignmentIds, subjectIdFilter?, limit? }) => Promise<HomeworkAssignmentWithSubject[]>",
            "purpose": "返回作业（含科目信息，via source exam），可选按科目过滤（P0-7 新增）",
            "usedBy": [
              "classes/data-access-stats.getClassHomeworkInsights"
            ]
          },
          {
            "name": "getHomeworkAssignmentsByIds",
            "file": "data-access-classes.ts",
            "signature": "(params: { assignmentIds, limit? }) => Promise<HomeworkAssignmentBrief[]>",
            "purpose": "按 ID 返回作业简要信息（不含科目，P0-7 新增）",
            "usedBy": [
              "classes/data-access-stats.getGradeHomeworkInsights"
            ]
          },
          {
            "name": "getAssignmentTargetCounts",
            "file": "data-access-classes.ts",
            "signature": "(params: { assignmentIds, studentIds }) => Promise<Map<string, number>>",
            "purpose": "返回每个作业在指定学生中的目标计数（P0-7 新增）",
            "usedBy": [
              "classes/data-access-stats.getClassHomeworkInsights",
              "classes/data-access-stats.getGradeHomeworkInsights"
            ]
          },
          {
            "name": "getHomeworkSubmissionsForStudents",
            "file": "data-access-classes.ts",
            "signature": "(params: { assignmentIds, studentIds }) => Promise<HomeworkSubmissionRecord[]>",
            "purpose": "返回指定作业和学生的提交记录（按 createdAt desc，P0-7 新增）",
            "usedBy": [
              "classes/data-access-stats.getClassHomeworkInsights",
              "classes/data-access-stats.getGradeHomeworkInsights"
            ]
          },
          {
            "name": "getPublishedHomeworkAssignmentsWithSubject",
            "file": "data-access-classes.ts",
            "signature": "(params: { assignmentIds }) => Promise<HomeworkAssignmentSubjectRow[]>",
            "purpose": "返回已发布作业（含科目信息，via source exam，P0-7 新增）",
            "usedBy": [
              "classes/data-access-students.getStudentsSubjectScores"
            ]
          },
          {
            "name": "getHomeworkSubmissionsForAssignments",
            "file": "data-access-classes.ts",
            "signature": "(assignmentIds: string[]) => Promise<HomeworkSubmissionScoreRecord[]>",
            "purpose": "返回指定作业的提交记录（按 createdAt desc，P0-7 新增）",
            "usedBy": [
              "classes/data-access-students.getStudentsSubjectScores"
            ]
          }
        ],
        "dataAccessWrite": [
          {
            "name": "createHomeworkAssignment",
            "file": "data-access-write.ts",
            "signature": "(input: { examId, classId, title, description, dueAt, allowLate?, lateDueAt?, maxAttempts? }) => Promise<{ assignmentId: string }>",
            "purpose": "创建作业（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "createHomeworkAssignmentAction"
            ]
          },
          {
            "name": "startHomeworkSubmission",
            "file": "data-access-write.ts",
            "signature": "(assignmentId: string, studentId: string) => Promise<{ submissionId: string }>",
            "purpose": "学生开始作答（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "startHomeworkSubmissionAction"
            ]
          },
          {
            "name": "saveHomeworkAnswer",
            "file": "data-access-write.ts",
            "signature": "(submissionId: string, questionId: string, answer: string) => Promise<void>",
            "purpose": "保存单题答案（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "saveHomeworkAnswerAction"
            ]
          },
          {
            "name": "submitHomework",
            "file": "data-access-write.ts",
            "signature": "(submissionId: string, studentId: string) => Promise<void>",
            "purpose": "提交作业（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "submitHomeworkAction"
            ]
          },
          {
            "name": "gradeHomeworkSubmission",
            "file": "data-access-write.ts",
            "signature": "(submissionId: string, grades: Array<{ answerId: string; isCorrect: boolean; score: number; feedback?: string }>) => Promise<void>",
            "purpose": "教师批改作业（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "gradeHomeworkSubmissionAction"
            ]
          },
          {
            "name": "batchAutoGradeSubmissions",
            "file": "data-access-write.ts",
            "signature": "(submissionIds: string[]) => Promise<Array<{ submissionId: string; success: boolean; isFullyAutoGraded: boolean; totalScore: number; message?: string }>>",
            "purpose": "V3-7 新增：对多个提交执行自动批改（复用 autoGradeSubmission 逻辑）。仅批改客观题（选择题/判断题），主观题保持原分数。返回每个提交的批改结果摘要。",
            "deps": [
              "shared.db",
              "shared.db.schema.homeworkSubmissions",
              "shared.db.schema.homeworkAnswers",
              "shared.db.schema.homeworkAssignmentQuestions",
              "drizzle-orm.eq"
            ],
            "usedBy": [
              "batchAutoGradeSubmissionsAction"
            ]
          }
        ],
        "statsService": [
          {
            "name": "getTeacherGradeTrends",
            "file": "stats-service.ts",
            "signature": "(teacherId: string, limit?: number) => Promise<TeacherGradeTrendItem[]>",
            "purpose": "教师仪表盘年级趋势数据",
            "deps": [
              "data-access.getAssignmentMaxScoreById"
            ],
            "usedBy": [
              "dashboard (教师仪表盘)"
            ],
            "reExportedFrom": "data-access.ts (向后兼容)"
          },
          {
            "name": "getHomeworkAssignmentAnalytics",
            "file": "stats-service.ts",
            "signature": "(assignmentId: string) => Promise<HomeworkAssignmentAnalytics | null>",
            "purpose": "作业整体分析(含题目错误率/错答样本)",
            "deps": [
              "data-access.isRecord",
              "data-access.toQuestionContent"
            ],
            "usedBy": [
              "homework错误分析组件"
            ],
            "reExportedFrom": "data-access.ts (向后兼容)"
          },
          {
            "name": "getStudentDashboardGrades",
            "file": "stats-service.ts",
            "signature": "(studentId: string) => Promise<StudentDashboardGradeProps>",
            "purpose": "学生仪表盘成绩(趋势/近期/班级排名)",
            "deps": [
              "data-access.getAssignmentMaxScoreById"
            ],
            "usedBy": [
              "dashboard/data-access.ts"
            ],
            "reExportedFrom": "data-access.ts (向后兼容)"
          },
          {
            "name": "getHomeworkDashboardStats",
            "file": "stats-service.ts",
            "signature": "(scope?: DataScope) => Promise<HomeworkDashboardStats>",
            "purpose": "获取作业仪表盘统计数据(作业数/已发布数/提交数/待批改数,支持数据范围过滤)",
            "usedBy": [
              "dashboard/data-access.getAdminDashboardData"
            ],
            "reExportedFrom": "data-access.ts (向后兼容)"
          }
        ],
        "schema": [
          {
            "name": "CreateHomeworkAssignmentSchema",
            "type": "const",
            "description": "zod schema 创建作业",
            "usedBy": [
              "createHomeworkAssignmentAction",
              "homework-assignment-form.tsx"
            ]
          },
          {
            "name": "CreateHomeworkAssignmentInput",
            "type": "type",
            "definition": "z.infer<typeof CreateHomeworkAssignmentSchema>",
            "usedBy": [
              "createHomeworkAssignmentAction"
            ]
          },
          {
            "name": "GradeHomeworkSchema",
            "type": "const",
            "description": "zod schema 批改作业",
            "usedBy": [
              "gradeHomeworkSubmissionAction",
              "homework-grading-view.tsx"
            ]
          }
        ],
        "types": [
          {
            "name": "StudentDashboardGradeProps",
            "definition": "{ trend, recent, ranking }",
            "usedBy": [
              "dashboard/types.ts"
            ]
          },
          {
            "name": "HomeworkAssignmentListItem",
            "usedBy": [
              "homework列表页"
            ]
          },
          {
            "name": "StudentHomeworkTakeData",
            "usedBy": [
              "homework-take-view.tsx"
            ]
          },
          {
            "name": "HomeworkAssignmentStatus",
            "type": "type",
            "definition": "作业状态枚举",
            "usedBy": [
              "homework/components",
              "homework/data-access"
            ]
          },
          {
            "name": "HomeworkSubmissionStatus",
            "type": "type",
            "definition": "提交状态枚举",
            "usedBy": [
              "homework/components",
              "homework/data-access"
            ]
          },
          {
            "name": "TeacherGradeTrendItem",
            "type": "type",
            "definition": "教师年级趋势项",
            "usedBy": [
              "dashboard (教师仪表盘)"
            ]
          },
          {
            "name": "HomeworkAssignmentReviewListItem",
            "type": "type",
            "definition": "批改列表项",
            "usedBy": [
              "teacher批改列表"
            ]
          },
          {
            "name": "HomeworkSubmissionListItem",
            "type": "type",
            "definition": "提交列表项",
            "usedBy": [
              "teacher提交列表"
            ]
          },
          {
            "name": "HomeworkQuestionContent",
            "type": "type",
            "definition": "作业题目内容",
            "usedBy": [
              "homework/components"
            ]
          },
          {
            "name": "HomeworkSubmissionAnswerDetails",
            "type": "type",
            "definition": "提交答案详情",
            "usedBy": [
              "homework-grading-view.tsx"
            ]
          },
          {
            "name": "HomeworkSubmissionDetails",
            "type": "type",
            "definition": "提交详情(含答案列表)",
            "usedBy": [
              "homework-grading-view.tsx"
            ]
          },
          {
            "name": "StudentHomeworkProgressStatus",
            "type": "type",
            "definition": "学生作业进度状态",
            "usedBy": [
              "student/dashboard"
            ]
          },
          {
            "name": "StudentHomeworkAssignmentListItem",
            "type": "type",
            "definition": "学生作业列表项",
            "usedBy": [
              "student/dashboard"
            ]
          },
          {
            "name": "StudentHomeworkPerformanceItem",
            "type": "type",
            "definition": "学生表现项",
            "usedBy": [
              "student/dashboard"
            ]
          },
          {
            "name": "StudentHomeworkPerformanceSummary",
            "type": "type",
            "definition": "学生表现汇总",
            "usedBy": [
              "student/dashboard"
            ]
          },
          {
            "name": "StudentHomeworkTakeQuestion",
            "type": "type",
            "definition": "学生作答题目",
            "usedBy": [
              "homework-take-view.tsx"
            ]
          },
          {
            "name": "HomeworkAssignmentQuestionAnalytics",
            "type": "type",
            "definition": "作业题目分析",
            "usedBy": [
              "homework错误分析组件"
            ]
          },
          {
            "name": "HomeworkAssignmentAnalytics",
            "type": "type",
            "definition": "作业整体分析",
            "usedBy": [
              "homework错误分析组件"
            ]
          },
          {
            "name": "StudentHomeworkScoreAnalytics",
            "type": "type",
            "definition": "学生成绩分析",
            "usedBy": [
              "student/dashboard"
            ]
          },
          {
            "name": "StudentRanking",
            "type": "type",
            "definition": "学生排名",
            "usedBy": [
              "student/dashboard"
            ]
          }
        ],
        "components": [
          {
            "name": "HomeworkAssignmentExamContentCard",
            "file": "homework-assignment-exam-content-card.tsx",
            "purpose": "作业考试内容卡片"
          },
          {
            "name": "HomeworkAssignmentExamErrorExplorerLazy",
            "file": "homework-assignment-exam-error-explorer-lazy.tsx",
            "purpose": "作业错误分析(懒加载)"
          },
          {
            "name": "HomeworkAssignmentExamErrorExplorer",
            "file": "homework-assignment-exam-error-explorer.tsx",
            "purpose": "作业错误分析探索器"
          },
          {
            "name": "HomeworkAssignmentExamPreviewPane",
            "file": "homework-assignment-exam-preview-pane.tsx",
            "purpose": "作业考试预览面板"
          },
          {
            "name": "HomeworkAssignmentForm",
            "file": "homework-assignment-form.tsx",
            "purpose": "作业创建表单"
          },
          {
            "name": "HomeworkAssignmentQuestionErrorDetailPanel",
            "file": "homework-assignment-question-error-detail-panel.tsx",
            "purpose": "题目错误详情面板"
          },
          {
            "name": "HomeworkAssignmentQuestionErrorOverviewCard",
            "file": "homework-assignment-question-error-overview-card.tsx",
            "purpose": "题目错误概览卡片"
          },
          {
            "name": "HomeworkGradingView",
            "file": "homework-grading-view.tsx",
            "purpose": "作业批改视图"
          },
          {
            "name": "HomeworkTakeView",
            "file": "homework-take-view.tsx",
            "purpose": "学生作答视图"
          },
          {
            "name": "HomeworkReviewView",
            "file": "student-homework-review-view.tsx",
            "purpose": "学生作业复习视图"
          },
          {
            "name": "HomeworkBatchGradingView",
            "file": "homework-batch-grading-view.tsx",
            "purpose": "V3-7 新增：批量批改视图（use client）。教师在提交列表页可勾选多份提交，一键自动批改所有客观题。含全选/反选、 selectable 过滤（仅 status='submitted' 可选）、useTransition 异步提交、toast 反馈、revalidate 刷新。对标智学网批量批改功能。"
          },
          {
            "name": "HomeworkSubmissionResult",
            "file": "homework-submission-result.tsx",
            "purpose": "V3-9 新增：提交后即时反馈页。学生提交后立即看到分数汇总（总分/满分、得分率 Progress）、对错分布（正确/错误/部分正确/待批改）、错题预览（题目文本、学生答案、正确答案）。对标智学网/猿题库提交后反馈。"
          }
        ],
        "hooks": [
          {
            "name": "useExamCountdown",
            "file": "hooks/use-exam-countdown.ts",
            "signature": "(options: { durationMinutes: number | null, startedAt: string | null, onExpire?: () => void, enabled?: boolean }) => ExamCountdownState | null",
            "purpose": "P0-竞品新增：考试倒计时 Hook（对标智学网/猿题库）。每秒更新剩余时间，剩余≤5分钟标记紧急状态，到时触发 onExpire 回调自动提交。setInterval + setState 仅在 interval 回调中异步调用，符合 react-hooks/purity 与 set-state-in-effect 规则。",
            "usedBy": [
              "homework/components/homework-take-view.HomeworkTakeView"
            ]
          }
        ]
      }
    },
    "questions": {
      "path": "src/modules/questions",
      "description": "题库管理：题目CRUD、知识点关联、多题型支持",
      "exports": {
        "actions": [
          {
            "name": "createNestedQuestion",
            "permission": "QUESTION_CREATE",
            "signature": "(prevState: ActionState<string> | undefined, formData: FormData | CreateQuestionInput) => Promise<ActionState<string>>",
            "purpose": "创建题目(含嵌套)",
            "deps": [
              "requirePermission",
              "data-access.createQuestionWithRelations"
            ],
            "usedBy": [
              "create-question-dialog.tsx"
            ]
          },
          {
            "name": "updateQuestionAction",
            "permission": "QUESTION_UPDATE",
            "signature": "同上",
            "purpose": "更新题目",
            "deps": [
              "requirePermission",
              "data-access.updateQuestionById"
            ],
            "usedBy": [
              "question-actions.tsx"
            ]
          },
          {
            "name": "deleteQuestionAction",
            "permission": "QUESTION_DELETE",
            "signature": "同上",
            "purpose": "删除题目",
            "deps": [
              "requirePermission",
              "data-access.deleteQuestionByIdRecursive"
            ],
            "usedBy": [
              "question-actions.tsx"
            ]
          },
          {
            "name": "getQuestionsAction",
            "permission": "QUESTION_READ",
            "signature": "(params: GetQuestionsParams) => Promise<...>",
            "purpose": "查询题目列表",
            "deps": [
              "requirePermission",
              "data-access.getQuestions"
            ],
            "usedBy": [
              "teacher/questions/page.tsx"
            ]
          },
          {
            "name": "getKnowledgePointOptionsAction",
            "permission": "QUESTION_READ",
            "signature": "() => Promise<KnowledgePointOption[]>",
            "purpose": "获取知识点选项",
            "deps": [
              "requirePermission",
              "data-access.getKnowledgePointOptions"
            ],
            "usedBy": [
              "create-question-dialog.tsx"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getQuestions",
            "signature": "(params?: GetQuestionsParams) => Promise<{ data: Question[], meta: { page, pageSize, total, totalPages } }>",
            "type": "cache function",
            "purpose": "查询题目列表(缓存)",
            "usedBy": [
              "getQuestionsAction",
              "teacher/questions/page.tsx"
            ]
          },
          {
            "name": "getQuestionsDashboardStats",
            "signature": "() => Promise<QuestionsDashboardStats>",
            "purpose": "获取题目仪表盘统计数据(题目总数)",
            "usedBy": [
              "dashboard/data-access.getAdminDashboardData"
            ]
          },
          {
            "name": "createQuestionWithRelations",
            "signature": "(input: CreateQuestionInput) => Promise<{ questionId: string }>",
            "purpose": "创建题目（含嵌套子题目、知识点关联）（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "createNestedQuestion"
            ]
          },
          {
            "name": "updateQuestionById",
            "signature": "(questionId: string, input: Partial<CreateQuestionInput>) => Promise<void>",
            "purpose": "按ID更新题目（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "updateQuestionAction"
            ]
          },
          {
            "name": "deleteQuestionByIdRecursive",
            "signature": "(questionId: string) => Promise<void>",
            "purpose": "递归删除题目（含子题目、知识点关联）（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "deleteQuestionAction"
            ]
          },
          {
            "name": "getKnowledgePointOptions",
            "signature": "() => Promise<KnowledgePointOption[]>",
            "purpose": "获取知识点选项（含章节/教材信息）（P1-2 新增，从 actions 下沉）",
            "usedBy": [
              "getKnowledgePointOptionsAction"
            ]
          },
          {
            "name": "GetQuestionsParams",
            "type": "type",
            "definition": "{ q?, page?, pageSize?, ids?, knowledgePointId?, type?, difficulty? }",
            "usedBy": [
              "getQuestions",
              "getQuestionsAction"
            ]
          }
        ],
        "schema": [
          {
            "name": "QuestionTypeEnum",
            "type": "const",
            "description": "zod enum: z.enum([\"single_choice\", \"multiple_choice\", \"text\", \"judgment\", \"composite\"])",
            "usedBy": [
              "CreateQuestionSchema",
              "questions/components"
            ]
          },
          {
            "name": "BaseQuestionSchema",
            "type": "const",
            "description": "zod schema 基础题目校验",
            "usedBy": [
              "CreateQuestionSchema"
            ]
          },
          {
            "name": "CreateQuestionInput",
            "type": "type",
            "definition": "z.infer<typeof CreateQuestionSchema>",
            "usedBy": [
              "createNestedQuestion",
              "create-question-dialog.tsx"
            ]
          },
          {
            "name": "CreateQuestionSchema",
            "type": "const",
            "description": "zod schema 创建题目(递归支持嵌套)",
            "usedBy": [
              "createNestedQuestion",
              "create-question-dialog.tsx"
            ]
          }
        ],
        "types": [
          {
            "name": "Question",
            "definition": "{ id, content, type, difficulty, createdAt, updatedAt, author, knowledgePoints, childrenCount? }",
            "usedBy": [
              "exams (题目选择)",
              "homework (作业题目)"
            ]
          },
          {
            "name": "KnowledgePointOption",
            "definition": "{ id, name, chapterId, chapterTitle, textbookId, textbookTitle, subject, grade }",
            "usedBy": [
              "create-question-dialog.tsx"
            ]
          },
          {
            "name": "QuestionType",
            "type": "type",
            "definition": "z.infer<typeof QuestionTypeEnum>",
            "usedBy": [
              "questions/components",
              "exams/components"
            ]
          }
        ],
        "components": [
          {
            "name": "CreateQuestionButton",
            "file": "create-question-button.tsx",
            "purpose": "创建题目按钮"
          },
          {
            "name": "CreateQuestionDialog",
            "file": "create-question-dialog.tsx",
            "purpose": "创建题目对话框"
          },
          {
            "name": "QuestionActions",
            "file": "question-actions.tsx",
            "purpose": "题目操作按钮"
          },
          {
            "name": "columns",
            "file": "question-columns.tsx",
            "type": "ColumnDef[]",
            "purpose": "题目表格列定义"
          },
          {
            "name": "QuestionDataTable",
            "file": "question-data-table.tsx",
            "purpose": "题目数据表格"
          },
          {
            "name": "QuestionFilters",
            "file": "question-filters.tsx",
            "purpose": "题目筛选器"
          }
        ]
      }
    },
    "textbooks": {
      "path": "src/modules/textbooks",
      "description": "教材与知识体系：教材/章节树形结构、知识点CRUD、Markdown内容编辑、知识图谱",
      "exports": {
        "actions": [
          {
            "name": "createTextbookAction",
            "permission": "TEXTBOOK_CREATE",
            "signature": "(prevState, formData) => Promise<ActionState>",
            "purpose": "创建教材"
          },
          {
            "name": "updateTextbookAction",
            "permission": "TEXTBOOK_UPDATE",
            "signature": "(textbookId, prevState, formData) => Promise<ActionState>",
            "purpose": "更新教材元信息"
          },
          {
            "name": "deleteTextbookAction",
            "permission": "TEXTBOOK_DELETE",
            "signature": "(textbookId) => Promise<ActionState>",
            "purpose": "删除教材"
          },
          {
            "name": "createChapterAction",
            "permission": "TEXTBOOK_CREATE",
            "signature": "(textbookId, parentId?, prevState, formData) => Promise<ActionState>",
            "purpose": "创建章节"
          },
          {
            "name": "updateChapterContentAction",
            "permission": "TEXTBOOK_UPDATE",
            "signature": "(chapterId, content, textbookId) => Promise<ActionState>",
            "purpose": "更新章节内容(Markdown)"
          },
          {
            "name": "deleteChapterAction",
            "permission": "TEXTBOOK_DELETE",
            "signature": "(chapterId, textbookId) => Promise<ActionState>",
            "purpose": "删除章节"
          },
          {
            "name": "createKnowledgePointAction",
            "permission": "TEXTBOOK_CREATE",
            "signature": "(chapterId, textbookId, prevState, formData) => Promise<ActionState>",
            "purpose": "创建知识点"
          },
          {
            "name": "updateKnowledgePointAction",
            "permission": "TEXTBOOK_UPDATE",
            "signature": "(kpId, textbookId, prevState, formData) => Promise<ActionState>",
            "purpose": "更新知识点"
          },
          {
            "name": "deleteKnowledgePointAction",
            "permission": "TEXTBOOK_DELETE",
            "signature": "(kpId, textbookId) => Promise<ActionState>",
            "purpose": "删除知识点"
          },
          {
            "name": "reorderChaptersAction",
            "permission": "TEXTBOOK_UPDATE",
            "signature": "(chapterId, newIndex, parentId, textbookId) => Promise<ActionState>",
            "purpose": "章节排序"
          },
          {
            "name": "getKnowledgePointsByChapterAction",
            "permission": "TEXTBOOK_READ",
            "signature": "(chapterId, textbookId) => Promise<ActionState<KnowledgePoint[]>>",
            "purpose": "获取章节下的知识点列表（含资源归属校验）"
          },
          {
            "name": "getKnowledgeGraphDataAction",
            "permission": "TEXTBOOK_READ",
            "signature": "(textbookId, viewMode: GraphViewMode) => Promise<ActionState<KnowledgeGraphData>>",
            "purpose": "Task 7 新增：获取知识图谱数据，支持 structure/student-mastery/class-mastery 三种视图模式"
          },
          {
            "name": "createPrerequisiteAction",
            "permission": "TEXTBOOK_UPDATE",
            "signature": "(formData) => Promise<ActionState>",
            "purpose": "Task 7 新增：声明前置依赖（含循环检测 + 归属校验）"
          },
          {
            "name": "deletePrerequisiteAction",
            "permission": "TEXTBOOK_UPDATE",
            "signature": "(formData) => Promise<ActionState>",
            "purpose": "Task 7 新增：删除前置依赖"
          }
        ],
        "dataAccess": [
          {
            "name": "getTextbooks",
            "signature": "(query?, subject?, grade?) => Promise<Textbook[]>",
            "usedBy": [
              "teacher/textbooks/page.tsx",
              "student/learning/textbooks/page.tsx"
            ]
          },
          {
            "name": "getTextbookById",
            "signature": "(id) => Promise<Textbook | undefined>",
            "usedBy": [
              "teacher/textbooks/[id]/page.tsx",
              "student/learning/textbooks/[id]/page.tsx"
            ]
          },
          {
            "name": "getChaptersByTextbookId",
            "signature": "(textbookId) => Promise<Chapter[]>",
            "usedBy": [
              "textbook-reader.tsx"
            ]
          },
          {
            "name": "getKnowledgePointsByChapterId",
            "signature": "(chapterId) => Promise<KnowledgePoint[]>",
            "usedBy": [
              "textbook-reader.tsx"
            ]
          },
          {
            "name": "getKnowledgePointsByTextbookId",
            "signature": "(textbookId) => Promise<KnowledgePoint[]>",
            "usedBy": [
              "textbook-reader.tsx"
            ]
          },
          {
            "name": "createTextbook",
            "signature": "(input: CreateTextbookInput) => Promise<Textbook>",
            "purpose": "创建教材",
            "usedBy": [
              "createTextbookAction"
            ]
          },
          {
            "name": "updateTextbook",
            "signature": "(id: string, input: UpdateTextbookInput) => Promise<void>",
            "purpose": "更新教材",
            "usedBy": [
              "updateTextbookAction"
            ]
          },
          {
            "name": "deleteTextbook",
            "signature": "(id: string) => Promise<void>",
            "purpose": "删除教材",
            "usedBy": [
              "deleteTextbookAction"
            ]
          },
          {
            "name": "createChapter",
            "signature": "(input: CreateChapterInput) => Promise<Chapter>",
            "purpose": "创建章节",
            "usedBy": [
              "createChapterAction"
            ]
          },
          {
            "name": "updateChapterContent",
            "signature": "(input: UpdateChapterContentInput) => Promise<void>",
            "purpose": "更新章节内容",
            "usedBy": [
              "updateChapterContentAction"
            ]
          },
          {
            "name": "deleteChapter",
            "signature": "(chapterId: string, textbookId: string) => Promise<void>",
            "purpose": "删除章节",
            "usedBy": [
              "deleteChapterAction"
            ]
          },
          {
            "name": "createKnowledgePoint",
            "signature": "(input: CreateKnowledgePointInput) => Promise<KnowledgePoint>",
            "purpose": "创建知识点",
            "usedBy": [
              "createKnowledgePointAction"
            ]
          },
          {
            "name": "updateKnowledgePoint",
            "signature": "(input: UpdateKnowledgePointInput) => Promise<void>",
            "purpose": "更新知识点",
            "usedBy": [
              "updateKnowledgePointAction"
            ]
          },
          {
            "name": "deleteKnowledgePoint",
            "signature": "(kpId: string, textbookId: string) => Promise<void>",
            "purpose": "删除知识点",
            "usedBy": [
              "deleteKnowledgePointAction"
            ]
          },
          {
            "name": "reorderChapters",
            "signature": "(chapterId: string, newIndex: number, parentId: string | null, textbookId: string) => Promise<void>",
            "purpose": "章节排序",
            "usedBy": [
              "reorderChaptersAction"
            ]
          },
          {
            "name": "getTextbooksDashboardStats",
            "signature": "() => Promise<TextbooksDashboardStats>",
            "purpose": "获取教材仪表盘统计数据(教材总数、章节总数)",
            "usedBy": [
              "dashboard/data-access.getAdminDashboardData"
            ]
          },
          {
            "name": "getKnowledgePointOptions",
            "signature": "() => Promise<KnowledgePointOption[]>",
            "purpose": "跨模块接口：获取所有知识点选项(含章节/教材信息)，供 questions 模块调用",
            "usedBy": [
              "questions/data-access.getKnowledgePointOptions"
            ]
          },
          {
            "name": "getTextbooksWithScope",
            "signature": "(query?, subject?, grade?, scope?: TextbookQueryScope) => Promise<Textbook[]>",
            "purpose": "P1-1 新增：按数据范围获取教材列表，学生端强制按年级过滤",
            "usedBy": [
              "student/learning/textbooks/page.tsx"
            ]
          },
          {
            "name": "verifyChapterBelongsToTextbook",
            "signature": "(chapterId, textbookId) => Promise<boolean>",
            "purpose": "P0-4 新增：资源归属校验，防止跨教材越权操作章节",
            "usedBy": [
              "reorderChaptersAction",
              "updateChapterContentAction",
              "deleteChapterAction",
              "createKnowledgePointAction"
            ]
          },
          {
            "name": "verifyKnowledgePointBelongsToTextbook",
            "signature": "(kpId, textbookId) => Promise<boolean>",
            "purpose": "P0-4 新增：资源归属校验，防止跨教材越权操作知识点",
            "usedBy": [
              "updateKnowledgePointAction",
              "deleteKnowledgePointAction",
              "createPrerequisiteAction"
            ]
          },
          {
            "name": "createPrerequisite",
            "signature": "(input: CreatePrerequisiteInput) => Promise<void>",
            "purpose": "Task 7 新增：创建知识点前置依赖",
            "usedBy": [
              "createPrerequisiteAction"
            ]
          },
          {
            "name": "deletePrerequisite",
            "signature": "(input: DeletePrerequisiteInput) => Promise<void>",
            "purpose": "Task 7 新增：删除知识点前置依赖",
            "usedBy": [
              "deletePrerequisiteAction"
            ]
          },
          {
            "name": "getPrerequisiteEdgesForTextbook",
            "signature": "(textbookId) => Promise<Array<[string, string]>>",
            "purpose": "Task 7 新增：获取教材下所有前置依赖边列表，用于循环检测",
            "usedBy": [
              "createPrerequisiteAction"
            ]
          },
          {
            "name": "getSubjectLabelKey",
            "signature": "(subject: string) => string",
            "purpose": "获取学科的 i18n 标签键",
            "usedBy": [
              "textbooks/components/textbook-filters.tsx",
              "textbooks/components/textbook-card.tsx"
            ]
          },
          {
            "name": "getGradeLabelKey",
            "signature": "(grade: string) => string",
            "purpose": "获取年级的 i18n 标签键",
            "usedBy": [
              "textbooks/components/textbook-filters.tsx",
              "textbooks/components/textbook-card.tsx"
            ]
          }
        ],
        "hooks": [
          {
            "name": "useTextSelection",
            "file": "hooks/use-text-selection.ts",
            "signature": "() => { selectedText, setSelectedText, selectionRef, contentRef, setCreateDialogOpen, setIsCreating, createDialogOpen, isCreating, handleContentPointerDown, handleContextMenuChange }",
            "purpose": "文本选区Hook(无参数)",
            "usedBy": [
              "textbook-content-panel.tsx"
            ]
          },
          {
            "name": "useKpDialogState",
            "file": "hooks/use-kp-dialog-state.ts",
            "signature": "() => { editingKp, setEditingKp, editKpDialogOpen, setEditKpDialogOpen, isUpdatingKp, setIsUpdatingKp, questionDialogOpen, setQuestionDialogOpen, targetKpForQuestion, setTargetKpForQuestion, deleteConfirmOpen, setDeleteConfirmOpen, pendingDeleteKpId, setPendingDeleteKpId }",
            "purpose": "知识点对话框状态管理 Hook（编辑/题目/删除确认），从 use-knowledge-point-actions 拆分",
            "usedBy": [
              "hooks/use-knowledge-point-actions.ts"
            ]
          },
          {
            "name": "useKpCrud",
            "file": "hooks/use-kp-crud.ts",
            "signature": "(args: UseKpCrudArgs) => { handleCreateKnowledgePoint, requestDeleteKnowledgePoint, confirmDeleteKnowledgePoint, handleUpdateKnowledgePoint }",
            "purpose": "知识点 CRUD 操作 Hook，依赖 useKpDialogState 提供的状态，从 use-knowledge-point-actions 拆分",
            "usedBy": [
              "hooks/use-knowledge-point-actions.ts"
            ]
          },
          {
            "name": "useKnowledgePointActions",
            "file": "hooks/use-knowledge-point-actions.ts",
            "signature": "(textbookId, selectedChapterId, selectedChapterTextbookId, highlightedKpId, setHighlightedKpId, onKpCreated?) => { editingKp, setEditingKp, editKpDialogOpen, setEditKpDialogOpen, isUpdatingKp, questionDialogOpen, setQuestionDialogOpen, targetKpForQuestion, setTargetKpForQuestion, deleteConfirmOpen, setDeleteConfirmOpen, handleCreateKnowledgePoint, requestDeleteKnowledgePoint, confirmDeleteKnowledgePoint, handleUpdateKnowledgePoint }",
            "purpose": "知识点操作门面 Hook（组合 useKpDialogState + useKpCrud，对外保持原有 API 不变）",
            "usedBy": [
              "textbook-reader.tsx"
            ]
          },
          {
            "name": "useGraphData",
            "file": "hooks/use-graph-data.ts",
            "signature": "(textbookId: string, viewMode: GraphViewMode) => { data: KnowledgeGraphData | null, isLoading: boolean, isRefreshing: boolean, error: string | null, reload: () => void }",
            "purpose": "Task 11 新增：知识图谱数据加载 Hook，按 textbookId + viewMode 加载，使用派生值模式避免 effect 中 setState。区分 isLoading（首次加载）和 isRefreshing（切换模式刷新，保留旧数据避免 UI 闪烁）",
            "usedBy": [
              "components/knowledge-graph.tsx"
            ]
          }
        ],
        "types": [
          {
            "name": "Chapter",
            "definition": "{ id, textbookId, title, order, parentId, content?, children? }",
            "usedBy": [
              "textbooks/components",
              "questions (知识点关联)"
            ]
          },
          {
            "name": "ChapterTreeNode",
            "definition": "Chapter & { children: ChapterTreeNode[] }",
            "purpose": "P1-5 新增：buildChapterTree 返回类型，强制 children 为非空数组",
            "usedBy": [
              "textbooks/utils.buildChapterTree",
              "textbooks/components/chapter-sidebar-list.tsx"
            ]
          },
          {
            "name": "KnowledgePoint",
            "definition": "{ id, name, description?, anchorText?, parentId?, chapterId?, level, order }",
            "usedBy": [
              "textbooks/components",
              "questions/types"
            ]
          },
          {
            "name": "Textbook",
            "type": "type",
            "definition": "{ id, title, subject, grade, publisher, createdAt, updatedAt }",
            "usedBy": [
              "textbooks/components",
              "textbooks/data-access"
            ]
          },
          {
            "name": "CreateTextbookInput",
            "type": "type",
            "definition": "创建教材输入",
            "usedBy": [
              "createTextbook",
              "createTextbookAction"
            ]
          },
          {
            "name": "UpdateTextbookInput",
            "type": "type",
            "definition": "更新教材输入",
            "usedBy": [
              "updateTextbook",
              "updateTextbookAction"
            ]
          },
          {
            "name": "CreateChapterInput",
            "type": "type",
            "definition": "创建章节输入",
            "usedBy": [
              "createChapter",
              "createChapterAction"
            ]
          },
          {
            "name": "UpdateChapterContentInput",
            "type": "type",
            "definition": "更新章节内容输入",
            "usedBy": [
              "updateChapterContent",
              "updateChapterContentAction"
            ]
          },
          {
            "name": "CreateKnowledgePointInput",
            "type": "type",
            "definition": "创建知识点输入",
            "usedBy": [
              "createKnowledgePoint",
              "createKnowledgePointAction"
            ]
          },
          {
            "name": "UpdateKnowledgePointInput",
            "type": "type",
            "definition": "更新知识点输入",
            "usedBy": [
              "updateKnowledgePoint",
              "updateKnowledgePointAction"
            ]
          },
          {
            "name": "GraphViewMode",
            "type": "type",
            "definition": "\"structure\" | \"student-mastery\" | \"class-mastery\"",
            "purpose": "Task 7 新增：图谱视图模式",
            "usedBy": [
              "getKnowledgeGraphDataAction"
            ]
          },
          {
            "name": "MasteryInfo",
            "type": "interface",
            "definition": "{ masteryLevel, totalQuestions, correctQuestions, lastAssessedAt }",
            "purpose": "Task 7 新增：掌握度信息",
            "usedBy": [
              "getKnowledgeGraphDataAction",
              "data-access-graph.getStudentKpMastery",
              "data-access-graph.getClassKpMastery"
            ]
          },
          {
            "name": "KpWithRelations",
            "type": "interface",
            "definition": "{ id, name, description, parentId, chapterId, level, order, questionCount, prerequisiteIds, chapterTitle }",
            "purpose": "Task 7 新增：带关联关系的知识点（图谱数据）",
            "usedBy": [
              "data-access-graph.getKnowledgePointsWithRelations",
              "getKnowledgeGraphDataAction"
            ]
          },
          {
            "name": "KnowledgeGraphData",
            "type": "interface",
            "definition": "{ knowledgePoints: KpWithRelations[], masteryMap: Record<string, MasteryInfo>, viewMode: GraphViewMode }",
            "purpose": "Task 7 新增：图谱完整数据（Server Action 返回）",
            "usedBy": [
              "getKnowledgeGraphDataAction"
            ]
          },
          {
            "name": "GraphNodeData",
            "type": "interface",
            "definition": "{ kp: KpWithRelations, mastery: MasteryInfo | null, viewMode: GraphViewMode, isSelected: boolean, isHighlighted: boolean, chapterColor: string }",
            "purpose": "Task 9 新增：React Flow 自定义节点数据（传递给 graph-kp-node 组件）",
            "usedBy": [
              "components/graph-kp-node.tsx",
              "components/knowledge-graph.tsx"
            ]
          },
          {
            "name": "GraphEdgeData",
            "type": "interface",
            "definition": "{ edgeType: 'parent' | 'prerequisite', isHighlighted: boolean }",
            "purpose": "Task 9 新增：React Flow 自定义边数据",
            "usedBy": [
              "components/graph-prerequisite-edge.tsx",
              "components/knowledge-graph.tsx"
            ]
          },
          {
            "name": "MasteryLevel",
            "type": "type",
            "definition": "'low' | 'medium' | 'high' | 'unassessed'",
            "purpose": "Task 9 新增：掌握度等级（红/黄/绿/灰）",
            "usedBy": [
              "components/graph-kp-node.tsx"
            ]
          },
          {
            "name": "CreatePrerequisiteInput",
            "type": "type",
            "definition": "{ knowledgePointId, prerequisiteKpId }",
            "purpose": "Task 7 新增：创建前置依赖输入",
            "usedBy": [
              "createPrerequisite",
              "createPrerequisiteAction"
            ]
          },
          {
            "name": "DeletePrerequisiteInput",
            "type": "type",
            "definition": "{ knowledgePointId, prerequisiteKpId }",
            "purpose": "Task 7 新增：删除前置依赖输入",
            "usedBy": [
              "deletePrerequisite",
              "deletePrerequisiteAction"
            ]
          }
        ],
        "constants": [
          {
            "name": "SUBJECTS",
            "file": "constants.ts",
            "purpose": "学科常量数组（含 Chinese/Mathematics/Physics/Chemistry/Biology/English/History/Geography）"
          },
          {
            "name": "GRADES",
            "file": "constants.ts",
            "purpose": "年级常量数组（含 Grade 1/Grade 2/Grade 7-12）"
          },
          {
            "name": "SUBJECT_COLORS",
            "file": "constants.ts",
            "purpose": "学科颜色映射"
          },
          {
            "name": "getSubjectColor",
            "file": "constants.ts",
            "signature": "(subject: string) => string",
            "purpose": "获取学科对应颜色"
          },
          {
            "name": "getSubjectLabelKey",
            "file": "constants.ts",
            "signature": "(subject: string) => string",
            "purpose": "获取学科 i18n 标签键"
          },
          {
            "name": "getGradeLabelKey",
            "file": "constants.ts",
            "signature": "(grade: string) => string",
            "purpose": "获取年级 i18n 标签键"
          }
        ],
        "utils": [
          {
            "name": "sortChapters",
            "file": "utils.ts",
            "purpose": "章节排序"
          },
          {
            "name": "buildChapterTree",
            "file": "utils.ts",
            "purpose": "构建章节树（返回 ChapterTreeNode[]）"
          },
          {
            "name": "buildChapterIndex",
            "file": "utils.ts",
            "purpose": "构建章节索引 Map"
          },
          {
            "name": "findChapterParent",
            "file": "utils.ts",
            "purpose": "查找章节父节点"
          },
          {
            "name": "filterKnowledgePointsByChapter",
            "file": "utils.ts",
            "purpose": "按章节过滤知识点"
          },
          {
            "name": "normalizeOptional",
            "file": "utils.ts",
            "purpose": "可选字段归一化"
          },
          {
            "name": "highlightKnowledgePoints",
            "file": "utils.ts",
            "purpose": "高亮知识点"
          },
          {
            "name": "hasCycleAfterAddingEdge",
            "file": "utils.ts",
            "purpose": "检测添加有向边后是否形成环（DFS）"
          }
        ],
        "graphLayout": [
          {
            "name": "computeGraphLayout",
            "file": "graph-layout.ts",
            "purpose": "知识图谱布局计算纯函数（Task 8：dagre 集成，输入 KpWithRelations[]，输出 React Flow Node[]/Edge[] + 画布尺寸）"
          }
        ],
        "analytics": [
          {
            "name": "TextbookAnalytics",
            "file": "analytics.tsx",
            "type": "context",
            "purpose": "教材分析 Context"
          },
          {
            "name": "TextbookAnalyticsProvider",
            "file": "analytics.tsx",
            "type": "component",
            "purpose": "教材分析 Provider"
          },
          {
            "name": "useTextbookAnalytics",
            "file": "analytics.tsx",
            "type": "hook",
            "purpose": "教材分析 Hook"
          }
        ],
        "components": [
          {
            "name": "ChapterSidebarList",
            "purpose": "章节侧边栏列表"
          },
          {
            "name": "CreateChapterDialog",
            "purpose": "创建章节对话框"
          },
          {
            "name": "KnowledgeGraph",
            "purpose": "知识图谱可视化"
          },
          {
            "name": "GraphNodeDetailPanel",
            "purpose": "知识图谱节点详情侧边面板（Task 13 新增：展示知识点名称/描述/掌握度/关联题目/前置后置知识点，支持跳转与前置关系增删）"
          },
          {
            "name": "KnowledgePointDialogs",
            "purpose": "知识点对话框集合"
          },
          {
            "name": "KnowledgePointList",
            "purpose": "知识点列表"
          },
          {
            "name": "SectionErrorBoundary",
            "purpose": "章节内容错误边界（class component + i18n + router.refresh 重试）"
          },
          {
            "name": "TextbookCard",
            "purpose": "教材卡片"
          },
          {
            "name": "TextbookContentPanel",
            "purpose": "教材内容面板"
          },
          {
            "name": "TextbookFilters",
            "purpose": "教材筛选器"
          },
          {
            "name": "TextbookFormDialog",
            "purpose": "教材表单对话框"
          },
          {
            "name": "TextbookReader",
            "purpose": "教材阅读器"
          },
          {
            "name": "TextbookSettingsDialog",
            "purpose": "教材设置对话框"
          }
        ],
        "uiDeps": [],
        "uiDepsNote": "已通过 render prop 解耦，TeacherTextbookReader 已移至 app 层（src/app/(dashboard)/teacher/textbooks/[id]/_components/），textbooks 模块不再直接 import questions 模块组件",
        "knownIssues": [
          "i18n 覆盖率约 98%（chapter-sidebar-list/actions.ts/section-error-boundary/8 处硬编码英文 toast 已全部接入，Zod refine 消息改为英文，错误分支不再复用成功消息）",
          "类型断言残留 3 处 as string",
          "P2 图谱方向键导航未实现",
          "v1 测试已修复：textbook-reader.tsx SheetTrigger 越界、Server→Client 函数 prop 序列化、seed 数据 i18n key 不匹配",
          "v2 核查修复：class-mastery 视图模式已实现、跨教材前置依赖双向 IN 过滤、ChapterSidebarList canEdit 默认 false、isTeacher 冗余变量移除、graph-kp-node 节点宽度常量化、GraphNodeDetailPanel textbookId 未使用 prop 移除、use-knowledge-point-actions 拆分为门面+状态+CRUD 三 Hook、use-graph-data 区分 isLoading/isRefreshing 避免 UI 闪烁、TeacherTextbookReader 移至 app 层解耦跨模块依赖"
        ],
        "files": {
          "actions.ts": 515,
          "data-access.ts": 662,
          "data-access-graph.ts": 207,
          "types.ts": 106,
          "schema.ts": 81,
          "constants.ts": 96,
          "utils.ts": 225,
          "graph-layout.ts": 121,
          "analytics.tsx": 43,
          "hooks/use-knowledge-point-actions.ts": 49,
          "hooks/use-kp-dialog-state.ts": 38,
          "hooks/use-kp-crud.ts": 122,
          "hooks/use-text-selection.ts": 57,
          "hooks/use-graph-data.ts": 76,
          "components/chapter-sidebar-list.tsx": 342,
          "components/create-chapter-dialog.tsx": 111,
          "components/graph-kp-node.tsx": 92,
          "components/graph-node-detail-panel.tsx": 181,
          "components/graph-prerequisite-edge.tsx": 48,
          "components/graph-toolbar.tsx": 93,
          "components/knowledge-graph.tsx": 376,
          "components/knowledge-point-dialogs.tsx": 175,
          "components/knowledge-point-list.tsx": 122,
          "components/section-error-boundary.tsx": 71,
          "components/textbook-card.tsx": 181,
          "components/textbook-content-panel.tsx": 189,
          "components/textbook-filters.tsx": 71,
          "components/textbook-form-dialog.tsx": 139,
          "components/textbook-reader.tsx": 446,
          "components/textbook-settings-dialog.tsx": 203
        },
        "auditReport": "audit/textbooks-audit-report.md"
      }
    },
    "classes": {
      "path": "src/modules/classes",
      "description": "班级管理：班级CRUD、学生注册/退班、邀请码、课表、学科教师分配",
      "exports": {
        "actions": [
          {
            "name": "createTeacherClassAction",
            "permission": "CLASS_CREATE",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "purpose": "教师创建班级"
          },
          {
            "name": "updateTeacherClassAction",
            "permission": "CLASS_UPDATE",
            "signature": "(classId, prevState, formData) => Promise<ActionState>",
            "purpose": "教师更新班级"
          },
          {
            "name": "deleteTeacherClassAction",
            "permission": "CLASS_DELETE",
            "signature": "(classId) => Promise<ActionState>",
            "purpose": "教师删除班级"
          },
          {
            "name": "createGradeClassAction",
            "permission": "CLASS_CREATE",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "purpose": "年级主任创建班级"
          },
          {
            "name": "updateGradeClassAction",
            "permission": "CLASS_UPDATE",
            "signature": "(classId, prevState, formData) => Promise<ActionState>",
            "purpose": "年级主任更新班级"
          },
          {
            "name": "deleteGradeClassAction",
            "permission": "CLASS_DELETE",
            "signature": "(classId) => Promise<ActionState>",
            "purpose": "年级主任删除班级"
          },
          {
            "name": "enrollStudentByEmailAction",
            "permission": "CLASS_ENROLL",
            "signature": "(classId, prevState, formData) => Promise<ActionState>",
            "purpose": "通过邮箱注册学生"
          },
          {
            "name": "joinClassByInvitationCodeAction",
            "permission": "CLASS_ENROLL",
            "signature": "(prevState, formData) => Promise<ActionState<{classId:string}>>",
            "purpose": "通过邀请码加入"
          },
          {
            "name": "ensureClassInvitationCodeAction",
            "permission": "CLASS_ENROLL",
            "signature": "(classId) => Promise<ActionState<{code:string}>>",
            "purpose": "确保邀请码存在"
          },
          {
            "name": "regenerateClassInvitationCodeAction",
            "permission": "CLASS_ENROLL",
            "signature": "(classId) => Promise<ActionState<{code:string}>>",
            "purpose": "重新生成邀请码"
          },
          {
            "name": "setStudentEnrollmentStatusAction",
            "permission": "CLASS_ENROLL",
            "signature": "(classId, studentId, status) => Promise<ActionState>",
            "purpose": "设置学生状态"
          },
          {
            "name": "createClassScheduleItemAction",
            "permission": "CLASS_SCHEDULE",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "purpose": "创建课表项"
          },
          {
            "name": "updateClassScheduleItemAction",
            "permission": "CLASS_SCHEDULE",
            "signature": "(scheduleId, prevState, formData) => Promise<ActionState>",
            "purpose": "更新课表项"
          },
          {
            "name": "deleteClassScheduleItemAction",
            "permission": "CLASS_SCHEDULE",
            "signature": "(scheduleId) => Promise<ActionState>",
            "purpose": "删除课表项"
          },
          {
            "name": "createAdminClassAction",
            "permission": "CLASS_CREATE",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "purpose": "管理员创建班级"
          },
          {
            "name": "updateAdminClassAction",
            "permission": "CLASS_UPDATE",
            "signature": "(classId, prevState, formData) => Promise<ActionState>",
            "purpose": "管理员更新班级"
          },
          {
            "name": "deleteAdminClassAction",
            "permission": "CLASS_DELETE",
            "signature": "(classId) => Promise<ActionState>",
            "purpose": "管理员删除班级"
          }
        ],
        "dataAccess": [
          {
            "name": "getTeacherClasses",
            "signature": "(params?: { teacherId?: string }) => Promise<TeacherClass[]>",
            "usedBy": [
              "teacher/classes/my",
              "dashboard"
            ]
          },
          {
            "name": "getAdminClasses",
            "signature": "() => Promise<AdminClassListItem[]>",
            "usedBy": [
              "admin班级管理"
            ]
          },
          {
            "name": "getGradeManagedClasses",
            "signature": "(userId) => Promise<AdminClassListItem[]>",
            "usedBy": [
              "grade_head班级管理"
            ]
          },
          {
            "name": "getStudentClasses",
            "signature": "(studentId) => Promise<StudentEnrolledClass[]>",
            "usedBy": [
              "student/dashboard"
            ]
          },
          {
            "name": "getStudentSchedule",
            "signature": "(studentId) => Promise<StudentScheduleItem[]>",
            "usedBy": [
              "student课表"
            ]
          },
          {
            "name": "getClassStudents",
            "signature": "(params?: { classId?, q?, status?, teacherId? }) => Promise<ClassStudent[]>",
            "usedBy": [
              "teacher/classes/students"
            ]
          },
          {
            "name": "getClassSchedule",
            "signature": "(params?: { classId?, teacherId? }) => Promise<ClassScheduleItem[]>",
            "usedBy": [
              "teacher/classes/schedule"
            ]
          },
          {
            "name": "getClassHomeworkInsights",
            "signature": "(params: { classId, teacherId?, limit? }) => Promise<ClassHomeworkInsights | null>",
            "usedBy": [
              "classes作业洞察"
            ]
          },
          {
            "name": "getGradeHomeworkInsights",
            "signature": "(params: { gradeId, limit? }) => Promise<GradeHomeworkInsights | null>",
            "usedBy": [
              "年级作业洞察"
            ]
          },
          {
            "name": "getTeacherIdForMutations",
            "signature": "() => Promise<string>",
            "purpose": "获取当前教师ID(用于写操作)",
            "usedBy": [
              "classes写操作内部"
            ]
          },
          {
            "name": "getClassSubjects",
            "signature": "(classId: string) => Promise<ClassSubject[]>",
            "purpose": "获取班级学科列表",
            "usedBy": [
              "class-detail组件"
            ]
          },
          {
            "name": "getTeacherOptions",
            "signature": "() => Promise<TeacherOption[]>",
            "purpose": "获取教师选项列表",
            "usedBy": [
              "class-detail组件"
            ]
          },
          {
            "name": "getTeacherTeachingSubjects",
            "signature": "(teacherId: string) => Promise<...>",
            "purpose": "获取教师所教学科",
            "usedBy": [
              "classes内部"
            ]
          },
          {
            "name": "getManagedGrades",
            "signature": "(userId: string) => Promise<...>",
            "purpose": "获取所管年级",
            "usedBy": [
              "grade_head视图"
            ]
          },
          {
            "name": "getStudentsSubjectScores",
            "signature": "(...) => Promise<...>",
            "purpose": "获取学生学科成绩",
            "usedBy": [
              "classes内部"
            ]
          },
          {
            "name": "getClassStudentSubjectScoresV2",
            "signature": "(...) => Promise<...>",
            "purpose": "获取班级学生学科成绩V2",
            "usedBy": [
              "classes内部"
            ]
          },
          {
            "name": "getClassesDashboardStats",
            "signature": "() => Promise<ClassesDashboardStats>",
            "purpose": "获取班级仪表盘统计数据(班级总数)",
            "usedBy": [
              "dashboard/data-access.getAdminDashboardData"
            ]
          },
          {
            "name": "createTeacherClass",
            "signature": "(input) => Promise<string>",
            "purpose": "教师创建班级",
            "usedBy": [
              "createTeacherClassAction"
            ]
          },
          {
            "name": "createAdminClass",
            "signature": "(input) => Promise<string>",
            "purpose": "管理员创建班级",
            "usedBy": [
              "createAdminClassAction"
            ]
          },
          {
            "name": "ensureClassInvitationCode",
            "signature": "(classId: string) => Promise<{ code: string }>",
            "purpose": "确保邀请码存在",
            "usedBy": [
              "ensureClassInvitationCodeAction"
            ]
          },
          {
            "name": "regenerateClassInvitationCode",
            "signature": "(classId: string) => Promise<{ code: string }>",
            "purpose": "重新生成邀请码",
            "usedBy": [
              "regenerateClassInvitationCodeAction"
            ]
          },
          {
            "name": "enrollStudentByInvitationCode",
            "signature": "(code: string, studentId: string) => Promise<{ classId: string }>",
            "purpose": "通过邀请码注册学生",
            "usedBy": [
              "joinClassByInvitationCodeAction"
            ]
          },
          {
            "name": "enrollTeacherByInvitationCode",
            "signature": "(code: string, teacherId: string) => Promise<...>",
            "purpose": "通过邀请码注册教师",
            "usedBy": [
              "classes内部"
            ]
          },
          {
            "name": "updateTeacherClass",
            "signature": "(classId: string, input) => Promise<void>",
            "purpose": "教师更新班级",
            "usedBy": [
              "updateTeacherClassAction"
            ]
          },
          {
            "name": "updateAdminClass",
            "signature": "(classId: string, input) => Promise<void>",
            "purpose": "管理员更新班级",
            "usedBy": [
              "updateAdminClassAction"
            ]
          },
          {
            "name": "setClassSubjectTeachers",
            "signature": "(classId: string, assignments: ClassSubjectTeacherAssignment[]) => Promise<void>",
            "purpose": "设置班级学科教师",
            "usedBy": [
              "classes内部"
            ]
          },
          {
            "name": "deleteTeacherClass",
            "signature": "(classId: string) => Promise<void>",
            "purpose": "教师删除班级",
            "usedBy": [
              "deleteTeacherClassAction"
            ]
          },
          {
            "name": "deleteAdminClass",
            "signature": "(classId: string) => Promise<void>",
            "purpose": "管理员删除班级",
            "usedBy": [
              "deleteAdminClassAction"
            ]
          },
          {
            "name": "enrollStudentByEmail",
            "signature": "(classId: string, email: string) => Promise<void>",
            "purpose": "通过邮箱注册学生",
            "usedBy": [
              "enrollStudentByEmailAction"
            ]
          },
          {
            "name": "setStudentEnrollmentStatus",
            "signature": "(classId: string, studentId: string, status: string) => Promise<void>",
            "purpose": "设置学生状态",
            "usedBy": [
              "setStudentEnrollmentStatusAction"
            ]
          },
          {
            "name": "verifyTeacherOwnsClass",
            "signature": "(classId: string, teacherId: string) => Promise<boolean>",
            "purpose": "校验教师是否拥有班级（P0-5 新增，供 scheduling 模块跨模块校验 classSchedule 写权限）",
            "usedBy": [
              "scheduling/data-access-class-schedule.createClassScheduleItem",
              "scheduling/data-access-class-schedule.updateClassScheduleItem",
              "scheduling/data-access-class-schedule.deleteClassScheduleItem"
            ]
          },
          {
            "name": "getStudentIdsByClassId",
            "signature": "(classId: string) => Promise<string[]>",
            "purpose": "获取班级所有学生 ID（跨模块接口）",
            "usedBy": [
              "notifications/actions.sendClassNotificationAction"
            ]
          },
          {
            "name": "getStudentIdsByClassIds",
            "signature": "(classIds: string[]) => Promise<string[]>",
            "purpose": "获取多个班级的所有学生 ID（跨模块接口）",
            "usedBy": [
              "homework/data-access",
              "homework/stats-service"
            ]
          },
          {
            "name": "getActiveStudentIdsByClassId",
            "signature": "(classId: string) => Promise<string[]>",
            "purpose": "获取班级所有活跃学生 ID（跨模块接口）",
            "usedBy": [
              "homework/data-access",
              "homework/stats-service",
              "grades/data-access",
              "diagnostic/data-access"
            ]
          },
          {
            "name": "getTeacherSubjectIdsByClass",
            "signature": "(classId: string, teacherId: string) => Promise<string[]>",
            "purpose": "获取教师在班级所教科目标识（跨模块接口）",
            "usedBy": [
              "homework/data-access-write"
            ]
          },
          {
            "name": "getStudentActiveClassId",
            "signature": "(studentId: string) => Promise<string | null>",
            "purpose": "获取学生当前活跃班级 ID（跨模块接口）",
            "usedBy": [
              "homework/stats-service",
              "grades/data-access-ranking",
              "parent/data-access"
            ]
          },
          {
            "name": "getStudentActiveGradeId",
            "signature": "(studentId: string) => Promise<string | null>",
            "purpose": "获取学生当前活跃班级对应的年级 ID（跨模块接口）",
            "usedBy": [
              "elective/data-access-selections.getStudentGradeId"
            ]
          },
          {
            "name": "getClassExists",
            "signature": "(classId: string) => Promise<boolean>",
            "purpose": "校验班级是否存在（跨模块接口）",
            "usedBy": [
              "notifications/actions.sendClassNotificationAction",
              "grades/data-access",
              "diagnostic/data-access"
            ]
          },
          {
            "name": "getClassNameById",
            "signature": "(classId: string) => Promise<string | null>",
            "purpose": "获取班级名称（跨模块接口）",
            "usedBy": [
              "grades/data-access",
              "grades/data-access-analytics",
              "grades/export",
              "parent/data-access",
              "diagnostic/data-access"
            ]
          },
          {
            "name": "getClassGradeId",
            "signature": "(classId: string) => Promise<string | null>",
            "purpose": "获取班级关联的年级 ID（跨模块接口）",
            "usedBy": [
              "classes/actions.updateGradeClassAction",
              "classes/actions.deleteGradeClassAction"
            ]
          },
          {
            "name": "getGradeIdsByClassIds",
            "signature": "(classIds: string[]) => Promise<string[]>",
            "purpose": "获取多个班级关联的年级 ID 列表（跨模块接口）",
            "usedBy": [
              "homework/stats-service"
            ]
          },
          {
            "name": "getClassNamesByIds",
            "signature": "(classIds: string[]) => Promise<Map<string, string>>",
            "purpose": "批量获取班级名称（跨模块接口）",
            "usedBy": [
              "grades/data-access"
            ]
          },
          {
            "name": "getClassesByGradeId",
            "signature": "(gradeId: string) => Promise<Array<{ id: string; name: string }>>",
            "purpose": "获取指定年级下的所有班级（跨模块接口）",
            "usedBy": [
              "grades/data-access-analytics"
            ]
          },
          {
            "name": "getTeacherIdsByClassIds",
            "signature": "(classIds: string[]) => Promise<string[]>",
            "purpose": "获取多个班级的所有教师 ID（班主任 + 任课教师，跨模块接口）",
            "usedBy": [
              "messaging/data-access.getRecipients"
            ]
          }
        ],
        "schema": [
          {
            "name": "CreateTeacherClassSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 教师创建班级（name, grade 必填，schoolName/schoolId/gradeId/homeroom/room 可选）",
            "usedBy": [
              "actions.createTeacherClassAction"
            ]
          },
          {
            "name": "UpdateTeacherClassSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 教师更新班级（classId 必填，其余可选）",
            "usedBy": [
              "actions.updateTeacherClassAction"
            ]
          },
          {
            "name": "DeleteTeacherClassSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 教师删除班级（classId）",
            "usedBy": [
              "actions.deleteTeacherClassAction"
            ]
          },
          {
            "name": "CreateAdminClassSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 管理员创建班级（name, grade, teacherId 必填，其余可选）",
            "usedBy": [
              "actions.createAdminClassAction"
            ]
          },
          {
            "name": "UpdateAdminClassSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 管理员更新班级（classId 必填，其余可选）",
            "usedBy": [
              "actions.updateAdminClassAction"
            ]
          },
          {
            "name": "DeleteAdminClassSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 管理员删除班级（classId）",
            "usedBy": [
              "actions.deleteAdminClassAction"
            ]
          },
          {
            "name": "CreateGradeClassSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 年级主任创建班级（name, gradeId, teacherId 必填，其余可选）",
            "usedBy": [
              "actions.createGradeClassAction"
            ]
          },
          {
            "name": "UpdateGradeClassSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 年级主任更新班级（classId 必填，其余可选）",
            "usedBy": [
              "actions.updateGradeClassAction"
            ]
          },
          {
            "name": "DeleteGradeClassSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 年级主任删除班级（classId）",
            "usedBy": [
              "actions.deleteGradeClassAction"
            ]
          },
          {
            "name": "CreateClassScheduleItemSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 创建课表项（classId, weekday 1-7, course, startTime, endTime 必填，location 可选）",
            "usedBy": [
              "actions.createClassScheduleItemAction"
            ]
          },
          {
            "name": "UpdateClassScheduleItemSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 更新课表项（scheduleId 必填，其余可选）",
            "usedBy": [
              "actions.updateClassScheduleItemAction"
            ]
          },
          {
            "name": "DeleteClassScheduleItemSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 删除课表项（scheduleId）",
            "usedBy": [
              "actions.deleteClassScheduleItemAction"
            ]
          },
          {
            "name": "EnrollStudentByEmailSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 通过邮箱注册学生（classId, email）",
            "usedBy": [
              "actions.enrollStudentByEmailAction"
            ]
          }
        ],
        "types": [
          {
            "name": "TeacherClass",
            "type": "type",
            "definition": "教师班级对象",
            "usedBy": [
              "getTeacherClasses",
              "dashboard"
            ]
          },
          {
            "name": "AssignmentSummary",
            "type": "type",
            "definition": "作业摘要",
            "usedBy": [
              "classes/components"
            ]
          },
          {
            "name": "TeacherOption",
            "type": "type",
            "definition": "教师选项",
            "usedBy": [
              "getTeacherOptions",
              "class-detail组件"
            ]
          },
          {
            "name": "DEFAULT_CLASS_SUBJECTS",
            "type": "const",
            "definition": "默认班级学科列表",
            "usedBy": [
              "classes内部"
            ]
          },
          {
            "name": "ClassSubject",
            "type": "type",
            "definition": "班级学科",
            "usedBy": [
              "getClassSubjects",
              "classes/components"
            ]
          },
          {
            "name": "ClassSubjectTeacherAssignment",
            "type": "type",
            "definition": "班级学科教师分配",
            "usedBy": [
              "setClassSubjectTeachers"
            ]
          },
          {
            "name": "AdminClassListItem",
            "type": "type",
            "definition": "管理员班级列表项",
            "usedBy": [
              "getAdminClasses",
              "getGradeManagedClasses"
            ]
          },
          {
            "name": "CreateTeacherClassInput",
            "type": "type",
            "definition": "教师创建班级输入",
            "usedBy": [
              "createTeacherClass"
            ]
          },
          {
            "name": "UpdateTeacherClassInput",
            "type": "type",
            "definition": "教师更新班级输入",
            "usedBy": [
              "updateTeacherClass"
            ]
          },
          {
            "name": "ClassStudent",
            "type": "type",
            "definition": "班级学生",
            "usedBy": [
              "getClassStudents",
              "classes/components"
            ]
          },
          {
            "name": "ClassScheduleItem",
            "type": "type",
            "definition": "班级课表项",
            "usedBy": [
              "getClassSchedule",
              "classes/components"
            ]
          },
          {
            "name": "StudentEnrolledClass",
            "type": "type",
            "definition": "学生已注册班级",
            "usedBy": [
              "getStudentClasses",
              "dashboard"
            ]
          },
          {
            "name": "StudentScheduleItem",
            "type": "type",
            "definition": "学生课表项",
            "usedBy": [
              "getStudentSchedule",
              "dashboard"
            ]
          },
          {
            "name": "ScoreStats",
            "type": "type",
            "definition": "成绩统计",
            "usedBy": [
              "classes/components"
            ]
          },
          {
            "name": "ClassHomeworkAssignmentStats",
            "type": "type",
            "definition": "班级作业统计",
            "usedBy": [
              "classes/components"
            ]
          },
          {
            "name": "ClassHomeworkInsights",
            "type": "type",
            "definition": "班级作业洞察",
            "usedBy": [
              "getClassHomeworkInsights",
              "classes/components"
            ]
          },
          {
            "name": "GradeHomeworkClassSummary",
            "type": "type",
            "definition": "年级作业班级汇总",
            "usedBy": [
              "classes/components"
            ]
          },
          {
            "name": "GradeHomeworkInsights",
            "type": "type",
            "definition": "年级作业洞察",
            "usedBy": [
              "getGradeHomeworkInsights",
              "classes/components"
            ]
          }
        ],
        "components": [
          {
            "name": "StudentsTable",
            "purpose": "学生表格"
          },
          {
            "name": "StudentsFilters",
            "purpose": "学生筛选器"
          },
          {
            "name": "ScheduleView",
            "purpose": "课表视图"
          },
          {
            "name": "MyClassesGrid",
            "purpose": "我的班级网格"
          },
          {
            "name": "AdminClassesClient",
            "purpose": "管理员班级客户端"
          },
          {
            "name": "ScheduleFilters",
            "purpose": "课表筛选器"
          },
          {
            "name": "GradeClassesClient",
            "purpose": "年级班级客户端"
          },
          {
            "name": "transformAssignmentsToChartData",
            "file": "class-detail/transformAssignmentsToChartData",
            "type": "function",
            "purpose": "转换作业为图表数据"
          },
          {
            "name": "ClassSubmissionTrendChart",
            "file": "class-detail/ClassSubmissionTrendChart",
            "purpose": "班级提交趋势图表"
          },
          {
            "name": "ClassTrendsWidget",
            "file": "class-detail/ClassTrendsWidget",
            "purpose": "班级趋势组件"
          },
          {
            "name": "ClassStudentsWidget",
            "file": "class-detail/ClassStudentsWidget",
            "purpose": "班级学生组件"
          },
          {
            "name": "ClassScheduleGrid",
            "file": "class-detail/ClassScheduleGrid",
            "purpose": "班级课表网格"
          },
          {
            "name": "ClassScheduleWidget",
            "file": "class-detail/ClassScheduleWidget",
            "purpose": "班级课表组件"
          },
          {
            "name": "ClassQuickActions",
            "file": "class-detail/ClassQuickActions",
            "purpose": "班级快捷操作"
          },
          {
            "name": "EditClassDialog",
            "file": "class-detail/EditClassDialog",
            "purpose": "编辑班级对话框"
          },
          {
            "name": "ClassOverviewStats",
            "file": "class-detail/ClassOverviewStats",
            "purpose": "班级概览统计"
          },
          {
            "name": "ClassHeader",
            "file": "class-detail/ClassHeader",
            "purpose": "班级头部"
          },
          {
            "name": "ClassAssignmentsWidget",
            "file": "class-detail/ClassAssignmentsWidget",
            "purpose": "班级作业组件"
          }
        ]
      },
      "files": [
        {
          "path": "data-access.ts",
          "lines": 548,
          "description": "核心班级 CRUD + 邀请码 + 教师班级管理（含 re-export 向后兼容）",
          "exports": [
            "getTeacherClasses",
            "getTeacherOptions",
            "getTeacherTeachingSubjects",
            "createTeacherClass",
            "ensureClassInvitationCode",
            "regenerateClassInvitationCode",
            "enrollStudentByInvitationCode",
            "enrollTeacherByInvitationCode",
            "updateTeacherClass",
            "setClassSubjectTeachers",
            "deleteTeacherClass",
            "enrollStudentByEmail",
            "setStudentEnrollmentStatus",
            "getSessionTeacherId",
            "getTeacherIdForMutations",
            "getAccessibleClassIdsForTeacher",
            "getClassGradeIdsByClassIds",
            "getTeacherSubjectIdsForClass",
            "getClassSubjects",
            "compareClassLike",
            "isDuplicateInvitationCodeError",
            "generateUniqueInvitationCode"
          ]
        },
        {
          "path": "data-access-stats.ts",
          "lines": 513,
          "description": "作业统计查询（班级/年级作业洞察，通过 homework/data-access-classes 获取数据）",
          "exports": [
            "getClassHomeworkInsights",
            "getGradeHomeworkInsights",
            "getClassesDashboardStats"
          ]
        },
        {
          "path": "data-access-schedule.ts",
          "lines": 93,
          "description": "课表查询（学生/班级课表只读，P0-5 已修复：写函数已迁移至 scheduling/data-access-class-schedule.ts）",
          "exports": [
            "getStudentSchedule",
            "getClassSchedule"
          ]
        },
        {
          "path": "data-access-students.ts",
          "lines": 253,
          "description": "学生相关查询（科目成绩、学生名单、学生班级，通过 homework/data-access-classes 获取数据）",
          "exports": [
            "getStudentsSubjectScores",
            "getClassStudentSubjectScoresV2",
            "getStudentClasses",
            "getClassStudents"
          ]
        },
        {
          "path": "data-access-admin.ts",
          "lines": 406,
          "description": "管理员班级管理（管理员班级 CRUD、年级管理班级查询）",
          "exports": [
            "getAdminClasses",
            "getGradeManagedClasses",
            "getManagedGrades",
            "createAdminClass",
            "updateAdminClass",
            "deleteAdminClass"
          ]
        },
        {
          "path": "actions.ts",
          "lines": 50,
          "description": "Barrel re-export（P0-3 修复：从 974 行拆分为 6 个职责文件）",
          "exports": [
            "createTeacherClassAction",
            "updateTeacherClassAction",
            "deleteTeacherClassAction",
            "createAdminClassAction",
            "updateAdminClassAction",
            "deleteAdminClassAction",
            "createGradeClassAction",
            "updateGradeClassAction",
            "deleteGradeClassAction",
            "enrollStudentByEmailAction",
            "joinClassByInvitationCodeAction",
            "ensureClassInvitationCodeAction",
            "regenerateClassInvitationCodeAction",
            "createClassInvitationCodeAction",
            "revokeClassInvitationCodeAction",
            "listClassInvitationCodesAction",
            "setStudentEnrollmentStatusAction",
            "createClassScheduleItemAction",
            "updateClassScheduleItemAction",
            "deleteClassScheduleItemAction"
          ]
        },
        {
          "path": "actions-teacher.ts",
          "lines": 100,
          "description": "教师班级 CRUD（3 个 Action，P0-3 修复）"
        },
        {
          "path": "actions-admin.ts",
          "lines": 120,
          "description": "管理员班级 CRUD（3 个 Action，P0-3 修复）"
        },
        {
          "path": "actions-grade.ts",
          "lines": 110,
          "description": "年级组长班级 CRUD（3 个 Action，P0-3 修复）"
        },
        {
          "path": "actions-invitations.ts",
          "lines": 502,
          "description": "邀请码与注册 + 批量操作（10 个 Action，P0-3 修复；P2-4 新增批量导入学生/批量分配教师）"
        },
        {
          "path": "actions-schedule.ts",
          "lines": 90,
          "description": "班级课表 CRUD（3 个 Action，P0-3 修复）"
        },
        {
          "path": "actions-shared.ts",
          "lines": 60,
          "description": "共享工具（hasAdminScope/hasTeacherScope/hasStudentScope/parseSubjectTeachers/toWeekday，P1-1 修复）"
        }
      ]
    },
    "school": {
      "path": "src/modules/school",
      "description": "学校基础数据管理：学校、年级、部门、学年的CRUD。actions 层仅做编排（权限校验+Zod+revalidatePath+after(logAudit)），所有 DB 操作下沉至 data-access。学校CRUD actions 在写操作成功后通过 Next.js after() 异步调用 logAudit() 记录操作日志",
      "exports": {
        "actions": [
          {
            "name": "createSchoolAction",
            "permission": "SCHOOL_MANAGE",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "purpose": "创建学校",
            "auditLog": "school.create"
          },
          {
            "name": "updateSchoolAction",
            "permission": "SCHOOL_MANAGE",
            "signature": "(schoolId, prevState, formData) => Promise<ActionState<string>>",
            "purpose": "更新学校",
            "auditLog": "school.update"
          },
          {
            "name": "deleteSchoolAction",
            "permission": "SCHOOL_MANAGE",
            "signature": "(schoolId) => Promise<ActionState<string>>",
            "purpose": "删除学校",
            "auditLog": "school.delete"
          },
          {
            "name": "createGradeAction",
            "permission": "GRADE_MANAGE",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "purpose": "创建年级"
          },
          {
            "name": "updateGradeAction",
            "permission": "GRADE_MANAGE",
            "signature": "(gradeId, prevState, formData) => Promise<ActionState<string>>",
            "purpose": "更新年级"
          },
          {
            "name": "deleteGradeAction",
            "permission": "GRADE_MANAGE",
            "signature": "(gradeId) => Promise<ActionState<string>>",
            "purpose": "删除年级"
          },
          {
            "name": "promoteGradesAction",
            "permission": "GRADE_MANAGE",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "purpose": "年级升级（order +1 + 名称升级）",
            "auditLog": "grade.promote"
          },
          {
            "name": "createDepartmentAction",
            "permission": "SCHOOL_MANAGE",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "purpose": "创建部门"
          },
          {
            "name": "updateDepartmentAction",
            "permission": "SCHOOL_MANAGE",
            "signature": "(departmentId, prevState, formData) => Promise<ActionState<string>>",
            "purpose": "更新部门"
          },
          {
            "name": "deleteDepartmentAction",
            "permission": "SCHOOL_MANAGE",
            "signature": "(departmentId) => Promise<ActionState<string>>",
            "purpose": "删除部门"
          },
          {
            "name": "createAcademicYearAction",
            "permission": "SCHOOL_MANAGE",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "purpose": "创建学年"
          },
          {
            "name": "updateAcademicYearAction",
            "permission": "SCHOOL_MANAGE",
            "signature": "(academicYearId, prevState, formData) => Promise<ActionState<string>>",
            "purpose": "更新学年"
          },
          {
            "name": "deleteAcademicYearAction",
            "permission": "SCHOOL_MANAGE",
            "signature": "(academicYearId) => Promise<ActionState<string>>",
            "purpose": "删除学年"
          }
        ],
        "dataAccess": [
          {
            "name": "getSchools",
            "signature": "() => Promise<SchoolListItem[]>",
            "usedBy": [
              "admin学校管理",
              "onboarding"
            ]
          },
          {
            "name": "getGrades",
            "signature": "() => Promise<GradeListItem[]>",
            "usedBy": [
              "admin年级管理",
              "exams",
              "onboarding"
            ]
          },
          {
            "name": "getDepartments",
            "signature": "() => Promise<DepartmentListItem[]>",
            "usedBy": [
              "admin部门管理"
            ]
          },
          {
            "name": "getAcademicYears",
            "signature": "() => Promise<AcademicYearListItem[]>",
            "usedBy": [
              "admin学年管理"
            ]
          },
          {
            "name": "getStaffOptions",
            "signature": "() => Promise<StaffOption[]>",
            "usedBy": [
              "school组件"
            ]
          },
          {
            "name": "getGradesForStaff",
            "signature": "(staffId) => Promise<GradeListItem[]>",
            "usedBy": [
              "grade_head视图"
            ]
          },
          {
            "name": "getSchoolsForUser",
            "signature": "(userId) => Promise<SchoolListItem[]>",
            "usedBy": [
              "非admin角色学校视图"
            ]
          },
          {
            "name": "getGradesForUser",
            "signature": "(userId) => Promise<GradeListItem[]>",
            "usedBy": [
              "非admin角色年级视图"
            ]
          },
          {
            "name": "createDepartment",
            "signature": "(data: DepartmentInsertData) => Promise<void>",
            "usedBy": [
              "createDepartmentAction"
            ]
          },
          {
            "name": "updateDepartment",
            "signature": "(id: string, data: DepartmentUpdateData) => Promise<void>",
            "usedBy": [
              "updateDepartmentAction"
            ]
          },
          {
            "name": "deleteDepartment",
            "signature": "(id: string) => Promise<void>",
            "usedBy": [
              "deleteDepartmentAction"
            ]
          },
          {
            "name": "createSchool",
            "signature": "(data: SchoolInsertData) => Promise<void>",
            "usedBy": [
              "createSchoolAction"
            ]
          },
          {
            "name": "updateSchool",
            "signature": "(id: string, data: SchoolUpdateData) => Promise<void>",
            "usedBy": [
              "updateSchoolAction"
            ]
          },
          {
            "name": "deleteSchool",
            "signature": "(id: string) => Promise<void>",
            "usedBy": [
              "deleteSchoolAction"
            ]
          },
          {
            "name": "createGrade",
            "signature": "(data: GradeInsertData) => Promise<void>",
            "usedBy": [
              "createGradeAction"
            ]
          },
          {
            "name": "updateGrade",
            "signature": "(id: string, data: GradeUpdateData) => Promise<void>",
            "usedBy": [
              "updateGradeAction"
            ]
          },
          {
            "name": "deleteGrade",
            "signature": "(id: string) => Promise<void>",
            "usedBy": [
              "deleteGradeAction"
            ]
          },
          {
            "name": "createAcademicYear",
            "signature": "(data: AcademicYearInsertData) => Promise<void>",
            "usedBy": [
              "createAcademicYearAction"
            ]
          },
          {
            "name": "updateAcademicYear",
            "signature": "(id: string, data: AcademicYearUpdateData) => Promise<void>",
            "usedBy": [
              "updateAcademicYearAction"
            ]
          },
          {
            "name": "deleteAcademicYear",
            "signature": "(id: string) => Promise<void>",
            "usedBy": [
              "deleteAcademicYearAction"
            ]
          },
          {
            "name": "getSubjectOptions",
            "signature": "() => Promise<SubjectOption[]>",
            "purpose": "获取科目选项列表（跨模块接口）",
            "usedBy": [
              "homework/data-access",
              "grades/data-access",
              "grades/data-access-analytics",
              "grades/export",
              "elective/data-access-selections"
            ]
          },
          {
            "name": "getGradeOptions",
            "signature": "() => Promise<GradeOption[]>",
            "purpose": "获取年级选项列表（跨模块接口）",
            "usedBy": [
              "grades/data-access",
              "grades/export",
              "parent/data-access",
              "elective/data-access-selections"
            ]
          },
          {
            "name": "isGradeHead",
            "signature": "(gradeId: string, userId: string) => Promise<boolean>",
            "purpose": "校验用户是否为指定年级的年级主任（跨模块接口）",
            "usedBy": [
              "classes/actions.createTeacherClassAction"
            ]
          },
          {
            "name": "isGradeManager",
            "signature": "(gradeId: string, userId: string) => Promise<boolean>",
            "purpose": "校验用户是否为指定年级的年级主任或教学主任（跨模块接口）",
            "usedBy": [
              "classes/actions.createGradeClassAction",
              "classes/actions.updateGradeClassAction",
              "classes/actions.deleteGradeClassAction"
            ]
          },
          {
            "name": "findGradeIdByHeadAndName",
            "signature": "(userId: string, gradeName: string) => Promise<string | null>",
            "purpose": "根据年级名称查找用户担任年级主任的年级 ID（跨模块接口）",
            "usedBy": [
              "classes/actions.createTeacherClassAction"
            ]
          },
          {
            "name": "getSubjectNameMapByIds",
            "signature": "(subjectIds: string[]) => Promise<Map<string, string | null>>",
            "purpose": "按 ID 批量获取科目名称映射（跨模块接口，P1-1 新增供 homework/data-access-classes 调用，替代直查 subjects 表）",
            "usedBy": [
              "homework/data-access-classes.getHomeworkAssignmentsWithSubject",
              "homework/data-access-classes.getPublishedHomeworkAssignmentsWithSubject"
            ]
          }
        ],
        "schema": [
          {
            "name": "UpsertDepartmentSchema",
            "type": "const",
            "description": "zod schema 部门upsert",
            "usedBy": [
              "createDepartmentAction",
              "updateDepartmentAction"
            ]
          },
          {
            "name": "UpsertAcademicYearSchema",
            "type": "const",
            "description": "zod schema 学年upsert",
            "usedBy": [
              "createAcademicYearAction",
              "updateAcademicYearAction"
            ]
          },
          {
            "name": "UpsertSchoolSchema",
            "type": "const",
            "description": "zod schema 学校upsert",
            "usedBy": [
              "createSchoolAction",
              "updateSchoolAction"
            ]
          },
          {
            "name": "UpsertGradeSchema",
            "type": "const",
            "description": "zod schema 年级upsert",
            "usedBy": [
              "createGradeAction",
              "updateGradeAction"
            ]
          }
        ],
        "types": [
          {
            "name": "DepartmentListItem",
            "type": "type",
            "definition": "部门列表项",
            "usedBy": [
              "getDepartments",
              "school/components"
            ]
          },
          {
            "name": "AcademicYearListItem",
            "type": "type",
            "definition": "学年列表项",
            "usedBy": [
              "getAcademicYears",
              "school/components"
            ]
          },
          {
            "name": "SchoolListItem",
            "type": "type",
            "definition": "学校列表项",
            "usedBy": [
              "getSchools",
              "school/components"
            ]
          },
          {
            "name": "StaffOption",
            "type": "type",
            "definition": "员工选项",
            "usedBy": [
              "getStaffOptions",
              "school/components"
            ]
          },
          {
            "name": "GradeListItem",
            "type": "type",
            "definition": "年级列表项",
            "usedBy": [
              "getGrades",
              "school/components",
              "exams"
            ]
          },
          {
            "name": "DepartmentInsertData",
            "type": "type",
            "definition": "部门创建入参",
            "usedBy": [
              "createDepartment"
            ]
          },
          {
            "name": "DepartmentUpdateData",
            "type": "type",
            "definition": "部门更新入参",
            "usedBy": [
              "updateDepartment"
            ]
          },
          {
            "name": "SchoolInsertData",
            "type": "type",
            "definition": "学校创建入参",
            "usedBy": [
              "createSchool"
            ]
          },
          {
            "name": "SchoolUpdateData",
            "type": "type",
            "definition": "学校更新入参",
            "usedBy": [
              "updateSchool"
            ]
          },
          {
            "name": "GradeInsertData",
            "type": "type",
            "definition": "年级创建入参",
            "usedBy": [
              "createGrade"
            ]
          },
          {
            "name": "GradeUpdateData",
            "type": "type",
            "definition": "年级更新入参",
            "usedBy": [
              "updateGrade"
            ]
          },
          {
            "name": "AcademicYearInsertData",
            "type": "type",
            "definition": "学年创建入参",
            "usedBy": [
              "createAcademicYear"
            ]
          },
          {
            "name": "AcademicYearUpdateData",
            "type": "type",
            "definition": "学年更新入参",
            "usedBy": [
              "updateAcademicYear"
            ]
          },
          {
            "name": "OrgTreeNode",
            "type": "type",
            "definition": "组织架构树节点（学校/年级/班级三级，P2-2 修复）",
            "usedBy": [
              "getOrgTree",
              "school/components/org-tree-nav.tsx"
            ]
          }
        ],
        "components": [
          {
            "name": "SchoolsClient",
            "purpose": "学校管理客户端（组合容器，P1-5/P2-1 重构后仅负责组合子组件与 Table 渲染）"
          },
          {
            "name": "SchoolFormDialog",
            "file": "components/school-form-dialog.tsx",
            "purpose": "学校创建/编辑表单对话框（内部管理 createMutation/updateMutation）",
            "props": "open, onOpenChange, editItem?, onSuccess"
          },
          {
            "name": "SchoolDeleteDialog",
            "file": "components/school-delete-dialog.tsx",
            "purpose": "学校删除确认对话框（内部管理 deleteMutation）",
            "props": "deleteItem, onOpenChange, onSuccess"
          },
          {
            "name": "SchoolListToolbar",
            "file": "components/school-list-toolbar.tsx",
            "purpose": "学校列表工具栏（数量 Badge + 新建按钮）",
            "props": "count, onCreate, isWorking"
          },
          {
            "name": "GradesClient",
            "purpose": "年级管理客户端"
          },
          {
            "name": "DepartmentsClient",
            "purpose": "部门管理客户端"
          },
          {
            "name": "AcademicYearClient",
            "purpose": "学年管理客户端"
          },
          {
            "name": "SchoolErrorBoundary",
            "file": "components/school-error-boundary.tsx",
            "purpose": "school 模块共享 Error Boundary（class component + i18n + router.refresh 重试），4 个页面均包裹",
            "props": "children, fallback?"
          },
          {
            "name": "SchoolListSkeleton",
            "file": "components/school-skeleton.tsx",
            "purpose": "表格加载骨架屏（animate-pulse）",
            "props": "rows?"
          },
          {
            "name": "SchoolCardSkeleton",
            "file": "components/school-skeleton.tsx",
            "purpose": "卡片加载骨架屏（animate-pulse）",
            "props": ""
          },
          {
            "name": "OrgTreeNav",
            "file": "components/org-tree-nav.tsx",
            "purpose": "学校→年级→班级三级树形导航（P2-2 修复）：搜索过滤 + 选中高亮 + 展开/折叠 + 不同节点类型图标（School/GraduationCap/Users）+ 默认展开第一级",
            "props": "nodes: OrgTreeNode[], onSelect?: (node: OrgTreeNode) => void, selectedId?: string"
          }
        ],
        "hooks": [
          {
            "name": "useSchoolData",
            "file": "hooks/use-school-data.ts",
            "signature": "useSchoolData(): { createOpen, editItem, deleteItem, setCreateOpen, setEditItem, setDeleteItem, isWorking }",
            "purpose": "学校管理客户端状态 Hook，集中管理创建/编辑/删除对话框开关状态与当前操作项，isWorking 表示任意对话框处于打开状态",
            "usedBy": [
              "school/components/schools-view.tsx"
            ]
          }
        ]
      }
    },
    "dashboard": {
      "path": "src/modules/dashboard",
      "description": "各角色仪表盘数据聚合与展示（含权限校验 + i18n + 纯逻辑工具函数）",
      "exports": {
        "actions": [
          {
            "name": "getAdminDashboardAction",
            "signature": "() => Promise<AdminDashboardData>",
            "deps": [
              "shared/lib/auth-guard.requirePermission",
              "dashboard/data-access.getAdminDashboardData",
              "Permissions.DASHBOARD_ADMIN_READ"
            ],
            "usedBy": [
              "admin/dashboard/page.tsx"
            ]
          },
          {
            "name": "getTeacherDashboardAction",
            "signature": "() => Promise<TeacherDashboardData & { metrics: TeacherDashboardMetrics }>",
            "deps": [
              "shared/lib/auth-guard.requirePermission",
              "classes/data-access.getTeacherClasses/getClassSchedule/getTeacherIdForMutations",
              "homework/data-access.getHomeworkAssignments/getHomeworkSubmissions/getTeacherGradeTrends",
              "users/data-access.getUserBasicInfo",
              "dashboard/lib/dashboard-utils.computeTeacherMetrics",
              "Permissions.DASHBOARD_TEACHER_READ"
            ],
            "usedBy": [
              "teacher/dashboard/page.tsx"
            ]
          },
          {
            "name": "getStudentDashboardAction",
            "signature": "() => Promise<{ student, dashboardProps }>",
            "deps": [
              "shared/lib/auth-guard.requirePermission",
              "users/data-access.getCurrentStudentUser",
              "classes/data-access.getStudentClasses/getStudentSchedule",
              "homework/data-access.getStudentHomeworkAssignments/getStudentDashboardGrades",
              "dashboard/lib/dashboard-utils.countStudentAssignments/sortUpcomingAssignments/toWeekday/filterTodaySchedule",
              "Permissions.DASHBOARD_STUDENT_READ"
            ],
            "usedBy": [
              "student/dashboard/page.tsx"
            ]
          },
          {
            "name": "getParentDashboardAction",
            "signature": "() => Promise<{ data, hasChildren }>",
            "deps": [
              "shared/lib/auth-guard.requirePermission",
              "parent/data-access.getParentDashboardData",
              "Permissions.DASHBOARD_PARENT_READ"
            ],
            "usedBy": [
              "parent/dashboard/page.tsx"
            ]
          }
        ],
        "lib": [
          {
            "name": "toWeekday",
            "signature": "(d: Date) => Weekday",
            "purpose": "Date 转 1-7 周几（周一=1，周日=7）"
          },
          {
            "name": "countStudentAssignments",
            "signature": "(assignments, now, dueSoonWindowDays?) => StudentAssignmentStats",
            "purpose": "单次遍历统计学生作业：即将到期/已逾期/已批改"
          },
          {
            "name": "sortUpcomingAssignments",
            "signature": "(assignments, limit?) => StudentHomeworkAssignmentListItem[]",
            "purpose": "按截止日期升序排序取前 N 条"
          },
          {
            "name": "filterTodaySchedule",
            "signature": "<T extends StudentTodayScheduleItem | TeacherTodayScheduleItem>(schedule, weekday, classNameById?) => T[]",
            "purpose": "筛选指定周几课表并按开始时间排序（V3 改为泛型函数，调用方指定返回类型，消除 as 断言）"
          },
          {
            "name": "computeTeacherMetrics",
            "signature": "(classes, schedule, assignments, submissions, gradeTrends, now) => TeacherDashboardMetrics",
            "purpose": "计算教师仪表盘派生指标：待批改数/进行中作业/平均分/提交率/今日课表/待批改列表"
          },
          {
            "name": "getGreetingKey",
            "signature": "(now: Date) => 'morning' | 'afternoon' | 'evening'",
            "purpose": "根据当前小时返回问候语时段 key"
          }
        ],
        "components": [
          {
            "name": "DashboardSection",
            "path": "components/dashboard-section.tsx",
            "purpose": "分区 Error Boundary + Suspense + 骨架屏包装器，包裹每个独立数据区块",
            "variants": ["stats", "card", "chart", "table", "list"],
            "usedBy": [
              "admin-dashboard.tsx",
              "teacher-dashboard-view.tsx",
              "student-dashboard-view.tsx"
            ]
          },
          {
            "name": "DashboardSectionErrorBoundary",
            "path": "components/dashboard-section.tsx",
            "purpose": "仪表盘分区级 Error Boundary（class component），单区块崩溃仅替换该区块"
          },
          {
            "name": "DashboardSectionSkeleton",
            "path": "components/dashboard-section.tsx",
            "purpose": "分区骨架屏，5 种变体匹配不同数据区块布局"
          }
        ],
        "dataAccess": [
          {
            "name": "getAdminDashboardData",
            "signature": "(scope?: DataScope) => Promise<AdminDashboardData>",
            "deps": [
              "users/data-access.getUsersDashboardStats",
              "classes/data-access.getClassesDashboardStats",
              "textbooks/data-access.getTextbooksDashboardStats",
              "questions/data-access.getQuestionsDashboardStats",
              "exams/data-access.getExamsDashboardStats",
              "homework/stats-service.getHomeworkDashboardStats",
              "DataScope"
            ],
            "usedBy": [
              "dashboard/actions.getAdminDashboardAction"
            ]
          },
          {
            "name": "getAdminDashboardStreams",
            "signature": "() => Promise<AdminDashboardStreams>",
            "deps": [
              "auth-guard.requirePermission",
              "users/data-access.getUsersDashboardStats",
              "classes/data-access.getClassesDashboardStats",
              "textbooks/data-access.getTextbooksDashboardStats",
              "questions/data-access.getQuestionsDashboardStats",
              "exams/data-access.getExamsDashboardStats",
              "homework/stats-service.getHomeworkDashboardStats"
            ],
            "purpose": "V4（P2-1）新增：管理员仪表盘流式数据源，返回各独立数据源的未解析 Promise，供各分区组件用 React use() 独立消费实现流式渲染",
            "usedBy": [
              "app/(dashboard)/admin/dashboard/page.tsx"
            ]
          }
        ],
        "types": [
          {
            "name": "StudentDashboardProps",
            "definition": "{ studentName, enrolledClassCount, dueSoonCount, overdueCount, gradedCount, todayScheduleItems, upcomingAssignments, grades }",
            "deps": [
              "homework/types.StudentDashboardGradeProps"
            ],
            "usedBy": [
              "student-dashboard-view.tsx"
            ]
          },
          {
            "name": "TeacherDashboardData",
            "definition": "{ classes, schedule, assignments, submissions, teacherName, gradeTrends }",
            "deps": [
              "homework/data-access.getTeacherGradeTrends",
              "classes/data-access.getTeacherClasses"
            ],
            "usedBy": [
              "teacher-dashboard-view.tsx"
            ]
          },
          {
            "name": "AdminDashboardData",
            "definition": "{ activeSessionsCount, userCount, userRoleCounts, classCount, textbookCount, chapterCount, questionCount, examCount, homeworkAssignmentCount, homeworkAssignmentPublishedCount, homeworkSubmissionCount, homeworkSubmissionToGradeCount, recentUsers, userGrowth, homeworkTrend }",
            "usedBy": [
              "admin/dashboard/page.tsx"
            ]
          },
          {
            "name": "AdminDashboardUserRoleCount",
            "type": "type",
            "definition": "管理员仪表盘用户角色计数",
            "usedBy": [
              "admin-dashboard/AdminDashboardView"
            ]
          },
          {
            "name": "AdminDashboardRecentUser",
            "type": "type",
            "definition": "管理员仪表盘最近用户",
            "usedBy": [
              "admin-dashboard/AdminDashboardView"
            ]
          },
          {
            "name": "StudentTodayScheduleItem",
            "type": "type",
            "definition": "学生今日课表项",
            "usedBy": [
              "student-dashboard/StudentTodayScheduleCard"
            ]
          },
          {
            "name": "TeacherTodayScheduleItem",
            "type": "type",
            "definition": "教师今日课表项",
            "usedBy": [
              "teacher-dashboard/TeacherSchedule"
            ]
          }
        ],
        "components": [
          {
            "name": "AdminDashboardView",
            "file": "admin-dashboard/AdminDashboardView",
            "purpose": "管理员仪表盘视图（V1 新增趋势图表区域：用户增长趋势 + 作业提交趋势）"
          },
          {
            "name": "UserGrowthChart",
            "file": "admin-dashboard/user-growth-chart",
            "purpose": "recharts 折线图组件（V1 新增，复用于用户增长趋势与作业提交趋势两个卡片；V3 新增 labelKey prop 区分图表标签 + 空数据时渲染 EmptyState）"
          },
          {
            "name": "StudentDashboard",
            "file": "student-dashboard/StudentDashboard",
            "purpose": "学生仪表盘(注意:非StudentDashboardView)"
          },
          {
            "name": "StudentDashboardHeader",
            "file": "student-dashboard/StudentDashboardHeader",
            "purpose": "学生仪表盘头部（V3 从客户端组件转为服务端组件）"
          },
          {
            "name": "StudentGradesCard",
            "file": "student-dashboard/StudentGradesCard",
            "purpose": "学生成绩卡片（V3 新增 useLocale 传入 formatDate 实现日期本地化）"
          },
          {
            "name": "StudentStatsGrid",
            "file": "student-dashboard/StudentStatsGrid",
            "purpose": "学生统计网格"
          },
          {
            "name": "StudentTodayScheduleCard",
            "file": "student-dashboard/StudentTodayScheduleCard",
            "purpose": "学生今日课表卡片（V3 空状态新增 CTA 跳转 /student/schedule）"
          },
          {
            "name": "StudentUpcomingAssignmentsCard",
            "file": "student-dashboard/StudentUpcomingAssignmentsCard",
            "purpose": "学生即将到来作业卡片（V3 新增 getLocale 日期本地化 + 空状态 CTA 跳转 /student/learning/assignments）"
          },
          {
            "name": "TeacherDashboardView",
            "file": "teacher-dashboard/TeacherDashboardView",
            "purpose": "教师仪表盘视图"
          },
          {
            "name": "TeacherClassesCard",
            "file": "teacher-dashboard/TeacherClassesCard",
            "purpose": "教师班级卡片"
          },
          {
            "name": "TeacherDashboardHeader",
            "file": "teacher-dashboard/TeacherDashboardHeader",
            "purpose": "教师仪表盘头部（V3 从客户端组件转为服务端组件）"
          },
          {
            "name": "TeacherGradeTrends",
            "file": "teacher-dashboard/TeacherGradeTrends",
            "purpose": "教师年级趋势"
          },
          {
            "name": "TeacherHomeworkCard",
            "file": "teacher-dashboard/TeacherHomeworkCard",
            "purpose": "教师作业卡片（V3 新增 getLocale 日期本地化 + aria-label 无障碍标签）"
          },
          {
            "name": "TeacherQuickActions",
            "file": "teacher-dashboard/TeacherQuickActions",
            "purpose": "教师快捷操作（V3 从客户端组件转为异步服务端组件）"
          },
          {
            "name": "TeacherSchedule",
            "file": "teacher-dashboard/TeacherSchedule",
            "purpose": "教师课表（V3 getStatus 函数新增显式返回类型）"
          },
          {
            "name": "TeacherStats",
            "file": "teacher-dashboard/TeacherStats",
            "purpose": "教师统计（V3 移除未使用的 isLoading prop）"
          },
          {
            "name": "RecentSubmissions",
            "file": "teacher-dashboard/RecentSubmissions",
            "purpose": "最近提交（V3 移除 AvatarImage 死代码 + 新增 getLocale 日期本地化）"
          },
          {
            "name": "DashboardGreetingHeader",
            "file": "dashboard-greeting-header",
            "purpose": "共享问候头部组件（V2 抽象，消除 teacher/student 头部 90% 重复代码，接收 userName 和可选 actions slot；V3 从客户端组件转为异步服务端组件，使用 getTranslations/getLocale）"
          },
          {
            "name": "DashboardLoadingSkeleton",
            "file": "dashboard-loading-skeleton",
            "purpose": "V3 新增：仪表盘共享骨架屏组件，消除 5 个 loading.tsx 路由文件的重复代码"
          },
          {
            "name": "DashboardErrorFallback",
            "file": "dashboard-error-fallback",
            "purpose": "V3 新增：仪表盘共享错误边界组件，含 i18n + reset() 重试，消除 5 个 error.tsx 路由文件的重复代码"
          },
          {
            "name": "AdminStatsBar",
            "file": "admin-dashboard/admin-sections",
            "purpose": "V4（P2-1）新增：管理员顶部统计栏流式组件，用 React use() 独立消费 usersStats/classesStats/homeworkStats Promise"
          },
          {
            "name": "AdminContentCard",
            "file": "admin-dashboard/admin-sections",
            "purpose": "V4（P2-1）新增：管理员内容统计卡片流式组件，用 React use() 消费 textbooksStats/questionsStats/examsStats Promise"
          },
          {
            "name": "AdminHomeworkActivityCard",
            "file": "admin-dashboard/admin-sections",
            "purpose": "V4（P2-1）新增：管理员作业活跃度卡片流式组件，用 React use() 消费 homeworkStats Promise"
          },
          {
            "name": "AdminUserRolesCard",
            "file": "admin-dashboard/admin-sections",
            "purpose": "V4（P2-1）新增：管理员用户角色分布卡片流式组件，用 React use() 消费 usersStats Promise"
          },
          {
            "name": "AdminRecentUsersTable",
            "file": "admin-dashboard/admin-sections",
            "purpose": "V4（P2-1）新增：管理员最近注册用户表流式组件，用 React use() 消费 usersStats Promise + getLocale"
          },
          {
            "name": "AdminTrendCharts",
            "file": "admin-dashboard/admin-sections",
            "purpose": "V4（P2-1）新增：管理员趋势图表组件（静态，趋势数据待接入）"
          },
          {
            "name": "AdminHeaderBadges",
            "file": "admin-dashboard/admin-sections",
            "purpose": "V4（P2-1）新增：管理员页头徽章流式组件，用 React use() 消费 usersStats Promise"
          }
        ]
      }
    },
    "layout": {
      "path": "src/modules/layout",
      "description": "应用布局框架：侧边栏、顶栏、导航配置",
      "exports": {
        "components": [
          {
            "name": "AppSidebar",
            "purpose": "根据权限渲染侧边栏导航；N3 新增多角色切换：从 SidebarContext 读取 currentRole（null 时自动检测 admin>student>parent>teacher），用户拥有多个角色时在侧边栏底部显示 Select 下拉切换",
            "internalDeps": [
              "usePermission",
              "NAV_CONFIG",
              "useSidebar",
              "shared/components/ui/select"
            ]
          },
          {
            "name": "SiteHeader",
            "purpose": "顶部导航栏（集成 GlobalSearch 全局搜索：Cmd/Ctrl+K 唤起、300ms 防抖、↑/↓ 导航、Enter 跳转；NotificationDropdown 通知下拉）",
            "internalDeps": [
              "useSession",
              "signOut",
              "shared/components/global-search.GlobalSearch",
              "messaging/components/notification-dropdown.NotificationDropdown"
            ]
          },
          {
            "name": "SidebarProvider",
            "props": "{ children, sidebar }",
            "purpose": "侧边栏上下文Provider（N3 新增 currentRole/setCurrentRole 状态，null 表示自动检测角色）"
          },
          {
            "name": "useSidebar",
            "type": "hook",
            "purpose": "侧边栏状态Hook（返回 expanded/isMobile/toggleSidebar/currentRole/setCurrentRole）"
          }
        ],
        "types": [
          {
            "name": "Role",
            "type": "type",
            "definition": "\"admin\" | \"teacher\" | \"student\" | \"parent\"",
            "usedBy": [
              "NAV_CONFIG",
              "usePermission"
            ]
          },
          {
            "name": "NavItem",
            "type": "type",
            "definition": "{ title, href, icon?, permission? }",
            "usedBy": [
              "NAV_CONFIG",
              "AppSidebar"
            ]
          }
        ],
        "config": [
          {
            "name": "NAV_CONFIG",
            "type": "Record<Role, NavItem[]>",
            "note": "每个NavItem含permission字段用于权限过滤。admin角色菜单包含Audit Logs项(icon: ScrollText, href: /admin/audit-logs, permission: AUDIT_LOG_READ)，含子项Operation Logs与Login Logs。admin角色菜单的School Management子菜单包含Import Users项(href: /admin/users/import, permission: USER_MANAGE)。admin角色菜单包含Scheduling项(icon: CalendarClock, href: /admin/scheduling/rules, permission: SCHEDULE_ADJUST)，含子项Rules(/admin/scheduling/rules, permission: SCHEDULE_ADJUST)、Auto Schedule(/admin/scheduling/auto, permission: SCHEDULE_AUTO)、Change Requests(/admin/scheduling/changes, permission: SCHEDULE_ADJUST)。teacher角色菜单包含Grades项(icon: GraduationCap, permission: GRADE_RECORD_READ)，含子项All Grades(/teacher/grades)、Batch Entry(/teacher/grades/entry, permission: GRADE_RECORD_MANAGE)、Statistics(/teacher/grades/stats)。teacher角色菜单包含Schedule Changes项(icon: CalendarClock, href: /teacher/schedule-changes, permission: SCHEDULE_ADJUST)。teacher角色菜单包含Diagnostic项(icon: Stethoscope, href: /teacher/diagnostic, permission: DIAGNOSTIC_READ)。student角色菜单包含My Grades项(icon: GraduationCap, href: /student/grades, permission: GRADE_RECORD_READ)。student角色菜单包含Diagnostic项(icon: Stethoscope, href: /student/diagnostic, permission: DIAGNOSTIC_READ)。parent角色菜单包含Dashboard项(icon: LayoutDashboard, href: /parent/dashboard，无permission字段仅需登录)、Grades项(icon: GraduationCap, href: /parent/grades, permission: GRADE_RECORD_READ)、Announcements项(icon: Megaphone, href: /announcements, permission: ANNOUNCEMENT_READ)"
          }
        ]
      }
    },
    "settings": {
      "path": "src/modules/settings",
      "description": "系统设置 + AI Provider配置 + 用户偏好 + 密码安全（修改密码、强度校验）。系统设置页 /admin/settings 提供学校信息、安全策略、文件上传、通知配置等运行参数管理",
      "exports": {
        "actions": [
          {
            "name": "getAiProviderSummaries",
            "permission": "AI_CONFIGURE",
            "signature": "() => Promise<ActionState<AiProviderSummary[]>>",
            "purpose": "获取AI Provider列表（P1 已修复：DB 操作下沉到 data-access.getAiProviderSummaries；v3 已修复：返回值统一为 ActionState）",
            "deps": [
              "data-access.getAiProviderSummaries"
            ]
          },
          {
            "name": "upsertAiProviderAction",
            "permission": "AI_CONFIGURE",
            "signature": "(data) => Promise<ActionState<string>>",
            "purpose": "创建/更新AI Provider（P1 已修复：DB 操作下沉到 data-access，并行查询优化）",
            "deps": [
              "shared/lib/ai (encrypt/decrypt)",
              "data-access.countDefaultAiProviders",
              "data-access.getAiProviderForUpdate",
              "data-access.updateAiProvider",
              "data-access.createAiProvider"
            ]
          },
          {
            "name": "testAiProviderAction",
            "permission": "AI_CONFIGURE",
            "signature": "(data) => Promise<ActionState<null>>",
            "purpose": "测试AI Provider连通性",
            "deps": [
              "shared/lib/ai.testAiProviderConfig"
            ]
          },
          {
            "name": "changePasswordAction",
            "file": "actions-password.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "(prevState: ActionState<null>, formData: FormData) => Promise<ActionState<null>>",
            "purpose": "修改当前用户密码（Zod 校验 + 校验当前密码 + 新密码策略 + 速率限制 PASSWORD_CHANGE: 5次/分钟 + 并行查询优化）",
            "deps": [
              "requirePermission",
              "validatePassword",
              "rateLimit",
              "bcryptjs (hash/compare)",
              "data-access.getUserPasswordHash",
              "data-access.getPasswordSecurityByUserId",
              "data-access.updateUserPassword",
              "data-access.upsertPasswordSecurityOnPasswordChange"
            ],
            "usedBy": [
              "components/password-change-form.tsx"
            ]
          },
          {
            "name": "updateUserAvatarAction",
            "file": "actions-avatar.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "(imageUrl: string) => Promise<ActionState<{ image: string }>>",
            "purpose": "更新用户头像 URL（P2-8 新增；v2 已增强：更新成功后清理旧头像文件，包括磁盘文件和 DB 记录）",
            "deps": [
              "requirePermission",
              "users/data-access.getUserProfile",
              "users/data-access.updateUserAvatar",
              "files/data-access.getFileByUrl",
              "files/data-access.deleteFileAttachment"
            ],
            "usedBy": [
              "components/avatar-upload.tsx"
            ]
          },
          {
            "name": "removeUserAvatarAction",
            "file": "actions-avatar.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "() => Promise<ActionState<null>>",
            "purpose": "移除用户头像（P2-8 新增；v2 已增强：同时清理旧头像文件）",
            "deps": [
              "requirePermission",
              "users/data-access.getUserProfile",
              "users/data-access.updateUserAvatar",
              "files/data-access.getFileByUrl",
              "files/data-access.deleteFileAttachment"
            ],
            "usedBy": [
              "components/avatar-upload.tsx"
            ]
          },
          {
            "name": "sendTestNotificationAction",
            "file": "actions-notifications.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "(input: { channel: 'push' | 'email' | 'sms' }) => Promise<ActionState<null>>",
            "purpose": "发送测试通知（P2-10 新增；v2 已增强：接入 notifications/dispatcher.sendNotification 真实发送）",
            "deps": [
              "requirePermission",
              "notifications/dispatcher.sendNotification"
            ],
            "usedBy": [
              "components/notification-preferences-form.tsx"
            ]
          },
          {
            "name": "getAdminSystemSettingsAction",
            "file": "actions-system-settings.ts",
            "permission": "SETTINGS_ADMIN",
            "signature": "() => Promise<ActionState<AdminSettingsFormValues>>",
            "purpose": "获取管理员系统设置（P0-3 新增：从 system_settings 表加载 4 个分类配置）",
            "deps": [
              "requirePermission",
              "data-access-system-settings.getAllSystemSettings"
            ],
            "usedBy": [
              "components/admin-settings-view.tsx"
            ]
          },
          {
            "name": "saveAdminSystemSettingsAction",
            "file": "actions-system-settings.ts",
            "permission": "SETTINGS_ADMIN",
            "signature": "(values: AdminSettingsFormValues) => Promise<ActionState<null>>",
            "purpose": "保存管理员系统设置（P0-3 新增：4 分类 Zod 校验 + 批量 upsert）",
            "deps": [
              "requirePermission",
              "data-access-system-settings.upsertSystemSettings"
            ],
            "usedBy": [
              "components/admin-settings-view.tsx"
            ]
          },
          {
            "name": "getSecurityCenterAction",
            "file": "actions-security.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "() => Promise<ActionState<SecurityCenterData>>",
            "purpose": "获取安全中心数据（P2-9 新增：2FA 状态 + 最近 10 条登录历史；v2 已优化：使用 getSystemSettingsByCategory 一次查询替代 3 次单独查询）",
            "deps": [
              "requirePermission",
              "data-access-system-settings.getSystemSettingsByCategory",
              "shared.db.schema.loginLogs"
            ],
            "usedBy": [
              "components/security-center-card.tsx"
            ]
          },
          {
            "name": "setupTwoFactorAction",
            "file": "actions-security.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "() => Promise<ActionState<TwoFactorSetupData>>",
            "purpose": "2FA 启用流程第一步：生成 TOTP 密钥 + QR 码 Data URL（v3 新增：完整 TOTP 实现，替代 v2 的占位开关）",
            "deps": [
              "requirePermission",
              "users/data-access.getUserProfile",
              "data-access-two-factor.getTwoFactorEnabled",
              "data-access-two-factor.setTotpSecret",
              "lib/totp.generateTotpSecret",
              "lib/totp.buildOtpAuthUrl",
              "lib/totp.generateQrCodeDataUrl"
            ],
            "usedBy": [
              "components/security-center-card.tsx"
            ]
          },
          {
            "name": "verifyTwoFactorAction",
            "file": "actions-security.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "(token: string) => Promise<ActionState<{ backupCodes: string[]; status: TwoFactorStatus }>>",
            "purpose": "2FA 启用流程第二步：校验一次性码 + 生成 10 个备份码（bcrypt 哈希存储）+ 启用 2FA（v3 新增）",
            "deps": [
              "requirePermission",
              "data-access-two-factor.getTotpSecret",
              "data-access-two-factor.setBackupCodesHashed",
              "data-access-two-factor.setTwoFactorEnabled",
              "data-access-two-factor.setTwoFactorEnabledAt",
              "lib/totp.verifyTotpCode",
              "lib/totp.generateBackupCodes",
              "lib/totp.hashBackupCodes"
            ],
            "usedBy": [
              "components/security-center-card.tsx"
            ]
          },
          {
            "name": "disableTwoFactorAction",
            "file": "actions-security.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "(token: string) => Promise<ActionState<TwoFactorStatus>>",
            "purpose": "关闭 2FA：需提供有效 TOTP 码或备份码确认身份，清除密钥和备份码（v3 新增）",
            "deps": [
              "requirePermission",
              "data-access-two-factor.getTwoFactorEnabled",
              "data-access-two-factor.getTotpSecret",
              "data-access-two-factor.getBackupCodesHashed",
              "data-access-two-factor.setTwoFactorEnabled",
              "data-access-two-factor.setTwoFactorEnabledAt",
              "data-access-two-factor.deleteTotpSecret",
              "data-access-two-factor.deleteBackupCodes",
              "data-access-two-factor.setBackupCodesHashed",
              "lib/totp.verifyTotpCode",
              "lib/totp.verifyBackupCode",
              "lib/totp.consumeBackupCode"
            ],
            "usedBy": [
              "components/security-center-card.tsx"
            ]
          },
          {
            "name": "regenerateBackupCodesAction",
            "file": "actions-security.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "(token: string) => Promise<ActionState<{ backupCodes: string[]; status: TwoFactorStatus }>>",
            "purpose": "重新生成备份码：需 TOTP 码确认身份，使旧备份码失效（v3 新增）",
            "deps": [
              "requirePermission",
              "data-access-two-factor.getTwoFactorEnabled",
              "data-access-two-factor.getTotpSecret",
              "data-access-two-factor.setBackupCodesHashed",
              "lib/totp.verifyTotpCode",
              "lib/totp.generateBackupCodes",
              "lib/totp.hashBackupCodes"
            ],
            "usedBy": [
              "components/security-center-card.tsx"
            ]
          },
          {
            "name": "preflightTwoFactorAction",
            "file": "actions-security.ts",
            "permission": "(public, login 前预检)",
            "signature": "(email: string) => Promise<{ required: boolean }>",
            "purpose": "登录预检：根据邮箱查询用户是否启用 2FA，登录表单据此展示 2FA 输入框（v3 新增；不验证密码，防邮箱枚举）",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "data-access-two-factor.getTwoFactorEnabled"
            ],
            "usedBy": [
              "modules/auth/components/login-form.tsx"
            ]
          },
          {
            "name": "verifyTwoFactorForLogin",
            "file": "actions-security.ts",
            "permission": "(internal, 供 auth.ts 调用)",
            "signature": "(params: { userId: string; token?: string }) => Promise<{ required: boolean; valid: boolean }>",
            "purpose": "登录时 2FA 校验：检查用户是否启用 2FA 并校验 TOTP 码或备份码（消耗备份码）；由 auth.ts authorize 回调调用（v3 新增）",
            "deps": [
              "data-access-two-factor.getTwoFactorEnabled",
              "data-access-two-factor.getTotpSecret",
              "data-access-two-factor.getBackupCodesHashed",
              "data-access-two-factor.setBackupCodesHashed",
              "lib/totp.verifyTotpCode",
              "lib/totp.verifyBackupCode",
              "lib/totp.consumeBackupCode"
            ],
            "usedBy": [
              "auth.ts"
            ]
          },
          {
            "name": "revokeAllOtherSessionsAction",
            "file": "actions-security.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "() => Promise<ActionState<{ revokedCount: number }>>",
            "purpose": "远程登出当前用户的所有其他会话（v2 新增：删除 sessions 表记录 + 记录安全处置日志；注意当前为 JWT 策略，sessions 表可能无活跃记录）",
            "deps": [
              "requirePermission",
              "users/data-access.getUserProfile",
              "shared.db.schema.sessions",
              "shared.lib.login-logger.logLoginEvent"
            ],
            "usedBy": [
              "components/security-center-card.tsx"
            ]
          },
          {
            "name": "updateProfileAction",
            "file": "actions-service.ts",
            "permission": "USER_PROFILE_UPDATE",
            "signature": "(input: UpdateUserProfileInput) => Promise<ActionState<void>>",
            "purpose": "设置页个人资料更新 Server Action wrapper（v1.1 新增：委托给 users/actions.updateUserProfile，作为 Server Action 引用传递给 Client Component，避免 Server→Client 函数传递违规）",
            "deps": [
              "users/actions.updateUserProfile"
            ],
            "usedBy": [
              "app/(dashboard)/settings/page.tsx"
            ]
          },
          {
            "name": "updateNotificationPreferencesAction",
            "file": "actions-service.ts",
            "permission": "MESSAGE_READ",
            "signature": "(input: UpdateNotificationPreferencesInput) => Promise<ActionState<NotificationPreferences>>",
            "purpose": "设置页通知偏好更新 Server Action wrapper（v1.1 新增：直接调用 notifications/preferences.upsertNotificationPreferences，绕过 messaging/actions 的 FormData 签名，适配 SettingsService 接口）",
            "deps": [
              "requirePermission",
              "notifications/preferences.upsertNotificationPreferences"
            ],
            "usedBy": [
              "app/(dashboard)/settings/page.tsx"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getAiProviderSummaries",
            "signature": "() => Promise<AiProviderSummary[]>",
            "file": "data-access.ts",
            "purpose": "获取AI Provider列表（P1 新增，从 actions 下沉）",
            "deps": [
              "shared.db",
              "shared.db.schema.aiProviders"
            ]
          },
          {
            "name": "countDefaultAiProviders",
            "signature": "() => Promise<number>",
            "file": "data-access.ts",
            "purpose": "统计默认AI Provider数量（P1 新增）",
            "deps": [
              "shared.db",
              "shared.db.schema.aiProviders"
            ]
          },
          {
            "name": "getAiProviderForUpdate",
            "signature": "(id: string) => Promise<AiProviderExisting | null>",
            "file": "data-access.ts",
            "purpose": "获取AI Provider更新所需字段（P1 新增）",
            "deps": [
              "shared.db",
              "shared.db.schema.aiProviders"
            ]
          },
          {
            "name": "updateAiProvider",
            "signature": "(id: string, data: UpdateAiProviderInput, resetOtherDefaults: boolean) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "更新AI Provider（事务，P1 新增）",
            "deps": [
              "shared.db",
              "shared.db.schema.aiProviders"
            ]
          },
          {
            "name": "createAiProvider",
            "signature": "(data: CreateAiProviderInput, resetOtherDefaults: boolean) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "创建AI Provider（事务，P1 新增）",
            "deps": [
              "shared.db",
              "shared.db.schema.aiProviders"
            ]
          },
          {
            "name": "getUserPasswordHash",
            "signature": "(userId: string) => Promise<{ password: string | null } | null>",
            "file": "data-access.ts",
            "purpose": "获取用户密码哈希（P1 新增，从 actions-password 下沉）",
            "deps": [
              "shared.db",
              "shared.db.schema.users"
            ]
          },
          {
            "name": "getPasswordSecurityByUserId",
            "signature": "(userId: string) => Promise<{ id: string } | null>",
            "file": "data-access.ts",
            "purpose": "获取用户密码安全记录（P1 新增，从 actions-password 下沉）",
            "deps": [
              "shared.db",
              "shared.db.schema.passwordSecurity"
            ]
          },
          {
            "name": "updateUserPassword",
            "signature": "(userId: string, newHash: string, now: Date) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "更新用户密码（P1 新增，从 actions-password 下沉）",
            "deps": [
              "shared.db",
              "shared.db.schema.users"
            ]
          },
          {
            "name": "upsertPasswordSecurityOnPasswordChange",
            "signature": "(userId: string, now: Date, existing: { id: string } | null) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "密码修改后更新或创建密码安全记录（P1 新增，从 actions-password 下沉）",
            "deps": [
              "shared.db",
              "shared.db.schema.passwordSecurity"
            ]
          },
          {
            "name": "getSystemSettingsByCategory",
            "signature": "(category: SystemSettingCategory) => Promise<SystemSettingRecord[]>",
            "file": "data-access-system-settings.ts",
            "purpose": "获取指定分类下所有设置项（P0-3 新增）",
            "deps": [
              "shared.db",
              "shared.db.schema.systemSettings"
            ]
          },
          {
            "name": "getAllSystemSettings",
            "signature": "() => Promise<SystemSettingRecord[]>",
            "file": "data-access-system-settings.ts",
            "purpose": "获取所有系统设置项（P0-3 新增）",
            "deps": [
              "shared.db",
              "shared.db.schema.systemSettings"
            ]
          },
          {
            "name": "getSystemSetting",
            "signature": "(category: SystemSettingCategory, key: string) => Promise<SystemSettingRecord | null>",
            "file": "data-access-system-settings.ts",
            "purpose": "获取单个设置项（P0-3 新增）",
            "deps": [
              "shared.db",
              "shared.db.schema.systemSettings"
            ]
          },
          {
            "name": "upsertSystemSetting",
            "signature": "(params: { category, key, value, valueType, updatedBy? }) => Promise<void>",
            "file": "data-access-system-settings.ts",
            "purpose": "插入或更新设置项（P0-3 新增）",
            "deps": [
              "shared.db",
              "shared.db.schema.systemSettings"
            ]
          },
          {
            "name": "upsertSystemSettings",
            "signature": "(items: Array<{ category, key, value, valueType }>, updatedBy?: string) => Promise<void>",
            "file": "data-access-system-settings.ts",
            "purpose": "批量 upsert 设置项（P0-3 新增）",
            "deps": [
              "shared.db",
              "shared.db.schema.systemSettings"
            ]
          }
        ],
        "types": [
          {
            "name": "AiProviderSummary",
            "file": "types.ts",
            "type": "interface",
            "definition": "AI Provider摘要（P1 从 actions.ts 迁至 types.ts）",
            "usedBy": [
              "getAiProviderSummaries",
              "settings/components",
              "exams/components"
            ]
          },
          {
            "name": "AiProviderName",
            "file": "types.ts",
            "type": "type",
            "definition": "AI Provider名称联合类型（P1 新增）",
            "usedBy": [
              "data-access",
              "types.AiProviderSummary"
            ]
          },
          {
            "name": "AiProviderExisting",
            "file": "types.ts",
            "type": "interface",
            "definition": "AI Provider更新所需字段（P1 新增）",
            "usedBy": [
              "data-access.getAiProviderForUpdate"
            ]
          },
          {
            "name": "SettingsService",
            "file": "types.ts",
            "type": "interface",
            "definition": "设置模块统一服务接口（profile + notifications + trackEvent），通过 React Context 注入实现解耦",
            "usedBy": [
              "components/settings-service-context.tsx",
              "app/(dashboard)/settings/page.tsx"
            ]
          },
          {
            "name": "ProfileService",
            "file": "types.ts",
            "type": "interface",
            "definition": "个人资料服务接口（getProfile + updateProfile），解耦组件对 users/actions 的直接依赖",
            "usedBy": [
              "types.SettingsService",
              "components/profile-settings-form.tsx"
            ]
          },
          {
            "name": "NotificationPreferenceService",
            "file": "types.ts",
            "type": "interface",
            "definition": "通知偏好服务接口（getPreferences + updatePreferences），解耦组件对 messaging/actions 的直接依赖",
            "usedBy": [
              "types.SettingsService",
              "components/notification-preferences-form.tsx"
            ]
          }
        ],
        "components": [
          {
            "name": "AiProviderSettingsCard",
            "purpose": "AI Provider设置卡片（i18n：settings.ai.providers）"
          },
          {
            "name": "AdminSettingsView",
            "purpose": "系统设置视图（P0-3 已修复：从 mock 改为真实数据层，通过 Server Actions 加载/保存到 system_settings 表；4 个 Card：学校信息/安全策略/文件上传/通知配置；i18n：settings.admin）",
            "deps": [
              "getAdminSystemSettingsAction",
              "saveAdminSystemSettingsAction"
            ],
            "usedBy": [
              "app/(dashboard)/admin/settings/page.tsx"
            ]
          },
          {
            "name": "AvatarUpload",
            "file": "components/avatar-upload.tsx",
            "purpose": "头像上传/预览/删除客户端组件（P2-8 新增；v2 已增强：文件名长度校验 ≤255 字符；文件通过 /api/upload 上传，调用 updateUserAvatarAction 更新 users.image；验证 JPEG/PNG/WebP/GIF + 2MB 上限；i18n：settings.profile.avatar）",
            "deps": [
              "updateUserAvatarAction",
              "removeUserAvatarAction"
            ],
            "usedBy": [
              "app/(dashboard)/profile/page.tsx"
            ]
          },
          {
            "name": "SecurityCenterCard",
            "file": "components/security-center-card.tsx",
            "purpose": "安全中心卡片（P2-9 新增；v2 增强：会话远程登出、currentDeviceLabel、纯函数抽取；v3 增强：完整 TOTP 2FA 流程 — 启用(QR码+验证码+备份码)/关闭/重新生成备份码，三个 Dialog；2FA 状态存储在 system_settings 表；登录历史来自 login_logs 表；i18n：settings.security.center）",
            "deps": [
              "getSecurityCenterAction",
              "setupTwoFactorAction",
              "verifyTwoFactorAction",
              "disableTwoFactorAction",
              "regenerateBackupCodesAction",
              "revokeAllOtherSessionsAction",
              "lib/security-utils.parseUserAgent",
              "lib/security-utils.formatRelativeTime"
            ],
            "usedBy": [
              "components/settings-view.tsx"
            ]
          },
          {
            "name": "ProfileSettingsForm",
            "purpose": "个人资料设置表单（通过 useSettingsService().profile.updateProfile 调用，i18n：settings.profile）",
            "deps": [
              "useSettingsService",
              "shared/components/form-fields/text-field",
              "shared/components/form-fields/select-field"
            ]
          },
          {
            "name": "ThemePreferencesCard",
            "purpose": "主题偏好卡片（i18n：settings.appearance；P2-11 已增强：集成 LocaleSwitcher 语言切换到 Appearance 标签页）",
            "deps": [
              "shared/components/locale-switcher"
            ]
          },
          {
            "name": "SettingsView",
            "purpose": "统一设置页布局（5 标签页：General/Notifications/Appearance/Security/AI；角色差异通过 resolveRoleSettingsConfig 配置驱动 + generalExtra props 注入；Tab URL 持久化；每个 TabsContent 包裹 SettingsSectionErrorBoundary + Suspense 骨架屏；AI 标签页条件渲染需 AI_CONFIGURE 权限；登出 AlertDialog 二次确认；i18n：settings 命名空间）",
            "usedBy": [
              "app/(dashboard)/settings/page.tsx"
            ]
          },
          {
            "name": "SettingsServiceProvider",
            "file": "components/settings-service-context.tsx",
            "purpose": "SettingsService React Context Provider，页面层注入服务实现，组件层通过 useSettingsService() 消费",
            "usedBy": [
              "app/(dashboard)/settings/page.tsx"
            ]
          },
          {
            "name": "SettingsSectionErrorBoundary",
            "file": "components/settings-section-error-boundary.tsx",
            "purpose": "分区 Error Boundary，包裹每个 TabsContent 和 profile 角色概览区块，局部失败不影响整页",
            "usedBy": [
              "components/settings-view.tsx",
              "app/(dashboard)/profile/page.tsx"
            ]
          },
          {
            "name": "QuickLinksCard",
            "file": "components/quick-links-card.tsx",
            "purpose": "快捷链接卡片（客户端组件，i18n 键驱动：settings.quickLinks）",
            "usedBy": [
              "config/role-settings-config.tsx"
            ]
          },
          {
            "name": "ProfileStudentOverview",
            "file": "components/profile-student-overview.tsx",
            "purpose": "学生概览异步 Server Component，独立获取学生数据并渲染（StatsGrid + UpcomingAssignments + Grades + TodaySchedule），可被 Suspense + ErrorBoundary 包裹实现流式渲染",
            "deps": [
              "classes/data-access.getStudentClasses",
              "classes/data-access.getStudentSchedule",
              "homework/data-access.getStudentHomeworkAssignments",
              "homework/data-access.getStudentDashboardGrades",
              "lib/student-overview-data.buildStudentOverviewData"
            ],
            "usedBy": [
              "app/(dashboard)/profile/page.tsx"
            ]
          },
          {
            "name": "ProfileTeacherOverview",
            "file": "components/profile-teacher-overview.tsx",
            "purpose": "教师概览异步 Server Component，独立获取教师数据并渲染（任教科目 + 任教班级），可被 Suspense + ErrorBoundary 包裹",
            "deps": [
              "classes/data-access.getTeacherClasses",
              "classes/data-access.getTeacherTeachingSubjects"
            ],
            "usedBy": [
              "app/(dashboard)/profile/page.tsx"
            ]
          },
          {
            "name": "PasswordChangeForm",
            "purpose": "密码修改表单（当前密码/新密码/确认密码 + 强度指示器 + 需求提示；i18n：settings.security；a11y：aria-label）",
            "deps": [
              "changePasswordAction",
              "getPasswordStrength",
              "PASSWORD_REQUIREMENT_HINTS"
            ]
          },
          {
            "name": "NotificationPreferencesForm",
            "file": "components/notification-preferences-form.tsx",
            "purpose": "通知偏好设置表单（Switch 切换 email/sms/push 通道 + 5 个分类开关；通过 useSettingsService().notifications.updatePreferences 调用，i18n：settings.notifications；P2-10 已增强：每个已启用渠道旁显示测试按钮，调用 sendTestNotificationAction）",
            "deps": [
              "useSettingsService",
              "shared/components/ui/switch",
              "shared/components/ui/card"
            ],
            "usedBy": [
              "SettingsView"
            ]
          }
        ],
        "lib": [
          {
            "name": "parseUserAgent",
            "file": "lib/security-utils.ts",
            "signature": "(ua: string | null) => { device: string; browser: string }",
            "purpose": "解析 User-Agent 字符串为设备类型 + 浏览器名称（v2 抽取的纯函数，便于单测）"
          },
          {
            "name": "formatRelativeTime",
            "file": "lib/security-utils.ts",
            "signature": "(iso: string, locale: string) => string",
            "purpose": "将 ISO 时间戳格式化为相对时间（v2 抽取的纯函数）"
          },
          {
            "name": "generateTotpSecret",
            "file": "lib/totp.ts",
            "signature": "() => string",
            "purpose": "生成 TOTP base32 密钥（v3 新增，基于 otplib v13）"
          },
          {
            "name": "buildOtpAuthUrl",
            "file": "lib/totp.ts",
            "signature": "(params: { serviceName: string; accountName: string; secret: string }) => string",
            "purpose": "构建 otpauth:// URL 用于二维码扫描（v3 新增）"
          },
          {
            "name": "generateQrCodeDataUrl",
            "file": "lib/totp.ts",
            "signature": "(otpAuthUrl: string) => Promise<string>",
            "purpose": "将 otpauth URL 转换为 QR 码 Data URL（base64 PNG）（v3 新增）"
          },
          {
            "name": "verifyTotpCode",
            "file": "lib/totp.ts",
            "signature": "(token: string, secret: string) => boolean",
            "purpose": "校验 TOTP 一次性码（±30s 窗口容差）（v3 新增）"
          },
          {
            "name": "generateBackupCodes",
            "file": "lib/totp.ts",
            "signature": "() => string[]",
            "purpose": "生成 10 个 8 位备份码（去除易混淆字符）（v3 新增）"
          },
          {
            "name": "hashBackupCodes",
            "file": "lib/totp.ts",
            "signature": "(codes: string[]) => Promise<string>",
            "purpose": "将备份码列表 bcrypt 哈希为 JSON 数组字符串（v3 新增）"
          },
          {
            "name": "verifyBackupCode",
            "file": "lib/totp.ts",
            "signature": "(input: string, storedHashedJson: string) => Promise<number>",
            "purpose": "校验备份码，返回匹配索引或 -1（v3 新增）"
          },
          {
            "name": "consumeBackupCode",
            "file": "lib/totp.ts",
            "signature": "(storedHashedJson: string, usedIndex: number) => Promise<string>",
            "purpose": "从哈希列表中移除已使用的备份码（v3 新增）"
          },
          {
            "name": "countRemainingBackupCodes",
            "file": "lib/totp.ts",
            "signature": "(storedHashedJson: string) => number",
            "purpose": "统计剩余备份码数量（v3 新增）"
          }
        ]
      }
    },
    "users": {
      "path": "src/modules/users",
      "description": "用户个人资料管理 + 用户批量导入/导出（Excel）",
      "exports": {
        "actions": [
          {
            "name": "updateUserProfile",
            "signature": "(data: UpdateUserProfileInput) => Promise<void>",
            "file": "actions.ts",
            "permission": "requireAuth()",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "shared.lib.auth-guard.requireAuth"
            ]
          },
          {
            "name": "UpdateUserProfileInput",
            "type": "type",
            "file": "actions.ts",
            "definition": "{ name?, phone?, address?, gender?, age? }"
          },
          {
            "name": "downloadUserTemplateAction",
            "signature": "() => Promise<ActionState<string>>",
            "file": "actions.ts",
            "permission": "USER_MANAGE",
            "purpose": "生成用户导入模板（返回 base64 编码的 Excel）",
            "deps": [
              "requirePermission",
              "import-export.generateUserImportTemplate"
            ],
            "usedBy": [
              "components/user-import-dialog.tsx"
            ]
          },
          {
            "name": "importUsersAction",
            "signature": "(prevState: ActionState<UserImportResult> | null, formData: FormData) => Promise<ActionState<UserImportResult>>",
            "file": "actions.ts",
            "permission": "USER_MANAGE",
            "purpose": "导入用户：接收文件，解析+验证+批量创建（默认密码 123456）",
            "deps": [
              "requirePermission",
              "shared.lib.excel.parseExcel",
              "import-export.parseUserImportData",
              "import-export.batchImportUsers"
            ],
            "usedBy": [
              "components/user-import-dialog.tsx"
            ]
          },
          {
            "name": "exportUsersAction",
            "signature": "(role?: string) => Promise<ActionState<{ buffer: string; filename: string }>>",
            "file": "actions.ts",
            "permission": "USER_MANAGE",
            "purpose": "导出用户列表（返回 base64 编码的 Excel）",
            "deps": [
              "requirePermission",
              "import-export.exportUsersToExcel"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "updateUserRoleAction",
            "signature": "(prevState: ActionState<unknown>, formData: FormData) => Promise<ActionState<unknown>>",
            "file": "actions.ts",
            "permission": "USER_MANAGE",
            "purpose": "更新用户角色（简化实现，预留扩展）",
            "deps": [
              "requirePermission"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "deleteUserAction",
            "signature": "(prevState: ActionState<unknown>, formData: FormData) => Promise<ActionState<unknown>>",
            "file": "actions.ts",
            "permission": "USER_MANAGE",
            "purpose": "删除用户（按 userId 物理删除 users 表记录，revalidatePath /admin/users）",
            "deps": [
              "requirePermission",
              "shared.db",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "待扩展"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getUserProfile",
            "signature": "(userId: string) => Promise<UserProfile | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.users"
            ]
          },
          {
            "name": "getUsersDashboardStats",
            "signature": "() => Promise<UsersDashboardStats>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "shared.db.schema.sessions",
              "shared.db.schema.usersToRoles",
              "shared.db.schema.roles"
            ],
            "usedBy": [
              "dashboard/data-access.getAdminDashboardData"
            ]
          },
          {
            "name": "getCurrentStudentUser",
            "signature": "() => Promise<{ id: string; name: string } | null>",
            "file": "data-access.ts",
            "purpose": "获取当前已认证的学生用户（id + name），通过 session + JOIN users/usersToRoles/roles 校验 student 角色",
            "deps": [
              "auth",
              "data-access.getUserWithRole"
            ],
            "usedBy": [
              "student/dashboard",
              "student/learning/assignments",
              "student/learning/assignments/[assignmentId]",
              "student/learning/courses",
              "student/learning/textbooks",
              "student/learning/textbooks/[id]",
              "student/schedule"
            ]
          },
          {
            "name": "UserProfile",
            "type": "type",
            "file": "data-access.ts",
            "definition": "{ id, name, email, image, role, phone, address, gender, age, onboardedAt, createdAt, updatedAt }"
          },
          {
            "name": "UsersDashboardStats",
            "type": "type",
            "file": "data-access.ts",
            "definition": "{ userCount, activeSessionsCount, userRoleCounts, recentUsers }"
          },
          {
            "name": "getAdminUsers",
            "signature": "(params: { page?, pageSize?, search?, role? }) => Promise<AdminUserListResult>",
            "file": "data-access.ts",
            "purpose": "管理员用户列表分页查询（支持按姓名/邮箱搜索，按 createdAt 倒序，关联 usersToRoles/roles 聚合角色名）",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "shared.db.schema.usersToRoles",
              "shared.db.schema.roles"
            ],
            "usedBy": [
              "app/(dashboard)/admin/users/page.tsx"
            ]
          },
          {
            "name": "getAdminUserRoles",
            "signature": "() => Promise<string[]>",
            "file": "data-access.ts",
            "purpose": "返回所有角色名列表（用于用户管理页角色筛选下拉框）",
            "deps": [
              "shared.db",
              "shared.db.schema.roles"
            ],
            "usedBy": [
              "app/(dashboard)/admin/users/page.tsx"
            ]
          },
          {
            "name": "AdminUserListItem",
            "type": "type",
            "file": "data-access.ts",
            "definition": "{ id, name, email, roles: string[], phone, createdAt }"
          },
          {
            "name": "AdminUserListResult",
            "type": "type",
            "file": "data-access.ts",
            "definition": "{ items: AdminUserListItem[], total, page, pageSize, totalPages }"
          }
        ],
        "importExport": [
          {
            "name": "generateUserImportTemplate",
            "signature": "() => Promise<Buffer>",
            "file": "import-export.ts",
            "purpose": "生成用户导入模板（列：姓名/邮箱/角色/手机/班级邀请码，含示例行）",
            "deps": [
              "shared.lib.excel.generateTemplate"
            ],
            "usedBy": [
              "actions.downloadUserTemplateAction"
            ]
          },
          {
            "name": "parseUserImportData",
            "signature": "(rows: Record<string, unknown>[]) => UserImportValidation",
            "file": "import-export.ts",
            "purpose": "解析并验证导入行（校验姓名/邮箱格式/角色枚举/邀请码仅 student）",
            "deps": [],
            "usedBy": [
              "actions.importUsersAction"
            ]
          },
          {
            "name": "exportUsersToExcel",
            "signature": "(params: { scope: DataScope; role?: string }) => Promise<Buffer>",
            "file": "import-export.ts",
            "purpose": "导出用户列表到 Excel（含姓名/邮箱/手机/性别/年龄/角色/创建时间）",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "shared.db.schema.roles",
              "shared.db.schema.usersToRoles",
              "shared.lib.excel.exportToExcel"
            ],
            "usedBy": [
              "actions.exportUsersAction",
              "app/api/export/route.ts"
            ]
          }
        ],
        "userService": [
          {
            "name": "batchImportUsers",
            "signature": "(records: UserImportRecord[]) => Promise<UserImportResult>",
            "file": "user-service.ts",
            "purpose": "批量创建用户（默认密码 123456 bcrypt 哈希，自动创建 usersToRoles，student 通过邀请码自动加入班级——委托 class-registration）",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "shared.db.schema.roles",
              "shared.db.schema.usersToRoles",
              "bcryptjs",
              "@paralleldrive/cuid2",
              "class-registration.registerStudentByInvitationCode"
            ],
            "usedBy": [
              "actions.importUsersAction",
              "import-export.ts (re-export 向后兼容)"
            ]
          }
        ],
        "classRegistration": [
          {
            "name": "registerStudentByInvitationCode",
            "signature": "(studentId: string, invitationCode: string) => Promise<ClassRegistrationResult>",
            "file": "class-registration.ts",
            "purpose": "通过邀请码将学生注册到班级，委托 classes/data-access.enrollStudentByInvitationCode，返回结构化结果（不抛异常）",
            "deps": [
              "classes/data-access.enrollStudentByInvitationCode"
            ],
            "usedBy": [
              "user-service.batchImportUsers"
            ]
          },
          {
            "name": "ClassRegistrationResult",
            "type": "type",
            "file": "class-registration.ts",
            "definition": "{ success: boolean; error?: string }"
          }
        ],
        "types": [
          {
            "name": "UserImportRecord",
            "type": "type",
            "file": "import-export.ts",
            "definition": "{ name, email, role, phone?, invitationCode? }",
            "usedBy": [
              "parseUserImportData",
              "batchImportUsers"
            ]
          },
          {
            "name": "UserImportValidation",
            "type": "type",
            "file": "import-export.ts",
            "definition": "{ valid: UserImportRecord[], invalid: Array<{ row, record, errors }> }",
            "usedBy": [
              "parseUserImportData"
            ]
          },
          {
            "name": "UserImportResult",
            "type": "type",
            "file": "user-service.ts",
            "definition": "{ successCount, failedCount, errors: Array<{ row, email, error }> }",
            "usedBy": [
              "batchImportUsers",
              "importUsersAction",
              "import-export.ts (re-export 向后兼容)"
            ]
          }
        ],
        "components": [
          {
            "name": "UserImportDialog",
            "file": "components/user-import-dialog.tsx",
            "purpose": "用户批量导入对话框（4 状态：idle/preview/importing/done；下载模板→上传预览→确认导入→结果展示）",
            "usedBy": [
              "app/(dashboard)/admin/users/import/page.tsx"
            ]
          },
          {
            "name": "AdminUsersView",
            "file": "components/admin-users-view.tsx",
            "purpose": "管理员用户列表客户端组件（搜索+角色筛选+表格+分页+删除确认对话框，通过 URL searchParams 驱动筛选状态）",
            "usedBy": [
              "app/(dashboard)/admin/users/page.tsx"
            ]
          }
        ]
      }
    },
    "audit": {
      "path": "src/modules/audit",
      "description": "操作日志、登录日志与数据变更日志查询，支持 Excel 导出",
      "exports": {
        "dataAccess": [
          {
            "name": "getAuditLogs",
            "signature": "(params?: AuditLogQueryParams) => Promise<PaginatedResult<AuditLog>>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.auditLogs"
            ],
            "usedBy": [
              "app/(dashboard)/admin/audit-logs/page.tsx"
            ]
          },
          {
            "name": "getLoginLogs",
            "signature": "(params?: LoginLogQueryParams) => Promise<PaginatedResult<LoginLog>>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.loginLogs"
            ],
            "usedBy": [
              "app/(dashboard)/admin/audit-logs/login-logs/page.tsx"
            ]
          },
          {
            "name": "getAuditModuleOptions",
            "signature": "() => Promise<string[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.auditLogs"
            ],
            "usedBy": [
              "app/(dashboard)/admin/audit-logs/page.tsx"
            ]
          },
          {
            "name": "getDataChangeLogs",
            "signature": "(params?: DataChangeLogQueryParams) => Promise<PaginatedResult<DataChangeLog>>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.dataChangeLogs"
            ],
            "usedBy": [
              "getDataChangeLogsAction"
            ]
          },
          {
            "name": "getDataChangeStats",
            "signature": "() => Promise<DataChangeStat[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.dataChangeLogs"
            ],
            "usedBy": [
              "getDataChangeLogsAction"
            ]
          },
          {
            "name": "getDataChangeTableOptions",
            "signature": "() => Promise<string[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.dataChangeLogs"
            ],
            "usedBy": [
              "getDataChangeLogsAction"
            ]
          },
          {
            "name": "getDataChangeLogsForExport",
            "signature": "(params?: DataChangeLogQueryParams) => Promise<DataChangeLog[]>",
            "file": "data-access.ts",
            "deps": [
              "getDataChangeLogs"
            ],
            "usedBy": [
              "exportDataChangeLogsAction"
            ]
          },
          {
            "name": "getAuditLogsForExport",
            "signature": "(params?: AuditLogQueryParams) => Promise<AuditLog[]>",
            "file": "data-access.ts",
            "deps": [
              "getAuditLogs"
            ],
            "usedBy": [
              "exportAuditLogsAction"
            ]
          },
          {
            "name": "getLoginLogsForExport",
            "signature": "(params?: LoginLogQueryParams) => Promise<LoginLog[]>",
            "file": "data-access.ts",
            "deps": [
              "getLoginLogs"
            ],
            "usedBy": [
              "exportLoginLogsAction"
            ]
          }
        ],
        "actions": [
          {
            "name": "getDataChangeLogsAction",
            "permission": "AUDIT_LOG_READ",
            "signature": "(params?: DataChangeLogQueryParams) => Promise<ActionState<{ items, total, page, pageSize, totalPages, tableOptions, stats }>>",
            "file": "actions.ts",
            "purpose": "获取数据变更日志（分页结果 + tableOptions + stats 三者并行加载）",
            "deps": [
              "requirePermission",
              "data-access.getDataChangeLogs",
              "data-access.getDataChangeTableOptions",
              "data-access.getDataChangeStats"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "exportAuditLogsAction",
            "permission": "AUDIT_LOG_READ",
            "signature": "(params?: AuditLogQueryParams) => Promise<ActionState<{ buffer: Buffer; filename: string }>>",
            "file": "actions.ts",
            "purpose": "导出操作日志为 Excel",
            "deps": [
              "requirePermission",
              "data-access.getAuditLogsForExport",
              "shared.lib.excel.exportToExcel"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "exportLoginLogsAction",
            "permission": "AUDIT_LOG_READ",
            "signature": "(params?: LoginLogQueryParams) => Promise<ActionState<{ buffer: Buffer; filename: string }>>",
            "file": "actions.ts",
            "purpose": "导出登录日志为 Excel",
            "deps": [
              "requirePermission",
              "data-access.getLoginLogsForExport",
              "shared.lib.excel.exportToExcel"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "exportDataChangeLogsAction",
            "permission": "AUDIT_LOG_READ",
            "signature": "(params?: DataChangeLogQueryParams) => Promise<ActionState<{ buffer: Buffer; filename: string }>>",
            "file": "actions.ts",
            "purpose": "导出数据变更日志为 Excel",
            "deps": [
              "requirePermission",
              "data-access.getDataChangeLogsForExport",
              "shared.lib.excel.exportToExcel"
            ],
            "usedBy": [
              "待扩展"
            ]
          }
        ],
        "types": [
          {
            "name": "AuditLog",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, userId, userName, action, module, targetId, targetType, detail, ipAddress, userAgent, status, createdAt }"
          },
          {
            "name": "LoginLog",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, userId, userEmail, action, status, ipAddress, userAgent, errorMessage, createdAt }"
          },
          {
            "name": "AuditLogQueryParams",
            "type": "type",
            "file": "types.ts",
            "definition": "{ userId?, module?, action?, status?, page?, pageSize?, startDate?, endDate? }"
          },
          {
            "name": "LoginLogQueryParams",
            "type": "type",
            "file": "types.ts",
            "definition": "{ userId?, action?, status?, page?, pageSize?, startDate?, endDate? }"
          },
          {
            "name": "PaginatedResult",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ items: T[], total, page, pageSize, totalPages }"
          },
          {
            "name": "DataChangeAction",
            "type": "type",
            "file": "types.ts",
            "definition": "'create' | 'update' | 'delete'",
            "usedBy": [
              "data-access",
              "shared/lib/change-logger",
              "DataChangeLog"
            ]
          },
          {
            "name": "DataChangeLog",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, tableName, recordId, action, oldValue, newValue, changedBy, changedByName, ipAddress, createdAt }",
            "usedBy": [
              "audit/data-access",
              "audit/actions"
            ]
          },
          {
            "name": "DataChangeStat",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ tableName: string, count: number }",
            "usedBy": [
              "getDataChangeStats",
              "getDataChangeLogsAction"
            ]
          },
          {
            "name": "DataChangeLogQueryParams",
            "type": "type",
            "file": "types.ts",
            "definition": "{ tableName?, recordId?, action?, userId?, page?, pageSize?, startDate?, endDate? }",
            "usedBy": [
              "getDataChangeLogs",
              "getDataChangeLogsForExport",
              "getDataChangeLogsAction",
              "exportDataChangeLogsAction"
            ]
          }
        ],
        "components": [
          {
            "name": "AuditLogTable",
            "file": "components/audit-log-table.tsx",
            "purpose": "操作日志表格（分页）"
          },
          {
            "name": "AuditLogFilters",
            "file": "components/audit-log-filters.tsx",
            "purpose": "操作日志筛选器（模块/操作/状态/日期）"
          },
          {
            "name": "AuditLogView",
            "file": "components/audit-log-view.tsx",
            "purpose": "操作日志视图（筛选+表格+分页）"
          },
          {
            "name": "LoginLogTable",
            "file": "components/login-log-table.tsx",
            "purpose": "登录日志表格（分页）"
          },
          {
            "name": "LoginLogFilters",
            "file": "components/login-log-filters.tsx",
            "purpose": "登录日志筛选器（操作/状态/日期）"
          },
          {
            "name": "LoginLogView",
            "file": "components/login-log-view.tsx",
            "purpose": "登录日志视图（筛选+表格+分页）"
          }
        ]
      }
    },
    "announcements": {
      "path": "src/modules/announcements",
      "description": "通知公告系统：创建、编辑、发布、归档、删除公告，所有登录用户可查看已发布公告",
      "exports": {
        "actions": [
          {
            "name": "createAnnouncementAction",
            "permission": "ANNOUNCEMENT_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "创建公告（草稿/已发布）；若直接发布则触发通知模块 sendBatchNotifications；V2-P0-2 新增通知标题 i18n 化，通过 getTranslations('announcements') 生成 notification.publishedTitle / publishedContent",
            "deps": [
              "requirePermission",
              "data-access.insertAnnouncement",
              "data-access.getAnnouncementById",
              "notifications.sendBatchNotifications",
              "users.data-access.getAllUserIds",
              "users.data-access.getUserIdsByGradeId",
              "classes.data-access.getStudentIdsByClassId",
              "classes.data-access.getTeacherIdsByClassIds",
              "next-intl/server.getTranslations"
            ],
            "usedBy": [
              "announcement-form.tsx"
            ]
          },
          {
            "name": "updateAnnouncementAction",
            "permission": "ANNOUNCEMENT_MANAGE",
            "signature": "(id: string, prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "更新公告；若状态从非发布变为发布则触发通知模块 sendBatchNotifications；V2-P0-2 新增通知标题 i18n 化（同 createAnnouncementAction）",
            "deps": [
              "requirePermission",
              "data-access.updateAnnouncementById",
              "data-access.getAnnouncementById",
              "notifications.sendBatchNotifications",
              "users.data-access.getAllUserIds",
              "users.data-access.getUserIdsByGradeId",
              "classes.data-access.getStudentIdsByClassId",
              "classes.data-access.getTeacherIdsByClassIds",
              "next-intl/server.getTranslations"
            ],
            "usedBy": [
              "announcement-form.tsx"
            ]
          },
          {
            "name": "deleteAnnouncementAction",
            "permission": "ANNOUNCEMENT_MANAGE",
            "signature": "(id: string) => Promise<ActionState<string>>",
            "purpose": "删除公告",
            "deps": [
              "requirePermission",
              "data-access.deleteAnnouncementById"
            ],
            "usedBy": [
              "announcement-detail.tsx"
            ]
          },
          {
            "name": "publishAnnouncementAction",
            "permission": "ANNOUNCEMENT_MANAGE",
            "signature": "(id: string) => Promise<ActionState<string>>",
            "purpose": "发布公告（发布成功后触发通知模块 sendBatchNotifications）；V2-P0-2 新增通知标题 i18n 化（同 createAnnouncementAction）",
            "deps": [
              "requirePermission",
              "data-access.publishAnnouncementById",
              "data-access.getAnnouncementById",
              "notifications.sendBatchNotifications",
              "users.data-access.getAllUserIds",
              "users.data-access.getUserIdsByGradeId",
              "classes.data-access.getStudentIdsByClassId",
              "classes.data-access.getTeacherIdsByClassIds",
              "next-intl/server.getTranslations"
            ],
            "usedBy": [
              "announcement-detail.tsx"
            ]
          },
          {
            "name": "archiveAnnouncementAction",
            "permission": "ANNOUNCEMENT_MANAGE",
            "signature": "(id: string) => Promise<ActionState<string>>",
            "purpose": "归档公告",
            "deps": [
              "requirePermission",
              "data-access.archiveAnnouncementById"
            ],
            "usedBy": [
              "announcement-detail.tsx"
            ]
          },
          {
            "name": "getAnnouncementsAction",
            "permission": "ANNOUNCEMENT_READ",
            "signature": "(params?: GetAnnouncementsParams) => Promise<ActionState<Announcement[]>>",
            "purpose": "获取公告列表（所有登录用户可读）",
            "deps": [
              "requirePermission",
              "data-access.getAnnouncements"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "toggleAnnouncementPinAction",
            "permission": "ANNOUNCEMENT_MANAGE",
            "signature": "(id: string) => Promise<ActionState<string>>",
            "purpose": "V2-P2-13d 新增：切换公告置顶状态（置顶公告在列表中优先显示）",
            "deps": [
              "requirePermission",
              "data-access.toggleAnnouncementPin"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "markAnnouncementAsReadAction",
            "permission": "ANNOUNCEMENT_READ",
            "signature": "(announcementId: string) => Promise<ActionState<string>>",
            "purpose": "V2-P2-13d 新增：标记公告为已读（当前用户维度，幂等）",
            "deps": [
              "requirePermission",
              "data-access.markAnnouncementAsRead"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getAnnouncementReadStatusAction",
            "permission": "ANNOUNCEMENT_READ",
            "signature": "(announcementIds: string[]) => Promise<ActionState<Record<string, boolean>>>",
            "purpose": "V2-P2-13d 新增：批量获取当前用户对多个公告的已读状态（用于列表页标记已读/未读）",
            "deps": [
              "requirePermission",
              "data-access.getAnnouncementReadStatusForUser"
            ],
            "usedBy": [
              "待扩展"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getAnnouncements",
            "signature": "(params?: { status?, type?, page?, pageSize?, audience?: { gradeId?: string; classId?: string } }) => Promise<Announcement[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.announcements"
            ],
            "usedBy": [
              "admin/announcements/page.tsx",
              "announcements/page.tsx"
            ]
          },
          {
            "name": "getAnnouncementById",
            "signature": "(id: string) => Promise<Announcement | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.announcements"
            ],
            "usedBy": [
              "admin/announcements/[id]/page.tsx",
              "announcements/[id]/page.tsx",
              "publishAnnouncementAction",
              "updateAnnouncementAction",
              "deleteAnnouncementAction",
              "archiveAnnouncementAction"
            ]
          },
          {
            "name": "insertAnnouncement",
            "signature": "(input: { title, content, type?, status?, targetGradeId?, targetClassId?, publishedAt?, authorId }) => Promise<{ announcementId: string }>",
            "file": "data-access.ts",
            "purpose": "插入公告（P1-2 新增，从 actions 下沉）",
            "deps": [
              "shared.db",
              "shared.db.schema.announcements"
            ],
            "usedBy": [
              "createAnnouncementAction"
            ]
          },
          {
            "name": "updateAnnouncementById",
            "signature": "(id: string, input: Partial<{ title, content, type?, status?, targetGradeId?, targetClassId?, publishedAt? }>) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "按ID更新公告（P1-2 新增，从 actions 下沉）",
            "deps": [
              "shared.db",
              "shared.db.schema.announcements"
            ],
            "usedBy": [
              "updateAnnouncementAction"
            ]
          },
          {
            "name": "deleteAnnouncementById",
            "signature": "(id: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "按ID删除公告（P1-2 新增，从 actions 下沉）",
            "deps": [
              "shared.db",
              "shared.db.schema.announcements"
            ],
            "usedBy": [
              "deleteAnnouncementAction"
            ]
          },
          {
            "name": "publishAnnouncementById",
            "signature": "(id: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "发布公告（P1-2 新增，从 actions 下沉）",
            "deps": [
              "shared.db",
              "shared.db.schema.announcements"
            ],
            "usedBy": [
              "publishAnnouncementAction"
            ]
          },
          {
            "name": "archiveAnnouncementById",
            "signature": "(id: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "归档公告（P1-2 新增，从 actions 下沉）",
            "deps": [
              "shared.db",
              "shared.db.schema.announcements"
            ],
            "usedBy": [
              "archiveAnnouncementAction"
            ]
          },
          {
            "name": "toggleAnnouncementPin",
            "signature": "(id: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13d 新增：切换公告置顶状态（查询当前 isPinned 后取反更新）",
            "deps": [
              "shared.db",
              "shared.db.schema.announcements"
            ],
            "usedBy": [
              "toggleAnnouncementPinAction"
            ]
          },
          {
            "name": "markAnnouncementAsRead",
            "signature": "(announcementId: string, userId: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13d 新增：标记公告为已读（先查后插，依赖唯一索引保证幂等）",
            "deps": [
              "shared.db",
              "shared.db.schema.announcementReads"
            ],
            "usedBy": [
              "markAnnouncementAsReadAction"
            ]
          },
          {
            "name": "isAnnouncementReadByUser",
            "signature": "(announcementId: string, userId: string) => Promise<boolean>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13d 新增：检查用户是否已读指定公告",
            "deps": [
              "shared.db",
              "shared.db.schema.announcementReads"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getAnnouncementReadCount",
            "signature": "(announcementId: string) => Promise<number>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13d 新增：获取公告的已读用户数（管理端统计）",
            "deps": [
              "shared.db",
              "shared.db.schema.announcementReads"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getAnnouncementReadStatusForUser",
            "signature": "(announcementIds: string[], userId: string) => Promise<Map<string, boolean>>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13d 新增：批量获取用户对多个公告的已读状态（列表页标记已读/未读）",
            "deps": [
              "shared.db",
              "shared.db.schema.announcementReads"
            ],
            "usedBy": [
              "getAnnouncementReadStatusAction"
            ]
          }
        ],
        "schemas": [
          {
            "name": "CreateAnnouncementSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ title, content, type?, status?, targetGradeId?, targetClassId?, publishedAt? }",
            "usedBy": [
              "createAnnouncementAction"
            ]
          },
          {
            "name": "UpdateAnnouncementSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ title, content, type?, status?, targetGradeId?, targetClassId?, publishedAt? }",
            "usedBy": [
              "updateAnnouncementAction"
            ]
          }
        ],
        "types": [
          {
            "name": "Announcement",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, title, content, type, status, targetGradeId, targetClassId, authorId, authorName, publishedAt, isPinned, isReadByCurrentUser?, readCount?, createdAt, updatedAt }",
            "usedBy": [
              "announcements/components",
              "页面"
            ]
          },
          {
            "name": "AnnouncementListItem",
            "type": "type",
            "file": "types.ts",
            "definition": "= Announcement",
            "usedBy": [
              "列表页"
            ]
          },
          {
            "name": "AnnouncementStatus",
            "type": "type",
            "file": "types.ts",
            "definition": "\"draft\" | \"published\" | \"archived\"",
            "usedBy": [
              "data-access",
              "components"
            ]
          },
          {
            "name": "AnnouncementType",
            "type": "type",
            "file": "types.ts",
            "definition": "\"school\" | \"grade\" | \"class\"",
            "usedBy": [
              "data-access",
              "components"
            ]
          },
          {
            "name": "GetAnnouncementsParams",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ status?, type?, page?, pageSize? }",
            "usedBy": [
              "getAnnouncements",
              "getAnnouncementsAction"
            ]
          },
          {
            "name": "AnnouncementRead",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, announcementId, userId, readAt }",
            "usedBy": [
              "data-access.announcement_reads 相关函数"
            ]
          }
        ],
        "components": [
          {
            "name": "AnnouncementList",
            "file": "components/announcement-list.tsx",
            "purpose": "公告列表（支持状态筛选）；V2-P1-1 优化：移除客户端 useState/useMemo 过滤，改为纯服务端过滤模式，Select 切换仅更新 URL ?status= 触发 RSC 重新渲染；V3：新增 detailHrefPrefix prop（字符串）替代 detailHrefBuilder 函数 prop，解决 Next.js 16 Server Component 不能向 Client Component 传递函数的序列化限制"
          },
          {
            "name": "AnnouncementCard",
            "file": "components/announcement-card.tsx",
            "purpose": "单条公告卡片"
          },
          {
            "name": "AnnouncementForm",
            "file": "components/announcement-form.tsx",
            "purpose": "创建/编辑表单；V2-P1-4 增强：fieldErrors 状态 + aria-invalid 字段级服务端校验错误展示（title/content/targetGradeId/targetClassId）"
          },
          {
            "name": "AnnouncementDetail",
            "file": "components/announcement-detail.tsx",
            "purpose": "详情查看（含发布/归档/删除操作）"
          },
          {
            "name": "AdminAnnouncementsView",
            "file": "components/admin-announcements-view.tsx",
            "purpose": "管理端公告视图（列表+创建对话框）"
          }
        ]
      }
    },
    "files": {
      "path": "src/modules/files",
      "description": "文件上传与管理：通过 API 路由处理文件上传（保存到 public/uploads/YYYY-MM/），记录文件元数据到 DB，支持按关联资源（exam/textbook/question/announcement）多态查询、下载与删除",
      "exports": {
        "dataAccess": [
          {
            "name": "createFileAttachment",
            "signature": "(data: CreateFileAttachmentInput) => Promise<FileAttachment | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.fileAttachments"
            ],
            "usedBy": [
              "app/api/upload/route.ts"
            ]
          },
          {
            "name": "getFileAttachment",
            "signature": "(id: string) => Promise<FileAttachment | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.fileAttachments"
            ],
            "usedBy": [
              "app/api/files/[id]/route.ts"
            ]
          },
          {
            "name": "getFileAttachmentsByTarget",
            "signature": "(targetType: string, targetId: string) => Promise<FileAttachment[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.fileAttachments"
            ],
            "usedBy": [
              "按关联资源查询文件列表"
            ]
          },
          {
            "name": "getFileAttachmentsByUploader",
            "signature": "(uploaderId: string) => Promise<FileAttachment[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.fileAttachments"
            ],
            "usedBy": [
              "按上传者查询文件列表"
            ]
          },
          {
            "name": "getAllFileAttachments",
            "signature": "(limit?: number) => Promise<FileAttachment[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.fileAttachments"
            ],
            "usedBy": [
              "app/(dashboard)/admin/files/page.tsx"
            ]
          },
          {
            "name": "deleteFileAttachment",
            "signature": "(id: string) => Promise<boolean>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.fileAttachments"
            ],
            "usedBy": [
              "app/api/files/[id]/route.ts"
            ]
          },
          {
            "name": "deleteFileAttachments",
            "signature": "(ids: string[]) => Promise<BatchDeleteResult>",
            "file": "data-access.ts",
            "purpose": "批量删除文件附件记录（仅删 DB 行，磁盘文件由调用方处理；失败时回退到逐条删除）",
            "deps": [
              "shared.db",
              "shared.db.schema.fileAttachments",
              "drizzle-orm.inArray"
            ],
            "usedBy": [
              "app/api/files/batch-delete/route.ts"
            ]
          },
          {
            "name": "getFileAttachmentsWithFilters",
            "signature": "(params: FileAttachmentQueryParams) => Promise<FileAttachment[]>",
            "file": "data-access.ts",
            "purpose": "按 mimeType（精确或前缀匹配）与 search（originalName/filename 模糊匹配）筛选文件列表，支持 limit/offset 分页（v3 修复：conditions 显式标注 SQL[] 类型，消除隐式 any[]）",
            "deps": [
              "shared.db",
              "shared.db.schema.fileAttachments",
              "drizzle-orm.like",
              "drizzle-orm.or",
              "drizzle-orm.and",
              "drizzle-orm.SQL"
            ],
            "usedBy": [
              "app/(dashboard)/admin/files/page.tsx"
            ]
          },
          {
            "name": "getFileStats",
            "signature": "() => Promise<FileStats>",
            "file": "data-access.ts",
            "purpose": "获取文件统计（总数、总大小、按 mimeType 分组的 count/size）",
            "deps": [
              "shared.db",
              "shared.db.schema.fileAttachments",
              "drizzle-orm.count",
              "drizzle-orm.sql"
            ],
            "usedBy": [
              "app/(dashboard)/admin/files/page.tsx"
            ]
          },
          {
            "name": "getFileAttachmentsByIds",
            "signature": "(ids: string[]) => Promise<FileAttachment[]>",
            "file": "data-access.ts",
            "purpose": "按 ID 列表批量查询文件（用于批量删除前获取磁盘路径）",
            "deps": [
              "shared.db",
              "shared.db.schema.fileAttachments",
              "drizzle-orm.inArray"
            ],
            "usedBy": [
              "app/api/files/batch-delete/route.ts"
            ]
          }
        ],
        "types": [
          {
            "name": "FileAttachment",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, filename, originalName, mimeType, size, storagePath, url, uploaderId, targetType, targetId, createdAt }",
            "usedBy": [
              "files/components",
              "data-access",
              "API 路由"
            ]
          },
          {
            "name": "FileUploadResult",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, url, filename, originalName, size, mimeType }",
            "usedBy": [
              "app/api/upload/route.ts 响应",
              "file-upload.tsx 回调"
            ]
          },
          {
            "name": "FileTargetType",
            "type": "type",
            "file": "types.ts",
            "definition": "\"exam\" | \"textbook\" | \"question\" | \"announcement\"",
            "usedBy": [
              "types.FileAttachment.targetType",
              "file-upload.tsx"
            ]
          },
          {
            "name": "CreateFileAttachmentInput",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, filename, originalName, mimeType, size, storagePath, url, uploaderId, targetType?, targetId? }",
            "usedBy": [
              "createFileAttachment"
            ]
          },
          {
            "name": "FileAttachmentQueryParams",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ mimeType?, search?, limit?, offset? }",
            "usedBy": [
              "getFileAttachmentsWithFilters"
            ]
          },
          {
            "name": "FileStats",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ totalCount, totalSize, byType: Array<{ mimeType, count, size }> }",
            "usedBy": [
              "getFileStats"
            ]
          },
          {
            "name": "BatchDeleteResult",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ success, deletedCount, failedIds: string[] }",
            "usedBy": [
              "deleteFileAttachments",
              "app/api/files/batch-delete/route.ts"
            ]
          }
        ],
        "components": [
          {
            "name": "FileUpload",
            "file": "components/file-upload.tsx",
            "purpose": "文件上传组件（拖拽+点击上传，进度条，文件类型校验，调用 /api/upload）"
          },
          {
            "name": "FileList",
            "file": "components/file-list.tsx",
            "purpose": "文件列表展示（图标、文件名、大小、下载链接、删除按钮）"
          },
          {
            "name": "FilePreview",
            "file": "components/file-preview.tsx",
            "purpose": "文件预览（图片直接预览，PDF iframe，其他下载）"
          },
          {
            "name": "FileIcon",
            "file": "components/file-icon.tsx",
            "purpose": "根据 MIME 类型显示不同图标与颜色"
          },
          {
            "name": "AdminFilesView",
            "file": "components/admin-files-view.tsx",
            "purpose": "管理端文件视图（上传+列表+删除）"
          }
        ]
      }
    },
    "grades": {
      "path": "src/modules/grades",
      "description": "成绩分析模块：成绩录入（单条+批量）、查询（按班级/科目/考试/学期过滤）、统计报表（均分、中位数、标准差、及格率、优秀率、排名）、Excel 导出（成绩明细+统计汇总/班级多科目横向对比）、趋势对比分析（成绩趋势、班级对比、科目对比、分数分布、排名趋势）",
      "exports": {
        "dataAccess": [
          {
            "name": "getGradeRecords",
            "signature": "(params: GradeQueryParams & { scope: DataScope; currentUserId?: string; limit?: number; offset?: number }) => Promise<PaginatedGradeRecords>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords",
              "shared.db.schema.classes",
              "shared.db.schema.classEnrollments",
              "shared.db.schema.subjects",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "grades/actions.getGradeRecordsAction"
            ]
          },
          {
            "name": "getGradeRecordById",
            "signature": "(id: string) => Promise<GradeRecord | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords"
            ],
            "usedBy": [
              "grades/actions.getGradeRecordByIdAction"
            ]
          },
          {
            "name": "createGradeRecord",
            "signature": "(data: CreateGradeRecordInput, recordedBy: string) => Promise<string>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords"
            ],
            "usedBy": [
              "grades/actions.createGradeRecordAction"
            ]
          },
          {
            "name": "batchCreateGradeRecords",
            "signature": "(data: BatchCreateGradeRecordInput, recordedBy: string) => Promise<number>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords"
            ],
            "usedBy": [
              "grades/actions.batchCreateGradeRecordsAction"
            ]
          },
          {
            "name": "updateGradeRecord",
            "signature": "(id: string, data: UpdateGradeRecordInput) => Promise<boolean>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords"
            ],
            "usedBy": [
              "grades/actions.updateGradeRecordAction"
            ]
          },
          {
            "name": "deleteGradeRecord",
            "signature": "(id: string) => Promise<boolean>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords"
            ],
            "usedBy": [
              "grades/actions.deleteGradeRecordAction"
            ]
          },
          {
            "name": "bulkDeleteGradeRecords",
            "signature": "(ids: string[]) => Promise<number>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords"
            ],
            "usedBy": [
              "grades/actions.bulkDeleteGradeRecordsAction"
            ]
          },
          {
            "name": "getClassGradeStats",
            "signature": "(classId: string, subjectId?: string, examId?: string, scope?: DataScope, currentUserId?: string) => Promise<GradeStats | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords",
              "grades/lib/scope-filter.buildScopeClassFilter"
            ],
            "usedBy": [
              "grades/data-access.getClassGradeStatsWithMeta"
            ]
          },
          {
            "name": "getClassGradeStatsWithMeta",
            "signature": "(classId: string, subjectId?: string, examId?: string) => Promise<ClassGradeStats>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords",
              "shared.db.schema.classes",
              "shared.db.schema.subjects"
            ],
            "usedBy": [
              "grades/actions.getClassGradeStatsAction"
            ]
          },
          {
            "name": "getStudentGradeSummary",
            "signature": "(studentId: string, scope?: DataScope) => Promise<StudentGradeSummary | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords",
              "shared.db.schema.subjects",
              "classes/data-access.getStudentActiveClassId"
            ],
            "usedBy": [
              "grades/actions.getStudentGradeSummaryAction"
            ]
          },
          {
            "name": "getClassRanking",
            "signature": "(classId: string, subjectId?: string, examId?: string, scope?: DataScope, currentUserId?: string) => Promise<ClassRankingItem[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords",
              "shared.db.schema.users",
              "grades/lib/scope-filter.buildScopeClassFilter",
              "users/data-access.getUserNamesByIds"
            ],
            "usedBy": [
              "grades/actions.getClassRankingAction"
            ]
          },
          {
            "name": "getClassStudentsForEntry",
            "signature": "(classId: string, scope?: DataScope) => Promise<{ id: string; name: string; email: string }[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.classEnrollments",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "grades/components/batch-grade-entry"
            ]
          },
          {
            "name": "getGradeTrend",
            "signature": "(params: { studentId; subjectId?; semester?; examId?; scope: DataScope }) => Promise<GradeTrendResult>",
            "file": "data-access-analytics.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords",
              "shared.db.schema.classEnrollments"
            ],
            "usedBy": [
              "grades/actions-analytics.getGradeTrendAction",
              "teacher/grades/analytics"
            ]
          },
          {
            "name": "getClassComparison",
            "signature": "(params: { gradeId; subjectId; examId?; semester?; scope: DataScope }) => Promise<ClassComparisonItem[]>",
            "file": "data-access-analytics.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords",
              "shared.db.schema.classes"
            ],
            "usedBy": [
              "grades/actions-analytics.getClassComparisonAction",
              "teacher/grades/analytics"
            ]
          },
          {
            "name": "getSubjectComparison",
            "signature": "(params: { classId; examId?; semester?; scope: DataScope }) => Promise<SubjectComparisonItem[]>",
            "file": "data-access-analytics.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords",
              "shared.db.schema.subjects"
            ],
            "usedBy": [
              "grades/actions-analytics.getSubjectComparisonAction",
              "teacher/grades/analytics"
            ]
          },
          {
            "name": "getGradeDistribution",
            "signature": "(params: { classId; subjectId?; examId?; semester?; scope: DataScope }) => Promise<GradeDistributionResult>",
            "file": "data-access-analytics.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords"
            ],
            "usedBy": [
              "grades/actions-analytics.getGradeDistributionAction",
              "teacher/grades/analytics"
            ]
          },
          {
            "name": "getRankingTrend",
            "signature": "(studentId: string, subjectId?, semester?, scope?: DataScope) => Promise<RankingTrendResult | null>",
            "file": "data-access-ranking.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords",
              "shared.db.schema.classEnrollments"
            ],
            "usedBy": [
              "grades/actions-analytics.getRankingTrendAction"
            ]
          },
          {
            "name": "saveGradeDraft",
            "signature": "(params: { userId: string; classId: string; subjectId: string; type: string; data: GradeDraftData }) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "v3-P2 新增：保存成绩录入草稿到 DB（upsert，按 userId+classId+subjectId+type 唯一键）",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeDrafts"
            ],
            "usedBy": [
              "grades/actions.saveGradeDraftAction"
            ]
          },
          {
            "name": "getGradeDraft",
            "signature": "(params: { userId: string; classId: string; subjectId: string; type: string }) => Promise<GradeDraftData | null>",
            "file": "data-access.ts",
            "purpose": "v3-P2 新增：获取成绩录入草稿（24 小时过期，超期返回 null）",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeDrafts"
            ],
            "usedBy": [
              "grades/actions.getGradeDraftAction"
            ]
          },
          {
            "name": "deleteGradeDraft",
            "signature": "(params: { userId: string; classId: string; subjectId: string; type: string }) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "v3-P2 新增：删除成绩录入草稿",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeDrafts"
            ],
            "usedBy": [
              "grades/actions.deleteGradeDraftAction"
            ]
          },
          {
            "name": "getExamOptionsForGrades",
            "signature": "(params: { classId?: string; subjectId?: string }) => Promise<{ id: string; title: string }[]>",
            "file": "data-access-analytics.ts",
            "purpose": "v3-P2 新增：获取指定班级/科目下有成绩记录的考试列表（供分析页考试筛选）",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords"
            ],
            "usedBy": [
              "teacher/grades/analytics/page.tsx"
            ]
          },
          {
            "name": "getSchoolWideGradeSummary",
            "signature": "() => Promise<SchoolWideGradeSummary>",
            "file": "data-access-analytics.ts",
            "purpose": "v3-P2 新增：获取全校各年级成绩汇总（管理员视图，按年级聚合平均分/及格率/优秀率/学生数/班级数，加权平均计算全校汇总）",
            "deps": [
              "shared.db",
              "shared.db.schema.gradeRecords",
              "shared.db.schema.classes",
              "shared.db.schema.subjects"
            ],
            "usedBy": [
              "admin/school/grades/insights/page.tsx"
            ]
          }
        ],
        "actions": [
          {
            "name": "createGradeRecordAction",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_MANAGE",
            "purpose": "创建单条成绩记录。v4-P1-6 增强：成绩录入后通知学生和家长（调用 notifyGradeEntered，内部使用 parent/data-access.getParentIdsByStudentIds 批量查询家长 ID），通知失败不阻断录入；v3-P1-5：录入后更新诊断掌握度（updateMasteryFromExamScore）",
            "deps": [
              "requirePermission",
              "data-access.createGradeRecord",
              "diagnostic/data-access.updateMasteryFromExamScore",
              "grades/lib/notify-grade-entered.notifyGradeEntered",
              "revalidatePath"
            ],
            "usedBy": [
              "grades/components/grade-record-form"
            ]
          },
          {
            "name": "batchCreateGradeRecordsAction",
            "signature": "(prevState, formData) => Promise<ActionState<number>>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_MANAGE",
            "purpose": "批量创建成绩记录（db.transaction 原子操作）。v4-P1-6 增强：批量录入后通知所有相关学生和家长（调用 notifyGradeEntered）；v3-P1-5：录入后更新诊断掌握度；v3-P2-3：返回创建的记录 ID 列表供前端撤销",
            "deps": [
              "requirePermission",
              "data-access.batchCreateGradeRecords",
              "diagnostic/data-access.updateMasteryFromExamScore",
              "grades/lib/notify-grade-entered.notifyGradeEntered",
              "shared/lib/action-utils.safeJsonParse",
              "revalidatePath"
            ],
            "usedBy": [
              "grades/components/batch-grade-entry"
            ]
          },
          {
            "name": "updateGradeRecordAction",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_MANAGE",
            "usedBy": [
              "grades/components/grade-record-list"
            ]
          },
          {
            "name": "deleteGradeRecordAction",
            "signature": "(prevState, formData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_MANAGE",
            "usedBy": [
              "grades/components/grade-record-list"
            ]
          },
          {
            "name": "bulkDeleteGradeRecordsAction",
            "signature": "(ids: string[]) => Promise<ActionState<number>>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_MANAGE",
            "usedBy": [
              "grades/components/grade-record-list"
            ]
          },
          {
            "name": "getGradeRecordsAction",
            "signature": "(params) => Promise<GradeRecordListItem[]>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_READ",
            "usedBy": [
              "teacher/grades/page"
            ]
          },
          {
            "name": "getClassGradeStatsAction",
            "signature": "(classId, subjectId?, examId?) => Promise<ClassGradeStats>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_READ",
            "usedBy": [
              "teacher/grades/stats/page"
            ]
          },
          {
            "name": "getStudentGradeSummaryAction",
            "signature": "(studentId?) => Promise<StudentGradeSummary>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_READ",
            "usedBy": [
              "student/grades/page",
              "parent/grades/page"
            ]
          },
          {
            "name": "getClassRankingAction",
            "signature": "(classId, subjectId?, examId?) => Promise<ClassRankingItem[]>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_READ",
            "usedBy": [
              "teacher/grades/stats/page"
            ]
          },
          {
            "name": "getGradeRecordByIdAction",
            "signature": "(id) => Promise<GradeRecord | null>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_READ",
            "usedBy": [
              "grades/components/grade-record-list"
            ]
          },
          {
            "name": "exportGradesAction",
            "signature": "(params: { classId?: string; studentId?: string; subjectId?: string; examId?: string; reportType?: \"detail\" | \"class\" }) => Promise<ActionState<{ buffer: string; filename: string }>>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_READ",
            "purpose": "导出成绩到 Excel（detail=成绩明细+统计汇总，class=班级多科目横向对比总表），返回 base64 buffer。v4-P1-12 增强：新增可选 studentId 参数支持按学生导出（家长视角）——当提供 studentId 且 scope 为 children 时校验该学生属于家长子女，调用 exportStudentGradeRecordsToExcel 导出单学生成绩单",
            "deps": [
              "requirePermission",
              "export.exportGradeRecordsToExcel",
              "export.exportClassGradeReportToExcel",
              "export.exportStudentGradeRecordsToExcel",
              "export.formatDateForFile",
              "actions.assertClassInScope"
            ],
            "usedBy": [
              "grades/components/export-button.tsx",
              "parent/components/parent-export-button.tsx"
            ]
          },
          {
            "name": "getGradeTrendAction",
            "signature": "(params) => Promise<GradeTrendResult>",
            "file": "actions-analytics.ts",
            "permission": "GRADE_RECORD_READ",
            "purpose": "获取成绩趋势（按学生/科目/学期，返回归一化分数趋势点）",
            "usedBy": [
              "teacher/grades/analytics"
            ]
          },
          {
            "name": "getClassComparisonAction",
            "signature": "(params) => Promise<ClassComparisonItem[]>",
            "file": "actions-analytics.ts",
            "permission": "GRADE_RECORD_READ",
            "purpose": "获取班级对比（同年级各班的均分/及格率/优秀率）",
            "usedBy": [
              "teacher/grades/analytics"
            ]
          },
          {
            "name": "getSubjectComparisonAction",
            "signature": "(params) => Promise<SubjectComparisonItem[]>",
            "file": "actions-analytics.ts",
            "permission": "GRADE_RECORD_READ",
            "purpose": "获取科目对比（同班级各科目雷达图数据）",
            "usedBy": [
              "teacher/grades/analytics"
            ]
          },
          {
            "name": "getGradeDistributionAction",
            "signature": "(params) => Promise<GradeDistributionResult>",
            "file": "actions-analytics.ts",
            "permission": "GRADE_RECORD_READ",
            "purpose": "获取分数分布（90-100/80-89/70-79/60-69/<60 各区间人数）",
            "usedBy": [
              "teacher/grades/analytics"
            ]
          },
          {
            "name": "getRankingTrendAction",
            "signature": "(studentId, subjectId?, semester?) => Promise<RankingTrendResult | null>",
            "file": "actions-analytics.ts",
            "permission": "GRADE_RECORD_READ",
            "purpose": "获取排名趋势（学生历次考试排名变化，含 DataScope 二次校验）",
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "assertClassInScope",
            "signature": "(classId: string, ctx: AuthContext) => void",
            "file": "actions.ts",
            "permission": "internal",
            "purpose": "P3 新增：校验 classId 是否在 ctx.dataScope 允许范围内，不在则抛 BusinessError。供 actions.ts 与 actions-analytics.ts 复用",
            "usedBy": [
              "grades/actions.getGradeRecordsAction",
              "grades/actions.getGradeRecordByIdAction",
              "grades/actions.exportGradesAction",
              "grades/actions-analytics.getGradeTrendAction",
              "grades/actions-analytics.getSubjectComparisonAction",
              "grades/actions-analytics.getGradeDistributionAction"
            ]
          },
          {
            "name": "saveGradeDraftAction",
            "signature": "(params: { classId: string; subjectId: string; type: string; data: GradeDraftData }) => Promise<ActionState<void>>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_MANAGE",
            "purpose": "v3-P2 新增：保存成绩录入草稿到服务端（调用 data-access.saveGradeDraft upsert）",
            "deps": [
              "requirePermission",
              "data-access.saveGradeDraft"
            ],
            "usedBy": [
              "grades/components/batch-grade-entry"
            ]
          },
          {
            "name": "getGradeDraftAction",
            "signature": "(params: { classId: string; subjectId: string; type: string }) => Promise<ActionState<GradeDraftData | null>>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_READ",
            "purpose": "v3-P2 新增：获取服务端成绩录入草稿（24 小时过期）",
            "deps": [
              "requirePermission",
              "data-access.getGradeDraft"
            ],
            "usedBy": [
              "grades/components/batch-grade-entry"
            ]
          },
          {
            "name": "deleteGradeDraftAction",
            "signature": "(params: { classId: string; subjectId: string; type: string }) => Promise<ActionState<void>>",
            "file": "actions.ts",
            "permission": "GRADE_RECORD_MANAGE",
            "purpose": "v3-P2 新增：删除服务端成绩录入草稿",
            "deps": [
              "requirePermission",
              "data-access.deleteGradeDraft"
            ],
            "usedBy": [
              "grades/components/batch-grade-entry"
            ]
          }
        ],
        "schemas": [
          {
            "name": "CreateGradeRecordSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "createGradeRecordAction"
            ]
          },
          {
            "name": "BatchCreateGradeRecordSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "batchCreateGradeRecordsAction"
            ]
          },
          {
            "name": "UpdateGradeRecordSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "updateGradeRecordAction"
            ]
          },
          {
            "name": "DeleteGradeRecordSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "deleteGradeRecordAction"
            ]
          },
          {
            "name": "GetGradeRecordByIdSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "getGradeRecordByIdAction"
            ]
          },
          {
            "name": "GradeQuerySchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "getGradeRecordsAction"
            ]
          },
          {
            "name": "ClassGradeStatsQuerySchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "getClassGradeStatsAction"
            ]
          },
          {
            "name": "StudentGradeSummaryQuerySchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "getStudentGradeSummaryAction"
            ]
          },
          {
            "name": "ClassRankingQuerySchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "getClassRankingAction"
            ]
          },
          {
            "name": "ExportGradesSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "exportGradesAction"
            ]
          },
          {
            "name": "GradeTrendQuerySchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "getGradeTrendAction"
            ]
          },
          {
            "name": "ClassComparisonQuerySchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "getClassComparisonAction"
            ]
          },
          {
            "name": "SubjectComparisonQuerySchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "getSubjectComparisonAction"
            ]
          },
          {
            "name": "GradeDistributionQuerySchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "getGradeDistributionAction"
            ]
          },
          {
            "name": "RankingTrendQuerySchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "usedBy": [
              "getRankingTrendAction"
            ]
          }
        ],
        "types": [
          {
            "name": "GradeRecord",
            "type": "interface",
            "file": "types.ts",
            "usedBy": [
              "data-access",
              "actions"
            ]
          },
          {
            "name": "GradeRecordListItem",
            "type": "interface",
            "file": "types.ts",
            "usedBy": [
              "data-access",
              "actions",
              "components/grade-record-list"
            ]
          },
          {
            "name": "PaginatedGradeRecords",
            "type": "interface",
            "file": "data-access.ts",
            "definition": "{ records: GradeRecordListItem[]; total: number }",
            "usedBy": [
              "data-access.getGradeRecords",
              "actions.getGradeRecordsAction",
              "teacher/grades/page",
              "export.exportGradeRecordsToExcel"
            ]
          },
          {
            "name": "GradeStats",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ count, average, median, stdDev, passRate, excellentRate, maxScore, minScore }",
            "usedBy": [
              "data-access",
              "components/grade-stats-card"
            ]
          },
          {
            "name": "ClassGradeStats",
            "type": "interface",
            "file": "types.ts",
            "usedBy": [
              "data-access",
              "actions",
              "components/class-grade-report"
            ]
          },
          {
            "name": "StudentGradeSummary",
            "type": "interface",
            "file": "types.ts",
            "usedBy": [
              "data-access",
              "actions",
              "components/student-grade-summary"
            ]
          },
          {
            "name": "ClassRankingItem",
            "type": "interface",
            "file": "types.ts",
            "usedBy": [
              "data-access",
              "actions",
              "components/class-grade-report"
            ]
          },
          {
            "name": "GradeRecordType",
            "type": "type",
            "file": "types.ts",
            "definition": "\"exam\" | \"quiz\" | \"assignment\" | \"monthly\" | \"midterm\" | \"final\"",
            "usedBy": [
              "types.GradeRecord.type"
            ]
          },
          {
            "name": "GradeRecordSemester",
            "type": "type",
            "file": "types.ts",
            "definition": "\"1\" | \"2\"",
            "usedBy": [
              "types.GradeRecord.semester"
            ]
          },
          {
            "name": "GradeQueryParams",
            "type": "interface",
            "file": "types.ts",
            "usedBy": [
              "data-access.getGradeRecords"
            ]
          },
          {
            "name": "GradeTrendPoint",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ date, title, score, fullScore, normalizedScore, type }",
            "usedBy": [
              "data-access-analytics.getGradeTrend",
              "grade-trend-chart"
            ]
          },
          {
            "name": "GradeTrendResult",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ label, points: GradeTrendPoint[], averageScore }",
            "usedBy": [
              "data-access-analytics.getGradeTrend",
              "grade-trend-chart"
            ]
          },
          {
            "name": "ClassComparisonItem",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ classId, className, averageScore, passRate, excellentRate, studentCount }",
            "usedBy": [
              "data-access-analytics.getClassComparison",
              "class-comparison-chart"
            ]
          },
          {
            "name": "SubjectComparisonItem",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ subjectId, subjectName, averageScore, passRate, excellentRate }",
            "usedBy": [
              "data-access-analytics.getSubjectComparison",
              "subject-comparison-chart"
            ]
          },
          {
            "name": "GradeDistributionBucket",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ label, min, max, count, percentage }",
            "usedBy": [
              "data-access-analytics.getGradeDistribution",
              "grade-distribution-chart"
            ]
          },
          {
            "name": "GradeDistributionResult",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ buckets: GradeDistributionBucket[], totalCount }",
            "usedBy": [
              "data-access-analytics.getGradeDistribution",
              "grade-distribution-chart"
            ]
          },
          {
            "name": "RankingTrendPoint",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ title, date, rank, totalStudents, score }",
            "usedBy": [
              "data-access-ranking.getRankingTrend"
            ]
          },
          {
            "name": "RankingTrendResult",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ studentName, points: RankingTrendPoint[] }",
            "usedBy": [
              "data-access-ranking.getRankingTrend"
            ]
          },
          {
            "name": "SchoolWideGradeSummaryItem",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ gradeId, gradeName, schoolName, classCount, studentCount, averageScore, passRate, excellentRate, recordCount }",
            "purpose": "v3-P2 新增：全校汇总按年级聚合项",
            "usedBy": [
              "data-access-analytics.getSchoolWideGradeSummary",
              "components/school-wide-summary-card"
            ]
          },
          {
            "name": "SchoolWideGradeSummary",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ grades: SchoolWideGradeSummaryItem[]; totals: SchoolWideGradeSummaryItem }",
            "purpose": "v3-P2 新增：全校汇总（grades 数组 + totals 汇总对象，加权平均计算全校汇总）",
            "usedBy": [
              "data-access-analytics.getSchoolWideGradeSummary",
              "components/school-wide-summary-card",
              "admin/school/grades/insights/page.tsx"
            ]
          },
          {
            "name": "GradeDraftData",
            "type": "interface",
            "file": "data-access.ts",
            "definition": "{ scores: Record<string, string>; timestamp: number }",
            "purpose": "v3-P2 新增：成绩录入草稿数据结构（scores 为学生 ID 到分数字符串的映射，timestamp 为草稿创建时间戳）",
            "usedBy": [
              "data-access.saveGradeDraft",
              "data-access.getGradeDraft",
              "actions.saveGradeDraftAction",
              "actions.getGradeDraftAction",
              "components/batch-grade-entry"
            ]
          }
        ],
        "importExport": [
          {
            "name": "exportGradeRecordsToExcel",
            "signature": "(params: { classId: string; subjectId?: string; examId?: string; scope: DataScope; currentUserId?: string }) => Promise<Buffer>",
            "file": "export.ts",
            "purpose": "导出成绩单（Sheet1 成绩明细，Sheet2 统计汇总：均分/中位数/最高分/最低分/标准差/及格率/优秀率/参考人数）（P3 更新：传递 scope/currentUserId 到 data-access；P3-7：硬编码中文改用 next-intl getTranslations）",
            "deps": [
              "shared.lib.excel.exportToExcel",
              "data-access.getGradeRecords",
              "data-access.getClassGradeStats",
              "next-intl/server.getTranslations"
            ],
            "usedBy": [
              "actions.exportGradesAction",
              "app/api/export/route.ts"
            ]
          },
          {
            "name": "exportClassGradeReportToExcel",
            "signature": "(params: { classId: string; scope: DataScope; currentUserId?: string }) => Promise<Buffer>",
            "file": "export.ts",
            "purpose": "导出班级成绩总表（多科目横向对比，含总分/平均分/排名列）（P3 更新：适配 PaginatedGradeRecords 结构 + 传递 scope/currentUserId；P3-6：复用 stats-service.computeAverageScore 替代局部 avg；P3-7：硬编码中文改用 next-intl getTranslations）",
            "deps": [
              "shared.db",
              "shared.db.schema.classes",
              "shared.db.schema.subjects",
              "shared.db.schema.gradeRecords",
              "shared.db.schema.users",
              "shared.lib.excel.exportToExcel",
              "data-access.getGradeRecords",
              "stats-service.computeAverageScore",
              "next-intl/server.getTranslations"
            ],
            "usedBy": [
              "actions.exportGradesAction"
            ]
          },
          {
            "name": "formatDateForFile",
            "signature": "(d?: Date) => string",
            "file": "export.ts",
            "purpose": "⚠️ P1-c/P2-c 已迁移：本地实现已删除，改为从 @/shared/lib/utils 导入。此条目保留仅作历史记录",
            "deps": [
              "shared/lib/utils.formatDateForFile"
            ],
            "usedBy": [],
            "migratedTo": "shared/lib/utils.formatDateForFile"
          }
        ],
        "lib": [
          {
            "name": "toNumber",
            "signature": "(v: unknown) => number",
            "file": "lib/grade-utils.ts",
            "purpose": "安全将 unknown 值转换为有限数字，非有限值返回 0（P1-2 新增：从 data-access/data-access-analytics/data-access-ranking 抽取）",
            "usedBy": [
              "data-access.serializeRecord",
              "data-access.getClassGradeStats",
              "data-access-analytics.getGradeTrend",
              "data-access-analytics.getClassComparison",
              "data-access-analytics.getSubjectComparison",
              "data-access-analytics.getGradeDistribution",
              "data-access-ranking.getRankingTrend"
            ]
          },
          {
            "name": "normalize",
            "signature": "(score: number, fullScore: number) => number",
            "file": "lib/grade-utils.ts",
            "purpose": "将原始分数归一化到 0-100 分制（P1-2 新增：从 data-access-analytics/data-access-ranking 抽取）",
            "usedBy": [
              "data-access-analytics.getGradeTrend",
              "data-access-analytics.getSubjectComparison",
              "data-access-analytics.getGradeDistribution",
              "data-access-ranking.getRankingTrend"
            ]
          },
          {
            "name": "buildScopeClassFilter",
            "signature": "(scope: DataScope, currentUserId?: string) => SQL | null",
            "file": "lib/scope-filter.ts",
            "purpose": "根据 DataScope 构建 gradeRecords 表的行级权限过滤条件（P1-2 新增：从 data-access/data-access-analytics 抽取；P3 更新：class_members scope 内置 studentId 过滤，需传入 currentUserId 参数；P3-26：从 grade-utils.ts 迁移至 scope-filter.ts）",
            "usedBy": [
              "data-access.getGradeRecords",
              "data-access.getClassGradeStats",
              "data-access.getStudentGradeSummary",
              "data-access.getClassRanking",
              "data-access-analytics.getGradeTrend",
              "data-access-analytics.getClassComparison",
              "data-access-analytics.getSubjectComparison",
              "data-access-analytics.getGradeDistribution",
              "data-access-ranking.getRankingTrend"
            ]
          }
        ],
        "statsService": [
          {
            "name": "computeGradeStats",
            "signature": "(rows: RawScoreRow[]) => GradeStats | null",
            "file": "stats-service.ts",
            "purpose": "从原始成绩行计算班级统计（均分、中位数、标准差、及格率、优秀率、最高分、最低分、参考人数）（P1-1 新增：从 data-access.getClassGradeStats 抽取为纯函数）",
            "usedBy": [
              "data-access.getClassGradeStats"
            ]
          },
          {
            "name": "computeAverageScore",
            "signature": "(scores: number[]) => number",
            "file": "stats-service.ts",
            "purpose": "计算分数平均值（空数组返回 0）（P1-1 新增：从 data-access.getStudentGradeSummary 抽取为纯函数；P3-6：export.ts 复用此函数替代局部 avg）",
            "usedBy": [
              "data-access.getStudentGradeSummary",
              "export.exportClassGradeReportToExcel"
            ]
          },
          {
            "name": "buildGradeTrendPoints",
            "signature": "(rows: RawScoreRow[]) => GradeTrendPoint[]",
            "file": "stats-service.ts",
            "purpose": "构建成绩趋势数据点（按考试标题分组，归一化分数 0-100）（P1-1 新增：从 data-access-analytics.getGradeTrend 抽取为纯函数；P3-24：使用 isGradeTrendType 类型守卫替代 as 断言）",
            "usedBy": [
              "data-access-analytics.getGradeTrend"
            ]
          },
          {
            "name": "computeTrendAverage",
            "signature": "(points: GradeTrendPoint[]) => number",
            "file": "stats-service.ts",
            "purpose": "计算趋势数据点的平均分（P1-1 新增：从 data-access-analytics.getGradeTrend 抽取为纯函数）",
            "usedBy": [
              "data-access-analytics.getGradeTrend"
            ]
          },
          {
            "name": "computeClassComparisonStats",
            "signature": "(rows: RawScoreRow[]) => Pick<ClassComparisonItem, 'averageScore' | 'passRate' | 'excellentRate' | 'studentCount'>",
            "file": "stats-service.ts",
            "purpose": "计算班级对比统计（均分、及格率、优秀率、参考人数）（P1-1 新增：从 data-access-analytics.getClassComparison 抽取为纯函数）",
            "usedBy": [
              "data-access-analytics.getClassComparison"
            ]
          },
          {
            "name": "computeSubjectComparisonStats",
            "signature": "(scores: number[]) => Pick<SubjectComparisonItem, 'averageScore' | 'passRate' | 'excellentRate' | 'maxScore' | 'minScore'>",
            "file": "stats-service.ts",
            "purpose": "计算科目对比统计（均分、及格率、优秀率、最高分、最低分）（P1-1 新增：从 data-access-analytics.getSubjectComparison 抽取为纯函数）",
            "usedBy": [
              "data-access-analytics.getSubjectComparison"
            ]
          },
          {
            "name": "computeGradeDistribution",
            "signature": "(rows: RawScoreRow[]) => GradeDistributionResult",
            "file": "stats-service.ts",
            "purpose": "计算分数分布（90-100/80-89/70-79/60-69/<60 五个区间）（P1-1 新增：从 data-access-analytics.getGradeDistribution 抽取为纯函数）",
            "usedBy": [
              "data-access-analytics.getGradeDistribution"
            ]
          },
          {
            "name": "buildRankingTrendPoints",
            "signature": "(byTitle: Map<string, RankingTrendEntry>, targetStudentId: string) => RankingTrendPoint[]",
            "file": "stats-service.ts",
            "purpose": "构建排名趋势数据点（按考试标题排序、计算每次考试学生排名）（P1-1 新增：从 data-access-ranking.getRankingTrend 抽取为纯函数）",
            "usedBy": [
              "data-access-ranking.getRankingTrend"
            ]
          }
        ],
        "components": [
          {
            "name": "GradeRecordForm",
            "file": "components/grade-record-form.tsx",
            "purpose": "单条成绩录入表单（选择学生、班级、科目、考试，输入分数、满分、类型、学期、备注）"
          },
          {
            "name": "BatchGradeEntry",
            "file": "components/batch-grade-entry.tsx",
            "purpose": "批量录入界面（选择班级+科目+考试，表格形式录入每个学生分数）"
          },
          {
            "name": "GradeRecordList",
            "file": "components/grade-record-list.tsx",
            "purpose": "成绩列表（含查询筛选、删除对话框）"
          },
          {
            "name": "GradeStatsCard",
            "file": "components/grade-stats-card.tsx",
            "purpose": "统计卡片（均分、中位数、标准差、及格率、优秀率、最高分、最低分）"
          },
          {
            "name": "GradeQueryFilters",
            "file": "components/grade-query-filters.tsx",
            "purpose": "查询筛选器（班级、科目、考试类型、学期）"
          },
          {
            "name": "StudentGradeSummary",
            "file": "components/student-grade-summary.tsx",
            "purpose": "学生成绩汇总视图（按科目分组展示成绩趋势）"
          },
          {
            "name": "ClassGradeReport",
            "file": "components/class-grade-report.tsx",
            "purpose": "班级成绩报表（含统计+排名）"
          },
          {
            "name": "ExportButton",
            "file": "components/export-button.tsx",
            "purpose": "成绩导出按钮（DropdownMenu 选择 detail/class 报表类型，调用 exportGradesAction 并触发浏览器下载）",
            "usedBy": [
              "teacher/grades/page.tsx",
              "teacher/grades/stats/page.tsx"
            ]
          },
          {
            "name": "GradeTrendChart",
            "file": "components/grade-trend-chart.tsx",
            "purpose": "成绩趋势折线图（recharts LineChart，归一化分数 0-100）",
            "deps": [
              "recharts",
              "shared/components/ui/chart"
            ]
          },
          {
            "name": "ClassComparisonChart",
            "file": "components/class-comparison-chart.tsx",
            "purpose": "班级对比柱状图（recharts BarChart，均分/及格率/优秀率；v3-P3-5 新增：显著性分析区域，基于极差和样本量经验规则判断班级间差异是否显著，含可折叠详细分析展示最高/最低分班级）",
            "deps": [
              "recharts",
              "shared/components/ui/chart",
              "shared/components/ui/collapsible",
              "shared/lib/utils"
            ]
          },
          {
            "name": "SubjectComparisonChart",
            "file": "components/subject-comparison-chart.tsx",
            "purpose": "科目对比雷达图（recharts RadarChart）",
            "deps": [
              "recharts",
              "shared/components/ui/chart"
            ]
          },
          {
            "name": "GradeDistributionChart",
            "file": "components/grade-distribution-chart.tsx",
            "purpose": "分数分布柱状图（recharts BarChart，彩色区间 90-100/80-89/70-79/60-69/<60；v4-P3-4 改进：每个分数段使用不同 SVG pattern + 颜色双重编码，色盲友好）",
            "deps": [
              "recharts",
              "shared/components/ui/chart"
            ]
          },
          {
            "name": "AnalyticsFilters",
            "file": "components/analytics-filters.tsx",
            "purpose": "成绩分析页筛选器（班级、科目、年级 Link 筛选按钮组，含 focus-visible 焦点样式；v3-P2 新增：exams/currentExamId/currentSemester props，添加学期和考试筛选 ChipNav）",
            "props": "{ classId?, gradeId?, subjectId?, currentSemester?, currentExamId?, exams: { id, title }[] }",
            "deps": [
              "next/link",
              "shared/lib/utils.cn",
              "shared/components/ui/chip-nav"
            ],
            "usedBy": [
              "teacher/grades/analytics/page.tsx"
            ]
          },
          {
            "name": "StatsClassSelector",
            "file": "components/stats-class-selector.tsx",
            "purpose": "统计页班级+科目筛选器（Link 筛选按钮组，含 focus-visible 焦点样式）",
            "deps": [
              "next/link",
              "shared/lib/utils.cn"
            ],
            "usedBy": [
              "teacher/grades/stats/page.tsx"
            ]
          },
          {
            "name": "WidgetBoundary",
            "file": "components/widget-boundary.tsx",
            "purpose": "通用 Widget 边界组件（Error Boundary + Suspense + Skeleton 组合，含 a11y 属性 role=alert/aria-live/aria-label）（P1-5 新增）",
            "deps": [
              "shared/components/ui/skeleton",
              "shared/components/ui/button"
            ]
          },
          {
            "name": "SchoolWideSummaryCard",
            "file": "components/school-wide-summary-card.tsx",
            "purpose": "v3-P2 新增：管理员全校成绩汇总卡片（4 个统计卡片：年级数/班级数/学生数/成绩记录数 + 各年级对比表格：年级/班级数/学生数/平均分/及格率/优秀率/记录数）",
            "props": "{ summary: SchoolWideGradeSummary }",
            "deps": [
              "shared/components/ui/card",
              "shared/components/ui/stat-item",
              "shared/components/ui/table"
            ],
            "usedBy": [
              "admin/school/grades/insights/page.tsx"
            ]
          },
          {
            "name": "ScoreCell",
            "file": "components/score-cell.tsx",
            "purpose": "v4-P1-7 新增：成绩单元格组件，根据得分率着色——红<60%（不及格 text-red-600）/黄60-84%（及格未优秀 text-yellow-600）/绿≥85%（优秀 text-green-600），使用语义化 Tailwind 类名避免动态拼接，fullScore<=0 时不着色（避免除零）",
            "props": "{ score: number; fullScore: number; className?: string }",
            "deps": [
              "shared/lib/utils.cn"
            ],
            "usedBy": [
              "grades/components/grade-record-list.tsx"
            ]
          }
        ]
      }
    },
    "course-plans": {
      "path": "src/modules/course-plans",
      "description": "课程计划管理：创建、编辑、删除课程计划（含周计划条目），管理员可管理全部，教师/学生/年级主任/教务主任可查看",
      "exports": {
        "actions": [
          {
            "name": "createCoursePlanAction",
            "permission": "COURSE_PLAN_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "创建课程计划",
            "deps": [
              "requirePermission",
              "shared/db",
              "data-access.createCoursePlan"
            ],
            "usedBy": [
              "course-plan-form.tsx"
            ]
          },
          {
            "name": "updateCoursePlanAction",
            "permission": "COURSE_PLAN_MANAGE",
            "signature": "(id: string, prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "更新课程计划",
            "deps": [
              "requirePermission",
              "shared/db",
              "data-access.updateCoursePlan"
            ],
            "usedBy": [
              "course-plan-form.tsx"
            ]
          },
          {
            "name": "deleteCoursePlanAction",
            "permission": "COURSE_PLAN_MANAGE",
            "signature": "(id: string) => Promise<ActionState<string>>",
            "purpose": "删除课程计划",
            "deps": [
              "requirePermission",
              "shared/db",
              "data-access.deleteCoursePlan"
            ],
            "usedBy": [
              "course-plan-detail.tsx"
            ]
          },
          {
            "name": "getCoursePlansAction",
            "permission": "COURSE_PLAN_READ",
            "signature": "(params?: GetCoursePlansParams) => Promise<ActionState<CoursePlanListItem[]>>",
            "purpose": "获取课程计划列表",
            "deps": [
              "requirePermission",
              "data-access.getCoursePlans"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getCoursePlanAction",
            "permission": "COURSE_PLAN_READ",
            "signature": "(id: string) => Promise<ActionState<CoursePlanWithItems>>",
            "purpose": "获取课程计划详情（含周计划条目）",
            "deps": [
              "requirePermission",
              "data-access.getCoursePlanById"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "createCoursePlanItemAction",
            "permission": "COURSE_PLAN_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "创建周计划条目",
            "deps": [
              "requirePermission",
              "shared/db",
              "data-access.createCoursePlanItem"
            ],
            "usedBy": [
              "course-plan-item-editor.tsx"
            ]
          },
          {
            "name": "updateCoursePlanItemAction",
            "permission": "COURSE_PLAN_MANAGE",
            "signature": "(id: string, prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "更新周计划条目",
            "deps": [
              "requirePermission",
              "shared/db",
              "data-access.updateCoursePlanItem"
            ],
            "usedBy": [
              "course-plan-item-editor.tsx"
            ]
          },
          {
            "name": "deleteCoursePlanItemAction",
            "permission": "COURSE_PLAN_MANAGE",
            "signature": "(id: string) => Promise<ActionState<string>>",
            "purpose": "删除周计划条目",
            "deps": [
              "requirePermission",
              "shared/db",
              "data-access.deleteCoursePlanItem"
            ],
            "usedBy": [
              "course-plan-item-editor.tsx"
            ]
          },
          {
            "name": "toggleCoursePlanItemCompletedAction",
            "permission": "COURSE_PLAN_MANAGE",
            "signature": "(id: string, completed: boolean) => Promise<ActionState<string>>",
            "purpose": "切换周计划条目完成状态",
            "deps": [
              "requirePermission",
              "shared/db",
              "data-access.updateCoursePlanItem"
            ],
            "usedBy": [
              "course-plan-detail.tsx"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getCoursePlans",
            "signature": "(params?: GetCoursePlansParams) => Promise<CoursePlanListItem[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.coursePlans",
              "shared.db.schema.classes",
              "shared.db.schema.subjects",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "admin/course-plans/page.tsx",
              "teacher/course-plans/page.tsx"
            ]
          },
          {
            "name": "getCoursePlanById",
            "signature": "(id: string) => Promise<CoursePlanWithItems | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.coursePlans",
              "shared.db.schema.coursePlanItems",
              "shared.db.schema.classes",
              "shared.db.schema.subjects",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "admin/course-plans/[id]/page.tsx",
              "teacher/course-plans/[id]/page.tsx"
            ]
          },
          {
            "name": "createCoursePlan",
            "signature": "(data: CreateCoursePlanInput, createdBy: string) => Promise<string>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.coursePlans",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "createCoursePlanAction"
            ]
          },
          {
            "name": "updateCoursePlan",
            "signature": "(id: string, data: Partial<UpdateCoursePlanInput>) => Promise<void>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.coursePlans"
            ],
            "usedBy": [
              "updateCoursePlanAction"
            ]
          },
          {
            "name": "deleteCoursePlan",
            "signature": "(id: string) => Promise<void>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.coursePlans"
            ],
            "usedBy": [
              "deleteCoursePlanAction"
            ]
          },
          {
            "name": "createCoursePlanItem",
            "signature": "(data: CreateCoursePlanItemInput) => Promise<string>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.coursePlanItems",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "createCoursePlanItemAction"
            ]
          },
          {
            "name": "updateCoursePlanItem",
            "signature": "(id: string, data: Partial<UpdateCoursePlanItemInput>) => Promise<void>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.coursePlanItems"
            ],
            "usedBy": [
              "updateCoursePlanItemAction",
              "toggleCoursePlanItemCompletedAction"
            ]
          },
          {
            "name": "deleteCoursePlanItem",
            "signature": "(id: string) => Promise<void>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.coursePlanItems"
            ],
            "usedBy": [
              "deleteCoursePlanItemAction"
            ]
          },
          {
            "name": "reorderCoursePlanItems",
            "signature": "(planId: string, items: ReorderCoursePlanItemInput[]) => Promise<void>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.coursePlanItems"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getSubjectOptions",
            "signature": "() => Promise<{id:string;name:string}[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.subjects"
            ],
            "usedBy": [
              "admin/course-plans/create/page.tsx",
              "admin/course-plans/[id]/edit/page.tsx"
            ]
          }
        ],
        "schemas": [
          {
            "name": "CreateCoursePlanSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ classId, subjectId, teacherId, academicYearId?, semester?, totalHours?, weeklyHours?, startDate?, endDate?, syllabus?, objectives?, status? }",
            "usedBy": [
              "createCoursePlanAction"
            ]
          },
          {
            "name": "UpdateCoursePlanSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ classId?, subjectId?, teacherId?, academicYearId?, semester?, totalHours?, completedHours?, weeklyHours?, startDate?, endDate?, syllabus?, objectives?, status? }",
            "usedBy": [
              "updateCoursePlanAction"
            ]
          },
          {
            "name": "CreateCoursePlanItemSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ planId, week, topic, content?, hours?, textbookChapter?, notes? }",
            "usedBy": [
              "createCoursePlanItemAction"
            ]
          },
          {
            "name": "UpdateCoursePlanItemSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ week?, topic?, content?, hours?, textbookChapter?, notes?, isCompleted?, completedAt? }",
            "usedBy": [
              "updateCoursePlanItemAction"
            ]
          }
        ],
        "types": [
          {
            "name": "CoursePlan",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, classId, subjectId, teacherId, academicYearId, semester, totalHours, completedHours, weeklyHours, startDate, endDate, syllabus, objectives, status, createdBy, createdAt, updatedAt }",
            "usedBy": [
              "course-plans/components",
              "data-access"
            ]
          },
          {
            "name": "CoursePlanItem",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, planId, week, topic, content, hours, textbookChapter, notes, isCompleted, completedAt, createdAt, updatedAt }",
            "usedBy": [
              "course-plans/components",
              "data-access"
            ]
          },
          {
            "name": "CoursePlanListItem",
            "type": "interface",
            "file": "types.ts",
            "definition": "= CoursePlan & { className, subjectName, teacherName }",
            "usedBy": [
              "列表页",
              "data-access"
            ]
          },
          {
            "name": "CoursePlanWithItems",
            "type": "interface",
            "file": "types.ts",
            "definition": "= CoursePlanListItem & { items: CoursePlanItem[] }",
            "usedBy": [
              "详情页",
              "data-access"
            ]
          },
          {
            "name": "CoursePlanStatus",
            "type": "type",
            "file": "types.ts",
            "definition": "\"planning\" | \"active\" | \"completed\" | \"paused\"",
            "usedBy": [
              "data-access",
              "components"
            ]
          },
          {
            "name": "CoursePlanSemester",
            "type": "type",
            "file": "types.ts",
            "definition": "\"1\" | \"2\"",
            "usedBy": [
              "data-access",
              "components"
            ]
          },
          {
            "name": "GetCoursePlansParams",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ classId?, teacherId?, subjectId?, status? }",
            "usedBy": [
              "getCoursePlans",
              "getCoursePlansAction"
            ]
          },
          {
            "name": "ReorderCoursePlanItemInput",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, week }",
            "usedBy": [
              "reorderCoursePlanItems"
            ]
          }
        ],
        "components": [
          {
            "name": "CoursePlanProgress",
            "file": "components/course-plan-progress.tsx",
            "purpose": "进度条组件（completedHours/totalHours 百分比）"
          },
          {
            "name": "CoursePlanList",
            "file": "components/course-plan-list.tsx",
            "purpose": "课程计划列表（支持状态筛选，URL同步）"
          },
          {
            "name": "CoursePlanForm",
            "file": "components/course-plan-form.tsx",
            "purpose": "创建/编辑表单（班级、科目、教师、学年、学期、状态、课时、日期、大纲、目标）"
          },
          {
            "name": "CoursePlanItemEditor",
            "file": "components/course-plan-item-editor.tsx",
            "purpose": "周计划条目编辑器（Dialog，支持创建/编辑/删除/切换完成）"
          },
          {
            "name": "CoursePlanDetail",
            "file": "components/course-plan-detail.tsx",
            "purpose": "详情视图（计划信息、进度、大纲、目标、周计划表格、删除确认）"
          }
        ]
      }
    },
    "parent": {
      "path": "src/modules/parent",
      "description": "家长端仪表盘：聚合家长关联子女的学习数据（课表、作业、成绩、班级），支持多子女切换查看。家长通过 parentStudentRelations 表关联子女，DataScope 解析为 children 类型",
      "exports": {
        "dataAccess": [
          {
            "name": "getChildren",
            "signature": "(parentId: string) => Promise<ParentChildRelation[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.parentStudentRelations",
              "react.cache"
            ],
            "usedBy": [
              "getParentDashboardData (内部)",
              "parent/children/[studentId]/page.tsx"
            ]
          },
          {
            "name": "getChildBasicInfo",
            "signature": "(studentId: string, relation?: string | null) => Promise<ChildBasicInfo | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "shared.db.schema.grades",
              "shared.db.schema.classEnrollments",
              "shared.db.schema.classes",
              "react.cache"
            ],
            "usedBy": [
              "getChildDashboardData (内部)"
            ]
          },
          {
            "name": "getChildDashboardData",
            "signature": "(studentId: string, relation?: string | null) => Promise<ChildDashboardData | null>",
            "file": "data-access.ts",
            "deps": [
              "getChildBasicInfo",
              "classes/data-access.getStudentClasses",
              "classes/data-access.getStudentSchedule",
              "homework/data-access.getStudentHomeworkAssignments",
              "homework/data-access.getStudentDashboardGrades",
              "homework/data-access.getStudentExamResults",
              "grades/data-access.getStudentGradeSummary",
              "react.cache"
            ],
            "usedBy": [
              "parent/children/[studentId]/page.tsx",
              "getParentDashboardData (内部)"
            ]
          },
          {
            "name": "getParentDashboardData",
            "signature": "(parentId: string) => Promise<ParentDashboardData>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "getChildren",
              "getChildDashboardData",
              "react.cache"
            ],
            "usedBy": [
              "parent/dashboard/page.tsx"
            ]
          },
          {
            "name": "verifyParentChildRelation",
            "signature": "(studentId: string, parentId: string) => Promise<string | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.parentStudentRelations",
              "react.cache"
            ],
            "usedBy": [
              "parent/children/[studentId]/page.tsx"
            ]
          },
          {
            "name": "getChildNameList",
            "signature": "(parentId: string) => Promise<Array<{ id: string; name: string | null }>>",
            "file": "data-access.ts",
            "deps": [
              "getChildren",
              "users/data-access.getUserNamesByIds",
              "react.cache"
            ],
            "usedBy": [
              "parent/children/[studentId]/page.tsx"
            ]
          },
          {
            "name": "getParentIdsByStudentIds",
            "signature": "(studentIds: string[]) => Promise<string[]>",
            "file": "data-access.ts",
            "purpose": "v4-P1-5 新增：批量查询多个学生的家长 userId 列表，用于通知场景——报告/成绩发布时同步通知所有相关家长。返回去重后的 parentId 数组，使用 react.cache 包装实现请求级 memoization，空数组直接返回 []",
            "deps": [
              "shared.db",
              "shared.db.schema.parentStudentRelations",
              "drizzle-orm.inArray",
              "react.cache"
            ],
            "usedBy": [
              "diagnostic/actions.publishReportAction",
              "grades/lib/notify-grade-entered.notifyGradeEntered"
            ]
          }
        ],
        "types": [
          {
            "name": "ParentChildRelation",
            "type": "type",
            "file": "types.ts",
            "definition": "{ id, parentId, studentId, relation: string | null, createdAt: string }",
            "usedBy": [
              "getChildren",
              "getParentDashboardData"
            ]
          },
          {
            "name": "ChildBasicInfo",
            "type": "type",
            "file": "types.ts",
            "definition": "{ id, name: string | null, email, image: string | null, gradeName: string | null, className: string | null, classId: string | null, relation: string | null }",
            "usedBy": [
              "getChildBasicInfo",
              "ChildDashboardData.basicInfo"
            ]
          },
          {
            "name": "ChildScheduleItem",
            "type": "type",
            "file": "types.ts",
            "definition": "{ id, classId, className, course, startTime, endTime, location: string | null }",
            "usedBy": [
              "ChildDashboardData.todaySchedule",
              "child-schedule-card.tsx"
            ]
          },
          {
            "name": "ChildWeeklyScheduleItem",
            "type": "type",
            "file": "types.ts",
            "definition": "ChildScheduleItem & { weekday: 1 | 2 | 3 | 4 | 5 | 6 | 7 }",
            "usedBy": [
              "ChildDashboardData.weeklySchedule",
              "child-schedule-card.tsx"
            ]
          },
          {
            "name": "ChildHomeworkSummary",
            "type": "type",
            "file": "types.ts",
            "definition": "{ pendingCount, submittedCount, gradedCount, overdueCount, recentAssignments: StudentHomeworkAssignmentListItem[] }",
            "usedBy": [
              "ChildDashboardData.homeworkSummary",
              "child-homework-summary.tsx"
            ]
          },
          {
            "name": "ChildDashboardData",
            "type": "type",
            "file": "types.ts",
            "definition": "{ basicInfo: ChildBasicInfo, enrolledClasses: StudentEnrolledClass[], todaySchedule: ChildScheduleItem[], weeklySchedule: ChildWeeklyScheduleItem[], homeworkSummary: ChildHomeworkSummary, gradeTrend: StudentDashboardGradeProps, gradeSummary: StudentGradeSummary | null, examResults: ChildExamResultItem[] }",
            "usedBy": [
              "getChildDashboardData",
              "ParentDashboardData.children",
              "所有 child-* 组件"
            ]
          },
          {
            "name": "ChildExamResultItem",
            "type": "type",
            "file": "types.ts",
            "definition": "{ submissionId, examId, examTitle, assignmentId, assignmentTitle, score, maxScore, submittedAt: string | null, status: string }",
            "usedBy": [
              "ChildDashboardData.examResults",
              "child-exam-detail.tsx"
            ]
          },
          {
            "name": "ParentDashboardData",
            "type": "type",
            "file": "types.ts",
            "definition": "{ parentName: string | null, children: ChildDashboardData[] }",
            "usedBy": [
              "getParentDashboardData",
              "parent-dashboard.tsx"
            ]
          }
        ],
        "components": [
          {
            "name": "ParentDashboard",
            "file": "components/parent-dashboard.tsx",
            "purpose": "主容器组件（问候语、待办横幅、4 宫格快捷入口、子女卡片网格、空状态引导）"
          },
          {
            "name": "ParentAttentionBanner",
            "file": "components/parent-attention-banner.tsx",
            "purpose": "v4 新增：仪表盘顶部待办事项横幅，聚合所有子女的逾期作业、待办、考勤、公告数量"
          },
          {
            "name": "ParentAttendanceWarning",
            "file": "components/parent-attendance-warning.tsx",
            "purpose": "v4 新增：考勤页异常预警横幅，聚合缺勤/迟到/低出勤率预警"
          },
          {
            "name": "ParentAttendanceRateCard",
            "file": "components/parent-attendance-rate-card.tsx",
            "purpose": "v4 新增：考勤页出勤率汇总卡片，聚合所有子女的平均出勤率、缺勤、迟到总数"
          },
          {
            "name": "ParentAttendanceCalendar",
            "file": "components/parent-attendance-calendar.tsx",
            "purpose": "v4 新增：考勤月历视图，按状态着色每日出勤状态，支持按月切换（use client）"
          },
          {
            "name": "ChildGradeDetail",
            "file": "components/child-grade-detail.tsx",
            "purpose": "v4 新增：成绩详情视图，按科目分组展示平均分、趋势、最近成绩，提供单科分析"
          },
          {
            "name": "ChildHomeworkDetail",
            "file": "components/child-homework-detail.tsx",
            "purpose": "v4 新增：作业详情视图，展示所有作业的完整信息（状态、截止时间、提交时间、分数、尝试次数）"
          },
          {
            "name": "ParentExportButton",
            "file": "components/parent-export-button.tsx",
            "purpose": "v4 新增：成绩导出按钮（占位，待后端实现 Server Action）"
          },
          {
            "name": "ChildCard",
            "file": "components/child-card.tsx",
            "purpose": "子女卡片（头像、姓名、班级、待完成/逾期/平均分统计、异常红色高亮、趋势图标、点击跳转详情）"
          },
          {
            "name": "ChildDetailHeader",
            "file": "components/child-detail-header.tsx",
            "purpose": "子女详情页头部（面包屑、返回按钮、头像、姓名、班级、年级、关系、邮箱掩码）"
          },
          {
            "name": "ChildDetailPanel",
            "file": "components/child-detail-panel.tsx",
            "purpose": "v4 重写：子女详情面板 Tab 容器（Overview/Homework/Grades/Schedule/Attendance/Diagnostic 六 Tab 切换，支持 ?tab= 参数）"
          },
          {
            "name": "SiblingSwitcher",
            "file": "components/child-detail-panel.tsx",
            "purpose": "v4 新增：详情页头部多子女切换器（Tabs 形式，避免返回仪表盘）"
          },
          {
            "name": "ChildHomeworkSummary",
            "file": "components/child-homework-summary.tsx",
            "purpose": "子女作业概览（pending/submitted/graded/overdue 统计 + 最近作业列表含科目标识）"
          },
          {
            "name": "ChildGradeSummary",
            "file": "components/child-grade-summary.tsx",
            "purpose": "子女成绩概览（Recharts 折线图趋势 + 最新分数 + 趋势图标 + 班级排名 Top X% + 最近成绩列表，use client）"
          },
          {
            "name": "ChildScheduleCard",
            "file": "components/child-schedule-card.tsx",
            "purpose": "子女课表卡片（支持今日课表 + 完整周课表两种视图，周课表按 weekday 分组并高亮今日）"
          },
          {
            "name": "ParentChildrenDataPage",
            "file": "components/parent-children-data-page.tsx",
            "purpose": "多子女数据聚合页共享布局（标题、描述、headerExtra 额外内容、空状态、子女列表）"
          },
          {
            "name": "ParentNoChildrenPage",
            "file": "components/parent-children-data-page.tsx",
            "purpose": "dataScope 为空时的统一空状态页面"
          },
          {
            "name": "ChildExamDetail",
            "file": "components/child-exam-detail.tsx",
            "purpose": "V3-11 新增：家长端子女考试详情视图。展示考试成绩汇总卡片（已参加考试数、平均分、最高分）、考试成绩列表（考试标题、分数、得分率 Progress、提交时间）。对标智学网家长端考试详情。集成于 child-detail-panel.tsx 的 exams Tab。"
          }
        ]
      }
    },
    "messaging": {
      "path": "src/modules/messaging",
      "description": "站内私信系统：用户间私信收发（支持回复链）。通知相关 UI 组件和 CRUD Action 已迁移至 notifications 模块（P1-4 修复）",
      "exports": {
        "actions": [
          {
            "name": "sendMessageAction",
            "permission": "MESSAGE_SEND",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "发送消息（同时通过 notifications dispatcher 为收件人创建多渠道通知；支持 parentMessageId 回复；P2-11 新增 trackEvent 埋点；V2-P0-2 新增通知标题 i18n 化，通过 getTranslations('messages') 生成 notification.messageTitle / messageTitleNoSubject）",
            "deps": [
              "requirePermission",
              "shared/db",
              "data-access.createMessage",
              "notifications.dispatcher.sendNotification",
              "trackEvent",
              "revalidatePath",
              "next-intl/server.getTranslations"
            ],
            "usedBy": [
              "message-compose.tsx"
            ]
          },
          {
            "name": "markMessageAsReadAction",
            "permission": "MESSAGE_READ",
            "signature": "(id: string) => Promise<ActionState<void>>",
            "purpose": "标记消息已读（设置 readAt；P2-11 新增 trackEvent 埋点）",
            "deps": [
              "requirePermission",
              "schema.MessageIdSchema",
              "data-access.markMessageAsRead",
              "trackEvent",
              "revalidatePath"
            ],
            "usedBy": [
              "message-detail.tsx",
              "messages/[id]/page.tsx"
            ]
          },
          {
            "name": "deleteMessageAction",
            "permission": "MESSAGE_DELETE",
            "signature": "(id: string) => Promise<ActionState<void>>",
            "purpose": "删除消息（仅发送者或接收者可删；P2-11 新增 trackEvent 埋点）",
            "deps": [
              "requirePermission",
              "schema.MessageIdSchema",
              "data-access.deleteMessage",
              "trackEvent",
              "revalidatePath"
            ],
            "usedBy": [
              "message-detail.tsx"
            ]
          },
          {
            "name": "getMessagesAction",
            "permission": "MESSAGE_READ",
            "signature": "(params?: { type?, page?, pageSize?, keyword? }) => Promise<ActionState<PaginatedResult<MessageListItem>>>",
            "purpose": "获取消息列表（收件箱/已发送，分页，关键词搜索；客户端通过 useMessageSearch hook 调用）",
            "deps": [
              "requirePermission",
              "data-access.getMessages"
            ],
            "usedBy": [
              "message-list.tsx (via useMessageSearch hook)"
            ]
          },
          {
            "name": "getMessageDetailAction",
            "permission": "MESSAGE_READ",
            "signature": "(id: string) => Promise<ActionState<Message>>",
            "purpose": "获取消息详情（含回复线程）",
            "deps": [
              "requirePermission",
              "schema.MessageIdSchema",
              "data-access.getMessageById",
              "data-access.getMessageThread"
            ],
            "usedBy": [
              "message-detail.tsx"
            ]
          },
          {
            "name": "getRecipientsAction",
            "permission": "MESSAGE_SEND",
            "signature": "() => Promise<ActionState<RecipientOption[]>>",
            "purpose": "获取可发送对象列表（按 DataScope 过滤）",
            "deps": [
              "requirePermission",
              "data-access.getRecipients"
            ],
            "usedBy": [
              "messages/compose/page.tsx"
            ]
          },
          {
            "name": "getUnreadMessageCountAction",
            "permission": "MESSAGE_READ",
            "signature": "() => Promise<ActionState<number>>",
            "purpose": "获取当前用户未读私信计数（unread-message-badge 组件每 60 秒轮询）",
            "deps": [
              "requirePermission",
              "data-access.getUnreadMessageCount"
            ],
            "usedBy": [
              "unread-message-badge.tsx"
            ]
          },
          {
            "name": "toggleMessageStarAction",
            "permission": "MESSAGE_READ",
            "signature": "(messageId: string) => Promise<ActionState<string>>",
            "purpose": "V2-P2-13c 新增：切换消息星标状态（仅接收方可标记；trackEvent 埋点 message.star_toggled）",
            "deps": [
              "requirePermission",
              "schema.MessageIdSchema",
              "data-access.toggleMessageStar",
              "trackEvent",
              "revalidatePath"
            ],
            "usedBy": [
              "message-detail.tsx",
              "message-list.tsx"
            ]
          },
          {
            "name": "getMessageDraftsAction",
            "permission": "MESSAGE_SEND",
            "signature": "() => Promise<ActionState<MessageDraft[]>>",
            "purpose": "V2-P2-13c 新增：获取当前用户的消息草稿列表（按 updatedAt 倒序）",
            "deps": [
              "requirePermission",
              "data-access.getMessageDrafts"
            ],
            "usedBy": [
              "messages/compose/page.tsx"
            ]
          },
          {
            "name": "saveMessageDraftAction",
            "permission": "MESSAGE_SEND",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "V2-P2-13c 新增：保存消息草稿（FormData 含 draftId 时更新，否则创建新草稿；返回草稿 ID）",
            "deps": [
              "requirePermission",
              "data-access.createMessageDraft",
              "data-access.updateMessageDraft"
            ],
            "usedBy": [
              "messages/compose/page.tsx"
            ]
          },
          {
            "name": "deleteMessageDraftAction",
            "permission": "MESSAGE_SEND",
            "signature": "(draftId: string) => Promise<ActionState<string>>",
            "purpose": "V2-P2-13c 新增：删除消息草稿（仅草稿所有者可删；revalidatePath /messages/compose）",
            "deps": [
              "requirePermission",
              "data-access.deleteMessageDraft",
              "revalidatePath"
            ],
            "usedBy": [
              "messages/compose/page.tsx"
            ]
          },
          {
            "name": "getNotificationPreferencesAction",
            "permission": "MESSAGE_READ",
            "signature": "() => Promise<ActionState<NotificationPreferences>>",
            "purpose": "获取当前用户的通知偏好设置（首次访问自动创建默认记录）",
            "deps": [
              "requirePermission",
              "notifications.preferences.getNotificationPreferences"
            ],
            "usedBy": [
              "settings/page.tsx",
              "settings/components/notification-preferences-form.tsx"
            ]
          },
          {
            "name": "updateNotificationPreferencesAction",
            "permission": "MESSAGE_READ",
            "signature": "(prevState: ActionState<NotificationPreferences> | null, formData: FormData) => Promise<ActionState<NotificationPreferences>>",
            "purpose": "更新当前用户的通知偏好设置（upsert 语义，未提供字段保留原值）",
            "deps": [
              "requirePermission",
              "schema.UpdateNotificationPreferencesSchema",
              "notifications.preferences.upsertNotificationPreferences",
              "revalidatePath"
            ],
            "usedBy": [
              "settings/components/notification-preferences-form.tsx"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getMessages",
            "signature": "(userId: string, params?: { type?, page?, pageSize? }) => Promise<PaginatedResult<MessageListItem>>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.messages",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "getMessagesAction"
            ]
          },
          {
            "name": "getMessageById",
            "signature": "(id: string, userId: string) => Promise<Message | null>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.messages",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "getMessageDetailAction",
              "messages/[id]/page.tsx"
            ]
          },
          {
            "name": "getMessageThread",
            "signature": "(rootId: string, userId: string) => Promise<Message[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.messages",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "getMessageDetailAction"
            ]
          },
          {
            "name": "createMessage",
            "signature": "(input: CreateMessageInput) => Promise<Message>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.messages",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "sendMessageAction"
            ]
          },
          {
            "name": "markMessageAsRead",
            "signature": "(id: string, userId: string) => Promise<void>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.messages"
            ],
            "usedBy": [
              "markMessageAsReadAction",
              "messages/[id]/page.tsx"
            ]
          },
          {
            "name": "deleteMessage",
            "signature": "(id: string, userId: string) => Promise<void>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.messages"
            ],
            "usedBy": [
              "deleteMessageAction"
            ]
          },
          {
            "name": "toggleMessageStar",
            "signature": "(id: string, userId: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13c 新增：切换消息星标状态（仅接收方可标记；查询当前 isStarred 后取反更新）",
            "deps": [
              "shared.db",
              "shared.db.schema.messages"
            ],
            "usedBy": [
              "toggleMessageStarAction"
            ]
          },
          {
            "name": "getUnreadMessageCount",
            "signature": "(userId: string) => Promise<number>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.messages"
            ],
            "usedBy": [
              "getUnreadMessageCountAction",
              "unread-message-badge.tsx"
            ]
          },
          {
            "name": "getMessagesPageData",
            "signature": "(userId: string) => Promise<{ messages: PaginatedResult<Message>, notifications: PaginatedResult<Notification> }>",
            "file": "data-access.ts",
            "purpose": "P1-5 新增：消息首页编排函数，一次性获取消息列表和通知列表（通知通过动态 import notifications/data-access）",
            "deps": [
              "data-access.getMessages",
              "notifications.data-access.getNotifications"
            ],
            "usedBy": [
              "messages/page.tsx"
            ]
          },
          {
            "name": "getMessageDetailPageData",
            "signature": "(id: string, userId: string) => Promise<Message | null>",
            "file": "data-access.ts",
            "purpose": "V2-P1-3 新增：消息详情页编排函数，一次性获取消息详情并自动标记已读（仅当 receiverId === userId 且未读时）；替代 page.tsx 中 after() + getMessageById + markMessageAsRead 的分散编排",
            "deps": [
              "data-access.getMessageById",
              "data-access.markMessageAsRead"
            ],
            "usedBy": [
              "messages/[id]/page.tsx"
            ]
          },
          {
            "name": "getRecipients",
            "signature": "(ctx: AuthContext) => Promise<RecipientOption[]>",
            "file": "data-access.ts",
            "purpose": "按 DataScope 过滤可发送对象列表：class_taught（教师→学生）、grade_managed（年级管理员→教师/学生）、all（管理员）、class_members（学生→自己班级的任课教师/班主任）、children（家长→孩子的班主任/任课教师）",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "shared.db.schema.classEnrollments",
              "shared.db.schema.classes",
              "shared.db.schema.grades",
              "classes.data-access.getTeacherIdsByClassIds",
              "classes.data-access.getStudentActiveClassId",
              "users.data-access.getUserNamesByIds"
            ],
            "usedBy": [
              "getRecipientsAction",
              "messages/compose/page.tsx"
            ]
          },
          {
            "name": "getMessageDrafts",
            "signature": "(userId: string) => Promise<MessageDraft[]>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13c 新增：获取用户消息草稿列表（按 updatedAt 倒序；React cache 包装；批量解析 receiverName）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageDrafts",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "getMessageDraftsAction"
            ]
          },
          {
            "name": "createMessageDraft",
            "signature": "(data: CreateMessageDraftInput) => Promise<string>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13c 新增：创建消息草稿（cuid2 生成 ID；返回新草稿 ID）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageDrafts",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "saveMessageDraftAction"
            ]
          },
          {
            "name": "updateMessageDraft",
            "signature": "(id: string, userId: string, data: UpdateMessageDraftInput) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13c 新增：更新消息草稿（仅 owner 可改；未提供字段保留原值；updatedAt 由 onUpdateNow 自动更新）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageDrafts"
            ],
            "usedBy": [
              "saveMessageDraftAction"
            ]
          },
          {
            "name": "deleteMessageDraft",
            "signature": "(id: string, userId: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13c 新增：删除消息草稿（仅 owner 可删）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageDrafts"
            ],
            "usedBy": [
              "deleteMessageDraftAction"
            ]
          },
          {
            "name": "getMessageDraftById",
            "signature": "(id: string, userId: string) => Promise<MessageDraft | null>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13c 新增：按 ID 获取单条消息草稿（仅 owner 可读；解析 receiverName）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageDrafts",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "messages/compose/page.tsx"
            ]
          }
        ],
        "notificationPreferences": [
          {
            "name": "getNotificationPreferences",
            "signature": "(userId: string) => Promise<NotificationPreferences>",
            "file": "notification-preferences.ts",
            "purpose": "re-export shim（实际逻辑在 notifications/preferences.ts，P0-4 / P1-5 修复后迁移；React cache 包装；若不存在则自动创建默认记录，并发冲突回退到查询）",
            "deps": [
              "notifications.preferences.getNotificationPreferences"
            ],
            "usedBy": [
              "getNotificationPreferencesAction",
              "settings/page.tsx"
            ]
          },
          {
            "name": "upsertNotificationPreferences",
            "signature": "(userId: string, input: UpdateNotificationPreferencesInput) => Promise<NotificationPreferences | null>",
            "file": "notification-preferences.ts",
            "purpose": "re-export shim（实际逻辑在 notifications/preferences.ts，P0-4 / P1-5 修复后迁移；存在则部分更新，不存在则插入；未提供字段保留原值）",
            "deps": [
              "notifications.preferences.upsertNotificationPreferences"
            ],
            "usedBy": [
              "updateNotificationPreferencesAction"
            ]
          }
        ],
        "schemas": [
          {
            "name": "SendMessageSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ receiverId: string, subject?: string, content: string, parentMessageId?: string }",
            "usedBy": [
              "sendMessageAction"
            ]
          }
        ],
        "types": [
          {
            "name": "Message",
            "type": "type",
            "file": "types.ts",
            "definition": "{ id, senderId, receiverId, subject: string | null, content, isRead, isStarred, readAt: string | null, parentMessageId: string | null, createdAt, senderName, receiverName }",
            "usedBy": [
              "messaging/components",
              "页面"
            ]
          },
          {
            "name": "MessageListItem",
            "type": "type",
            "file": "types.ts",
            "definition": "消息列表项类型（同 Message 精简版）",
            "usedBy": [
              "列表页"
            ]
          },
          {
            "name": "MessageThread",
            "type": "type",
            "file": "types.ts",
            "definition": "{ root: Message, replies: Message[] }",
            "usedBy": [
              "详情页"
            ]
          },
          {
            "name": "Notification",
            "type": "type",
            "file": "types.ts",
            "definition": "{ id, userId, type: NotificationType, title, content: string | null, link: string | null, isRead, priority: NotificationPriority, isArchived, createdAt }",
            "usedBy": [
              "notification-dropdown",
              "notification-list"
            ]
          },
          {
            "name": "NotificationListItem",
            "type": "type",
            "file": "types.ts",
            "definition": "通知列表项类型（同 Notification）",
            "usedBy": [
              "列表页"
            ]
          },
          {
            "name": "NotificationType",
            "type": "type",
            "file": "types.ts",
            "definition": "'message' | 'announcement' | 'homework' | 'grade'",
            "usedBy": [
              "data-access",
              "components"
            ]
          },
          {
            "name": "MessageType",
            "type": "type",
            "file": "types.ts",
            "definition": "'inbox' | 'sent'",
            "usedBy": [
              "getMessages 参数"
            ]
          },
          {
            "name": "CreateMessageInput",
            "type": "type",
            "file": "types.ts",
            "definition": "{ senderId, receiverId, subject?, content, parentMessageId? }",
            "usedBy": [
              "createMessage"
            ]
          },
          {
            "name": "CreateNotificationInput",
            "type": "type",
            "file": "types.ts",
            "definition": "{ userId, type: NotificationType, title, content?, link?, priority? }",
            "usedBy": [
              "createNotification"
            ]
          },
          {
            "name": "RecipientOption",
            "type": "type",
            "file": "types.ts",
            "definition": "{ id: string, name: string }",
            "usedBy": [
              "compose 页面下拉选项"
            ]
          },
          {
            "name": "MessageDraft",
            "type": "type",
            "file": "types.ts",
            "definition": "V2-P2-13c 新增：{ id, userId, receiverId: string | null, receiverName: string | null, subject: string | null, content: string | null, parentMessageId: string | null, createdAt, updatedAt }",
            "usedBy": [
              "getMessageDrafts",
              "getMessageDraftById",
              "getMessageDraftsAction",
              "messages/compose/page.tsx"
            ]
          },
          {
            "name": "CreateMessageDraftInput",
            "type": "type",
            "file": "types.ts",
            "definition": "V2-P2-13c 新增：{ userId, receiverId?, subject?, content?, parentMessageId? }",
            "usedBy": [
              "createMessageDraft"
            ]
          },
          {
            "name": "UpdateMessageDraftInput",
            "type": "type",
            "file": "types.ts",
            "definition": "V2-P2-13c 新增：{ receiverId?, subject?, content?, parentMessageId? }（未提供字段保留原值）",
            "usedBy": [
              "updateMessageDraft"
            ]
          },
          {
            "name": "PaginatedResult",
            "type": "type",
            "file": "types.ts",
            "definition": "{ items: T[], total: number, page: number, pageSize: number, totalPages: number }",
            "usedBy": [
              "getMessages",
              "getNotifications"
            ]
          },
          {
            "name": "NotificationPreferences",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, userId, emailEnabled, smsEnabled, pushEnabled, homeworkNotifications, gradeNotifications, announcementNotifications, messageNotifications, attendanceNotifications, createdAt, updatedAt }",
            "usedBy": [
              "notification-preferences",
              "getNotificationPreferencesAction",
              "updateNotificationPreferencesAction",
              "settings/components/notification-preferences-form"
            ]
          },
          {
            "name": "UpdateNotificationPreferencesInput",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ emailEnabled?, smsEnabled?, pushEnabled?, homeworkNotifications?, gradeNotifications?, announcementNotifications?, messageNotifications?, attendanceNotifications? }",
            "usedBy": [
              "upsertNotificationPreferences",
              "updateNotificationPreferencesAction"
            ]
          }
        ],
        "components": [
          {
            "name": "MessageList",
            "file": "components/message-list.tsx",
            "purpose": "消息列表（收件箱/已发送 Tab 切换，已读/未读标记，usePermission 控制写消息按钮；V2-P1-2 优化：客户端过滤仅在初始数据 type=all 时执行，搜索结果已由服务端按 tab 过滤）"
          },
          {
            "name": "MessageDetail",
            "file": "components/message-detail.tsx",
            "purpose": "消息详情（含回复线程、回复/删除操作，AlertDialog 删除确认，usePermission 控制按钮可见性）"
          },
          {
            "name": "MessageCompose",
            "file": "components/message-compose.tsx",
            "purpose": "写消息表单（收件人 Select、主题 Input、内容 Textarea，支持回复模式；V2-P1-4 增强：fieldErrors 状态 + aria-invalid 字段级服务端校验错误展示）"
          },
          {
            "name": "UnreadMessageBadge",
            "file": "components/unread-message-badge.tsx",
            "purpose": "未读消息计数徽章（侧边栏 Messages 导航项旁显示，每 30 秒轮询 getUnreadMessageCountAction；V2-P2-1 优化：轮询间隔提取为 POLL_INTERVAL_MS 常量；V2-P3 优化：间隔从 60s 缩短为 30s 与通知组件保持一致）"
          }
        ],
        "hooks": [
          {
            "name": "useMessageSearch",
            "file": "hooks/use-message-search.ts",
            "purpose": "P1-7 新增：消息搜索 hook（400ms 防抖 + 请求竞态取消，通过 requestIdRef 匹配最新请求）",
            "usedBy": [
              "message-list.tsx"
            ]
          }
        ]
      }
    },
    "notifications": {
      "path": "src/modules/notifications",
      "description": "通知渠道集成层：基于用户通知偏好（notification_preferences）将通知分发到站内消息/SMS/微信公众号/邮件多渠道。所有渠道实现统一 NotificationChannelSender 接口，dispatcher 按偏好并行发送。支持 Mock 模式（开发环境无需外部服务）。P1-4 修复后新增通知 UI 组件和通知 CRUD Server Action。",
      "exports": {
        "actions": [
          {
            "name": "sendNotificationAction",
            "permission": "MESSAGE_SEND",
            "signature": "(payload: NotificationPayload) => Promise<ActionState<ChannelSendResult[]>>",
            "purpose": "发送通知给指定用户（按偏好多渠道分发）",
            "deps": [
              "requirePermission",
              "dispatcher.sendNotification"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "sendClassNotificationAction",
            "permission": "MESSAGE_SEND",
            "signature": "(classId: string, payload: Omit<NotificationPayload, 'userId'>) => Promise<ActionState<ChannelSendResult[][]>>",
            "purpose": "发送班级通知（批量发送给班级所有学生；教师只能给自己所教班级发送）",
            "deps": [
              "requirePermission",
              "db.schema.classEnrollments",
              "db.schema.classes",
              "dispatcher.sendBatchNotifications"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getNotificationsAction",
            "permission": "MESSAGE_READ",
            "signature": "(params?: { page?, pageSize?, unreadOnly? }) => Promise<ActionState<PaginatedResult<Notification>>>",
            "purpose": "P1-4 新增（从 messaging 迁移）：获取当前用户通知列表（分页）",
            "deps": [
              "requirePermission",
              "data-access.getNotifications"
            ],
            "usedBy": [
              "hooks/use-notification-stream.ts",
              "notification-list.tsx"
            ]
          },
          {
            "name": "getUnreadNotificationCountAction",
            "permission": "MESSAGE_READ",
            "signature": "() => Promise<ActionState<number>>",
            "purpose": "P1-4 新增（从 messaging 迁移）：获取当前用户未读通知计数",
            "deps": [
              "requirePermission",
              "data-access.getUnreadNotificationCount"
            ],
            "usedBy": [
              "hooks/use-notification-stream.ts"
            ]
          },
          {
            "name": "markNotificationAsReadAction",
            "permission": "MESSAGE_READ",
            "signature": "(notificationId: string) => Promise<ActionState<string>>",
            "purpose": "P1-4 新增（从 messaging 迁移）：标记单条通知已读；P2-11 新增 trackEvent 埋点",
            "deps": [
              "requirePermission",
              "schema.NotificationIdSchema",
              "data-access.markNotificationAsRead",
              "trackEvent",
              "revalidatePath"
            ],
            "usedBy": [
              "notification-dropdown.tsx",
              "notification-list.tsx"
            ]
          },
          {
            "name": "markAllNotificationsAsReadAction",
            "permission": "MESSAGE_READ",
            "signature": "() => Promise<ActionState<string>>",
            "purpose": "P1-4 新增（从 messaging 迁移）：标记所有通知已读；P2-11 新增 trackEvent 埋点",
            "deps": [
              "requirePermission",
              "data-access.markAllNotificationsAsRead",
              "trackEvent",
              "revalidatePath"
            ],
            "usedBy": [
              "notification-dropdown.tsx",
              "notification-list.tsx"
            ]
          },
          {
            "name": "archiveNotificationAction",
            "permission": "MESSAGE_READ",
            "signature": "(notificationId: string) => Promise<ActionState<string>>",
            "purpose": "V2-P2-13b 新增：将单条通知归档（归档后不在默认列表显示）；含 trackEvent 埋点 notification.archived",
            "deps": [
              "requirePermission",
              "schema.NotificationIdSchema",
              "data-access.archiveNotification",
              "trackEvent",
              "revalidatePath"
            ],
            "usedBy": [
              "notification-list.tsx"
            ]
          }
        ],
        "dispatcher": [
          {
            "name": "sendNotification",
            "signature": "(payload: NotificationPayload) => Promise<ChannelSendResult[]>",
            "file": "dispatcher.ts",
            "purpose": "发送单条通知：读取用户偏好+联系方式，按偏好选择渠道并行发送，记录日志",
            "deps": [
              "preferences.getNotificationPreferences",
              "data-access.getUserContactInfo",
              "data-access.logNotificationSendBatch",
              "channels.sms-channel.createSmsSender",
              "channels.wechat-channel.createWechatSender",
              "channels.email-channel.createEmailSender",
              "channels.in-app-channel.createInAppSender"
            ],
            "usedBy": [
              "sendNotificationAction",
              "sendClassNotificationAction",
              "messaging.sendMessageAction"
            ]
          },
          {
            "name": "sendBatchNotifications",
            "signature": "(payloads: NotificationPayload[]) => Promise<ChannelSendResult[][]>",
            "file": "dispatcher.ts",
            "purpose": "批量发送通知（每个用户独立选择渠道，并行发送）",
            "deps": [
              "sendNotification"
            ],
            "usedBy": [
              "sendClassNotificationAction"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getNotifications",
            "signature": "(userId: string, params?: { page?, pageSize?, unreadOnly?, unarchivedOnly?, priority? }) => Promise<PaginatedResult<Notification>>",
            "file": "data-access.ts",
            "purpose": "获取用户站内通知列表（分页，支持 unreadOnly/unarchivedOnly/priority 过滤；P0-4 / P1-5 修复后从 messaging 迁移；V2-P2-13b 新增归档和优先级筛选，默认仅返回未归档）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageNotifications",
              "react.cache"
            ],
            "usedBy": [
              "messaging.getNotificationsAction (via re-export)",
              "messages/page.tsx (via re-export)"
            ]
          },
          {
            "name": "createNotification",
            "signature": "(input: CreateNotificationInput) => Promise<string>",
            "file": "data-access.ts",
            "purpose": "创建站内通知记录（写入 message_notifications 表；P0-4 / P1-5 修复后从 messaging 迁移；V2-P2-13b 支持 priority 可选参数，默认 normal）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageNotifications",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "channels.in-app-channel.InAppChannelSender.send"
            ]
          },
          {
            "name": "markNotificationAsRead",
            "signature": "(id: string, userId: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "标记单条通知已读（P0-4 / P1-5 修复后从 messaging 迁移）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageNotifications"
            ],
            "usedBy": [
              "messaging.markNotificationAsReadAction (via re-export)"
            ]
          },
          {
            "name": "markAllNotificationsAsRead",
            "signature": "(userId: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "标记用户所有通知已读（P0-4 / P1-5 修复后从 messaging 迁移）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageNotifications"
            ],
            "usedBy": [
              "messaging.markAllNotificationsAsReadAction (via re-export)"
            ]
          },
          {
            "name": "getUnreadNotificationCount",
            "signature": "(userId: string) => Promise<number>",
            "file": "data-access.ts",
            "purpose": "获取用户未读通知数（P0-4 / P1-5 修复后从 messaging 迁移）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageNotifications",
              "react.cache"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "archiveNotification",
            "signature": "(id: string, userId: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13b 新增：将单条通知归档（isArchived 置 true，归档后不在默认列表显示）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageNotifications"
            ],
            "usedBy": [
              "archiveNotificationAction"
            ]
          },
          {
            "name": "unarchiveNotification",
            "signature": "(id: string, userId: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "V2-P2-13b 新增：取消归档通知（isArchived 置 false）",
            "deps": [
              "shared.db",
              "shared.db.schema.messageNotifications"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getUserContactInfo",
            "signature": "(userId: string) => Promise<ChannelRecipient>",
            "file": "data-access.ts",
            "purpose": "获取用户联系方式（phone/email；wechatOpenId 暂不支持，users 表无此字段）",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "react.cache"
            ],
            "usedBy": [
              "dispatcher.sendNotification"
            ]
          },
          {
            "name": "logNotificationSend",
            "signature": "(result: ChannelSendResult, payload?: { userId: string; title: string }) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "记录单条发送日志到 notification_logs 表（DB 写入失败时降级 console，不阻塞通知流程）",
            "deps": [
              "shared.db",
              "shared.db.schema.notificationLogs",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "logNotificationSendBatch"
            ]
          },
          {
            "name": "logNotificationSendBatch",
            "signature": "(results: ChannelSendResult[], payload?: { userId: string; title: string }) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "批量记录发送日志（并行调用 logNotificationSend）",
            "deps": [
              "logNotificationSend"
            ],
            "usedBy": [
              "dispatcher.sendNotification",
              "dispatcher.sendBatchNotifications"
            ]
          }
        ],
        "preferences": [
          {
            "name": "getNotificationPreferences",
            "signature": "(userId: string) => Promise<NotificationPreferences>",
            "file": "preferences.ts",
            "purpose": "获取用户通知偏好（React cache 包装；若不存在则自动创建默认记录，并发冲突回退到查询；P0-4 / P1-5 修复后从 messaging 迁移）",
            "deps": [
              "shared.db",
              "shared.db.schema.notificationPreferences",
              "react.cache",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "dispatcher.sendNotification",
              "messaging.getNotificationPreferencesAction (via re-export)",
              "settings/page.tsx (via re-export)"
            ]
          },
          {
            "name": "upsertNotificationPreferences",
            "signature": "(userId: string, input: UpdateNotificationPreferencesInput) => Promise<NotificationPreferences | null>",
            "file": "preferences.ts",
            "purpose": "更新或插入用户通知偏好（存在则部分更新，不存在则插入；未提供字段保留原值；P0-4 / P1-5 修复后从 messaging 迁移）",
            "deps": [
              "shared.db",
              "shared.db.schema.notificationPreferences",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "messaging.updateNotificationPreferencesAction (via re-export)"
            ]
          }
        ],
        "channels": [
          {
            "name": "createSmsSender",
            "file": "channels/sms-channel.ts",
            "purpose": "创建 SMS 渠道发送器（aliyun/tencent/mock，根据 SMS_PROVIDER 环境变量选择；SDK 动态 import）",
            "deps": [
              "环境变量: SMS_PROVIDER, SMS_ACCESS_KEY_ID, SMS_ACCESS_KEY_SECRET, SMS_SIGN_NAME, SMS_TEMPLATE_CODE"
            ]
          },
          {
            "name": "createWechatSender",
            "file": "channels/wechat-channel.ts",
            "purpose": "创建微信渠道发送器（配置完整用真实发送器，否则 Mock；access_token 带缓存）",
            "deps": [
              "环境变量: WECHAT_APP_ID, WECHAT_APP_SECRET, WECHAT_TEMPLATE_ID"
            ]
          },
          {
            "name": "createEmailSender",
            "file": "channels/email-channel.ts",
            "purpose": "创建邮件渠道发送器（配置 EMAIL_HOST 用 Nodemailer SMTP，否则 Mock；HTML 模板按 type 着色）",
            "deps": [
              "环境变量: EMAIL_HOST, EMAIL_PORT, EMAIL_USER, EMAIL_PASS, EMAIL_FROM"
            ]
          },
          {
            "name": "createInAppSender",
            "file": "channels/in-app-channel.ts",
            "purpose": "创建站内消息渠道发送器（调用 notifications.data-access.createNotification 写入 message_notifications 表；总是启用；P0-4 / P1-5 修复后改为静态导入本地 createNotification，不再动态 import messaging）",
            "deps": [
              "data-access.createNotification"
            ]
          }
        ],
        "types": [
          {
            "name": "NotificationChannel",
            "type": "type",
            "file": "types.ts",
            "definition": "'in_app' | 'email' | 'sms' | 'wechat'",
            "usedBy": [
              "所有渠道文件",
              "dispatcher"
            ]
          },
          {
            "name": "NotificationPayload",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ userId, title, content, type: 'info'|'warning'|'error'|'success', metadata?, actionUrl? }",
            "usedBy": [
              "dispatcher",
              "actions",
              "所有渠道"
            ]
          },
          {
            "name": "ChannelSendResult",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ channel, success, messageId?, error?, sentAt }",
            "usedBy": [
              "dispatcher",
              "actions",
              "所有渠道"
            ]
          },
          {
            "name": "NotificationChannelConfig",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ enabled, sms?, wechat?, email? }",
            "usedBy": [
              "类型定义"
            ]
          },
          {
            "name": "NotificationChannelSender",
            "type": "interface",
            "file": "channels/types.ts",
            "definition": "{ channel: NotificationChannel, send(payload, recipient), sendBatch(items) }",
            "usedBy": [
              "所有渠道实现",
              "dispatcher"
            ]
          },
          {
            "name": "ChannelRecipient",
            "type": "interface",
            "file": "channels/types.ts",
            "definition": "{ userId, phone?, email?, wechatOpenId? }",
            "usedBy": [
              "所有渠道",
              "data-access.getUserContactInfo"
            ]
          },
          {
            "name": "NotificationType",
            "type": "type",
            "file": "types.ts",
            "definition": "'message' | 'announcement' | 'homework' | 'grade'",
            "usedBy": [
              "data-access",
              "channels.in-app-channel",
              "messaging (via re-export)"
            ]
          },
          {
            "name": "NotificationPriority",
            "type": "type",
            "file": "types.ts",
            "definition": "'low' | 'normal' | 'high' | 'urgent'",
            "usedBy": [
              "data-access",
              "components.notification-list",
              "messaging (via re-export)"
            ]
          },
          {
            "name": "Notification",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, userId, type: NotificationType, title, content: string | null, link: string | null, isRead, priority: NotificationPriority, isArchived, createdAt }",
            "usedBy": [
              "data-access.getNotifications",
              "messaging (via re-export)"
            ]
          },
          {
            "name": "NotificationListItem",
            "type": "type",
            "file": "types.ts",
            "definition": "通知列表项类型（同 Notification）",
            "usedBy": [
              "messaging (via re-export)"
            ]
          },
          {
            "name": "NotificationLog",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, userId, title, channel: NotificationChannel, status: 'success' | 'failure', messageId: string | null, error: string | null, sentAt }",
            "usedBy": []
          },
          {
            "name": "PaginatedResult",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ items: T[], total: number, page: number, pageSize: number, totalPages: number }",
            "usedBy": [
              "data-access.getNotifications",
              "messaging (via re-export)"
            ]
          },
          {
            "name": "GetNotificationsParams",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ page?, pageSize?, unreadOnly?, unarchivedOnly?, priority? }",
            "usedBy": [
              "data-access.getNotifications",
              "messaging (via re-export)"
            ]
          },
          {
            "name": "CreateNotificationInput",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ userId, type: NotificationType, title, content?, link?, priority? }",
            "usedBy": [
              "data-access.createNotification",
              "channels.in-app-channel",
              "messaging (via re-export)"
            ]
          },
          {
            "name": "NotificationPreferences",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, userId, emailEnabled, smsEnabled, pushEnabled, homeworkNotifications, gradeNotifications, announcementNotifications, messageNotifications, attendanceNotifications, createdAt, updatedAt }",
            "usedBy": [
              "preferences.getNotificationPreferences",
              "preferences.upsertNotificationPreferences",
              "dispatcher.sendNotification",
              "messaging (via re-export)"
            ]
          },
          {
            "name": "UpdateNotificationPreferencesInput",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ emailEnabled?, smsEnabled?, pushEnabled?, homeworkNotifications?, gradeNotifications?, announcementNotifications?, messageNotifications?, attendanceNotifications? }",
            "usedBy": [
              "preferences.upsertNotificationPreferences",
              "messaging (via re-export)"
            ]
          }
        ],
        "components": [
          {
            "name": "NotificationList",
            "file": "components/notification-list.tsx",
            "purpose": "P1-4 新增（从 messaging 迁移）：通知完整列表（全部标记已读、单条标记已读、查看链接）；V2-P0-1 优化：useTranslations 命名空间从 'messages' 切换到独立的 'notifications'；V2-P2-13b：支持优先级 Badge 显示和归档操作；V2-P2-13c：支持按类型筛选（all/message/announcement/homework/grade）+ 空状态区分'无通知'/'无筛选结果'"
          },
          {
            "name": "NotificationDropdown",
            "file": "components/notification-dropdown.tsx",
            "purpose": "P1-4 新增（从 messaging 迁移）：SiteHeader 通知下拉菜单（Bell 图标 + 未读数 Badge，每 30 秒轮询，滚动列表，标记已读，查看全部链接）；V2-P0-1 优化：useTranslations 命名空间从 'messages' 切换到独立的 'notifications'；V2-P2-1 优化：轮询间隔提取为 POLL_INTERVAL_MS 常量；V2-P3：改用 SSE 实时推送 + 轮询降级；V2-P2-13c：集成 useDesktopNotifications 桌面推送，监听新通知并触发桌面提醒（首次加载不批量推送）"
          }
        ],
        "hooks": [
          {
            "name": "useNotificationStream",
            "file": "hooks/use-notification-stream.ts",
            "purpose": "V2-P3 新增：SSE 实时推送 Hook（EventSource + 轮询降级），管理 SSE 连接生命周期，降级时通过 pollFnRef 调用 Server Actions 轮询",
            "usedBy": [
              "components/notification-dropdown.tsx"
            ]
          },
          {
            "name": "useDesktopNotifications",
            "file": "hooks/use-desktop-notifications.ts",
            "purpose": "V2-P2-13c 新增：浏览器桌面推送 Hook（Notification API），支持权限管理、自动请求权限、新通知桌面推送、点击跳转回调；SSR 安全（typeof window !== 'undefined' 检查）",
            "usedBy": [
              "components/notification-dropdown.tsx"
            ]
          }
        ]
      }
    },
    "attendance": {
      "path": "src/modules/attendance",
      "description": "学生考勤管理：教师按班级/日期点名（单条/批量）、查询考勤记录、统计出勤率/迟到率，学生/家长查看本人/子女考勤汇总，管理员查看全校考勤记录。支持班级考勤规则配置。",
      "exports": {
        "actions": [
          {
            "name": "recordAttendanceAction",
            "permission": "ATTENDANCE_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "创建单条考勤记录",
            "deps": [
              "requirePermission",
              "data-access.createAttendanceRecord",
              "revalidatePath"
            ],
            "usedBy": [
              "attendance-record-list.tsx"
            ]
          },
          {
            "name": "batchRecordAttendanceAction",
            "permission": "ATTENDANCE_MANAGE",
            "signature": "(prevState: ActionState<number> | null, formData: FormData) => Promise<ActionState<number>>",
            "purpose": "批量点名（班级+日期，表格形式录入每个学生状态）",
            "deps": [
              "requirePermission",
              "data-access.batchCreateAttendanceRecords",
              "revalidatePath"
            ],
            "usedBy": [
              "attendance-sheet.tsx"
            ]
          },
          {
            "name": "updateAttendanceAction",
            "permission": "ATTENDANCE_MANAGE",
            "signature": "(id: string, prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "更新考勤记录（状态、备注）",
            "deps": [
              "requirePermission",
              "data-access.updateAttendanceRecord",
              "revalidatePath"
            ],
            "usedBy": [
              "attendance-record-list.tsx"
            ]
          },
          {
            "name": "deleteAttendanceAction",
            "permission": "ATTENDANCE_MANAGE",
            "signature": "(id: string) => Promise<ActionState<void>>",
            "purpose": "删除考勤记录",
            "deps": [
              "requirePermission",
              "data-access.deleteAttendanceRecord",
              "revalidatePath"
            ],
            "usedBy": [
              "attendance-record-list.tsx"
            ]
          },
          {
            "name": "getAttendanceAction",
            "permission": "ATTENDANCE_READ",
            "signature": "(params?: AttendanceQueryParams) => Promise<ActionState<PaginatedAttendanceResult>>",
            "purpose": "分页查询考勤记录（按 scope 过滤）",
            "deps": [
              "requirePermission",
              "data-access.getAttendanceRecords"
            ],
            "usedBy": [],
            "issues": [
              "P0: 无调用方——admin/teacher 页面绕过 Action 直接调用 data-access.getAttendanceRecords，违反三层架构"
            ]
          },
          {
            "name": "getStudentAttendanceAction",
            "permission": "ATTENDANCE_READ",
            "signature": "(studentId: string) => Promise<ActionState<StudentAttendanceSummary>>",
            "purpose": "获取学生考勤汇总（含 DataScope 二次校验：class_members 仅查自己，children 仅查子女）",
            "deps": [
              "requirePermission",
              "data-access-stats.getStudentAttendanceSummary"
            ],
            "usedBy": [],
            "issues": [
              "P0: 无调用方——student/parent 页面绕过 Action 直接调用 data-access-stats.getStudentAttendanceSummary"
            ]
          },
          {
            "name": "getClassAttendanceStatsAction",
            "permission": "ATTENDANCE_READ",
            "signature": "(classId: string, startDate?: string, endDate?: string) => Promise<ActionState<ClassAttendanceSummary>>",
            "purpose": "获取班级考勤统计",
            "deps": [
              "requirePermission",
              "data-access-stats.getClassAttendanceStats"
            ],
            "usedBy": [],
            "issues": [
              "P0: 无调用方——teacher/attendance/stats 页面绕过 Action 直接调用 data-access-stats.getClassAttendanceStats"
            ]
          },
          {
            "name": "getClassAttendanceForDateAction",
            "permission": "ATTENDANCE_READ",
            "signature": "(classId: string, date: string) => Promise<ActionState<AttendanceListItem[]>>",
            "purpose": "获取班级指定日期考勤（用于点名页加载已有记录）",
            "deps": [
              "requirePermission",
              "data-access.getClassAttendanceForDate"
            ],
            "usedBy": [
              "attendance-sheet.tsx"
            ]
          },
          {
            "name": "saveAttendanceRulesAction",
            "permission": "ATTENDANCE_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "保存班级考勤规则（upsert）",
            "deps": [
              "requirePermission",
              "data-access.upsertAttendanceRules",
              "revalidatePath"
            ],
            "usedBy": [
              "attendance-rules-form.tsx"
            ]
          },
          {
            "name": "getAttendanceRulesAction",
            "permission": "ATTENDANCE_READ",
            "signature": "(classId?: string) => Promise<ActionState<AttendanceRule[]>>",
            "purpose": "获取班级考勤规则",
            "deps": [
              "requirePermission",
              "data-access.getAttendanceRules"
            ],
            "usedBy": [
              "attendance-rules-form.tsx"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getAttendanceRecords",
            "signature": "(params: AttendanceQueryParams & { scope: DataScope; currentUserId?: string }) => Promise<PaginatedAttendanceResult>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.attendanceRecords",
              "shared.db.schema.users",
              "shared.db.schema.classes",
              "types.DataScope"
            ],
            "usedBy": [
              "getAttendanceAction"
            ]
          },
          {
            "name": "getClassAttendanceForDate",
            "signature": "(classId: string, date: string) => Promise<AttendanceListItem[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.attendanceRecords",
              "shared.db.schema.users",
              "shared.db.schema.classes"
            ],
            "usedBy": [
              "getClassAttendanceForDateAction"
            ]
          },
          {
            "name": "createAttendanceRecord",
            "signature": "(data: RecordAttendanceInput, recordedBy: string) => Promise<string>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.attendanceRecords",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "recordAttendanceAction"
            ]
          },
          {
            "name": "batchCreateAttendanceRecords",
            "signature": "(data: BatchRecordAttendanceInput, recordedBy: string) => Promise<number>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.attendanceRecords",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "batchRecordAttendanceAction"
            ]
          },
          {
            "name": "updateAttendanceRecord",
            "signature": "(id: string, data: UpdateAttendanceInput) => Promise<void>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.attendanceRecords"
            ],
            "usedBy": [
              "updateAttendanceAction"
            ]
          },
          {
            "name": "deleteAttendanceRecord",
            "signature": "(id: string) => Promise<void>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.attendanceRecords"
            ],
            "usedBy": [
              "deleteAttendanceAction"
            ]
          },
          {
            "name": "getClassStudentsForAttendance",
            "signature": "(classId: string) => Promise<Array<{ id, name, email }>>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.classEnrollments",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "attendance-sheet.tsx"
            ],
            "issues": [
              "P0: 跨模块直查 classEnrollments 表，违反模块间只能通过对方 data-access 通信的规则（应改为调用 classes data-access）"
            ]
          },
          {
            "name": "getAttendanceRules",
            "signature": "(classId?: string) => Promise<AttendanceRule[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.attendanceRules"
            ],
            "usedBy": [
              "getAttendanceRulesAction"
            ]
          },
          {
            "name": "upsertAttendanceRules",
            "signature": "(data: AttendanceRuleInput) => Promise<string>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.attendanceRules",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "saveAttendanceRulesAction"
            ]
          },
          {
            "name": "getStudentAttendanceSummary",
            "signature": "(studentId: string, startDate?: string, endDate?: string) => Promise<StudentAttendanceSummary | null>",
            "file": "data-access-stats.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.attendanceRecords",
              "shared.db.schema.classes",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "getStudentAttendanceAction",
              "student/attendance/page.tsx",
              "parent/attendance/page.tsx"
            ]
          },
          {
            "name": "getClassAttendanceStats",
            "signature": "(classId: string, startDate?: string, endDate?: string) => Promise<ClassAttendanceSummary | null>",
            "file": "data-access-stats.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.attendanceRecords",
              "shared.db.schema.classes",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "getClassAttendanceStatsAction",
              "teacher/attendance/stats/page.tsx"
            ]
          },
          {
            "name": "getAttendanceStats",
            "signature": "(params: { scope: DataScope; currentUserId: string; classId?: string; date?: string }) => Promise<AttendanceOverviewStats>",
            "file": "data-access.ts",
            "deps": [
              "data-access.getAttendanceRecords"
            ],
            "usedBy": [
              "admin/attendance/page.tsx"
            ],
            "issues": [
              "P0: 统计失真——调用 getAttendanceRecords（默认 pageSize=20）后对 items 聚合，仅基于前 20 条记录计算总览数据，班级/状态/日期筛选后仍只统计前 20 条"
            ]
          }
        ],
        "schemas": [
          {
            "name": "AttendanceStatusEnum",
            "type": "zod",
            "file": "schema.ts",
            "definition": "enum('present','absent','late','early_leave','excused')",
            "usedBy": [
              "RecordAttendanceSchema",
              "BatchRecordAttendanceSchema",
              "UpdateAttendanceSchema"
            ]
          },
          {
            "name": "RecordAttendanceSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ studentId, classId, scheduleId?, date, status, remark? }",
            "usedBy": [
              "recordAttendanceAction"
            ]
          },
          {
            "name": "BatchRecordAttendanceSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ records: [{ studentId, classId, scheduleId?, date, status, remark? }] }",
            "usedBy": [
              "batchRecordAttendanceAction"
            ]
          },
          {
            "name": "UpdateAttendanceSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ status?, remark?, scheduleId? }",
            "usedBy": [
              "updateAttendanceAction"
            ]
          },
          {
            "name": "AttendanceRuleSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ classId, lateThresholdMinutes?, earlyLeaveThresholdMinutes?, enableAutoMark? }",
            "usedBy": [
              "saveAttendanceRulesAction"
            ]
          }
        ],
        "types": [
          {
            "name": "AttendanceStatus",
            "type": "type",
            "file": "types.ts",
            "definition": "'present' | 'absent' | 'late' | 'early_leave' | 'excused'",
            "usedBy": [
              "attendance/data-access",
              "attendance/components"
            ]
          },
          {
            "name": "AttendanceRecord",
            "type": "type",
            "file": "types.ts",
            "definition": "考勤记录完整类型",
            "usedBy": [
              "attendance/data-access"
            ]
          },
          {
            "name": "AttendanceListItem",
            "type": "type",
            "file": "types.ts",
            "definition": "{ id, studentId, studentName, classId, className, scheduleId, date, status, remark, recordedBy, recorderName, createdAt }",
            "usedBy": [
              "attendance/components",
              "页面"
            ]
          },
          {
            "name": "AttendanceStats",
            "type": "type",
            "file": "types.ts",
            "definition": "{ total, present, absent, late, earlyLeave, excused, presentRate, lateRate }",
            "usedBy": [
              "attendance-stats-card.tsx"
            ]
          },
          {
            "name": "StudentAttendanceSummary",
            "type": "type",
            "file": "types.ts",
            "definition": "{ studentId, studentName, stats: AttendanceStats, recentRecords: AttendanceListItem[] }",
            "usedBy": [
              "student-attendance-view.tsx"
            ]
          },
          {
            "name": "ClassAttendanceSummary",
            "type": "type",
            "file": "types.ts",
            "definition": "{ classId, className, date, stats: AttendanceStats, studentRecords: AttendanceListItem[] }",
            "usedBy": [
              "teacher/attendance/stats"
            ]
          },
          {
            "name": "AttendanceRule",
            "type": "type",
            "file": "types.ts",
            "definition": "{ id, classId, lateThresholdMinutes, earlyLeaveThresholdMinutes, enableAutoMark, createdAt, updatedAt }",
            "usedBy": [
              "attendance-rules-form.tsx"
            ]
          },
          {
            "name": "AttendanceQueryParams",
            "type": "type",
            "file": "types.ts",
            "definition": "{ classId?, studentId?, date?, startDate?, endDate?, status?, page?, pageSize? }",
            "usedBy": [
              "getAttendanceRecords",
              "getAttendanceAction"
            ]
          },
          {
            "name": "PaginatedAttendanceResult",
            "type": "type",
            "file": "types.ts",
            "definition": "{ items: AttendanceListItem[], total, page, pageSize, totalPages }",
            "usedBy": [
              "getAttendanceRecords",
              "getAttendanceAction"
            ]
          },
          {
            "name": "ATTENDANCE_STATUS_LABELS",
            "type": "const",
            "file": "types.ts",
            "definition": "状态中文标签常量",
            "usedBy": [
              "attendance/components"
            ]
          },
          {
            "name": "ATTENDANCE_STATUS_COLORS",
            "type": "const",
            "file": "types.ts",
            "definition": "状态颜色常量（用于 Badge）",
            "usedBy": [
              "attendance/components"
            ]
          }
        ],
        "components": [
          {
            "name": "AttendanceSheet",
            "file": "components/attendance-sheet.tsx",
            "purpose": "批量点名表单（班级/日期选择器 + 学生表格 + 每行状态 Select + 全部标记到场按钮）"
          },
          {
            "name": "AttendanceRecordList",
            "file": "components/attendance-record-list.tsx",
            "purpose": "考勤记录列表表格（含删除确认对话框）"
          },
          {
            "name": "AttendanceStatsCard",
            "file": "components/attendance-stats-card.tsx",
            "purpose": "统计卡片（总数、到场、缺勤、迟到、早退、请假、出勤率、迟到率）"
          },
          {
            "name": "AttendanceStatsCards",
            "file": "components/attendance-stats-cards.tsx",
            "purpose": "管理员考勤总览页统计概览卡片组（总记录数、出勤、缺勤、迟到、早退、出勤率，6 卡片网格布局）"
          },
          {
            "name": "AttendanceFilters",
            "file": "components/attendance-filters.tsx",
            "purpose": "URL 同步筛选器（班级、状态、日期）"
          },
          {
            "name": "StudentAttendanceView",
            "file": "components/student-attendance-view.tsx",
            "purpose": "学生/家长视图（统计卡片 + 最近记录表格）"
          },
          {
            "name": "AttendanceRulesForm",
            "file": "components/attendance-rules-form.tsx",
            "purpose": "考勤规则配置表单（班级选择器、迟到/早退阈值、自动标记勾选）"
          },
          {
            "name": "AttendanceStatsClassSelector",
            "file": "components/attendance-stats-class-selector.tsx",
            "purpose": "考勤统计页班级筛选器（Link 筛选按钮组，含 focus-visible 焦点样式）",
            "deps": [
              "next/link",
              "shared/lib/utils.cn"
            ],
            "usedBy": [
              "teacher/attendance/stats/page.tsx"
            ]
          },
          {
            "name": "AttendancePageLayout",
            "file": "components/attendance-page-layout.tsx",
            "purpose": "考勤页面布局骨架（P2-3 新增：抽取 admin/teacher 考勤页重复的 header/stats/filters/children 布局结构，统一 spacing 与响应式）",
            "deps": [
              "shared/lib/utils.cn"
            ],
            "usedBy": [
              "admin/attendance/page.tsx",
              "teacher/attendance/page.tsx"
            ]
          }
        ],
        "importExport": [
          {
            "name": "exportAttendanceRecordsToExcel",
            "signature": "(params: { scope: DataScope; currentUserId?: string; classId?: string; status?: string; date?: string }) => Promise<Buffer>",
            "file": "export.ts",
            "purpose": "导出考勤记录到 Excel（P2-11 新增：Sheet1 考勤明细—学生/班级/日期/状态/备注/记录人/创建时间；Sheet2 统计汇总—总记录数/到场/缺勤/迟到/早退/请假/出勤率；列头使用 next-intl getTranslations 国际化）",
            "deps": [
              "shared.lib.excel.exportToExcel",
              "data-access.getAttendanceRecords",
              "data-access.getAttendanceStats",
              "next-intl/server.getTranslations"
            ],
            "usedBy": [
              "app/api/export/route.ts"
            ]
          }
        ]
      }
    },
    "scheduling": {
      "path": "src/modules/scheduling",
      "description": "排课与调课：管理员配置班级排课规则（每日课时、连续课时、午休、上下学时间、避免背靠背、科目均衡），自动排课引擎按规则生成周课表，调课/代课申请与审批流程，课表冲突检测。",
      "exports": {
        "actions": [
          {
            "name": "saveSchedulingRulesAction",
            "permission": "SCHEDULE_ADJUST",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "保存班级排课规则（upsert，classId 为空时为全局规则）",
            "deps": [
              "requirePermission",
              "data-access.upsertSchedulingRules",
              "revalidatePath"
            ],
            "usedBy": [
              "scheduling-rules-form.tsx"
            ]
          },
          {
            "name": "autoScheduleAction",
            "permission": "SCHEDULE_AUTO",
            "signature": "(prevState: ActionState<AutoScheduleResult> | null, formData: FormData) => Promise<ActionState<AutoScheduleResult>>",
            "purpose": "根据规则与科目分配生成预览课表（不落库）",
            "deps": [
              "requirePermission",
              "data-access.getSchedulingRules",
              "data-access.getClassSubjectsForScheduling",
              "data-access.getClassroomsForScheduling",
              "auto-scheduler.autoSchedule",
              "auto-scheduler.buildDefaultTimeSlots",
              "revalidatePath"
            ],
            "usedBy": [
              "auto-schedule-panel.tsx"
            ]
          },
          {
            "name": "applyAutoScheduleAction",
            "permission": "SCHEDULE_AUTO",
            "signature": "(classId: string, schedules: Array<{ weekday, startTime, endTime, course, location }>) => Promise<ActionState<number>>",
            "purpose": "将生成的课表写入 classSchedule 表（先删除该班旧课表再插入新课表，事务）",
            "deps": [
              "requirePermission",
              "shared.db",
              "shared.db.schema.classSchedule",
              "@paralleldrive/cuid2",
              "revalidatePath"
            ],
            "usedBy": [
              "auto-schedule-panel.tsx"
            ]
          },
          {
            "name": "requestScheduleChangeAction",
            "permission": "SCHEDULE_ADJUST",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "提交调课/代课申请（status=pending）",
            "deps": [
              "requirePermission",
              "data-access.createScheduleChange",
              "revalidatePath"
            ],
            "usedBy": [
              "schedule-change-form.tsx"
            ]
          },
          {
            "name": "approveScheduleChangeAction",
            "permission": "SCHEDULE_AUTO",
            "signature": "(changeId: string) => Promise<ActionState>",
            "purpose": "审批通过调课申请（status=approved）",
            "deps": [
              "requirePermission",
              "data-access.updateScheduleChangeStatus",
              "revalidatePath"
            ],
            "usedBy": [
              "schedule-change-list.tsx"
            ]
          },
          {
            "name": "rejectScheduleChangeAction",
            "permission": "SCHEDULE_AUTO",
            "signature": "(changeId: string, reason?: string) => Promise<ActionState>",
            "purpose": "驳回调课申请（status=rejected）",
            "deps": [
              "requirePermission",
              "data-access.updateScheduleChangeStatus",
              "revalidatePath"
            ],
            "usedBy": [
              "schedule-change-list.tsx"
            ]
          },
          {
            "name": "getScheduleChangesAction",
            "permission": "SCHEDULE_ADJUST",
            "signature": "(params: ScheduleChangeQueryParams) => Promise<ActionState<ScheduleChangeListItem[]>>",
            "purpose": "查询调课申请列表（可按 classId/status/requesterId 过滤）",
            "deps": [
              "requirePermission",
              "data-access.getScheduleChanges"
            ],
            "usedBy": [
              "admin/scheduling/changes/page.tsx",
              "teacher/schedule-changes/page.tsx"
            ]
          },
          {
            "name": "getClassConflictsAction",
            "permission": "SCHEDULE_ADJUST",
            "signature": "(classId: string) => Promise<ActionState<ScheduleConflict[]>>",
            "purpose": "检测班级课表时间重叠冲突",
            "deps": [
              "requirePermission",
              "data-access.getClassConflicts"
            ],
            "usedBy": [
              "schedule-conflicts-view.tsx"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getSchedulingRules",
            "signature": "(classId?: string) => Promise<SchedulingRule[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.schedulingRules"
            ],
            "usedBy": [
              "saveSchedulingRulesAction",
              "autoScheduleAction",
              "admin/scheduling/rules/page.tsx"
            ]
          },
          {
            "name": "upsertSchedulingRules",
            "signature": "(data: SchedulingRuleInput) => Promise<string>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.schedulingRules",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "saveSchedulingRulesAction"
            ]
          },
          {
            "name": "getScheduleChanges",
            "signature": "(params: ScheduleChangeQueryParams) => Promise<ScheduleChangeListItem[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.scheduleChanges",
              "shared.db.schema.classes",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "getScheduleChangesAction",
              "admin/scheduling/changes/page.tsx",
              "teacher/schedule-changes/page.tsx"
            ]
          },
          {
            "name": "createScheduleChange",
            "signature": "(data: ScheduleChangeInput, requestedBy: string) => Promise<string>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.scheduleChanges",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "requestScheduleChangeAction"
            ]
          },
          {
            "name": "updateScheduleChangeStatus",
            "signature": "(id: string, status: 'approved'|'rejected'|'completed', approverId: string) => Promise<void>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.scheduleChanges"
            ],
            "usedBy": [
              "approveScheduleChangeAction",
              "rejectScheduleChangeAction"
            ]
          },
          {
            "name": "getClassConflicts",
            "signature": "(classId: string) => Promise<ScheduleConflict[]>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.classSchedule"
            ],
            "usedBy": [
              "getClassConflictsAction"
            ]
          },
          {
            "name": "getAdminClassesForScheduling",
            "signature": "() => Promise<Array<{ id, name, grade }>>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.classes"
            ],
            "usedBy": [
              "admin/scheduling/rules/page.tsx",
              "admin/scheduling/auto/page.tsx",
              "admin/scheduling/changes/page.tsx",
              "teacher/schedule-changes/page.tsx"
            ]
          },
          {
            "name": "getTeachersForScheduling",
            "signature": "() => Promise<Array<{ id, name, email }>>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "shared.db.schema.classSubjectTeachers"
            ],
            "usedBy": [
              "teacher/schedule-changes/page.tsx"
            ]
          },
          {
            "name": "getClassroomsForScheduling",
            "signature": "() => Promise<Array<{ id, name, building }>>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.classrooms"
            ],
            "usedBy": [
              "autoScheduleAction"
            ]
          },
          {
            "name": "getClassSubjectsForScheduling",
            "signature": "(classId: string) => Promise<Array<{ subjectId, subjectName, teacherId }>>",
            "file": "data-access.ts",
            "deps": [
              "shared.db",
              "shared.db.schema.classSubjectTeachers",
              "shared.db.schema.subjects"
            ],
            "usedBy": [
              "autoScheduleAction"
            ]
          },
          {
            "name": "createClassScheduleItem",
            "signature": "(data: CreateClassScheduleItemInput) => Promise<string>",
            "file": "data-access-class-schedule.ts",
            "purpose": "创建课表项（P0-5 从 classes 模块迁移，含教师班级归属校验）",
            "deps": [
              "classes/data-access.getTeacherIdForMutations",
              "classes/data-access.verifyTeacherOwnsClass",
              "data-access.insertClassScheduleItem"
            ],
            "usedBy": [
              "classes/actions.createClassScheduleItemAction"
            ]
          },
          {
            "name": "updateClassScheduleItem",
            "signature": "(scheduleId: string, data: UpdateClassScheduleItemInput) => Promise<void>",
            "file": "data-access-class-schedule.ts",
            "purpose": "更新课表项（P0-5 从 classes 模块迁移，含教师班级归属校验）",
            "deps": [
              "classes/data-access.getTeacherIdForMutations",
              "classes/data-access.verifyTeacherOwnsClass",
              "data-access.updateClassScheduleItemById"
            ],
            "usedBy": [
              "classes/actions.updateClassScheduleItemAction"
            ]
          },
          {
            "name": "deleteClassScheduleItem",
            "signature": "(scheduleId: string) => Promise<void>",
            "file": "data-access-class-schedule.ts",
            "purpose": "删除课表项（P0-5 从 classes 模块迁移，含教师班级归属校验）",
            "deps": [
              "classes/data-access.getTeacherIdForMutations",
              "classes/data-access.verifyTeacherOwnsClass",
              "data-access.deleteClassScheduleItemById"
            ],
            "usedBy": [
              "classes/actions.deleteClassScheduleItemAction"
            ]
          }
        ],
        "autoScheduler": [
          {
            "name": "autoSchedule",
            "signature": "(params: AutoScheduleParams) => AutoScheduleResult",
            "file": "auto-scheduler.ts",
            "purpose": "贪心+冲突检测排课算法：按科目每周课时降序，为每节课选择第一个满足约束的时段（午休、每日窗口、班级/教师/教室冲突、每日最大课时、避免背靠背）",
            "deps": [
              "findOptimalSlot",
              "validateSchedule"
            ],
            "usedBy": [
              "autoScheduleAction"
            ]
          },
          {
            "name": "findOptimalSlot",
            "signature": "(args) => TimeSlot | null",
            "file": "auto-scheduler.ts",
            "purpose": "在候选时段中找到第一个满足所有约束的时段",
            "usedBy": [
              "autoSchedule"
            ]
          },
          {
            "name": "validateSchedule",
            "signature": "(schedules: GeneratedSchedule[], rules: SchedulingRule) => ScheduleConflict[]",
            "file": "auto-scheduler.ts",
            "purpose": "校验生成的课表是否违反规则，返回冲突列表",
            "usedBy": [
              "autoSchedule"
            ]
          },
          {
            "name": "buildDefaultTimeSlots",
            "signature": "(morningStart, afternoonEnd, lunchBreakStart, lunchBreakEnd) => TimeSlot[]",
            "file": "auto-scheduler.ts",
            "purpose": "根据上下学时间和午休时间构建默认时段（周一至周五，上午4节+下午4节）",
            "usedBy": [
              "autoScheduleAction"
            ]
          }
        ],
        "schemas": [
          {
            "name": "SchedulingRuleSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ classId, maxDailyHours?, maxContinuousHours?, lunchBreakStart?, lunchBreakEnd?, morningStart?, afternoonEnd?, avoidBackToBack?, balancedSubjects? }",
            "usedBy": [
              "saveSchedulingRulesAction"
            ]
          },
          {
            "name": "ScheduleChangeSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ classId, originalScheduleId?, originalTeacherId?, substituteTeacherId?, originalDate?, newDate?, newStartTime?, newEndTime?, reason }",
            "usedBy": [
              "requestScheduleChangeAction"
            ]
          },
          {
            "name": "AutoScheduleParamsSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ classId, rules: SchedulingRuleSchema, subjects: [{ subjectId, subjectName, weeklyHours, teacherId? }], teachers: [{ id, name }], classrooms: [{ id, name }], timeSlots: [{ weekday, startTime, endTime }] }",
            "usedBy": [
              "autoScheduleAction"
            ]
          },
          {
            "name": "ScheduleChangeStatusEnum",
            "type": "zod",
            "file": "schema.ts",
            "definition": "enum('pending','approved','rejected','completed')",
            "usedBy": [
              "ScheduleChangeSchema"
            ]
          },
          {
            "name": "ApproveScheduleChangeSchema",
            "type": "zod",
            "file": "schema.ts",
            "definition": "{ changeId, reason? }",
            "usedBy": [
              "approveScheduleChangeAction"
            ]
          }
        ],
        "types": [
          {
            "name": "ScheduleChangeStatus",
            "type": "type",
            "file": "types.ts",
            "definition": "'pending' | 'approved' | 'rejected' | 'completed'",
            "usedBy": [
              "scheduling/data-access",
              "scheduling/components"
            ]
          },
          {
            "name": "Weekday",
            "type": "type",
            "file": "types.ts",
            "definition": "1 | 2 | 3 | 4 | 5 | 6 | 7",
            "usedBy": [
              "CreateClassScheduleItemInput",
              "UpdateClassScheduleItemInput"
            ]
          },
          {
            "name": "CreateClassScheduleItemInput",
            "type": "type",
            "file": "types.ts",
            "definition": "{ classId, weekday: Weekday, startTime, endTime, course, location? }（P0-5 从 classes/types.ts 迁移）",
            "usedBy": [
              "scheduling/data-access-class-schedule.createClassScheduleItem"
            ]
          },
          {
            "name": "UpdateClassScheduleItemInput",
            "type": "type",
            "file": "types.ts",
            "definition": "{ classId?, weekday?: Weekday, startTime?, endTime?, course?, location? }（P0-5 从 classes/types.ts 迁移）",
            "usedBy": [
              "scheduling/data-access-class-schedule.updateClassScheduleItem"
            ]
          },
          {
            "name": "SchedulingRule",
            "type": "type",
            "file": "types.ts",
            "definition": "{ id, classId, maxDailyHours, maxContinuousHours, lunchBreakStart, lunchBreakEnd, morningStart, afternoonEnd, avoidBackToBack, balancedSubjects, createdAt, updatedAt }",
            "usedBy": [
              "scheduling-rules-form.tsx",
              "auto-scheduler.ts"
            ]
          },
          {
            "name": "ScheduleChange",
            "type": "type",
            "file": "types.ts",
            "definition": "调课申请完整类型",
            "usedBy": [
              "scheduling/data-access"
            ]
          },
          {
            "name": "ScheduleChangeListItem",
            "type": "type",
            "file": "types.ts",
            "definition": "{ ...ScheduleChange, className, originalTeacherName, substituteTeacherName, requesterName, approverName }",
            "usedBy": [
              "schedule-change-list.tsx",
              "页面"
            ]
          },
          {
            "name": "TimeSlot",
            "type": "type",
            "file": "types.ts",
            "definition": "{ weekday, startTime, endTime }",
            "usedBy": [
              "auto-scheduler.ts"
            ]
          },
          {
            "name": "ScheduleConflict",
            "type": "type",
            "file": "types.ts",
            "definition": "{ type: 'teacher_overlap'|'classroom_overlap'|'class_overlap'|'rule_violation', description, scheduleIds }",
            "usedBy": [
              "auto-schedule-result.tsx",
              "schedule-conflicts-view.tsx"
            ]
          },
          {
            "name": "AutoScheduleResult",
            "type": "type",
            "file": "types.ts",
            "definition": "{ success, scheduledCount, conflictCount, conflicts, schedules: GeneratedSchedule[] }",
            "usedBy": [
              "auto-schedule-panel.tsx",
              "auto-schedule-result.tsx"
            ]
          },
          {
            "name": "GeneratedSchedule",
            "type": "type",
            "file": "types.ts",
            "definition": "{ classId, weekday, startTime, endTime, course, location, teacherId, subjectId }",
            "usedBy": [
              "auto-scheduler.ts",
              "auto-schedule-result.tsx"
            ]
          },
          {
            "name": "AutoScheduleParams",
            "type": "type",
            "file": "types.ts",
            "definition": "{ classId, rules, subjects, teachers, classrooms, timeSlots }",
            "usedBy": [
              "auto-scheduler.ts",
              "autoScheduleAction"
            ]
          },
          {
            "name": "ScheduleChangeQueryParams",
            "type": "type",
            "file": "types.ts",
            "definition": "{ classId?, status?, requesterId? }",
            "usedBy": [
              "getScheduleChanges",
              "getScheduleChangesAction"
            ]
          },
          {
            "name": "SCHEDULE_CHANGE_STATUS_LABELS",
            "type": "const",
            "file": "types.ts",
            "definition": "状态英文标签常量",
            "usedBy": [
              "schedule-change-list.tsx"
            ]
          },
          {
            "name": "SCHEDULE_CHANGE_STATUS_COLORS",
            "type": "const",
            "file": "types.ts",
            "definition": "状态颜色常量（用于 Badge）",
            "usedBy": [
              "schedule-change-list.tsx"
            ]
          }
        ],
        "components": [
          {
            "name": "SchedulingRulesForm",
            "file": "components/scheduling-rules-form.tsx",
            "purpose": "排课规则配置表单（班级选择器、每日最大课时、连续课时、午休时间、上下学时间、避免背靠背、科目均衡）"
          },
          {
            "name": "AutoSchedulePanel",
            "file": "components/auto-schedule-panel.tsx",
            "purpose": "自动排课面板（班级选择→预览→应用流程，调用 autoScheduleAction 和 applyAutoScheduleAction）"
          },
          {
            "name": "AutoScheduleResultView",
            "file": "components/auto-schedule-result.tsx",
            "purpose": "排课结果预览（课表表格 + 冲突/警告列表）"
          },
          {
            "name": "ScheduleChangeForm",
            "file": "components/schedule-change-form.tsx",
            "purpose": "调课/代课申请表单（班级、原任课教师、代课教师、原日期、新日期、新时间、原因）"
          },
          {
            "name": "ScheduleChangeList",
            "file": "components/schedule-change-list.tsx",
            "purpose": "调课申请列表表格（含审批/驳回对话框，canApprove 控制是否显示审批按钮）"
          },
          {
            "name": "ScheduleConflictsView",
            "file": "components/schedule-conflicts-view.tsx",
            "purpose": "冲突检测视图（班级选择器 + 检测按钮 + 冲突结果列表）"
          }
        ]
      }
    },
    "proctoring": {
      "path": "src/modules/proctoring",
      "description": "考试监考模块：监考模式考试实时监控、防作弊事件采集、教师监考面板、学生端防作弊监控、考试模式配置",
      "exports": {
        "actions": [
          {
            "name": "recordProctoringEventAction",
            "permission": "EXAM_SUBMIT",
            "signature": "(prevState: ActionState<{id:string}> | null, formData: FormData) => Promise<ActionState<{id:string}>>",
            "purpose": "学生端上报监考事件（含 submission 归属校验，刷新监考面板缓存）",
            "deps": [
              "requirePermission(EXAM_SUBMIT)",
              "data-access.getExamSubmissionForProctoring",
              "data-access.recordProctoringEvent",
              "revalidatePath"
            ],
            "usedBy": [
              "anti-cheat-monitor.tsx"
            ]
          },
          {
            "name": "getProctoringDashboardAction",
            "permission": "EXAM_PROCTOR",
            "signature": "(examId: string) => Promise<ActionState<ProctoringDashboardData>>",
            "purpose": "获取监考面板数据（摘要+学生状态+最近事件）",
            "deps": [
              "requirePermission(EXAM_PROCTOR)",
              "data-access.getExamForProctoring,getExamProctoringSummary,getStudentProctoringStatuses,getRecentProctoringEvents"
            ],
            "usedBy": [
              "proctoring-dashboard.tsx"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getExamSubmissionForProctoring",
            "signature": "(submissionId: string, studentId: string) => Promise<{id,examId,studentId} | null>",
            "purpose": "校验提交记录归属（监考事件上报前的安全校验）",
            "usedBy": [
              "actions.recordProctoringEventAction"
            ]
          },
          {
            "name": "recordProctoringEvent",
            "signature": "(input: RecordProctoringEventInput) => Promise<ProctoringEvent>",
            "purpose": "记录一条监考事件",
            "usedBy": [
              "actions.recordProctoringEventAction",
              "api/proctoring/event/route.ts"
            ]
          },
          {
            "name": "getProctoringEvents",
            "signature": "(examId: string, filters?: GetProctoringEventsFilters) => Promise<ProctoringEventWithDetails[]>",
            "purpose": "查询考试监考事件（含学生姓名、考试标题）",
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getProctoringEventsBySubmission",
            "signature": "(submissionId: string) => Promise<ProctoringEvent[]>",
            "purpose": "查询提交的监考事件",
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getExamProctoringSummary",
            "signature": "(examId: string) => Promise<ExamProctoringSummary>",
            "purpose": "获取考试监考摘要",
            "usedBy": [
              "actions.getProctoringDashboardAction",
              "teacher/exams/[id]/proctoring/page.tsx"
            ]
          },
          {
            "name": "getStudentProctoringStatuses",
            "signature": "(examId: string) => Promise<StudentProctoringStatus[]>",
            "purpose": "获取所有学生监考状态（v3 优化：Promise.all 并行执行 getUserNamesByIds 与事件聚合查询）",
            "usedBy": [
              "actions.getProctoringDashboardAction",
              "teacher/exams/[id]/proctoring/page.tsx"
            ]
          },
          {
            "name": "getExamForProctoring",
            "signature": "(examId: string) => Promise<{id,title,examMode,config} | null>",
            "purpose": "获取考试信息（含 examMode 设置）",
            "usedBy": [
              "actions.getProctoringDashboardAction",
              "teacher/exams/[id]/proctoring/page.tsx"
            ]
          },
          {
            "name": "getRecentProctoringEvents",
            "signature": "(examId: string, limit?: number) => Promise<ProctoringEventWithDetails[]>",
            "purpose": "获取最近 N 条监考事件",
            "usedBy": [
              "actions.getProctoringDashboardAction",
              "teacher/exams/[id]/proctoring/page.tsx"
            ]
          }
        ],
        "types": [
          {
            "name": "ProctoringEventType",
            "type": "type",
            "definition": "\"tab_switch\" | \"window_blur\" | \"copy_attempt\" | \"paste_attempt\" | \"right_click\" | \"devtools_open\" | \"fullscreen_exit\" | \"idle_timeout\""
          },
          {
            "name": "ExamMode",
            "type": "type",
            "definition": "\"homework\" | \"timed\" | \"proctored\""
          },
          {
            "name": "ProctoringEvent",
            "type": "interface",
            "definition": "{ id, submissionId, studentId, examId, eventType, eventDetail?, occurredAt, createdAt }"
          },
          {
            "name": "ProctoringEventWithDetails",
            "type": "interface",
            "definition": "ProctoringEvent & { studentName, examTitle }"
          },
          {
            "name": "ExamProctoringSummary",
            "type": "interface",
            "definition": "{ examId, examTitle, examMode, totalStudents, startedStudents, submittedStudents, totalEvents, abnormalStudents, eventsByType }"
          },
          {
            "name": "StudentProctoringStatus",
            "type": "interface",
            "definition": "{ studentId, studentName, submissionId, submissionStatus, eventCount, lastEventAt, isAbnormal, eventsByType }"
          },
          {
            "name": "ProctoringDashboardData",
            "type": "interface",
            "definition": "{ summary, students, recentEvents }"
          },
          {
            "name": "ExamModeConfig",
            "type": "interface",
            "definition": "{ examMode, durationMinutes, shuffleQuestions, allowLateStart, lateStartGraceMinutes, antiCheatEnabled }"
          },
          {
            "name": "PROCTORING_EVENT_LABELS",
            "type": "const",
            "description": "事件类型中文标签常量"
          },
          {
            "name": "EXAM_MODE_LABELS",
            "type": "const",
            "description": "考试模式中文标签常量"
          },
          {
            "name": "ABNORMAL_EVENT_THRESHOLD",
            "type": "const",
            "description": "异常学生事件数阈值（3）"
          }
        ],
        "components": [
          {
            "name": "ProctoringDashboard",
            "file": "components/proctoring-dashboard.tsx",
            "purpose": "教师监考面板（实时学生状态、异常事件统计、异常学生高亮、10 秒轮询、usePermission 权限控制；✅ P1-3 已修复：用类型守卫函数 isProctoringEventType + toProctoringEventTypes 替代 Object.keys(...) as ProctoringEventType[] 断言）"
          },
          {
            "name": "AntiCheatMonitor",
            "file": "components/anti-cheat-monitor.tsx",
            "purpose": "学生端防作弊监控（visibilitychange/blur/copy/paste/contextmenu/keydown/fullscreenchange 监听、空闲超时检测、强制全屏、警告提示、事件上报）"
          },
          {
            "name": "ExamModeConfig",
            "file": "components/exam-mode-config.tsx",
            "purpose": "考试模式配置（✅ P1-2 已修复：已集成到 exam-form.tsx，useFormContext 替代 Control prop 避免 Control<T> 不变型问题，移除全部 10 处 as 类型断言；作业/限时/监考模式选择，限时设置时长，监考设置防作弊选项）"
          }
        ]
      }
    },
    "diagnostic": {
      "path": "src/modules/diagnostic",
      "description": "学情诊断报告模块：基于知识点掌握度（knowledgePointMastery）生成个人/班级诊断报告，掌握度雷达图（学生 vs 班级平均），强项/弱项分析，知识点掌握度热力图，需重点关注学生列表，报告发布/删除管理",
      "exports": {
        "dataAccess": [
          {
            "name": "getStudentMastery",
            "signature": "(studentId: string) => Promise<MasteryWithKnowledgePoint[]>",
            "file": "data-access.ts",
            "purpose": "获取学生在所有知识点的掌握度（含知识点名称，按掌握度降序）。P3-19 修复：移除 export，改为模块内部函数",
            "deps": [
              "shared.db",
              "shared.db.schema.knowledgePointMastery",
              "shared.db.schema.knowledgePoints"
            ],
            "usedBy": [
              "data-access.getStudentMasterySummary"
            ]
          },
          {
            "name": "getStudentMasterySummary",
            "signature": "(studentId: string) => Promise<StudentMasterySummary | null>",
            "file": "data-access.ts",
            "purpose": "获取学生掌握度摘要（平均掌握度、强项≥80%、弱项<80%[P3-16修复]。P3-18 修复：getUserNamesByIds 与 getStudentMastery 并行查询）",
            "deps": [
              "shared.db",
              "shared.db.schema.users",
              "data-access.getStudentMastery"
            ],
            "usedBy": [
              "data-access-reports.generateDiagnosticReport",
              "teacher/diagnostic/student/[studentId]/page.tsx",
              "student/diagnostic/page.tsx"
            ]
          },
          {
            "name": "updateMasteryFromSubmission",
            "signature": "(submissionId: string) => Promise<void>",
            "file": "data-access.ts",
            "purpose": "从提交答案更新掌握度（按知识点聚合正确率，onDuplicateKeyUpdate upsert；v3 优化：Promise.all 并行执行多个知识点 upsert）",
            "deps": [
              "shared.db",
              "shared.db.schema.examSubmissions",
              "shared.db.schema.submissionAnswers",
              "shared.db.schema.questionsToKnowledgePoints",
              "shared.db.schema.knowledgePointMastery"
            ],
            "usedBy": [
              "待扩展（作业/考试提交后触发）"
            ]
          },
          {
            "name": "getClassMasterySummary",
            "signature": "(classId: string) => Promise<ClassMasterySummary | null>",
            "file": "data-access.ts",
            "purpose": "获取班级掌握度摘要（学生数、平均掌握度、知识点统计、需重点关注学生；v3 优化：两阶段 Promise.all 并行查询班级信息+学生 ID、用户名+掌握度）",
            "deps": [
              "shared.db",
              "shared.db.schema.classes",
              "shared.db.schema.classEnrollments",
              "shared.db.schema.users",
              "shared.db.schema.knowledgePointMastery",
              "shared.db.schema.knowledgePoints"
            ],
            "usedBy": [
              "data-access-reports.generateClassDiagnosticReport",
              "teacher/diagnostic/class/[classId]/page.tsx"
            ]
          },
          {
            "name": "getKnowledgePointStats",
            "signature": "(classId?: string, gradeId?: string) => Promise<KnowledgePointStat[]>",
            "file": "data-access.ts",
            "purpose": "获取知识点统计（按班级或年级聚合平均掌握度、掌握人数、未掌握人数）",
            "deps": [
              "shared.db",
              "shared.db.schema.classEnrollments",
              "shared.db.schema.users",
              "shared.db.schema.knowledgePointMastery",
              "shared.db.schema.knowledgePoints"
            ],
            "usedBy": [
              "teacher/diagnostic/student/[studentId]/page.tsx (班级平均对比)"
            ]
          },
          {
            "name": "generateDiagnosticReport",
            "signature": "(studentId: string, period: string, generatedBy: string) => Promise<string>",
            "file": "data-access-reports.ts",
            "purpose": "生成个人诊断报告（计算 overallScore、强项/弱项列表、复习建议，status=draft。P3-27 修复：使用 DiagnosticReportError 结构化错误码；P3-1 修复：toNumber 从 grades 模块导入）",
            "deps": [
              "shared.db",
              "shared.db.schema.learningDiagnosticReports",
              "data-access.getStudentMasterySummary",
              "grades.lib.grade-utils.toNumber",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "actions.generateStudentReportAction"
            ]
          },
          {
            "name": "generateClassDiagnosticReport",
            "signature": "(classId: string, period: string, generatedBy: string) => Promise<string>",
            "file": "data-access-reports.ts",
            "purpose": "生成班级诊断报告（聚合班级掌握度，识别薄弱知识点，status=draft，studentId 存生成者 ID。P3-27 修复：使用 DiagnosticReportError 结构化错误码）",
            "deps": [
              "shared.db",
              "shared.db.schema.learningDiagnosticReports",
              "data-access.getClassMasterySummary",
              "@paralleldrive/cuid2"
            ],
            "usedBy": [
              "actions.generateClassReportAction"
            ]
          },
          {
            "name": "getDiagnosticReports",
            "signature": "(filters: DiagnosticReportQueryParams, scope?: DataScope) => Promise<DiagnosticReportListResult>",
            "file": "data-access-reports.ts",
            "purpose": "查询诊断报告列表（可按 studentId/reportType/status/period 过滤，含学生名和生成者名。P3-15 修复：支持分页 limit/offset，返回 { reports, total } 结构，Promise.all 并行查询总数和数据）",
            "deps": [
              "shared.db",
              "shared.db.schema.learningDiagnosticReports",
              "shared.db.schema.users",
              "grades.lib.grade-utils.toNumber"
            ],
            "usedBy": [
              "teacher/diagnostic/page.tsx",
              "teacher/diagnostic/student/[studentId]/page.tsx",
              "student/diagnostic/page.tsx",
              "parent/diagnostic/page.tsx"
            ]
          },
          {
            "name": "getDiagnosticReportById",
            "signature": "(id: string) => Promise<DiagnosticReportWithDetails | null>",
            "file": "data-access-reports.ts",
            "purpose": "获取报告详情（含学生名和生成者名）",
            "deps": [
              "shared.db",
              "shared.db.schema.learningDiagnosticReports",
              "shared.db.schema.users"
            ],
            "usedBy": [
              "actions.getDiagnosticReportByIdAction"
            ]
          },
          {
            "name": "publishDiagnosticReport",
            "signature": "(id: string) => Promise<void>",
            "file": "data-access-reports.ts",
            "purpose": "发布诊断报告（status=published）",
            "deps": [
              "shared.db",
              "shared.db.schema.learningDiagnosticReports"
            ],
            "usedBy": [
              "actions.publishReportAction"
            ]
          },
          {
            "name": "deleteDiagnosticReport",
            "signature": "(id: string) => Promise<void>",
            "file": "data-access-reports.ts",
            "purpose": "删除诊断报告",
            "deps": [
              "shared.db",
              "shared.db.schema.learningDiagnosticReports"
            ],
            "usedBy": [
              "actions.deleteReportAction"
            ]
          }
        ],
        "actions": [
          {
            "name": "generateStudentReportAction",
            "permission": "DIAGNOSTIC_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "生成学生个人诊断报告（formData: studentId, period）",
            "deps": [
              "requirePermission",
              "data-access-reports.generateDiagnosticReport",
              "revalidatePath"
            ],
            "usedBy": [
              "components/student-diagnostic-view.tsx"
            ]
          },
          {
            "name": "generateClassReportAction",
            "permission": "DIAGNOSTIC_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "生成班级诊断报告（formData: classId, period）",
            "deps": [
              "requirePermission",
              "data-access-reports.generateClassDiagnosticReport",
              "revalidatePath"
            ],
            "usedBy": [
              "components/class-diagnostic-view.tsx"
            ]
          },
          {
            "name": "publishReportAction",
            "permission": "DIAGNOSTIC_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "发布诊断报告（formData: id）",
            "deps": [
              "requirePermission",
              "data-access-reports.publishDiagnosticReport",
              "revalidatePath"
            ],
            "usedBy": [
              "components/report-list.tsx"
            ]
          },
          {
            "name": "deleteReportAction",
            "permission": "DIAGNOSTIC_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "删除诊断报告（formData: id）",
            "deps": [
              "requirePermission",
              "data-access-reports.deleteDiagnosticReport",
              "revalidatePath"
            ],
            "usedBy": [
              "components/report-list.tsx"
            ]
          },
          {
            "name": "getDiagnosticReportsAction",
            "permission": "DIAGNOSTIC_READ",
            "signature": "(params: DiagnosticReportQueryParams) => Promise<ActionState<DiagnosticReportWithDetails[]>>",
            "file": "actions.ts",
            "purpose": "查询诊断报告列表（读权限）",
            "deps": [
              "requirePermission",
              "data-access-reports.getDiagnosticReports"
            ],
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getDiagnosticReportByIdAction",
            "permission": "DIAGNOSTIC_READ",
            "signature": "(id: string) => Promise<ActionState<DiagnosticReportWithDetails | null>>",
            "file": "actions.ts",
            "purpose": "获取诊断报告详情（读权限）",
            "deps": [
              "requirePermission",
              "data-access-reports.getDiagnosticReportById"
            ],
            "usedBy": [
              "待扩展"
            ]
          }
        ],
        "schema": [
          {
            "name": "GenerateStudentReportSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 生成学生个人诊断报告（studentId, period）",
            "usedBy": [
              "actions.generateStudentReportAction"
            ]
          },
          {
            "name": "GenerateClassReportSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 生成班级诊断报告（classId, period）",
            "usedBy": [
              "actions.generateClassReportAction"
            ]
          },
          {
            "name": "PublishReportSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 发布诊断报告（id）",
            "usedBy": [
              "actions.publishReportAction"
            ]
          },
          {
            "name": "DeleteReportSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 删除诊断报告（id）",
            "usedBy": [
              "actions.deleteReportAction"
            ]
          },
          {
            "name": "GetDiagnosticReportsSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 查询诊断报告列表（studentId?, reportType?, status?, period?）",
            "usedBy": [
              "actions.getDiagnosticReportsAction"
            ]
          },
          {
            "name": "GetDiagnosticReportByIdSchema",
            "type": "const",
            "file": "schema.ts",
            "description": "zod schema 获取诊断报告详情（id）",
            "usedBy": [
              "actions.getDiagnosticReportByIdAction"
            ]
          }
        ],
        "types": [
          {
            "name": "DiagnosticReportType",
            "type": "type",
            "file": "types.ts",
            "definition": "\"individual\" | \"class\" | \"grade\"",
            "usedBy": [
              "types.DiagnosticReport.reportType",
              "actions",
              "components/report-list.tsx"
            ]
          },
          {
            "name": "DiagnosticReportStatus",
            "type": "type",
            "file": "types.ts",
            "definition": "\"draft\" | \"published\" | \"archived\"",
            "usedBy": [
              "types.DiagnosticReport.status",
              "actions",
              "components/report-list.tsx"
            ]
          },
          {
            "name": "KnowledgePointMastery",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, studentId, knowledgePointId, masteryLevel(0-100), totalQuestions, correctQuestions, lastAssessedAt, createdAt, updatedAt }",
            "usedBy": [
              "data-access",
              "types.MasteryWithKnowledgePoint"
            ]
          },
          {
            "name": "MasteryWithKnowledgePoint",
            "type": "interface",
            "file": "types.ts",
            "definition": "KnowledgePointMastery & { knowledgePointName, knowledgePointDescription }",
            "usedBy": [
              "data-access.getStudentMastery",
              "types.StudentMasterySummary"
            ]
          },
          {
            "name": "StudentMasterySummary",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ studentId, studentName, averageMastery, totalKnowledgePoints, strengths(≥80), weaknesses(<80)[P3-16修复：消除60-79盲区], allMastery }",
            "usedBy": [
              "data-access.getStudentMasterySummary",
              "data-access-reports.generateDiagnosticReport",
              "components/student-diagnostic-view.tsx"
            ]
          },
          {
            "name": "DiagnosticReport",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, studentId, classId(v4-P1-4 新增: 班级报告关联班级 ID，个人报告为 null), generatedBy, reportType, period, summary, strengths[], weaknesses[], recommendations[], overallScore, status, createdAt, updatedAt }",
            "usedBy": [
              "data-access-reports",
              "types.DiagnosticReportWithDetails"
            ]
          },
          {
            "name": "DiagnosticReportWithDetails",
            "type": "interface",
            "file": "types.ts",
            "definition": "DiagnosticReport & { studentName, generatedByName }",
            "usedBy": [
              "data-access-reports.getDiagnosticReports",
              "actions",
              "components/report-list.tsx",
              "components/student-diagnostic-view.tsx"
            ]
          },
          {
            "name": "ClassMasterySummary",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ classId, className, studentCount, averageMastery, knowledgePointStats[], studentsNeedingAttention[] }",
            "usedBy": [
              "data-access.getClassMasterySummary",
              "data-access-reports.generateClassDiagnosticReport",
              "components/class-diagnostic-view.tsx"
            ]
          },
          {
            "name": "KnowledgePointStat",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ knowledgePointId, knowledgePointName, averageMastery, masteredCount(≥80), notMasteredCount(<60), totalStudents }",
            "usedBy": [
              "data-access.getKnowledgePointStats",
              "types.ClassMasterySummary",
              "components/class-diagnostic-view.tsx"
            ]
          },
          {
            "name": "DiagnosticReportQueryParams",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ studentId?, reportType?, status?, period?, limit?(P3-15), offset?(P3-15) }",
            "usedBy": [
              "data-access-reports.getDiagnosticReports",
              "actions.getDiagnosticReportsAction"
            ]
          },
          {
            "name": "DiagnosticReportListResult",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ reports: DiagnosticReportWithDetails[], total: number }（P3-15 修复：分页查询结果）",
            "usedBy": [
              "data-access-reports.getDiagnosticReports"
            ]
          },
          {
            "name": "MasteryRadarPoint",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ knowledgePoint, student(0-100), classAverage?(0-100) }",
            "usedBy": [
              "components/mastery-radar-chart.tsx",
              "components/student-diagnostic-view.tsx",
              "teacher/diagnostic/student/[studentId]/page.tsx"
            ]
          }
        ],
        "components": [
          {
            "name": "MasteryRadarChart",
            "file": "components/mastery-radar-chart.tsx",
            "purpose": "知识点掌握度雷达图（recharts RadarChart，学生 vs 班级平均对比，无数据时显示 EmptyState）",
            "deps": [
              "recharts",
              "shared/components/ui/card",
              "shared/components/ui/chart",
              "shared/components/ui/empty-state"
            ]
          },
          {
            "name": "StudentDiagnosticView",
            "file": "components/student-diagnostic-view.tsx",
            "purpose": "学生诊断视图（概览卡片、雷达图、强项/弱项列表、生成报告表单[DIAGNOSTIC_MANAGE]、最新报告与建议展示；v3-P2 新增：practiceHrefBase prop，null 时隐藏练习按钮；P3-22 改进：练习按钮添加 aria-label 含知识点名）",
            "props": "{ studentId, summary, classAverage?, reports?, practiceHrefBase?: string | null }",
            "deps": [
              "usePermission",
              "actions.generateStudentReportAction",
              "components/mastery-radar-chart",
              "shared/components/ui/*"
            ]
          },
          {
            "name": "ClassDiagnosticView",
            "file": "components/class-diagnostic-view.tsx",
            "purpose": "班级诊断视图（概览卡片、知识点掌握度热力图[绿/黄/橙/红]、知识点排名表、需重点关注学生表[链接到学生视图]、生成班级报告表单[DIAGNOSTIC_MANAGE]）",
            "deps": [
              "usePermission",
              "actions.generateClassReportAction",
              "shared/components/ui/*"
            ]
          },
          {
            "name": "ReportList",
            "file": "components/report-list.tsx",
            "purpose": "诊断报告列表（reportType/status 过滤器[URL searchParams]、报告表格、发布/删除操作[DIAGNOSTIC_MANAGE]、确认对话框）",
            "deps": [
              "usePermission",
              "actions.publishReportAction",
              "actions.deleteReportAction",
              "shared/components/ui/*"
            ]
          }
        ]
      }
    },
    "elective": {
      "path": "src/modules/elective",
      "description": "选课管理模块：选修课程 CRUD、选课开放/关闭、学生选课/退课、抽签模式批量录取、FCFS 即时录取、DataScope 行级过滤（admin 全部、teacher 所教、grade_head 所管年级、student 可选课程）",
      "exports": {
        "actions": [
          {
            "name": "createElectiveCourseAction",
            "permission": "ELECTIVE_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "创建选修课程（formData: name, subjectId?, teacherId, gradeId?, description?, capacity?, classroom?, schedule?, startDate?, endDate?, selectionStartAt?, selectionEndAt?, selectionMode?, credit?）",
            "deps": [
              "requirePermission(ELECTIVE_MANAGE)",
              "data-access.createElectiveCourse",
              "revalidatePath"
            ],
            "usedBy": [
              "admin/elective/create/page.tsx"
            ]
          },
          {
            "name": "updateElectiveCourseAction",
            "permission": "ELECTIVE_MANAGE",
            "signature": "(id: string, prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "更新选修课程",
            "deps": [
              "requirePermission(ELECTIVE_MANAGE)",
              "data-access.getElectiveCourseById",
              "data-access.updateElectiveCourse",
              "revalidatePath"
            ],
            "usedBy": [
              "admin/elective/[id]/edit/page.tsx"
            ]
          },
          {
            "name": "deleteElectiveCourseAction",
            "permission": "ELECTIVE_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "删除选修课程（formData: courseId）",
            "deps": [
              "requirePermission(ELECTIVE_MANAGE)",
              "data-access.deleteElectiveCourse",
              "revalidatePath"
            ],
            "usedBy": [
              "components/elective-course-list.tsx"
            ]
          },
          {
            "name": "openSelectionAction",
            "permission": "ELECTIVE_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "开放选课（formData: courseId）",
            "deps": [
              "requirePermission(ELECTIVE_MANAGE)",
              "data-access.openSelection",
              "revalidatePath"
            ],
            "usedBy": [
              "components/elective-course-list.tsx"
            ]
          },
          {
            "name": "closeSelectionAction",
            "permission": "ELECTIVE_MANAGE",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "关闭选课（formData: courseId）",
            "deps": [
              "requirePermission(ELECTIVE_MANAGE)",
              "data-access.closeSelection",
              "revalidatePath"
            ],
            "usedBy": [
              "components/elective-course-list.tsx"
            ]
          },
          {
            "name": "runLotteryAction",
            "permission": "ELECTIVE_MANAGE",
            "signature": "(prevState: ActionState<{enrolled:number,waitlist:number}> | null, formData: FormData) => Promise<ActionState<{enrolled:number,waitlist:number}>>",
            "file": "actions.ts",
            "purpose": "执行抽签录取（formData: courseId）",
            "deps": [
              "requirePermission(ELECTIVE_MANAGE)",
              "data-access-operations.runLottery",
              "revalidatePath"
            ],
            "usedBy": [
              "components/elective-course-list.tsx"
            ]
          },
          {
            "name": "selectCourseAction",
            "permission": "ELECTIVE_SELECT",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "学生选课（formData: courseId, priority?）",
            "deps": [
              "requirePermission(ELECTIVE_SELECT)",
              "data-access-operations.selectCourse",
              "revalidatePath"
            ],
            "usedBy": [
              "components/student-selection-view.tsx"
            ]
          },
          {
            "name": "dropCourseAction",
            "permission": "ELECTIVE_SELECT",
            "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
            "file": "actions.ts",
            "purpose": "学生退课（formData: courseId）",
            "deps": [
              "requirePermission(ELECTIVE_SELECT)",
              "data-access-operations.dropCourse",
              "revalidatePath"
            ],
            "usedBy": [
              "components/student-selection-view.tsx"
            ]
          },
          {
            "name": "getElectiveCoursesAction",
            "permission": "ELECTIVE_READ",
            "signature": "(params?: GetElectiveCoursesParams) => Promise<ActionState<ElectiveCourseWithDetails[]>>",
            "file": "actions.ts",
            "purpose": "查询选修课程列表（按 DataScope 过滤）",
            "deps": [
              "requirePermission(ELECTIVE_READ)",
              "data-access.getElectiveCourses (scope, currentUserId)"
            ],
            "usedBy": [],
            "issues": [
              "P0: 无调用方——admin/teacher 页面绕过 Action 直接调用 data-access.getElectiveCourses，违反三层架构"
            ]
          },
          {
            "name": "getStudentSelectionsAction",
            "permission": "ELECTIVE_READ",
            "signature": "(studentId: string) => Promise<ActionState<CourseSelectionWithDetails[]>>",
            "file": "actions.ts",
            "purpose": "查询学生选课记录（含 DataScope 二次校验：class_members 仅自己，children 仅子女）",
            "deps": [
              "requirePermission(ELECTIVE_READ)",
              "data-access-selections.getStudentSelections"
            ],
            "usedBy": [],
            "issues": [
              "P0: 无调用方——页面绕过 Action 直接调用 data-access-selections.getStudentSelections"
            ]
          },
          {
            "name": "getAvailableCoursesAction",
            "permission": "ELECTIVE_SELECT",
            "signature": "() => Promise<ActionState<ElectiveCourseWithDetails[]>>",
            "file": "actions.ts",
            "purpose": "获取学生可选课程（status=open 且匹配年级）",
            "deps": [
              "requirePermission(ELECTIVE_SELECT)",
              "data-access-selections.getAvailableCoursesForStudent"
            ],
            "usedBy": [],
            "issues": [
              "P0: 无调用方——student/elective 页面绕过 Action 直接调用 data-access-selections.getAvailableCoursesForStudent"
            ]
          }
        ],
        "dataAccess": [
          {
            "name": "getElectiveCourses",
            "file": "data-access.ts",
            "signature": "(params?: GetElectiveCoursesParams & { scope?: DataScope; currentUserId?: string }) => Promise<ElectiveCourseWithDetails[]>",
            "purpose": "查询选修课程列表（按 scope 行级过滤：owned/class_taught 按 teacherId，grade_managed 按 gradeIds）",
            "usedBy": [
              "actions.getElectiveCoursesAction",
              "admin/elective/page.tsx",
              "teacher/elective/page.tsx"
            ]
          },
          {
            "name": "getElectiveCourseById",
            "file": "data-access.ts",
            "signature": "(id: string) => Promise<ElectiveCourseWithDetails | null>",
            "purpose": "获取课程详情",
            "usedBy": [
              "actions.updateElectiveCourseAction",
              "admin/elective/[id]/edit/page.tsx"
            ]
          },
          {
            "name": "createElectiveCourse",
            "file": "data-access.ts",
            "signature": "(data: CreateElectiveCourseInput, teacherId: string) => Promise<string>",
            "purpose": "创建选修课程（status=draft, enrolledCount=0）",
            "usedBy": [
              "actions.createElectiveCourseAction"
            ]
          },
          {
            "name": "updateElectiveCourse",
            "file": "data-access.ts",
            "signature": "(id: string, data: Partial<UpdateElectiveCourseInput>) => Promise<void>",
            "purpose": "更新选修课程字段",
            "usedBy": [
              "actions.updateElectiveCourseAction"
            ]
          },
          {
            "name": "deleteElectiveCourse",
            "file": "data-access.ts",
            "signature": "(id: string) => Promise<void>",
            "purpose": "删除选修课程",
            "usedBy": [
              "actions.deleteElectiveCourseAction"
            ]
          },
          {
            "name": "openSelection",
            "file": "data-access.ts",
            "signature": "(courseId: string) => Promise<void>",
            "purpose": "开放选课（status=open）",
            "usedBy": [
              "actions.openSelectionAction"
            ]
          },
          {
            "name": "closeSelection",
            "file": "data-access.ts",
            "signature": "(courseId: string) => Promise<void>",
            "purpose": "关闭选课（status=closed）",
            "usedBy": [
              "actions.closeSelectionAction"
            ]
          },
          {
            "name": "buildCourseSelect",
            "file": "data-access.ts",
            "signature": "() => query builder",
            "purpose": "构建 electiveCourses 表查询（仅查询本表字段，不跨表 JOIN；v3 重构：移除跨模块 LEFT JOIN，名称解析改由 resolveCourseDisplayNames 异步聚合）",
            "usedBy": [
              "data-access.getElectiveCourses",
              "data-access.getElectiveCourseById",
              "data-access-selections.getAvailableCoursesForStudent"
            ]
          },
          {
            "name": "mapCourseRow",
            "file": "data-access.ts",
            "signature": "(row: CourseCoreRow, display: {teacherName?, subjectName?, gradeName?}) => ElectiveCourseWithDetails",
            "purpose": "将核心行 + 显示名映射为 ElectiveCourseWithDetails（v3 抽取：消除 data-access 与 data-access-selections 重复代码）",
            "usedBy": [
              "data-access.getElectiveCourses",
              "data-access.getElectiveCourseById",
              "data-access-selections.getAvailableCoursesForStudent"
            ]
          },
          {
            "name": "resolveCourseDisplayNames",
            "file": "data-access.ts",
            "signature": "(rows: CourseCoreRow[]) => Promise<{teacherName?, subjectName?, gradeName?}[]>",
            "purpose": "并行聚合教师名（users.getUserNamesByIds）、学科（school.getSubjectOptions）、年级（school.getGradeOptions），返回每行的显示名映射（v3 重构：替代跨模块 LEFT JOIN）",
            "usedBy": [
              "data-access.getElectiveCourses",
              "data-access.getElectiveCourseById"
            ]
          },
          {
            "name": "getCourseSelections",
            "file": "data-access-selections.ts",
            "signature": "(courseId: string) => Promise<CourseSelectionWithDetails[]>",
            "purpose": "查询课程所有选课记录（按 priority, selectedAt 排序）",
            "usedBy": [
              "待扩展"
            ]
          },
          {
            "name": "getStudentSelections",
            "file": "data-access-selections.ts",
            "signature": "(studentId: string) => Promise<CourseSelectionWithDetails[]>",
            "purpose": "查询学生选课记录（按 selectedAt 降序）",
            "usedBy": [
              "actions.getStudentSelectionsAction",
              "student/elective/page.tsx"
            ]
          },
          {
            "name": "getStudentGradeId",
            "file": "data-access-selections.ts",
            "signature": "(studentId: string) => Promise<string | null>",
            "purpose": "获取学生所在年级 ID（通过 classEnrollments active 记录）",
            "usedBy": [
              "data-access-selections.getAvailableCoursesForStudent"
            ]
          },
          {
            "name": "getAvailableCoursesForStudent",
            "file": "data-access-selections.ts",
            "signature": "(studentId: string, gradeId?: string | null) => Promise<ElectiveCourseWithDetails[]>",
            "purpose": "获取学生可选课程（status=open 且 gradeId 匹配或为空）",
            "usedBy": [
              "actions.getAvailableCoursesAction",
              "student/elective/page.tsx"
            ]
          },
          {
            "name": "runLottery",
            "file": "data-access-operations.ts",
            "signature": "(courseId: string) => Promise<{enrolled: number, waitlist: number}>",
            "purpose": "抽签录取（Fisher-Yates 无偏洗牌 selected 记录，前 capacity 名 enrolled，其余 waitlist，课程 status=closed；v3 修复：替换 sort(Math.random) 有偏洗牌）",
            "usedBy": [
              "actions.runLotteryAction"
            ]
          },
          {
            "name": "selectCourse",
            "file": "data-access-operations.ts",
            "signature": "(courseId: string, studentId: string, priority?: number) => Promise<{status: CourseSelectionStatus, message: string}>",
            "purpose": "学生选课（校验课程状态/时间窗口/重复选课；FCFS 模式即时 enrolled/waitlist，lottery 模式 selected；v3 修复：db.transaction 包裹 + .for('update') 锁课程行防 FCFS 超卖）",
            "usedBy": [
              "actions.selectCourseAction"
            ]
          },
          {
            "name": "dropCourse",
            "file": "data-access-operations.ts",
            "signature": "(courseId: string, studentId: string) => Promise<void>",
            "purpose": "学生退课（status=dropped；FCFS 模式自动递补 waitlist 首位；v3 修复：db.transaction 包裹 + .for('update') 锁课程行保证递补一致性）",
            "usedBy": [
              "actions.dropCourseAction"
            ]
          },
          {
            "name": "checkScheduleConflict",
            "file": "data-access-operations.ts",
            "signature": "(tx: Tx, studentId: string, courseId: string) => Promise<boolean>",
            "purpose": "查询学生已选课程时间段，与新课程时间段逐一调用 isScheduleConflict 判断冲突（P2-9 新增：选课时间冲突检测，selectCourse 调用）",
            "deps": [
              "shared.db",
              "shared.db.schema.courseSelections",
              "shared.db.schema.electiveCourses",
              "lib.isScheduleConflict"
            ],
            "usedBy": [
              "data-access-operations.selectCourse"
            ]
          },
          {
            "name": "checkCreditLimit",
            "file": "data-access-operations.ts",
            "signature": "(tx: Tx, studentId: string, courseId: string) => Promise<{ exceeded: boolean; current: number; max: number }>",
            "purpose": "查询学生本学期已选课程学分总和，加上新课程学分后判断是否超过 MAX_CREDIT_PER_TERM(10)（P2-10 新增：学分上限校验，selectCourse 调用）",
            "deps": [
              "shared.db",
              "shared.db.schema.courseSelections",
              "shared.db.schema.electiveCourses"
            ],
            "usedBy": [
              "data-access-operations.selectCourse"
            ]
          }
        ],
        "types": [
          {
            "name": "ElectiveCourseStatus",
            "type": "type",
            "file": "types.ts",
            "definition": "\"draft\" | \"open\" | \"closed\" | \"cancelled\""
          },
          {
            "name": "ElectiveSelectionMode",
            "type": "type",
            "file": "types.ts",
            "definition": "\"fcfs\" | \"lottery\""
          },
          {
            "name": "CourseSelectionStatus",
            "type": "type",
            "file": "types.ts",
            "definition": "\"selected\" | \"enrolled\" | \"waitlist\" | \"dropped\" | \"rejected\""
          },
          {
            "name": "ElectiveCourse",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, name, subjectId?, teacherId, gradeId?, description?, capacity, enrolledCount, classroom?, schedule?, startDate?, endDate?, selectionStartAt?, selectionEndAt?, status, selectionMode, credit, createdAt, updatedAt }"
          },
          {
            "name": "ElectiveCourseWithDetails",
            "type": "interface",
            "file": "types.ts",
            "definition": "ElectiveCourse & { teacherName?, subjectName?, gradeName? }"
          },
          {
            "name": "CourseCoreRow",
            "type": "type",
            "file": "data-access.ts",
            "definition": "buildCourseSelect 返回行的推断类型（v3 新增：供 mapCourseRow/resolveCourseDisplayNames 共享）"
          },
          {
            "name": "CourseSelection",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ id, courseId, studentId, status, priority?, selectedAt, enrolledAt?, droppedAt?, lotteryRank?, createdAt, updatedAt }"
          },
          {
            "name": "CourseSelectionWithDetails",
            "type": "interface",
            "file": "types.ts",
            "definition": "CourseSelection & { courseName?, studentName?, courseCapacity?, courseEnrolledCount?, courseStatus? }"
          },
          {
            "name": "GetElectiveCoursesParams",
            "type": "interface",
            "file": "types.ts",
            "definition": "{ status?, gradeId?, subjectId?, teacherId? }"
          },
          {
            "name": "ELECTIVE_STATUS_LABELS",
            "type": "const",
            "file": "types.ts",
            "description": "课程状态标签常量"
          },
          {
            "name": "ELECTIVE_STATUS_COLORS",
            "type": "const",
            "file": "types.ts",
            "description": "课程状态颜色常量（Badge variant）"
          },
          {
            "name": "SELECTION_MODE_LABELS",
            "type": "const",
            "file": "types.ts",
            "description": "选课模式标签常量"
          },
          {
            "name": "COURSE_SELECTION_STATUS_LABELS",
            "type": "const",
            "file": "types.ts",
            "description": "选课状态标签常量"
          },
          {
            "name": "COURSE_SELECTION_STATUS_COLORS",
            "type": "const",
            "file": "types.ts",
            "description": "选课状态颜色常量（Badge variant）"
          }
        ],
        "schemas": [
          {
            "name": "ElectiveCourseStatusEnum",
            "file": "schema.ts",
            "definition": "z.enum([\"draft\",\"open\",\"closed\",\"cancelled\"])"
          },
          {
            "name": "ElectiveSelectionModeEnum",
            "file": "schema.ts",
            "definition": "z.enum([\"fcfs\",\"lottery\"])"
          },
          {
            "name": "CourseSelectionStatusEnum",
            "file": "schema.ts",
            "definition": "z.enum([\"selected\",\"enrolled\",\"waitlist\",\"dropped\",\"rejected\"])"
          },
          {
            "name": "CreateElectiveCourseSchema",
            "file": "schema.ts",
            "purpose": "创建课程校验（name 必填，teacherId 必填，capacity 1-500 默认 30，selectionMode 默认 fcfs，credit 默认 1.0）"
          },
          {
            "name": "UpdateElectiveCourseSchema",
            "file": "schema.ts",
            "purpose": "更新课程校验（所有字段可选，含 status）"
          },
          {
            "name": "SelectCourseSchema",
            "file": "schema.ts",
            "purpose": "选课校验（courseId 必填，priority 1-10 可选）"
          },
          {
            "name": "DropCourseSchema",
            "file": "schema.ts",
            "purpose": "退课校验（courseId 必填）"
          },
          {
            "name": "RunLotterySchema",
            "file": "schema.ts",
            "purpose": "抽签校验（courseId 必填）"
          }
        ],
        "components": [
          {
            "name": "ElectiveCourseList",
            "file": "components/elective-course-list.tsx",
            "purpose": "课程卡片列表（管理员/教师视图，含编辑/开放/关闭/抽签/删除操作按钮，usePermission 控制权限）",
            "deps": [
              "usePermission",
              "actions.deleteElectiveCourseAction",
              "actions.openSelectionAction",
              "actions.closeSelectionAction",
              "actions.runLotteryAction",
              "shared/components/ui/*"
            ]
          },
          {
            "name": "ElectiveCourseForm",
            "file": "components/elective-course-form.tsx",
            "purpose": "课程创建/编辑表单（name, subjectId, teacherId, gradeId, description, capacity, classroom, schedule, dates, selectionMode, credit）",
            "deps": [
              "react-hook-form",
              "actions.createElectiveCourseAction",
              "actions.updateElectiveCourseAction",
              "shared/components/ui/*"
            ]
          },
          {
            "name": "StudentSelectionView",
            "file": "components/student-selection-view.tsx",
            "purpose": "学生选课视图（可选课程列表 + 我的选课记录，含选课/退课按钮）",
            "deps": [
              "usePermission",
              "actions.selectCourseAction",
              "actions.dropCourseAction",
              "shared/components/ui/*"
            ]
          },
          {
            "name": "ElectivePageLayout",
            "file": "components/elective-page-layout.tsx",
            "purpose": "选课页面布局骨架（P2-4 新增：抽取 admin/teacher 选课页重复的 header/children 布局结构，统一 spacing）",
            "deps": [
              "shared/lib/utils.cn"
            ],
            "usedBy": [
              "admin/elective/page.tsx",
              "teacher/elective/page.tsx"
            ]
          }
        ],
        "resolvers": [
          {
            "name": "CourseDisplayResolver",
            "type": "interface",
            "file": "resolvers.ts",
            "definition": "{ getUserNamesByIds(ids: string[]): Promise<Map<string,{name:string|null}>>; getSubjectOptions(): Promise<Array<{id,name}>>; getGradeOptions(): Promise<Array<{id,name}>> }",
            "purpose": "课程显示名解析接口（P2-8 新增：抽象跨模块依赖 users/school data-access，便于测试注入 mock）",
            "usedBy": [
              "data-access.resolveCourseDisplayNames",
              "data-access-selections.resolveStudentDisplayNames"
            ]
          },
          {
            "name": "StudentGradeResolver",
            "type": "interface",
            "file": "resolvers.ts",
            "definition": "{ getStudentActiveGradeId(studentId: string): Promise<string|null> }",
            "purpose": "学生年级解析接口（P2-8 新增：抽象跨模块依赖 classes data-access，便于测试注入 mock）",
            "usedBy": [
              "data-access-selections.getStudentGradeId"
            ]
          },
          {
            "name": "getCourseDisplayResolver",
            "signature": "() => CourseDisplayResolver",
            "file": "resolvers.ts",
            "purpose": "获取当前课程显示名解析器（默认委托 users/school data-access；测试可通过 setCourseDisplayResolver 注入 mock）",
            "usedBy": [
              "data-access.resolveCourseDisplayNames",
              "data-access-selections.resolveStudentDisplayNames"
            ]
          },
          {
            "name": "getStudentGradeResolver",
            "signature": "() => StudentGradeResolver",
            "file": "resolvers.ts",
            "purpose": "获取当前学生年级解析器（默认委托 classes data-access；测试可通过 setStudentGradeResolver 注入 mock）",
            "usedBy": [
              "data-access-selections.getStudentGradeId"
            ]
          },
          {
            "name": "setCourseDisplayResolver",
            "signature": "(resolver: CourseDisplayResolver) => void",
            "file": "resolvers.ts",
            "purpose": "注入自定义课程显示名解析器（仅测试用，生产代码勿调）"
          },
          {
            "name": "setStudentGradeResolver",
            "signature": "(resolver: StudentGradeResolver) => void",
            "file": "resolvers.ts",
            "purpose": "注入自定义学生年级解析器（仅测试用，生产代码勿调）"
          },
          {
            "name": "resetResolvers",
            "signature": "() => void",
            "file": "resolvers.ts",
            "purpose": "重置解析器为默认实现（测试 afterEach 清理用）"
          }
        ],
        "lib": [
          {
            "name": "parseSchedule",
            "signature": "(schedule: string) => { day: number; startMinutes: number; endMinutes: number } | null",
            "file": "data-access-operations.ts",
            "purpose": "纯函数：解析时间段字符串（支持 '周一 14:00-15:30' / 'Mon 14:00-15:30' 两种格式），返回星期 0-6 + 起止分钟数；无法解析返回 null（P2-9 新增）",
            "usedBy": [
              "data-access-operations.isScheduleConflict",
              "data-access-operations.checkScheduleConflict"
            ]
          },
          {
            "name": "isScheduleConflict",
            "signature": "(a: string, b: string) => boolean",
            "file": "data-access-operations.ts",
            "purpose": "纯函数：判断两个时间段是否冲突（同一天且时间区间重叠）；任一无法解析返回 false（P2-9 新增，可独立单测）",
            "usedBy": [
              "data-access-operations.checkScheduleConflict"
            ]
          }
        ],
        "importExport": [
          {
            "name": "exportElectiveCoursesToExcel",
            "signature": "(params: { status?: string; teacherId?: string }) => Promise<Buffer>",
            "file": "export.ts",
            "purpose": "导出选修课程列表到 Excel（P2-11 新增：课程名/学科/教师/年级/容量/已选/状态/模式/学分/时间段；列头使用 next-intl getTranslations 国际化）",
            "deps": [
              "shared.lib.excel.exportToExcel",
              "data-access.getElectiveCourses",
              "next-intl/server.getTranslations"
            ],
            "usedBy": [
              "app/api/export/route.ts"
            ]
          },
          {
            "name": "exportCourseSelectionsToExcel",
            "signature": "(params: { courseId: string }) => Promise<Buffer>",
            "file": "export.ts",
            "purpose": "导出课程选课名单到 Excel（P2-11 新增：学生名/状态/优先级/选课时间/录取时间；列头使用 next-intl getTranslations 国际化）",
            "deps": [
              "shared.lib.excel.exportToExcel",
              "data-access-selections.getCourseSelections",
              "next-intl/server.getTranslations"
            ],
            "usedBy": [
              "app/api/export/route.ts"
            ]
          }
        ]
      }
    },
    "lesson_preparation": {
      "path": "src/modules/lesson-preparation",
      "description": "教师备课模块（v3 锚点画布）：以课文正文为核心主体（textbook_content 节点置于画布中央，draggable=false 可缩放），教学目标/重难点等 11 种定制节点围绕正文组织。两种锚定机制：range（选择一段文本关联节点，未选中 opacity:0）+ point（插入带圈数字占位符，未选中 opacity:0.3）。锚点连线默认 10% 透明度，选中时完整显示。数据模型 v3：LessonPlanDocument 含 textbookContentNodeId、nodes、edges、anchors 数组。anchor-injector 实现 Markdown 纯文本偏移映射与占位符注入。实时拖动（onNodeDrag 替代 onNodeDragStop）。v1→v2→v3 链式迁移。强制选定教材+章节才能备课（template-picker 四步流程）。保留模板、版本管理、知识点标注、题目创建/拉取、作业发布能力",
      "exports": {
        "dataAccess": [
          {
            "name": "getLessonPlans",
            "file": "data-access.ts",
            "purpose": "查询课案列表（按教师/教材/章节过滤）"
          },
          {
            "name": "getLessonPlanById",
            "file": "data-access.ts",
            "purpose": "按 ID 获取课案详情"
          },
          {
            "name": "createLessonPlan",
            "file": "data-access.ts",
            "purpose": "创建课案（V2-2：接受 translateTitle 函数翻译 SYSTEM_TEMPLATES 的 i18n 键后存储到 DB）"
          },
          {
            "name": "updateLessonPlanContent",
            "file": "data-access.ts",
            "purpose": "更新课案内容（v2 nodes+edges JSON）"
          },
          {
            "name": "softDeleteLessonPlan",
            "file": "data-access.ts",
            "purpose": "软删除课案"
          },
          {
            "name": "duplicateLessonPlan",
            "file": "data-access.ts",
            "purpose": "复制课案"
          },
          {
            "name": "getTemplateById",
            "file": "data-access.ts",
            "purpose": "按 ID 获取模板"
          },
          {
            "name": "buildInitialContent",
            "file": "lib/document-migration.ts（data-access.ts re-export）",
            "purpose": "基于模板构建初始课案内容（v3 nodes+edges+anchors+textbookContentNodeId）"
          },
          {
            "name": "migrateV1ToV2",
            "file": "lib/document-migration.ts（data-access.ts re-export）",
            "purpose": "v1→v2 迁移：将旧 blocks 数组转换为 nodes + 线性 edges（节点按网格布局），使用类型守卫 isV1Document/isV2Document 替代 as 断言"
          },
          {
            "name": "migrateV2ToV3",
            "file": "lib/document-migration.ts（data-access.ts re-export）",
            "purpose": "v2→v3 迁移：注入 textbook_content 节点（draggable=false）、初始化 anchors 数组、设置 textbookContentNodeId；可选传入 chapterId/chapterContent 填充正文"
          },
          {
            "name": "normalizeDocument",
            "file": "lib/document-migration.ts（data-access.ts re-export）",
            "purpose": "规范化：确保 content 为 v3 格式，兼容旧 v1/v2 数据（自动调用 migrateV1ToV2 → migrateV2ToV3 链式迁移）"
          },
          {
            "name": "buildDefaultSkeleton",
            "file": "lib/document-migration.ts",
            "purpose": "v3 默认骨架：生成 1 个 textbook_content 节点（画布中央）+ 10 个教学节点（objective/key_point/import/new_teaching/consolidation/summary/homework/blackboard/text_study/reflection）+ 默认 flow edges"
          },
          {
            "name": "defaultDataForType",
            "file": "lib/document-migration.ts",
            "purpose": "按 BlockType 返回初始 data（11 种定制节点 + rich_text），供 addNode/buildDefaultSkeleton 使用"
          },
          {
            "name": "isTextbookContentNode",
            "file": "lib/document-migration.ts",
            "purpose": "类型守卫：判断节点是否为 textbook_content 类型"
          },
          {
            "name": "isAnchorEdge",
            "file": "lib/document-migration.ts",
            "purpose": "类型守卫：判断边是否为 anchor 锚点连线（vs flow 流程连线）"
          },
          {
            "name": "getAnchorsForNode",
            "file": "lib/document-migration.ts",
            "purpose": "按 nodeId 过滤 anchors 数组"
          },
          {
            "name": "getActiveAnchorIds",
            "file": "lib/document-migration.ts",
            "purpose": "获取选中节点关联的 anchor ID 集合（用于正文渲染时高亮）"
          },
          {
            "name": "getAnchorEdges",
            "file": "lib/document-migration.ts",
            "purpose": "获取锚点连线列表（用于 rf-mappers 区分透明度策略）"
          },
          {
            "name": "markdownToPlainText",
            "file": "lib/anchor-injector.ts",
            "purpose": "Markdown 转纯文本（去除语法标记），用于建立纯文本偏移↔Markdown 偏移映射"
          },
          {
            "name": "injectPlaceholders",
            "file": "lib/anchor-injector.ts",
            "purpose": "将 anchors 注入 Markdown 正文：range 锚点包裹为 [[anchor:id]]...[[/anchor:id]]，point 锚点插入 ①②③ 带圈数字"
          },
          {
            "name": "parseAnchoredText",
            "file": "lib/anchor-injector.ts",
            "purpose": "解析注入后的 Markdown 为段落数组（text/anchor-range/anchor-point），供 textbook-content-node 自定义渲染"
          },
          {
            "name": "getNextPointIndex",
            "file": "lib/anchor-injector.ts",
            "purpose": "计算下一个 point 锚点的带圈数字序号"
          },
          {
            "name": "toCircledNumber",
            "file": "lib/anchor-injector.ts",
            "purpose": "数字转带圈数字字符（1→①, 2→②, ...）"
          },
          {
            "name": "relocateAnchors",
            "file": "lib/anchor-injector.ts",
            "purpose": "正文变更后按文本相似度重定位锚点偏移量"
          },
          {
            "name": "getTextbooksForPicker",
            "file": "data-access.ts",
            "purpose": "获取教材列表（id+title），供 template-picker 选择教材"
          },
          {
            "name": "getChaptersForPicker",
            "file": "data-access.ts",
            "purpose": "按 textbookId 获取章节树（含 children 嵌套），供 template-picker 选择章节"
          },
          {
            "name": "getLessonPlanVersions",
            "file": "data-access-versions.ts",
            "purpose": "查询课案版本列表"
          },
          {
            "name": "createLessonPlanVersion",
            "file": "data-access-versions.ts",
            "purpose": "创建课案版本（手动/自动）"
          },
          {
            "name": "getVersionContent",
            "file": "data-access-versions.ts",
            "purpose": "获取指定版本内容"
          },
          {
            "name": "revertToVersion",
            "file": "data-access-versions.ts",
            "purpose": "回滚到指定版本"
          },
          {
            "name": "pruneAutoVersions",
            "file": "data-access-versions.ts",
            "purpose": "清理自动版本（保留最近 N 个）"
          },
          {
            "name": "getLessonPlanTemplates",
            "file": "data-access-templates.ts",
            "purpose": "查询模板列表（系统/个人）"
          },
          {
            "name": "saveAsTemplate",
            "file": "data-access-templates.ts",
            "purpose": "将课案保存为个人模板"
          },
          {
            "name": "deletePersonalTemplate",
            "file": "data-access-templates.ts",
            "purpose": "删除个人模板"
          },
          {
            "name": "getLessonPlansByKnowledgePoint",
            "file": "data-access-knowledge.ts",
            "purpose": "按知识点反查课案"
          },
          {
            "name": "getLessonPlansByQuestion",
            "file": "data-access-knowledge.ts",
            "purpose": "按题目反查课案"
          },
          {
            "name": "publishLessonPlanHomework",
            "file": "publish-service.ts",
            "purpose": "发布课案为作业（编排 homework/exams/classes，通过对方 data-access 调用 addExamQuestions/getStudentIdsByClassIds，无直查跨模块表；V2-1：抛出 PublishServiceError 错误码；V2-3：显式字段映射替代 as unknown as）"
          },
          {
            "name": "suggestKnowledgePoints",
            "file": "ai-suggest.ts",
            "purpose": "AI 建议知识点（基于课案内容）"
          }
        ],
        "actions": [
          {
            "name": "getLessonPlansAction",
            "permission": "LESSON_PLAN_READ",
            "file": "actions.ts",
            "purpose": "查询课案列表"
          },
          {
            "name": "getLessonPlanByIdAction",
            "permission": "LESSON_PLAN_READ",
            "file": "actions.ts",
            "purpose": "获取课案详情"
          },
          {
            "name": "createLessonPlanAction",
            "permission": "LESSON_PLAN_CREATE",
            "file": "actions.ts",
            "purpose": "创建课案"
          },
          {
            "name": "updateLessonPlanAction",
            "permission": "LESSON_PLAN_UPDATE",
            "file": "actions.ts",
            "purpose": "更新课案内容"
          },
          {
            "name": "saveLessonPlanVersionAction",
            "permission": "LESSON_PLAN_UPDATE",
            "file": "actions.ts",
            "purpose": "保存课案版本"
          },
          {
            "name": "getLessonPlanVersionsAction",
            "permission": "LESSON_PLAN_READ",
            "file": "actions.ts",
            "purpose": "查询课案版本列表"
          },
          {
            "name": "revertLessonPlanVersionAction",
            "permission": "LESSON_PLAN_UPDATE",
            "file": "actions.ts",
            "purpose": "回滚到指定版本"
          },
          {
            "name": "deleteLessonPlanAction",
            "permission": "LESSON_PLAN_DELETE",
            "file": "actions.ts",
            "purpose": "删除课案"
          },
          {
            "name": "duplicateLessonPlanAction",
            "permission": "LESSON_PLAN_CREATE",
            "file": "actions.ts",
            "purpose": "复制课案"
          },
          {
            "name": "getLessonPlanTemplatesAction",
            "permission": "LESSON_PLAN_READ",
            "file": "actions.ts",
            "purpose": "查询模板列表"
          },
          {
            "name": "saveAsTemplateAction",
            "permission": "LESSON_PLAN_UPDATE",
            "file": "actions.ts",
            "purpose": "保存为个人模板"
          },
          {
            "name": "deleteTemplateAction",
            "permission": "LESSON_PLAN_DELETE",
            "file": "actions.ts",
            "purpose": "删除个人模板"
          },
          {
            "name": "suggestKnowledgePointsAction",
            "permission": "LESSON_PLAN_UPDATE",
            "file": "actions-ai.ts",
            "purpose": "AI 建议知识点"
          },
          {
            "name": "publishLessonPlanHomeworkAction",
            "permission": "LESSON_PLAN_PUBLISH",
            "file": "actions-publish.ts",
            "purpose": "发布课案为作业"
          },
          {
            "name": "getKnowledgePointOptionsAction",
            "permission": "LESSON_PLAN_READ",
            "file": "actions-kp.ts",
            "purpose": "获取知识点选项（委托 textbooks data-access）"
          },
          {
            "name": "getTextbooksForPickerAction",
            "permission": "LESSON_PLAN_READ",
            "file": "actions.ts",
            "purpose": "获取教材列表（供 template-picker 选择教材）"
          },
          {
            "name": "getChaptersForPickerAction",
            "permission": "LESSON_PLAN_READ",
            "file": "actions.ts",
            "purpose": "按 textbookId 获取章节树（供 template-picker 选择章节）"
          }
        ],
        "schemas": [
          {
            "name": "createLessonPlanSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "purpose": "创建课案输入校验（title 必填 1-255 字符，templateId 必填，textbookId/chapterId/subjectId/gradeId 可选）",
            "usedBy": [
              "createLessonPlanAction"
            ]
          },
          {
            "name": "updateLessonPlanContentSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "purpose": "更新课案内容输入校验（planId 必填，title 可选 1-255 字符，content 为 record<string, unknown>）",
            "usedBy": [
              "updateLessonPlanAction"
            ]
          },
          {
            "name": "saveVersionSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "purpose": "保存版本输入校验（planId 必填，label 可选 1-100 字符）",
            "usedBy": [
              "saveLessonPlanVersionAction"
            ]
          },
          {
            "name": "revertVersionSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "purpose": "回滚版本输入校验（planId 必填，versionNo 正整数）",
            "usedBy": [
              "revertLessonPlanVersionAction"
            ]
          },
          {
            "name": "saveAsTemplateSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "purpose": "保存为模板输入校验（sourcePlanId 必填，name 必填 1-100 字符）",
            "usedBy": [
              "saveAsTemplateAction"
            ]
          },
          {
            "name": "suggestKnowledgePointsSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "purpose": "AI 知识点推荐输入校验（doc 为 record<string, unknown>，textbookId/chapterId 可选）",
            "usedBy": [
              "suggestKnowledgePointsAction"
            ]
          },
          {
            "name": "getKnowledgePointOptionsSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "purpose": "知识点选项查询输入校验（textbookId/chapterId 可选）",
            "usedBy": [
              "getKnowledgePointOptionsAction"
            ]
          },
          {
            "name": "publishLessonPlanHomeworkSchema",
            "type": "ZodSchema",
            "file": "schema.ts",
            "purpose": "2026-06-23 审计修复新增：发布作业输入校验（planId/blockId 必填，classIds 至少 1 个，availableAt/dueAt 日期格式校验通过 refine(new Date(v).getTime()) 验证）。导出类型 PublishLessonPlanHomeworkInput",
            "usedBy": [
              "publishLessonPlanHomeworkAction"
            ]
          }
        ]
      },
      "dependencies": [
        "textbooks",
        "questions",
        "exams",
        "homework",
        "classes",
        "files",
        "shared/lib/ai",
        "@xyflow/react"
      ],
      "files": [
        "types.ts",
        "constants.ts",
        "schema.ts",
        "lib/document-migration.ts",
        "lib/anchor-injector.ts",
        "lib/node-summary.ts",
        "lib/rf-mappers.ts",
        "config/block-registry.tsx",
        "providers/lesson-plan-provider.tsx",
        "services/default-data-service.ts",
        "data-access.ts",
        "data-access-versions.ts",
        "data-access-templates.ts",
        "data-access-knowledge.ts",
        "actions.ts",
        "actions-publish.ts",
        "actions-ai.ts",
        "actions-kp.ts",
        "publish-service.ts",
        "ai-suggest.ts",
        "seed-templates.ts",
        "hooks/use-lesson-plan-editor.ts",
        "components/lesson-plan-list.tsx",
        "components/lesson-plan-card.tsx",
        "components/lesson-plan-filters.tsx",
        "components/lesson-plan-editor.tsx",
        "components/node-editor.tsx",
        "components/node-edit-panel.tsx",
        "components/nodes/lesson-node.tsx",
        "components/nodes/textbook-content-node.tsx",
        "components/lesson-plan-error-boundary.tsx",
        "components/lesson-plan-skeleton.tsx",
        "components/block-renderer.tsx",
        "components/template-picker.tsx",
        "components/version-history-drawer.tsx",
        "components/knowledge-point-picker.tsx",
        "components/question-bank-picker.tsx",
        "components/inline-question-editor.tsx",
        "components/publish-homework-dialog.tsx",
        "components/blocks/rich-text-block.tsx",
        "components/blocks/text-study-block.tsx",
        "components/blocks/exercise-block.tsx",
        "components/blocks/reflection-block.tsx",
        "components/blocks/objective-block.tsx",
        "components/blocks/key-point-block.tsx",
        "components/blocks/import-block.tsx",
        "components/blocks/new-teaching-block.tsx",
        "components/blocks/summary-block.tsx",
        "components/blocks/homework-block.tsx",
        "components/blocks/blackboard-block.tsx"
      ],
      "i18n": {
        "namespace": "lessonPreparation",
        "status": "implemented",
        "messageFiles": [
          "shared/i18n/messages/zh-CN/lesson-preparation.json",
          "shared/i18n/messages/en/lesson-preparation.json"
        ]
      },
      "auditFixes": {
        "P0-1": "publish-service.ts 跨模块直查修复：改用 exams/classes data-access",
        "P0-2": "i18n 接入：17 个组件改造为 useTranslations/getTranslations",
        "P0-3": "buildScopeCondition 按 scope 类型精确过滤（class_taught/grade_managed/class_members/children）",
        "P1-1": "as never 断言替换为类型守卫函数；BLOCK_TYPE_LABELS/LESSON_PLAN_STATUS_LABELS 改为 i18n 键",
        "P1-2": "新增 LessonPlanErrorBoundary 错误边界",
        "P1-3": "新增 4 个 Skeleton 骨架屏组件",
        "P1-4": "alert/confirm/window.location.reload 替换为 toast/AlertDialog/router.refresh",
        "P1-5": "新增 LessonPlanProvider + Context 注入数据服务，支持多实例",
        "P1-7": "新增 4 个角色配置（TEACHER/ADMIN/STUDENT/PARENT）+ ROLE_CONFIGS 注册表",
        "P1-8": "新增 BLOCK_REGISTRY 注册表，node-edit-panel 配置驱动渲染",
        "P2-1": "5 个组件添加 role=dialog/aria-modal/aria-label",
        "P2-4": "预留 LessonPlanTracker 接口 + noopTracker 默认实现",
        "V2-1": "Server Actions i18n + 错误码模式：12 个 Action 通过 getTranslations 翻译错误消息；Service/DataAccess 层抛出 PublishServiceError/LessonPlanDataError 错误码，Actions 层通过 PUBLISH_ERROR_KEY_MAP 翻译",
        "V2-2": "SYSTEM_TEMPLATES i18n 化：name/title 改为 i18n 键，createLessonPlan 接受 translateTitle 函数在服务端翻译后存储到 DB",
        "V2-3": "as unknown as 类型断言清零：8 处替换为显式类型映射函数（mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate/mapRowToVersion）+ 类型守卫（isLessonPlanStatus/isTemplateType/isTemplateScope）",
        "V2-4": "MiniMap nodeColor 复用 lib/node-summary.ts 的 getNodeColor",
        "V2-5": "a11y 深度修复：lesson-plan-filters/exercise-block/inline-question-editor 的 select 添加 label htmlFor 关联；exercise-block 题目列表改为 ul/li；node-editor 画布添加 role=application + 键盘导航配置",
        "V2-6": "Tracker 埋点接入：新增 useLessonPlanTrackerSafe hook，在 create/save/publish/revert/duplicate/archive 6 处调用 tracker.track"
      }
    },
    "error-book": {
      "path": "src/modules/error-book",
      "description": "错题本模块：自动采集考试/作业错题，SM-2 间隔重复算法复习，知识点薄弱度分析，支持学生/教师/家长/管理员多角色视图",
      "exports": {
        "actions": [
          {
            "name": "getErrorBookItemsAction",
            "permission": "ERROR_BOOK_READ",
            "signature": "(params: GetErrorBookItemsParams) => Promise<ActionState<ErrorBookListResult>>",
            "purpose": "查询错题本列表",
            "deps": ["requirePermission", "data-access.getErrorBookItems"],
            "usedBy": ["student/error-book/page.tsx"]
          },
          {
            "name": "getErrorBookItemDetailAction",
            "permission": "ERROR_BOOK_READ",
            "signature": "(itemId: string) => Promise<ActionState<ErrorBookItemDetail>>",
            "purpose": "查询错题详情（含复习历史）",
            "deps": ["requirePermission", "data-access.getErrorBookItemById"],
            "usedBy": ["error-book-detail-dialog.tsx"]
          },
          {
            "name": "getErrorBookStatsAction",
            "permission": "ERROR_BOOK_READ",
            "signature": "() => Promise<ActionState<ErrorBookStats>>",
            "purpose": "查询错题本统计",
            "deps": ["requirePermission", "data-access.getErrorBookStats"],
            "usedBy": ["student/error-book/page.tsx", "parent/error-book/page.tsx"]
          },
          {
            "name": "createErrorBookItemAction",
            "permission": "ERROR_BOOK_MANAGE",
            "signature": "(prevState: ActionState<string> | undefined, formData: FormData) => Promise<ActionState<string>>",
            "purpose": "手动添加错题",
            "deps": ["requirePermission", "data-access.createErrorBookItem"],
            "usedBy": ["add-error-book-dialog.tsx"]
          },
          {
            "name": "updateErrorBookNoteAction",
            "permission": "ERROR_BOOK_MANAGE",
            "signature": "(prevState: ActionState<void> | undefined, formData: FormData) => Promise<ActionState<void>>",
            "purpose": "更新错题笔记/标签",
            "deps": ["requirePermission", "data-access.updateErrorBookNote"],
            "usedBy": ["error-book-detail-dialog.tsx"]
          },
          {
            "name": "reviewErrorBookItemAction",
            "permission": "ERROR_BOOK_MANAGE",
            "signature": "(prevState: ActionState<void> | undefined, formData: FormData) => Promise<ActionState<void>>",
            "purpose": "记录复习结果（SM-2 算法）",
            "deps": ["requirePermission", "data-access.recordReview"],
            "usedBy": ["review-buttons.tsx"]
          },
          {
            "name": "deleteErrorBookItemAction",
            "permission": "ERROR_BOOK_MANAGE",
            "signature": "(prevState: ActionState<void> | undefined, formData: FormData) => Promise<ActionState<void>>",
            "purpose": "删除错题",
            "deps": ["requirePermission", "data-access.deleteErrorBookItem"],
            "usedBy": ["error-book-item-card.tsx"]
          },
          {
            "name": "archiveErrorBookItemAction",
            "permission": "ERROR_BOOK_MANAGE",
            "signature": "(prevState: ActionState<void> | undefined, formData: FormData) => Promise<ActionState<void>>",
            "purpose": "归档错题",
            "deps": ["requirePermission", "data-access.archiveErrorBookItem"],
            "usedBy": ["error-book-item-card.tsx"]
          },
          {
            "name": "collectFromSubmissionAction",
            "permission": "ERROR_BOOK_MANAGE",
            "signature": "(prevState: ActionState<number> | undefined, formData: FormData) => Promise<ActionState<number>>",
            "purpose": "从考试/作业提交自动采集错题",
            "deps": ["requirePermission", "data-access.collectFromExamSubmission", "data-access.collectFromHomeworkSubmission"],
            "usedBy": ["exam-homework 提交后调用"]
          }
        ],
        "dataAccess": [
          {
            "name": "getErrorBookItems",
            "signature": "(params: GetErrorBookItemsParams) => Promise<ErrorBookListResult>",
            "type": "cache function",
            "purpose": "查询错题本列表（缓存）",
            "usedBy": ["getErrorBookItemsAction", "student/error-book/page.tsx"]
          },
          {
            "name": "getErrorBookItemById",
            "signature": "(itemId: string, studentId: string) => Promise<ErrorBookItemDetail | null>",
            "type": "cache function",
            "purpose": "查询错题详情（含复习历史）",
            "usedBy": ["getErrorBookItemDetailAction"]
          },
          {
            "name": "getErrorBookStats",
            "signature": "(studentId: string) => Promise<ErrorBookStats>",
            "type": "cache function",
            "purpose": "查询错题本统计（总数/待复习/已掌握等）",
            "usedBy": ["getErrorBookStatsAction", "student/error-book/page.tsx", "parent/error-book/page.tsx"]
          },
          {
            "name": "createErrorBookItem",
            "signature": "(studentId: string, input: CreateErrorBookItemInput) => Promise<string>",
            "type": "async function",
            "purpose": "手动添加错题",
            "usedBy": ["createErrorBookItemAction"]
          },
          {
            "name": "updateErrorBookNote",
            "signature": "(itemId: string, studentId: string, input: { note?: string; errorTags?: string[] }) => Promise<void>",
            "type": "async function",
            "purpose": "更新错题笔记/标签",
            "usedBy": ["updateErrorBookNoteAction"]
          },
          {
            "name": "recordReview",
            "signature": "(itemId: string, studentId: string, result: ErrorBookReviewResult) => Promise<void>",
            "type": "async function",
            "purpose": "记录复习结果并应用 SM-2 算法更新间隔/掌握度",
            "usedBy": ["reviewErrorBookItemAction"]
          },
          {
            "name": "deleteErrorBookItem",
            "signature": "(itemId: string, studentId: string) => Promise<void>",
            "type": "async function",
            "purpose": "删除错题",
            "usedBy": ["deleteErrorBookItemAction"]
          },
          {
            "name": "archiveErrorBookItem",
            "signature": "(itemId: string, studentId: string) => Promise<void>",
            "type": "async function",
            "purpose": "归档错题",
            "usedBy": ["archiveErrorBookItemAction"]
          },
          {
            "name": "collectFromExamSubmission",
            "signature": "(submissionId: string, studentId: string) => Promise<number>",
            "type": "async function",
            "purpose": "从考试提交自动采集错题（去重）",
            "usedBy": ["collectFromSubmissionAction"]
          },
          {
            "name": "collectFromHomeworkSubmission",
            "signature": "(submissionId: string, studentId: string) => Promise<number>",
            "type": "async function",
            "purpose": "从作业提交自动采集错题（去重）",
            "usedBy": ["collectFromSubmissionAction"]
          },
          {
            "name": "getStudentErrorBookSummaries",
            "signature": "(studentIds: string[]) => Promise<StudentErrorBookSummary[]>",
            "type": "async function",
            "purpose": "查询多个学生的错题统计（教师/管理员视图）",
            "usedBy": ["teacher/error-book/page.tsx", "admin/error-book/page.tsx"]
          },
          {
            "name": "getTopWrongQuestionsByStudentIds",
            "signature": "(studentIds: string[], limit?: number) => Promise<TopWrongQuestion[]>",
            "type": "async function",
            "purpose": "查询高频错题（教师/家长视图）",
            "usedBy": ["teacher/error-book/page.tsx", "parent/error-book/page.tsx"]
          },
          {
            "name": "getKnowledgePointWeakness",
            "signature": "(studentIds: string[], limit?: number) => Promise<KnowledgePointWeakness[]>",
            "type": "async function",
            "purpose": "查询知识点薄弱度统计",
            "usedBy": ["teacher/error-book/page.tsx", "parent/error-book/page.tsx", "admin/error-book/page.tsx"]
          },
          {
            "name": "getSubjectErrorDistribution",
            "signature": "(studentIds: string[]) => Promise<SubjectErrorDistribution[]>",
            "type": "async function",
            "purpose": "查询学科错题分布",
            "usedBy": ["teacher/error-book/page.tsx", "admin/error-book/page.tsx"]
          },
          {
            "name": "getStudentNameMap",
            "signature": "(studentIds: string[]) => Promise<Map<string, string>>",
            "type": "async function",
            "purpose": "查询学生姓名映射",
            "usedBy": ["teacher/error-book/page.tsx", "parent/error-book/page.tsx"]
          },
          {
            "name": "getStudentIdsByClassIdList",
            "signature": "(classIds: string[]) => Promise<string[]>",
            "type": "async function",
            "purpose": "按班级 ID 查询学生 ID 列表（委托给 classes 模块）",
            "usedBy": ["teacher/error-book/page.tsx", "admin/error-book/page.tsx"]
          }
        ],
        "schema": [
          {
            "name": "CreateErrorBookItemSchema",
            "type": "const",
            "description": "zod schema 手动添加错题",
            "usedBy": ["createErrorBookItemAction"]
          },
          {
            "name": "UpdateErrorBookNoteSchema",
            "type": "const",
            "description": "zod schema 更新笔记/标签",
            "usedBy": ["updateErrorBookNoteAction"]
          },
          {
            "name": "ReviewErrorBookItemSchema",
            "type": "const",
            "description": "zod schema 复习结果",
            "usedBy": ["reviewErrorBookItemAction"]
          },
          {
            "name": "CollectFromSubmissionSchema",
            "type": "const",
            "description": "zod schema 自动采集",
            "usedBy": ["collectFromSubmissionAction"]
          }
        ],
        "types": [
          {
            "name": "ErrorBookItem",
            "definition": "{ id, studentId, questionId, sourceType, sourceId, studentAnswer, correctAnswer, subjectId, knowledgePointIds, status, masteryLevel, nextReviewAt, reviewInterval, reviewCount, correctStreak, note, errorTags, createdAt, updatedAt, question?, subjectName? }",
            "usedBy": ["error-book-list", "error-book-item-card"]
          },
          {
            "name": "ErrorBookItemDetail",
            "definition": "ErrorBookItem & { reviews: ErrorBookReviewRecord[] }",
            "usedBy": ["error-book-detail-dialog"]
          },
          {
            "name": "ErrorBookStats",
            "definition": "{ totalCount, newCount, learningCount, masteredCount, archivedCount, dueReviewCount, masteredRate }",
            "usedBy": ["error-book-stats-cards"]
          },
          {
            "name": "StudentErrorBookSummary",
            "definition": "{ studentId, totalCount, newCount, learningCount, masteredCount, dueReviewCount, masteredRate, lastActivityAt }",
            "usedBy": ["class-error-overview"]
          },
          {
            "name": "KnowledgePointWeakness",
            "definition": "{ knowledgePointId, knowledgePointName, errorCount, masteredCount, totalCount, masteryRate }",
            "usedBy": ["class-error-overview"]
          },
          {
            "name": "SubjectErrorDistribution",
            "definition": "{ subjectId, subjectName, errorCount, masteredCount, masteryRate }",
            "usedBy": ["class-error-overview"]
          }
        ],
        "components": [
          {
            "name": "ErrorBookStatsCards",
            "file": "error-book-stats-cards.tsx",
            "purpose": "错题统计卡片（5 个指标）"
          },
          {
            "name": "ErrorBookFilters",
            "file": "error-book-filters.tsx",
            "purpose": "筛选栏（搜索/状态/来源/待复习）"
          },
          {
            "name": "ErrorBookItemCard",
            "file": "error-book-item-card.tsx",
            "purpose": "错题卡片（预览/标签/笔记/掌握度）"
          },
          {
            "name": "ReviewButtons",
            "file": "review-buttons.tsx",
            "purpose": "复习按钮组（again/hard/good/easy）"
          },
          {
            "name": "ErrorBookDetailDialog",
            "file": "error-book-detail-dialog.tsx",
            "purpose": "错题详情对话框（题目/答案/复习/笔记/历史）"
          },
          {
            "name": "ErrorBookList",
            "file": "error-book-list.tsx",
            "purpose": "错题列表（网格布局）"
          },
          {
            "name": "AddErrorBookDialog",
            "file": "add-error-book-dialog.tsx",
            "purpose": "手动添加错题对话框（题库选择）"
          },
          {
            "name": "ClassErrorOverview",
            "file": "class-error-overview.tsx",
            "purpose": "班级错题概览（教师/管理员视图）"
          },
          {
            "name": "TopWrongQuestions",
            "file": "top-wrong-questions.tsx",
            "purpose": "高频错题列表（Top 10）"
          }
        ]
      },
      "dependencies": {
        "dependsOn": ["shared/db", "shared/lib/auth-guard", "shared/types/permissions", "modules/classes", "modules/questions"],
        "usedBy": ["app/(dashboard)/student/error-book", "app/(dashboard)/teacher/error-book", "app/(dashboard)/parent/error-book", "app/(dashboard)/admin/error-book"]
      },
      "algorithms": {
        "sm2": {
          "name": "SM-2 间隔重复算法（简化版）",
          "file": "sm2-algorithm.ts",
          "description": "独立纯函数模块，4 级评级（again/hard/good/easy），间隔 1/2/4/7 天起，指数增长（×1.2/×1.5/×2），连续 3 次答对标记为已掌握。支持时间注入便于测试。",
          "functions": ["calculateNewInterval", "calculateNewMastery", "deriveStatus", "calculateNextReviewAt", "calculateNewCorrectStreak", "calculateSm2Result"],
          "constants": ["REVIEW_INTERVALS", "INTERVAL_MULTIPLIERS", "MAX_MASTERY_LEVEL", "MIN_MASTERY_LEVEL", "MASTERED_REQUIRED_STREAK", "MASTERED_REQUIRED_MASTERY"],
          "testFile": "sm2-algorithm.test.ts",
          "testCount": 39
        }
      },
      "dataScope": {
        "student": "owned（仅自己的错题）",
        "parent": "children（子女的错题）",
        "teacher": "class_taught（所教班级学生的错题）",
        "admin": "all（所有错题）",
        "grade_head": "grade_managed（所管年级学生的错题）",
        "teaching_head": "grade_managed（所管年级学生的错题）"
      }
    }
  },
  "dbTables": {
    "_meta": {
      "total": 60,
      "orm": "Drizzle ORM 0.45",
      "database": "MySQL",
      "idStrategy": "CUID2 (varchar length 128)",
      "source": "src/shared/db/schema.ts"
    },
    "usersAndAuth": {
      "description": "用户与认证 (Auth.js v5 + RBAC)",
      "tables": {
        "users": {
          "owner": "users",
          "description": "用户主表(含未成年人信息保护字段)"
        },
        "accounts": {
          "owner": "auth",
          "description": "OAuth 账户"
        },
        "sessions": {
          "owner": "auth",
          "description": "Auth.js 会话"
        },
        "verificationTokens": {
          "owner": "auth",
          "description": "验证令牌"
        },
        "roles": {
          "owner": "auth",
          "description": "角色(admin/teacher/student/parent/grade_head/teaching_head)"
        },
        "usersToRoles": {
          "owner": "auth",
          "description": "用户-角色多对多"
        },
        "rolePermissions": {
          "owner": "auth",
          "description": "角色-权限(细粒度 RBAC)"
        },
        "passwordSecurity": {
          "owner": "auth",
          "description": "密码安全策略(失败次数/锁定/必须改密)"
        }
      }
    },
    "knowledgeStructure": {
      "description": "知识点树结构",
      "tables": {
        "knowledgePoints": {
          "owner": "textbooks",
          "description": "知识点(树结构,parentId 自引用)"
        },
        "knowledgePointPrerequisites": {
          "owner": "textbooks",
          "description": "知识点前置依赖(复合主键 knowledgePointId+prerequisiteKpId,双外键 cascade 删除)"
        }
      }
    },
    "questionBank": {
      "description": "题库核心",
      "tables": {
        "questions": {
          "owner": "questions",
          "description": "题目(content JSON,支持复合题 parentId 自引用)"
        },
        "questionsToKnowledgePoints": {
          "owner": "questions",
          "description": "题目-知识点多对多"
        }
      }
    },
    "academicStructure": {
      "description": "教学结构",
      "tables": {
        "subjects": {
          "owner": "school",
          "description": "科目"
        },
        "textbooks": {
          "owner": "textbooks",
          "description": "教材"
        },
        "chapters": {
          "owner": "textbooks",
          "description": "章节(支持嵌套 parentId)"
        }
      }
    },
    "schoolManagement": {
      "description": "学校管理",
      "tables": {
        "departments": {
          "owner": "school",
          "description": "部门"
        },
        "classrooms": {
          "owner": "school",
          "description": "教室"
        },
        "academicYears": {
          "owner": "school",
          "description": "学年"
        },
        "schools": {
          "owner": "school",
          "description": "学校"
        },
        "grades": {
          "owner": "school",
          "description": "年级(含 gradeHeadId/teachingHeadId)"
        }
      }
    },
    "classes": {
      "description": "班级/选课/课表/邀请码（v3 新增邀请码体系，对标 Google Classroom / 钉钉教育）",
      "tables": {
        "classes": {
          "owner": "classes",
          "description": "班级（invitationCode 字段保留作为 fallback，v3 迁移至 classInvitationCodes 表）"
        },
        "classSubjectTeachers": {
          "owner": "classes",
          "description": "班级-科目-教师"
        },
        "classEnrollments": {
          "owner": "classes",
          "description": "班级选课(active/inactive)"
        },
        "classSchedule": {
          "owner": "scheduling",
          "description": "课表(注意:三处写入口,见 knownIssues P0-6)"
        },
        "classInvitationCodes": {
          "owner": "classes",
          "description": "v3 新增：班级邀请码（独立表，支持有效期/次数限制/审计/多码并存，6 位字母数字剔除歧义字符）"
        }
      }
    },
    "exams": {
      "description": "考试与提交",
      "tables": {
        "exams": {
          "owner": "exams",
          "description": "考试(含 examMode: homework/timed/proctored)"
        },
        "examQuestions": {
          "owner": "exams",
          "description": "考试-题目多对多"
        },
        "examSubmissions": {
          "owner": "exams",
          "description": "考试提交(started/submitted/graded)"
        },
        "submissionAnswers": {
          "owner": "exams",
          "description": "提交答案"
        }
      }
    },
    "homework": {
      "description": "作业",
      "tables": {
        "homeworkAssignments": {
          "owner": "homework",
          "description": "作业(关联 sourceExamId)"
        },
        "homeworkAssignmentQuestions": {
          "owner": "homework",
          "description": "作业-题目"
        },
        "homeworkAssignmentTargets": {
          "owner": "homework",
          "description": "作业目标学生"
        },
        "homeworkSubmissions": {
          "owner": "homework",
          "description": "作业提交(含 attemptNo/isLate)"
        },
        "homeworkAnswers": {
          "owner": "homework",
          "description": "作业答案"
        }
      }
    },
    "ai": {
      "description": "AI 配置",
      "tables": {
        "aiProviders": {
          "owner": "settings",
          "description": "AI Provider(zhipu/openai/gemini/custom,apiKey 加密)"
        }
      }
    },
    "announcements": {
      "description": "公告",
      "tables": {
        "announcements": {
          "owner": "announcements",
          "description": "公告(school/grade/class,draft/published/archived;V2-P2-13d 新增 isPinned 置顶字段)"
        },
        "announcementReads": {
          "owner": "announcements",
          "description": "公告已读回执(V2-P2-13d 新增,唯一索引 announcementId+userId 保证幂等,cascade 删除)"
        }
      }
    },
    "auditLogs": {
      "description": "审计与登录日志",
      "tables": {
        "auditLogs": {
          "owner": "audit",
          "description": "审计日志(success/failure)"
        },
        "loginLogs": {
          "owner": "audit",
          "description": "登录日志(signin/signout/signup)"
        },
        "dataChangeLogs": {
          "owner": "audit",
          "description": "数据变更日志(create/update/delete)"
        }
      }
    },
    "gradeRecords": {
      "description": "成绩录入",
      "tables": {
        "gradeRecords": {
          "owner": "grades",
          "description": "成绩记录(exam/quiz/homework/other)"
        }
      }
    },
    "files": {
      "description": "文件附件",
      "tables": {
        "fileAttachments": {
          "owner": "files",
          "description": "文件附件(含 storagePath/url)"
        }
      }
    },
    "coursePlans": {
      "description": "课程计划",
      "tables": {
        "coursePlans": {
          "owner": "course-plans",
          "description": "课程计划(planning/active/completed/paused)"
        },
        "coursePlanItems": {
          "owner": "course-plans",
          "description": "课程计划项(按周)"
        }
      }
    },
    "messaging": {
      "description": "消息与通知",
      "tables": {
        "messages": {
          "owner": "messaging",
          "description": "站内消息(含回复链 parentMessageId；软删除 senderDeletedAt/receiverDeletedAt；V2-P2-13c 新增 isStarred 星标字段 + messages_receiver_starred_idx 复合索引)"
        },
        "messageDrafts": {
          "owner": "messaging",
          "description": "V2-P2-13c 新增：消息草稿(userId/receiverId/subject/content/parentMessageId + updatedAt/createdAt；含 message_drafts_user_idx 和 message_drafts_user_updated_idx 索引)"
        },
        "messageNotifications": {
          "owner": "notifications",
          "description": "消息通知(type/title/content/link/isRead/priority/isArchived/createdAt；V2-P2-13b 新增 priority 和 isArchived 字段)"
        },
        "notificationLogs": {
          "owner": "notifications",
          "description": "通知发送日志(channel/status/messageId/error/sentAt)"
        },
        "notificationPreferences": {
          "owner": "notifications",
          "description": "通知偏好(email/sms/push + 分类开关)"
        }
      }
    },
    "parentStudent": {
      "description": "家长-子女关联",
      "tables": {
        "parentStudentRelations": {
          "owner": "parent",
          "description": "家长-子女关系"
        }
      }
    },
    "attendance": {
      "description": "考勤",
      "tables": {
        "attendanceRecords": {
          "owner": "attendance",
          "description": "考勤记录(present/absent/late/early_leave/excused)"
        },
        "attendanceRules": {
          "owner": "attendance",
          "description": "考勤规则(迟到/早退阈值)"
        }
      }
    },
    "scheduling": {
      "description": "排课",
      "tables": {
        "schedulingRules": {
          "owner": "scheduling",
          "description": "排课规则(每日最大课时/连续课时/午休等)"
        },
        "scheduleChanges": {
          "owner": "scheduling",
          "description": "调课/代课申请(pending/approved/rejected/completed)"
        }
      }
    },
    "elective": {
      "description": "选课管理 (P2)",
      "tables": {
        "electiveCourses": {
          "owner": "elective",
          "description": "选修课程(draft/open/closed/cancelled,fcfs/lottery)"
        },
        "courseSelections": {
          "owner": "elective",
          "description": "选课记录(selected/enrolled/waitlist/dropped/rejected)"
        }
      }
    },
    "proctoring": {
      "description": "考试监考 (P2)",
      "tables": {
        "examProctoringEvents": {
          "owner": "proctoring",
          "description": "监考事件(tab_switch/window_blur/copy_attempt 等)"
        }
      }
    },
    "diagnostic": {
      "description": "学情诊断 (P2)",
      "tables": {
        "knowledgePointMastery": {
          "owner": "diagnostic",
          "description": "知识点掌握度(学生-知识点)"
        },
        "learningDiagnosticReports": {
          "owner": "diagnostic",
          "description": "学情诊断报告(individual/class/grade；v4-P1-4 新增 classId 字段关联班级)"
        }
      }
    },
    "lessonPreparation": {
      "description": "教师备课",
      "tables": {
        "lessonPlans": {
          "owner": "lesson-preparation",
          "description": "课案主表(Block JSON 内容,15 列/4 索引/5 外键)",
          "columns": 15,
          "indexes": 4,
          "foreignKeys": 5
        },
        "lessonPlanVersions": {
          "owner": "lesson-preparation",
          "description": "课案版本(手动/自动快照,8 列/2 索引/2 外键)",
          "columns": 8,
          "indexes": 2,
          "foreignKeys": 2
        },
        "lessonPlanTemplates": {
          "owner": "lesson-preparation",
          "description": "课案模板(系统/个人,8 列/1 索引/1 外键)",
          "columns": 8,
          "indexes": 1,
          "foreignKeys": 1
        },
        "systemSettings": {
          "owner": "settings",
          "description": "系统设置（键值对存储：category + key + value + valueType，P0-3 新增）",
          "columns": 7,
          "indexes": 2,
          "foreignKeys": 0
        }
      }
    },
    "errorBook": {
      "description": "错题本（SM-2 间隔重复算法）",
      "tables": {
        "errorBookItems": {
          "owner": "error-book",
          "description": "错题条目主表（学生-题目-来源-状态-掌握度-SM2 字段-笔记-标签）",
          "columns": 18,
          "indexes": 4,
          "foreignKeys": 2
        },
        "errorBookReviews": {
          "owner": "error-book",
          "description": "复习记录（错题条目-学生-评级-新间隔-新掌握度-复习时间）",
          "columns": 8,
          "indexes": 2,
          "foreignKeys": 2
        }
      }
    },
    "onboarding": {
      "description": "首次登录引导（独立路由 /onboarding + Server Action + middleware 重定向，v3 对标 PowerSchool/Veracross/Auth0）",
      "tables": {
        "users": {
          "owner": "shared",
          "description": "读写 users.onboardedAt / name / phone / address / birthDate / phone（v3 家长绑定三因子验证）",
          "columns": "subset"
        },
        "parentStudentRelations": {
          "owner": "shared",
          "description": "家长绑定子女（onboarding 中写入，支持多子女循环绑定）",
          "columns": "subset"
        },
        "auditLogs": {
          "owner": "shared",
          "description": "v3 新增：onboarding 完成后写审计日志（含失败项明细）",
          "columns": "subset"
        }
      },
      "exports": {
        "actions": [
          {
            "name": "getOnboardingStatusAction",
            "file": "actions.ts",
            "purpose": "查询当前用户 onboarding 状态（Server Action）"
          },
          {
            "name": "completeOnboardingAction",
            "file": "actions.ts",
            "purpose": "完成 onboarding（Server Action + requireAuth + Zod + db.transaction + logAudit，v3 新增幂等检查/局部错误收集/教师多科目循环绑定）"
          }
        ],
        "dataAccess": [
          {
            "name": "getOnboardingStatus",
            "file": "data-access.ts",
            "purpose": "读取 users.onboardedAt + usersToRoles"
          },
          {
            "name": "updateUserProfile",
            "file": "data-access.ts",
            "purpose": "更新 users.name/phone/address"
          },
          {
            "name": "bindParentToChild",
            "file": "data-access.ts",
            "purpose": "家长绑定子女（v3 三因子验证：邮箱+生日+手机号后4位，对标 PowerSchool Access ID+Password）"
          },
          {
            "name": "resolveDefaultPathByRoles",
            "file": "data-access.ts",
            "purpose": "按角色解析默认跳转路径"
          }
        ],
        "schema": [
          {
            "name": "OnboardingSchema",
            "file": "schema.ts",
            "purpose": "Zod 校验：name/phone/address/classCodes/teacherSubjects/children[]（v3 重构：children 数组替代单个 childEmail/childBindingCode，支持多子女）"
          }
        ],
        "types": [
          {
            "name": "OnboardingRoleInfo / OnboardingStatus / OnboardingCompleteData",
            "file": "types.ts",
            "purpose": "类型定义"
          },
          {
            "name": "OnboardingFailureItem",
            "file": "types.ts",
            "purpose": "v3 新增：局部失败项类型（班级码/子女绑定失败不回滚整个事务）"
          },
          {
            "name": "BindParentToChildParams",
            "file": "types.ts",
            "purpose": "v3 新增：家长绑定子女输入参数（三因子验证）"
          }
        ],
        "components": [
          {
            "name": "OnboardingStepper",
            "file": "components/onboarding-stepper.tsx",
            "purpose": "客户端 stepper 容器（4 步，v3 新增：URL query 持久化步骤/家长多子女动态行/跳过机制明确化）"
          }
        ]
      },
      "routes": {
        "/onboarding": {
          "methods": ["GET"],
          "handler": "OnboardingPage（服务端组件，读 session.onboarded 决定渲染，v3 新增 Suspense 边界 + 骨架屏）",
          "auth": "required",
          "validation": "server-side redirect"
        }
      },
      "securityNotes": [
        "角色只读：不写 usersToRoles，角色由管理员预分配",
        "班级绑定：调用 modules/classes data-access 的 enrollStudentByInvitationCode / enrollTeacherByInvitationCode（含校验）",
        "事务化：completeOnboardingAction 用 db.transaction 包裹全部写入",
        "Zod 校验：OnboardingSchema 校验所有输入",
        "middleware 拦截：proxy.ts 读取 token.onboarded，未完成则重定向 /onboarding",
        "v3 P0-2 家长绑定三因子验证：邮箱+生日+手机号后4位（组合空间 3.65M，对标 PowerSchool）",
        "v3 P0-3 教师多科目循环绑定：修复 UI 多选但服务端只取第一个的 bug",
        "v3 P0-4 审计日志：onboarding 完成后写 audit_logs（含失败项明细）",
        "v3 P0-5 服务端幂等：开始时检查 users.onboardedAt，已完成直接返回成功",
        "v3 P1-1 URL query 持久化步骤：?step=N，刷新不丢步，支持浏览器前进后退",
        "v3 P1-2 局部错误收集：班级码/子女绑定失败不回滚整个事务，收集失败列表返回前端",
        "v3 P1-4 家长多子女绑定：动态多行 UI，支持一次绑定多个子女",
        "v3 P1-5 跳过机制明确化：parent 不可跳过子女绑定（核心功能）"
      ]
    },
    "i18n": {
      "description": "v3 新增：项目国际化体系（next-intl 4.x，without i18n routing 模式，cookie 驱动）",
      "tables": {},
      "exports": {
        "config": [
          {
            "name": "getRequestConfig",
            "file": "src/i18n/request.ts",
            "purpose": "next-intl 请求配置：从 cookie 读取 locale，按命名空间加载字典"
          },
          {
            "name": "setLocaleAction",
            "file": "src/i18n/actions.ts",
            "purpose": "Server Action：切换语言（写 cookie + revalidatePath）"
          }
        ],
        "shared": [
          {
            "name": "LOCALES / Locale / DEFAULT_LOCALE / LOCALE_COOKIE",
            "file": "src/shared/i18n/locale.ts",
            "purpose": "locale 常量与工具函数"
          },
          {
            "name": "LocaleSwitcher",
            "file": "src/shared/components/locale-switcher.tsx",
            "purpose": "语言切换组件（DropdownMenu + cookie 持久化）"
          }
        ],
        "messages": [
          "src/shared/i18n/messages/zh-CN/{common,auth,onboarding,classes,errors,dashboard,examHomework,announcements,messages,notifications}.json",
          "src/shared/i18n/messages/en/{common,auth,onboarding,classes,errors,dashboard,examHomework,announcements,messages,notifications}.json"
        ]
      },
      "routes": {},
      "securityNotes": [
        "不使用 URL 路由段，避免破坏现有 (auth)/(dashboard)/(onboarding) 路由组结构",
        "locale 通过 cookie 持久化，SSR 时由 getRequestConfig 读取",
        "不使用 Accept-Language 自动协商，避免 SSR 与客户端 hydration 不一致",
        "字典放在 shared/i18n/messages/，符合三层架构约束（shared 为被依赖方）",
        "NextIntlClientProvider 在根 layout 注入，服务端/客户端组件均可使用 useTranslations"
      ]
    }
  },
  "dependencyMatrix": {
    "shared": {
      "dependsOn": []
    },
    "auth": {
      "dependsOn": [
        "shared"
      ],
      "uses": {
        "shared": [
          "db",
          "schema",
          "permissions"
        ]
      }
    },
    "exams": {
      "dependsOn": [
        "shared",
        "auth",
        "questions",
        "classes"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard",
          "types",
          "ai"
        ],
        "auth": [
          "auth"
        ],
        "questions": [
          "data-access.createQuestionWithRelations"
        ],
        "classes": [
          "data-access.getClassGradeIdsByClassIds"
        ]
      }
    },
    "homework": {
      "dependsOn": [
        "shared",
        "auth",
        "exams",
        "classes",
        "school",
        "users"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard",
          "types"
        ],
        "auth": [
          "auth"
        ],
        "exams": [
          "data-access.getExamIdsByGradeIds",
          "data-access.getExamSubjectIdMap",
          "data-access.getExamWithQuestionsForHomework"
        ],
        "classes": [
          "data-access.getStudentIdsByClassId",
          "data-access.getStudentIdsByClassIds",
          "data-access.getActiveStudentIdsByClassId",
          "data-access.getTeacherSubjectIdsByClass",
          "data-access.getStudentActiveClassId",
          "data-access.getGradeIdsByClassIds",
          "data-access.getClassTeacherById"
        ],
        "school": [
          "data-access.getSubjectOptions",
          "data-access.getSubjectNameMapByIds"
        ],
        "users": [
          "data-access.getUserWithRole",
          "data-access.getUserNamesByIds"
        ]
      }
    },
    "questions": {
      "dependsOn": [
        "shared",
        "auth"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard",
          "types"
        ],
        "auth": [
          "auth"
        ]
      }
    },
    "textbooks": {
      "dependsOn": [
        "shared",
        "auth",
        "users",
        "classes"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard",
          "types"
        ],
        "auth": [
          "auth"
        ],
        "users": [
          "data-access.getCurrentStudentUser"
        ],
        "classes": [
          "data-access-students.getClassStudents"
        ]
      }
    },
    "classes": {
      "dependsOn": [
        "shared",
        "auth",
        "homework",
        "scheduling",
        "school"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard",
          "types"
        ],
        "auth": [
          "auth"
        ],
        "homework": [
          "data-access-classes.getAssignmentIdsForStudents",
          "data-access-classes.getHomeworkAssignmentsWithSubject",
          "data-access-classes.getHomeworkAssignmentsByIds",
          "data-access-classes.getAssignmentMaxScoreById",
          "data-access-classes.getAssignmentTargetCounts",
          "data-access-classes.getHomeworkSubmissionsForStudents",
          "data-access-classes.getPublishedHomeworkAssignmentsWithSubject",
          "data-access-classes.getHomeworkSubmissionsForAssignments"
        ],
        "scheduling": [
          "data-access-class-schedule.createClassScheduleItem",
          "data-access-class-schedule.updateClassScheduleItem",
          "data-access-class-schedule.deleteClassScheduleItem"
        ],
        "school": [
          "data-access.isGradeHead",
          "data-access.isGradeManager",
          "data-access.findGradeIdByHeadAndName"
        ]
      }
    },
    "school": {
      "dependsOn": [
        "shared",
        "auth"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard",
          "types"
        ],
        "auth": [
          "auth"
        ]
      }
    },
    "dashboard": {
      "dependsOn": [
        "shared",
        "auth",
        "homework",
        "classes"
      ],
      "uses": {
        "shared": [
          "db",
          "types"
        ],
        "auth": [
          "auth"
        ],
        "homework": [
          "data-access.getTeacherGradeTrends",
          "data-access.getStudentDashboardGrades"
        ],
        "classes": [
          "data-access.getTeacherClasses",
          "data-access.getStudentClasses",
          "data-access.getStudentSchedule"
        ]
      }
    },
    "layout": {
      "dependsOn": [
        "shared",
        "auth",
        "messaging"
      ],
      "uses": {
        "shared": [
          "hooks.usePermission",
          "components.global-search.GlobalSearch"
        ],
        "auth": [
          "useSession"
        ],
        "messaging": [
          "components.notification-dropdown"
        ]
      }
    },
    "settings": {
      "dependsOn": [
        "shared",
        "auth",
        "classes",
        "homework",
        "dashboard",
        "users",
        "notifications",
        "files"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard",
          "ai",
          "types",
          "components.ui.switch",
          "components.ui.card",
          "components.ui.tabs",
          "components.ui.alert-dialog",
          "components.ui.badge",
          "components.form-fields",
          "components.locale-switcher"
        ],
        "auth": [
          "auth"
        ],
        "classes": [
          "data-access.getStudentClasses",
          "data-access.getStudentSchedule",
          "data-access.getTeacherClasses",
          "data-access.getTeacherTeachingSubjects"
        ],
        "homework": [
          "data-access.getStudentHomeworkAssignments",
          "data-access.getStudentDashboardGrades"
        ],
        "dashboard": [
          "components.student-dashboard.student-grades-card",
          "components.student-dashboard.student-stats-grid",
          "components.student-dashboard.student-today-schedule-card",
          "components.student-dashboard.student-upcoming-assignments-card"
        ],
        "users": [
          "data-access.UserProfile",
          "data-access.UpdateUserProfileInput",
          "data-access.updateUserAvatar"
        ],
        "notifications": [
          "types.NotificationPreferences",
          "types.UpdateNotificationPreferencesInput"
        ],
        "files": [
          "data-access.getFileByUrl",
          "data-access.deleteFileAttachment"
        ]
      },
      "note": "组件层通过 SettingsService 接口注入解耦，不直接 import messaging/actions；页面层 app/(dashboard)/settings/page.tsx 负责注入 users/actions + messaging/actions 实现。P0-3/P2-8/P2-9/P2-10/P2-11 已修复：AdminSettingsView 接入真实数据层（system_settings 表）、头像上传、2FA/登录历史、通知测试按钮、语言切换集成。v2 已增强：通知测试接入真实发送、头像上传清理旧文件、会话远程登出、AdminSettingsView/通知偏好表单 dirty 检测、currentDeviceLabel 标记当前会话、文件名长度校验、2FA 查询 N+1 优化。v3 已增强：完整 TOTP 2FA 流程（otplib v13 + qrcode）— 启用(QR码扫描+验证码校验+10个备份码 bcrypt 哈希存储)/关闭(需 TOTP 或备份码确认)/重新生成备份码；登录流程接入 2FA（preflightTwoFactorAction 预检 + auth.ts authorize 校验）；新增 29 个 TOTP 单元测试（总计 59 个）。依赖新增：otplib、qrcode。"
    },
    "users": {
      "dependsOn": [
        "shared",
        "auth",
        "classes"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requireAuth",
          "auth-guard.requirePermission",
          "db.schema.users",
          "db.schema.roles",
          "db.schema.usersToRoles",
          "types.permissions",
          "types.action-state",
          "lib.excel"
        ],
        "auth": [
          "auth"
        ],
        "classes": [
          "data-access.enrollStudentByInvitationCode"
        ]
      }
    },
    "audit": {
      "dependsOn": [
        "shared",
        "auth"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "db.schema.auditLogs",
          "db.schema.loginLogs",
          "db.schema.dataChangeLogs",
          "types.permissions",
          "lib.excel"
        ],
        "auth": [
          "auth"
        ]
      }
    },
    "announcements": {
      "dependsOn": [
        "shared",
        "auth",
        "school"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "auth-guard.requireAuth",
          "db.schema.announcements",
          "db.schema.announcementReads",
          "types.permissions"
        ],
        "auth": [
          "auth"
        ],
        "school": [
          "data-access.getGrades"
        ]
      }
    },
    "files": {
      "dependsOn": [
        "shared",
        "auth"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requireAuth",
          "auth-guard.requirePermission",
          "types.permissions",
          "lib.file-storage",
          "lib.storage-provider"
        ],
        "auth": [
          "auth"
        ]
      }
    },
    "course-plans": {
      "dependsOn": [
        "shared",
        "auth",
        "school",
        "classes"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "db.schema.coursePlans",
          "db.schema.coursePlanItems",
          "db.schema.classes",
          "db.schema.subjects",
          "db.schema.users",
          "types.permissions",
          "types.action-state"
        ],
        "auth": [
          "auth"
        ],
        "school": [
          "data-access.getAcademicYears"
        ],
        "classes": [
          "data-access.getAdminClasses",
          "data-access.getStaffOptions"
        ]
      }
    },
    "grades": {
      "dependsOn": [
        "shared",
        "auth",
        "classes",
        "school",
        "users"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "types.permissions",
          "types.action-state",
          "db.schema.gradeRecords",
          "db.schema.gradeDrafts",
          "lib.excel"
        ],
        "auth": [
          "auth"
        ],
        "classes": [
          "data-access.getClassExists",
          "data-access.getClassNameById",
          "data-access.getClassNamesByIds",
          "data-access.getActiveStudentIdsByClassId",
          "data-access.getStudentActiveClassId",
          "data-access.getClassesByGradeId"
        ],
        "school": [
          "data-access.getSubjectOptions",
          "data-access.getGradeOptions"
        ],
        "users": [
          "data-access.getUserNamesByIds"
        ]
      }
    },
    "parent": {
      "dependsOn": [
        "shared",
        "auth",
        "homework",
        "classes",
        "grades",
        "school",
        "users",
        "attendance"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requireAuth",
          "auth-guard.getAuthContext",
          "db.schema.parentStudentRelations",
          "types",
          "lib.utils.getSearchParam"
        ],
        "auth": [
          "auth"
        ],
        "homework": [
          "data-access.getStudentHomeworkAssignments",
          "data-access.getStudentDashboardGrades"
        ],
        "classes": [
          "data-access.getStudentClasses",
          "data-access.getStudentSchedule",
          "data-access.getStudentActiveClass"
        ],
        "grades": [
          "data-access.getStudentGradeSummary"
        ],
        "school": [
          "data-access.getGradeNameById"
        ],
        "users": [
          "data-access.getUserBasicInfo",
          "data-access.getUserNamesByIds"
        ],
        "attendance": [
          "data-access-stats.getStudentAttendanceSummary",
          "components.student-attendance-view",
          "types.StudentAttendanceSummary (⚠️ 跨模块 UI 类型依赖：parent-attendance-warning.tsx / parent-attendance-rate-card.tsx / parent-attendance-calendar.tsx 直接 import)"
        ]
      }
    },
    "messaging": {
      "dependsOn": [
        "shared",
        "auth",
        "notifications",
        "classes"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "auth-guard.requireAuth",
          "db.schema.messages",
          "db.schema.users",
          "db.schema.classEnrollments",
          "db.schema.classes",
          "db.schema.grades",
          "types.permissions",
          "types.action-state"
        ],
        "auth": [
          "auth"
        ],
        "notifications": [
          "dispatcher.sendNotification",
          "data-access.createNotification (via re-export)",
          "data-access.getNotifications (via re-export)",
          "data-access.markNotificationAsRead (via re-export)",
          "data-access.markAllNotificationsAsRead (via re-export)",
          "data-access.getUnreadNotificationCount (via re-export)",
          "preferences.getNotificationPreferences (via re-export)",
          "preferences.upsertNotificationPreferences (via re-export)"
        ],
        "classes": [
          "data-access.getTeacherIdsByClassIds",
          "data-access.getStudentActiveClassId"
        ]
      }
    },
    "notifications": {
      "dependsOn": [
        "shared",
        "auth",
        "classes"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "db.schema.messageNotifications",
          "db.schema.notificationPreferences",
          "types.permissions",
          "types.action-state"
        ],
        "auth": [
          "auth"
        ],
        "classes": [
          "data-access.getClassExists",
          "data-access.getStudentIdsByClassId"
        ]
      }
    },
    "attendance": {
      "dependsOn": [
        "shared",
        "auth",
        "classes"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "db.schema.attendanceRecords",
          "db.schema.attendanceRules",
          "db.schema.classEnrollments",
          "db.schema.users",
          "db.schema.classes",
          "types.permissions",
          "types.action-state",
          "types.DataScope"
        ],
        "auth": [
          "auth"
        ],
        "classes": [
          "data-access.getTeacherClasses",
          "data-access.getAdminClasses"
        ]
      }
    },
    "scheduling": {
      "dependsOn": [
        "shared",
        "auth",
        "classes",
        "users"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "auth-guard.getAuthContext",
          "db.schema.schedulingRules",
          "db.schema.scheduleChanges",
          "db.schema.classSchedule",
          "db.schema.classSubjectTeachers",
          "db.schema.subjects",
          "db.schema.classrooms",
          "types.permissions",
          "types.action-state"
        ],
        "auth": [
          "auth"
        ],
        "classes": [
          "data-access.verifyTeacherOwnsClass",
          "data-access.getTeacherIdForMutations"
        ],
        "users": [
          "data-access.getUserNamesByIds"
        ]
      }
    },
    "diagnostic": {
      "dependsOn": [
        "shared",
        "auth",
        "classes",
        "exams",
        "questions",
        "users"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "auth-guard.getAuthContext",
          "db.schema.knowledgePointMastery",
          "db.schema.learningDiagnosticReports",
          "db.schema.knowledgePoints",
          "types.permissions",
          "types.action-state",
          "hooks.usePermission",
          "components.ui.*"
        ],
        "auth": [
          "auth"
        ],
        "classes": [
          "data-access.getClassExists",
          "data-access.getClassNameById",
          "data-access.getActiveStudentIdsByClassId"
        ],
        "exams": [
          "data-access.getExamSubmissionWithAnswers"
        ],
        "questions": [
          "data-access.getKnowledgePointsForQuestions"
        ],
        "users": [
          "data-access.getUserNamesByIds",
          "data-access.getUserIdsByGradeId"
        ]
      }
    },
    "elective": {
      "dependsOn": [
        "shared",
        "auth",
        "classes",
        "school",
        "users"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "db.schema.electiveCourses",
          "db.schema.courseSelections",
          "types.permissions",
          "types.action-state",
          "types.DataScope",
          "hooks.usePermission",
          "components.ui.*"
        ],
        "auth": [
          "auth"
        ],
        "classes": [
          "data-access.getStudentActiveGradeId"
        ],
        "school": [
          "data-access.getSubjectOptions",
          "data-access.getGradeOptions"
        ],
        "users": [
          "data-access.getUserNamesByIds"
        ]
      }
    },
    "proctoring": {
      "dependsOn": [
        "shared",
        "auth",
        "exams",
        "users"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "db.schema.examProctoringEvents",
          "types.permissions",
          "types.action-state",
          "hooks.usePermission",
          "components.ui.*",
          "next/cache.revalidatePath"
        ],
        "auth": [
          "auth"
        ],
        "exams": [
          "data-access.getExamForProctoringCrossModule",
          "data-access.getExamSubmissionForProctoringCrossModule",
          "data-access.getExamSubmissionsForExam",
          "data-access.getExamTitleById"
        ],
        "users": [
          "data-access.getUserNamesByIds"
        ]
      }
    },
    "lesson_preparation": {
      "dependsOn": [
        "shared",
        "auth",
        "textbooks",
        "questions",
        "exams",
        "homework",
        "classes",
        "files"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "db.schema.lessonPlans",
          "db.schema.lessonPlanVersions",
          "db.schema.lessonPlanTemplates",
          "lib.ai.createAiChatCompletion",
          "types.permissions",
          "types.action-state",
          "hooks.usePermission",
          "components.ui.*",
          "next/cache.revalidatePath"
        ],
        "auth": [
          "auth"
        ],
        "textbooks": [
          "data-access.getChapters",
          "data-access.getKnowledgePoints"
        ],
        "questions": [
          "data-access.getQuestions",
          "data-access.createQuestionWithRelations"
        ],
        "exams": [
          "data-access.persistExamDraft"
        ],
        "homework": [
          "data-access-write.createHomeworkAssignment"
        ],
        "classes": [
          "data-access.getTeacherClasses"
        ],
        "files": [
          "data-access.createFileAttachment",
          "data-access.getFileAttachmentsByTarget"
        ],
        "external": [
          "@xyflow/react（React Flow 节点图编辑器：ReactFlow/Background/Controls/MiniMap/Handle/applyNodeChanges/applyEdgeChanges）",
          "@paralleldrive/cuid2（节点 ID 生成）",
          "zustand（编辑器状态管理）"
        ]
      }
    },
    "onboarding": {
      "dependsOn": [
        "shared",
        "auth",
        "classes"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requireAuth",
          "db.schema.users",
          "db.schema.usersToRoles",
          "db.schema.roles",
          "db.schema.parentStudentRelations",
          "lib.role-utils",
          "types.permissions",
          "types.action-state"
        ],
        "auth": [
          "auth"
        ],
        "classes": [
          "data-access.enrollStudentByInvitationCode",
          "data-access.enrollTeacherByInvitationCode"
        ]
      }
    },
    "error-book": {
      "dependsOn": [
        "shared",
        "classes",
        "questions"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "db.schema.errorBookItems",
          "db.schema.errorBookReviews",
          "db.schema.examSubmissions",
          "db.schema.submissionAnswers",
          "db.schema.homeworkSubmissions",
          "db.schema.homeworkAnswers",
          "db.schema.questions",
          "db.schema.questionsToKnowledgePoints",
          "db.schema.knowledgePoints",
          "db.schema.subjects",
          "db.schema.examQuestions",
          "db.schema.homeworkAssignmentQuestions",
          "db.schema.users",
          "types.permissions",
          "types.action-state",
          "lib.utils"
        ],
        "classes": [
          "data-access.getStudentIdsByClassIds"
        ],
        "questions": [
          "actions.getQuestionsAction"
        ]
      }
    },
    "ai": {
      "dependsOn": [
        "shared",
        "textbooks"
      ],
      "uses": {
        "shared": [
          "db",
          "auth-guard.requirePermission",
          "lib.ai.createAiChatCompletion",
          "lib.ai.createAiChatCompletionStream",
          "types.permissions",
          "types.action-state",
          "lib.track-event.trackEvent",
          "i18n.messages"
        ],
        "textbooks": [
          "data-access-graph.getKnowledgePointsWithRelations",
          "data-access-graph.getStudentKpMastery"
        ]
      },
      "exports": {
        "actions": [
          "aiChatAction",
          "suggestSimilarQuestionsAction",
          "suggestGradingAction",
          "generateLessonContentAction",
          "generateQuestionVariantAction",
          "analyzeWeaknessAction",
          "generateChildSummaryAction",
          "recommendStudyPathAction",
          "getAiUsageStatsAction"
        ],
        "types": [
          "AiService",
          "AiClientService",
          "AiChatMessage",
          "AiChatResult",
          "SimilarQuestionInput",
          "SimilarQuestionResult",
          "GradingInput",
          "GradingSuggestion",
          "LessonContentInput",
          "LessonContentResult",
          "QuestionVariantInput",
          "QuestionVariantResult",
          "WeaknessAnalysisInput",
          "WeaknessAnalysisResult",
          "ChildSummaryInput",
          "ChildSummaryResult",
          "StudyPathInput",
          "StudyPathResult",
          "AiUsageStats",
          "AiCapability"
        ],
        "components": [
          "AiGradingAssist",
          "AiErrorBookAnalysis",
          "AiLessonContentGenerator",
          "AiQuestionVariantGenerator",
          "AiChatPanel",
          "AiErrorBoundary",
          "AiSuggestionSkeleton",
          "AiProviderSelector",
          "AiAssistantWidget",
          "AiMarkdownRenderer",
          "AiChildSummary",
          "AiUsageDashboard",
          "AiStudyPath"
        ],
        "hooks": [
          "useAiClient",
          "useAiClientOptional",
          "useAiChat",
          "useAiSuggestion",
          "useAiChatStream"
        ],
        "providers": [
          "AiClientProvider"
        ],
        "services": [
          "DefaultAiService",
          "createAiService",
          "safeAiCall",
          "filterUserInput",
          "filterAiOutput",
          "checkDailyLimit",
          "incrementDailyUsage",
          "getDailyLimit",
          "tryConsumeDailyQuota",
          "refundDailyQuota",
          "validateSocraticOutput"
        ],
        "dataAccess": [
          "recordAiEvent",
          "getAiUsageStats"
        ],
        "streamUtils": [
          "consumeSseStream",
          "getStreamErrorKey",
          "extractErrorMessage",
          "removeTrailingEmptyAssistant",
          "appendTokenToLastAssistant"
        ],
        "promptTemplates": [
          "CHILD_SUMMARY_SYSTEM_PROMPT",
          "STUDY_PATH_SYSTEM_PROMPT",
          "SOCRATIC_TUTOR_SYSTEM_PROMPT",
          "CHAT_SYSTEM_PROMPT"
        ]
      },
      "integrations": {
        "homework": {
          "component": "AiGradingAssist",
          "page": "teacher/homework/submissions/[submissionId]",
          "capability": "grading-assist"
        },
        "error-book": {
          "component": "AiErrorBookAnalysis",
          "page": "student/error-book",
          "capability": "similar-question, weakness-analysis"
        },
        "lesson-preparation": {
          "component": "AiLessonContentGenerator",
          "page": "teacher/lesson-plans/[planId]/edit",
          "capability": "lesson-content"
        },
        "exams": {
          "component": "AiQuestionVariantGenerator",
          "page": "teacher/exams/[id]/build",
          "capability": "question-variant"
        },
        "parent-dashboard": {
          "component": "AiChildSummary",
          "page": "parent/dashboard",
          "capability": "child-summary"
        },
        "admin-dashboard": {
          "component": "AiUsageDashboard",
          "page": "admin/ai-usage",
          "capability": "usage-stats"
        },
        "student-learning": {
          "component": "AiStudyPath",
          "page": "student/learning/study-path",
          "capability": "study-path"
        },
        "global-assistant": {
          "component": "AiAssistantWidget",
          "page": "dashboard-layout (all pages)",
          "capability": "context-aware-chat"
        }
      },
      "permissions": [
        "ai:chat",
        "ai:configure"
      ],
      "safety": {
        "inputFilter": "filterUserInput — 阻断暴力/自残/色情/毒品/黑客/PII 索取等模式",
        "outputFilter": "filterAiOutput — 输出内容二次过滤，学生场景额外阻断直接答案模式",
        "dailyLimits": {
          "student": 50,
          "teacher": 200,
          "parent": 30,
          "admin": 500
        },
        "studentSocraticMode": "学生角色强制苏格拉底式引导，不直接给答案",
        "compliance": "COPPA/FERPA K12 合规设计"
      },
      "streaming": {
        "endpoint": "/api/ai/chat/stream",
        "protocol": "SSE (Server-Sent Events)",
        "clientHook": "useAiChatStream",
        "features": [
          "实时 token 流式渲染",
          "AbortController 支持停止生成",
          "错误事件流式回传",
          "过滤事件流式回传"
        ]
      },
      "i18n": {
        "namespace": "ai",
        "files": [
          "shared/i18n/messages/zh-CN/ai.json",
          "shared/i18n/messages/en/ai.json"
        ],
        "v2Keys": [
          "chat.streaming",
          "chat.stopGeneration",
          "chat.copy",
          "chat.clearConfirm",
          "chat.suggestedPrompts",
          "grading.description",
          "grading.batch*",
          "lessonPrep.description",
          "lessonPrep.additionalContext",
          "lessonPrep.insertContent",
          "exam.variantType.*",
          "exam.targetDifficulty",
          "exam.addVariant",
          "parent.*",
          "admin.*",
          "studyPath.*",
          "widget.*",
          "safety.*"
        ]
      }
    }
  },
  "moduleDependencyGraph": {
    "nodes": [
      "shared",
      "auth",
      "exams",
      "homework",
      "questions",
      "textbooks",
      "classes",
      "school",
      "dashboard",
      "layout",
      "settings",
      "users",
      "audit",
      "announcements",
      "files",
      "course-plans",
      "grades",
      "parent",
      "messaging",
      "notifications",
      "attendance",
      "scheduling",
      "proctoring",
      "diagnostic",
      "elective",
      "onboarding",
      "error-book",
      "ai"
    ],
    "edges": [
      {
        "from": "exams",
        "to": "questions",
        "type": "data-access",
        "description": "引用题目(examQuestions 关联)"
      },
      {
        "from": "exams",
        "to": "shared",
        "type": "normal",
        "description": "使用 db/auth-guard/ai"
      },
      {
        "from": "homework",
        "to": "exams",
        "type": "data-access",
        "description": "引用试卷结构(sourceExamId)"
      },
      {
        "from": "homework",
        "to": "questions",
        "type": "data-access",
        "description": "引用题目"
      },
      {
        "from": "grades",
        "to": "classes",
        "type": "data-access",
        "description": "查询班级"
      },
      {
        "from": "grades",
        "to": "exams",
        "type": "data-access",
        "description": "关联考试(examId)"
      },
      {
        "from": "grades",
        "to": "subjects",
        "type": "data-access",
        "description": "查询科目"
      },
      {
        "from": "dashboard",
        "to": "exams",
        "type": "data-access",
        "description": "调用 getExamsDashboardStats 获取考试统计(P0-4 已修复)"
      },
      {
        "from": "dashboard",
        "to": "homework",
        "type": "data-access",
        "description": "调用 getHomeworkDashboardStats 获取作业统计(P0-4 已修复)"
      },
      {
        "from": "dashboard",
        "to": "classes",
        "type": "data-access",
        "description": "调用 getClassesDashboardStats 获取班级统计(P0-4 已修复)"
      },
      {
        "from": "dashboard",
        "to": "users",
        "type": "data-access",
        "description": "调用 getUsersDashboardStats 获取用户/会话/角色统计(P0-4 已修复)"
      },
      {
        "from": "dashboard",
        "to": "textbooks",
        "type": "data-access",
        "description": "调用 getTextbooksDashboardStats 获取教材/章节统计(P0-4 已修复)"
      },
      {
        "from": "dashboard",
        "to": "questions",
        "type": "data-access",
        "description": "调用 getQuestionsDashboardStats 获取题目统计(P0-4 已修复)"
      },
      {
        "from": "messaging",
        "to": "notifications",
        "type": "data-access",
        "description": "✅ P0-4 / P1-5 已修复：messaging 通过 sendNotification dispatcher 发送通知，通知 CRUD 和偏好通过 re-export 保持向后兼容"
      },
      {
        "from": "messaging",
        "to": "classes",
        "type": "data-access",
        "description": "getRecipients 通过 classes data-access.getTeacherIdsByClassIds / getStudentActiveClassId 获取班级教师 ID，支持 class_members（学生）和 children（家长）数据范围"
      },
      {
        "from": "classes",
        "to": "homework",
        "type": "data-access",
        "description": "通过 homework/data-access-classes 获取作业数据(P0-7 已修复,原为 violation)"
      },
      {
        "from": "classes",
        "to": "scheduling",
        "type": "resolved",
        "description": "✅ P0-5 已修复：classSchedule 写函数从 classes 迁移至 scheduling/data-access-class-schedule.ts，classes 仅保留读函数"
      },
      {
        "from": "classes",
        "to": "grades",
        "type": "resolved",
        "description": "✅ P0-1 已修复：classes/data-access.ts 已拆分为 5 个文件，不再混入 grades 查询逻辑"
      },
      {
        "from": "proctoring",
        "to": "exams",
        "type": "data-access",
        "description": "关联考试与提交(examSubmissions)"
      },
      {
        "from": "diagnostic",
        "to": "questions",
        "type": "data-access",
        "description": "关联题目知识点(questionsToKnowledgePoints)"
      },
      {
        "from": "diagnostic",
        "to": "exams",
        "type": "data-access",
        "description": "关联考试提交(examSubmissions/submissionAnswers)"
      },
      {
        "from": "diagnostic",
        "to": "grades",
        "type": "lib-import",
        "description": "复用 toNumber 工具函数(P3-1 修复：从 grades/lib/grade-utils 导入)"
      },
      {
        "from": "elective",
        "to": "school",
        "type": "data-access",
        "description": "关联年级(grades)/科目(subjects)（P2-8：通过 resolvers.ts CourseDisplayResolver 接口抽象，默认实现委托 school data-access）"
      },
      {
        "from": "elective",
        "to": "users",
        "type": "data-access",
        "description": "查询教师/学生显示名（P2-8：通过 resolvers.ts CourseDisplayResolver 接口抽象，默认实现委托 users data-access.getUserNamesByIds）"
      },
      {
        "from": "elective",
        "to": "classes",
        "type": "data-access",
        "description": "查询学生所在年级（P2-8：通过 resolvers.ts StudentGradeResolver 接口抽象，默认实现委托 classes data-access.getStudentActiveGradeId）"
      },
      {
        "from": "attendance",
        "to": "classes",
        "type": "data-access",
        "description": "查询班级与课表"
      },
      {
        "from": "parent",
        "to": "grades",
        "type": "data-access",
        "description": "查询子女成绩"
      },
      {
        "from": "parent",
        "to": "attendance",
        "type": "data-access",
        "description": "查询子女考勤"
      },
      {
        "from": "parent",
        "to": "homework",
        "type": "data-access",
        "description": "查询子女作业"
      },
      {
        "from": "scheduling",
        "to": "classes",
        "type": "data-access",
        "description": "排课关联班级"
      },
      {
        "from": "course-plans",
        "to": "classes",
        "type": "data-access",
        "description": "课程计划关联班级"
      },
      {
        "from": "course-plans",
        "to": "school",
        "type": "data-access",
        "description": "关联科目"
      },
      {
        "from": "announcements",
        "to": "school",
        "type": "data-access",
        "description": "关联年级/班级"
      },
      {
        "from": "shared",
        "to": "auth",
        "type": "resolved",
        "description": "✅ 已修复：shared/lib 通过 session.ts 单一入口获取 session（dynamic import 打破静态循环）"
      },
      {
        "from": "auth",
        "to": "shared",
        "type": "normal",
        "description": "auth.ts 依赖 shared/lib（合理，单向）"
      },
      {
        "from": "layout",
        "to": "shared",
        "type": "normal",
        "description": "使用 shared 组件与配置"
      },
      {
        "from": "settings",
        "to": "shared",
        "type": "normal",
        "description": "使用 shared/lib/ai"
      },
      {
        "from": "settings",
        "to": "messaging",
        "type": "normal",
        "description": "通知偏好设置（v1.1 已重构：settings/actions-service.ts 直接调用 notifications/preferences.upsertNotificationPreferences，不再依赖 messaging/actions.updateNotificationPreferencesAction）"
      },
      {
        "from": "settings",
        "to": "users",
        "type": "normal",
        "description": "v1.1 新增：settings/actions-service.ts.updateProfileAction 委托 users/actions.updateUserProfile"
      },
      {
        "from": "settings",
        "to": "notifications",
        "type": "normal",
        "description": "v1.1 新增：settings/actions-service.ts.updateNotificationPreferencesAction 调用 notifications/preferences.upsertNotificationPreferences"
      },
      {
        "from": "audit",
        "to": "shared",
        "type": "normal",
        "description": "使用 shared/lib/audit-logger"
      },
      {
        "from": "files",
        "to": "shared",
        "type": "normal",
        "description": "使用 shared/db"
      },
      {
        "from": "users",
        "to": "classes",
        "type": "data-access",
        "description": "✅ P1-4 已修复：通过 class-registration.ts 调用 classes/data-access.enrollStudentByInvitationCode，不再直写 classEnrollments"
      },
      {
        "from": "error-book",
        "to": "shared",
        "type": "normal",
        "description": "使用 shared/db、auth-guard、types"
      },
      {
        "from": "error-book",
        "to": "classes",
        "type": "data-access",
        "description": "通过 classes/data-access.getStudentIdsByClassIds 查询班级学生"
      },
      {
        "from": "error-book",
        "to": "questions",
        "type": "data-access",
        "description": "通过 questions/actions.getQuestionsAction 查询题库（手动添加错题时）"
      },
      {
        "from": "ai",
        "to": "shared",
        "type": "normal",
        "description": "使用 lib/ai.createAiChatCompletion、auth-guard.requirePermission、types.permissions、types.action-state"
      },
      {
        "from": "ai",
        "to": "textbooks",
        "type": "normal",
        "description": "V3 新增：使用 data-access-graph.getKnowledgePointsWithRelations/getStudentKpMastery 获取知识图谱与掌握度，注入学习路径推荐"
      },
      {
        "from": "homework",
        "to": "ai",
        "type": "context",
        "description": "通过 AiClientProvider 注入 AiClientService，使用 AiGradingAssist 组件"
      },
      {
        "from": "error-book",
        "to": "ai",
        "type": "context",
        "description": "通过 AiClientProvider 注入 AiClientService，使用 AiErrorBookAnalysis 组件"
      },
      {
        "from": "lesson-preparation",
        "to": "ai",
        "type": "context",
        "description": "通过 AiClientProvider 注入 AiClientService，使用 AiLessonContentGenerator 组件"
      },
      {
        "from": "exams",
        "to": "ai",
        "type": "context",
        "description": "通过 AiClientProvider 注入 AiClientService，使用 AiQuestionVariantGenerator 组件"
      }
    ]
  },
  "knownIssues": [
    {
      "id": "P0-1",
      "severity": "P0",
      "title": "classes/data-access.ts 超过 1000 行",
      "file": "src/modules/classes/data-access.ts",
      "lines": 2104,
      "problem": "混入 homework/scheduling/grades 逻辑,严重违反模块职责单一原则",
      "suggestion": "按职责拆分为 class-query/schedule/homework-insights/grade-query",
      "status": "resolved",
      "resolvedAt": "2026-06-17",
      "resolution": "拆分为 5 个文件：data-access.ts(548行,核心CRUD+邀请码+教师班级管理) + data-access-stats.ts(531行,作业统计) + data-access-schedule.ts(194行,课表) + data-access-students.ts(244行,学生查询) + data-access-admin.ts(406行,管理员班级管理)，所有文件均 ≤800 行，data-access.ts 通过 re-export 保持向后兼容"
    },
    {
      "id": "P0-2",
      "severity": "P0",
      "title": "homework/data-access.ts 超过 1000 行",
      "file": "src/modules/homework/data-access.ts",
      "lines": 1038,
      "problem": "混入排名计算业务逻辑",
      "suggestion": "分离排名逻辑到独立文件(如 data-access-ranking.ts)",
      "status": "resolved",
      "resolvedAt": "2026-06-17",
      "resolution": "拆分为 data-access.ts(598行) + stats-service.ts(425行)，统计函数(getTeacherGradeTrends/getHomeworkAssignmentAnalytics/getStudentDashboardGrades)迁移至 stats-service.ts，data-access.ts 通过 re-export 保持向后兼容"
    },
    {
      "id": "P0-3",
      "severity": "P0",
      "title": "shared/lib ↔ auth 循环依赖",
      "file": "src/shared/lib/{audit-logger,change-logger,auth-guard}.ts ↔ src/auth.ts",
      "problem": "shared/lib 依赖 @/auth(src/auth.ts),auth.ts 又依赖 shared/lib,形成循环依赖",
      "suggestion": "拆分 auth.ts,将 shared 依赖部分抽出为独立模块",
      "status": "resolved",
      "resolvedAt": "2026-06-18",
      "resolution": "新增 shared/lib/session.ts 单一入口封装 getSession()（server-only，内部 dynamic import @/auth 打破模块级静态循环）。audit-logger.ts/change-logger.ts/auth-guard.ts 改为 import { getSession } from '@/shared/lib/session'，不再直接依赖 @/auth。运行时调用链保持不变，模块加载图无环。"
    },
    {
      "id": "P0-4",
      "severity": "P0",
      "title": "dashboard 跨模块直接查询 11 张表",
      "file": "src/modules/dashboard/data-access.ts",
      "problem": "getAdminDashboardData 直查 sessions/users/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions/usersToRoles/roles,严重违反模块封装",
      "suggestion": "改为通过各模块 data-access 获取数据",
      "status": "fixed",
      "fixedBy": "新增 getUsersDashboardStats/getClassesDashboardStats/getTextbooksDashboardStats/getQuestionsDashboardStats/getExamsDashboardStats/getHomeworkDashboardStats,dashboard 改为并行调用各模块 stats 函数"
    },
    {
      "id": "P0-5",
      "severity": "P0",
      "title": "messaging 绕过 notifications 直接写通知",
      "file": "src/modules/messaging/actions.ts",
      "lines": "66-72",
      "problem": "直接调用 createNotification,导致用户通知偏好失效、多渠道通知无效",
      "suggestion": "改为通过 notifications dispatcher 统一分发",
      "status": "resolved",
      "resolvedAt": "2026-06-17",
      "resolution": "将 messageNotifications 和 notificationPreferences 表的 CRUD 函数从 messaging 迁移到 notifications 模块；notifications/channels/in-app-channel.ts 改为静态导入本地 createNotification（不再动态 import messaging）；notifications/dispatcher.ts 改为从本地 preferences.ts 导入偏好函数；messaging 模块通过 re-export 保持向后兼容；依赖方向变为单向：messaging → notifications（messaging 调用 sendNotification dispatcher 发送通知）"
    },
    {
      "id": "P0-6",
      "severity": "P0",
      "title": "classSchedule 表三处写入口",
      "file": "src/modules/classes/data-access.ts, src/modules/scheduling/actions.ts, src/modules/scheduling/data-access.ts",
      "problem": "三处独立写入 classSchedule 表,数据完整性高风险",
      "suggestion": "统一写入口到 scheduling 模块",
      "status": "fixed",
      "fixedBy": "将 classSchedule 写函数(createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem)从 classes/data-access-schedule.ts 迁移到新文件 scheduling/data-access-class-schedule.ts;classes 模块仅保留 READ 函数(getStudentSchedule/getClassSchedule);新增 classes/data-access.verifyTeacherOwnsClass 供 scheduling 模块跨模块校验教师班级归属;classes/actions.ts 改为从 @/modules/scheduling/data-access-class-schedule 导入写函数;类型 CreateClassScheduleItemInput/UpdateClassScheduleItemInput 从 classes/types.ts 迁移到 scheduling/types.ts;所有 classSchedule DB 写入统一由 scheduling 模块管理"
    },
    {
      "id": "P0-7",
      "severity": "P0",
      "title": "classes 模块直查 homework/exams 表违反三层架构",
      "file": "src/modules/classes/data-access-stats.ts, src/modules/classes/data-access-students.ts",
      "problem": "data-access-stats.ts 和 data-access-students.ts 直接导入并查询 homeworkAssignmentQuestions/homeworkAssignmentTargets/homeworkAssignments/homeworkSubmissions/exams 表,违反 modules/ 不直接查询其他模块 DB 表的规则",
      "suggestion": "在 homework 模块新增 data-access-classes.ts 暴露所需数据,classes 模块改为调用这些函数",
      "status": "resolved",
      "resolvedAt": "2026-06-18",
      "resolution": "新增 src/modules/homework/data-access-classes.ts(232行,7个函数:getAssignmentIdsForStudents/getHomeworkAssignmentsWithSubject/getHomeworkAssignmentsByIds/getAssignmentTargetCounts/getHomeworkSubmissionsForStudents/getPublishedHomeworkAssignmentsWithSubject/getHomeworkSubmissionsForAssignments,re-export getAssignmentMaxScoreById);classes/data-access-stats.ts 和 data-access-students.ts 改为调用这些函数,移除对 homework/exams 表的直接导入;同时修复 P1 问题:移除 4 处 `as string` 断言(data-access-stats.ts),将 `studentScores.get(s.studentId)!` 非空断言替换为显式 null 检查(data-access-students.ts)"
    },
    {
      "id": "P1-1",
      "severity": "P1",
      "title": "跨模块直接 DB 查询普遍存在",
      "problem": "classes(8+)/classEnrollments(6+)/users(6+)/subjects(6+)/exams(5+) 表被多个模块直接查询",
      "suggestion": "建立模块间数据访问规范,通过对方 data-access 或导出查询函数",
      "status": "resolved",
      "resolvedAt": "2026-06-18",
      "resolution": "所有跨模块直查已改为通过对方 data-access 接口：exams 通过 school data-access.getSubjectOptions/getGradeOptions；questions 通过 textbooks data-access.getKnowledgePointOptions；homework 通过 exams/classes/school/users data-access；grades 通过 classes/school/users data-access；classes 通过 school/homework data-access；attendance 通过 classes data-access；scheduling 通过 users data-access；notifications 通过 classes data-access；proctoring 通过 exams/users data-access；diagnostic 通过 exams/questions/classes/users data-access；dashboard 通过各模块 data-access；users updateUserProfile 已下沉到 data-access。各模块 data-access 暴露的查询接口：classes（getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId/verifyTeacherOwnsClass/getTeacherIdForMutations 等）、exams（getExamIdsByGradeIds/getExamSubjectIdMap/getExamWithQuestionsForHomework/getExamSubmissionWithAnswers/getExamForProctoringCrossModule 等）、school（getSubjectOptions/getGradeOptions）、users（getUserNamesByIds/getUserWithRole/getUserBasicInfo/getUserIdsByGradeId/getCurrentStudentUser）、textbooks（getKnowledgePointOptions）、questions（createQuestionWithRelations/getKnowledgePointsForQuestions）、homework/data-access-classes（7 个函数供 classes 模块跨模块调用）"
    },
    {
      "id": "P1-2",
      "severity": "P1",
      "title": "actions 层混入数据访问逻辑",
      "file": "src/modules/{exams,homework,questions,announcements}/actions.ts",
      "problem": "actions.ts 中存在直接 db.insert/update/delete,应通过 data-access 层",
      "suggestion": "将 DB 操作下沉到 data-access 层",
      "status": "resolved",
      "resolvedAt": "2026-06-17",
      "resolution": "4 个模块的 actions 层 DB 操作全部下沉到 data-access：exams 新增 7 个 data-access 函数（actions.ts 831→691 行，data-access.ts 374→471 行）；homework 新建 data-access-write.ts（285 行，10 个写函数，actions.ts 387→239 行）；questions 新增 4 个 data-access 函数（actions.ts 294→149 行，data-access.ts 138→260 行）；announcements 新增 5 个 data-access 函数（actions.ts 242→197 行，data-access.ts 120→171 行）。users/scheduling 待后续处理"
    },
    {
      "id": "P1-3",
      "severity": "P1",
      "title": "auth.ts 混合 5 类职责",
      "file": "src/auth.ts",
      "problem": "NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数混合",
      "suggestion": "拆分为 auth-config/password-security/role-normalizer/ip-utils 等多文件",
      "status": "resolved",
      "resolution": "已拆分：密码安全DB操作→shared/lib/password-security-service.ts，角色规范化→shared/lib/role-utils.ts，bcrypt哈希规范化→shared/lib/bcrypt-utils.ts，IP解析→shared/lib/http-utils.ts。auth.ts 现 193 行仅保留 NextAuth 配置"
    },
    {
      "id": "P1-4",
      "severity": "P1",
      "title": "users/import-export.ts 四重职责",
      "file": "src/modules/users/import-export.ts",
      "problem": "导入解析 + 导出 + 用户创建(含密码哈希) + 班级注册(跨模块写 classEnrollments)",
      "suggestion": "按职责拆分为 import-parser/exporter/user-creator/enrollment",
      "status": "resolved",
      "resolvedAt": "2026-06-17",
      "resolution": "已拆分：import-export.ts(157行,仅保留文件解析/生成) + user-service.ts(82行,batchImportUsers 用户创建+密码哈希+角色分配) + class-registration.ts(21行,registerStudentByInvitationCode 委托 classes/data-access.enrollStudentByInvitationCode)。batchImportUsers 不再直写 classEnrollments，改为调用 classes/data-access.enrollStudentByInvitationCode。import-export.ts 通过 re-export batchImportUsers/UserImportResult 保持向后兼容"
    },
    {
      "id": "P1-5",
      "severity": "P1",
      "title": "proctoring 死代码",
      "file": "src/modules/proctoring/components/exam-mode-config.tsx",
      "problem": "组件已创建但未集成到考试表单,DB schema 有 examMode 字段但表单不收集;事件上报存在 Server Action 与 REST API 双通道重复",
      "suggestion": "集成 proctoring/exam-mode-config 到考试表单;删除重复的 REST API 路由",
      "status": "partially_fixed",
      "fixedBy": "删除 /api/proctoring/event REST 路由(移至 deletes/),Server Action recordProctoringEventAction 为唯一规范路径;exam-mode-config.tsx 集成属于功能新增,暂保留原位"
    },
    {
      "id": "P2-1",
      "severity": "P2",
      "title": "schema.ts 54 张表混合(1111 行)",
      "file": "src/shared/db/schema.ts",
      "lines": 1111,
      "problem": "所有表定义混合在单文件,虽可接受但需分节",
      "suggestion": "按业务域分节(加注释分隔)或拆分为多文件"
    },
    {
      "id": "P2-2",
      "severity": "P2",
      "title": "ai.ts 混合 5 类职责(218 行)",
      "file": "src/shared/lib/ai.ts",
      "lines": 218,
      "problem": "AI 调用 + Provider 配置 + API Key 加密 + 错误格式化 + 负载解析混合在单文件",
      "suggestion": "按职责拆分为 ai/ 目录多文件",
      "status": "resolved",
      "resolvedAt": "2026-06-17",
      "resolution": "已拆分为 src/shared/lib/ai/ 目录：payload-parser.ts(78 行,请求负载解析) + api-key-crypto.ts(28 行,API Key 加密/解密) + provider-config.ts(61 行,Provider 配置查询) + client.ts(58 行,AI 客户端创建与调用) + errors.ts(8 行,错误格式化) + index.ts(5 行,聚合导出)。原 ai.ts 保留为向后兼容的重导出文件(9 行)"
    },
    {
      "id": "P1-6",
      "severity": "P1",
      "title": "三个 logger 重复实现 IP/Header 提取",
      "file": "src/shared/lib/{audit-logger,change-logger,login-logger}.ts, src/auth.ts",
      "problem": "audit-logger.ts / change-logger.ts / login-logger.ts / auth.ts 四处重复实现 IP/User-Agent 提取逻辑,且实现略有差异",
      "suggestion": "提取 shared/lib/http-utils.ts 统一 IP/UA 提取",
      "status": "resolved",
      "resolvedAt": "2026-06-18",
      "resolution": "shared/lib/http-utils.ts 新增 getUserAgent() 函数（与已有 resolveClientIp() 配套）；audit-logger.ts / change-logger.ts / login-logger.ts 改为从 @/shared/lib/http-utils 导入 resolveClientIp 和 getUserAgent，删除本地重复实现；auth.ts 已在 P1-3 中改用 resolveClientIp。四处实现统一，消除不一致风险（resolveClientIp 取 x-forwarded-for 第一段，更准确）"
    },
    {
      "id": "P2-20",
      "severity": "P2",
      "title": "homework/data-access.getDemoStudentUser 使用 auth() 而非 auth-guard",
      "file": "src/modules/homework/data-access.ts",
      "problem": "getDemoStudentUser 直接调用 auth() 获取学生身份,绕过 shared/lib/auth-guard 的统一认证上下文,且导致 student 页面虚假依赖 homework 模块",
      "suggestion": "迁移到 users 模块 getCurrentStudentUser,使用 auth-guard.getAuthContext",
      "status": "resolved",
      "resolvedAt": "2026-06-18",
      "resolution": "getDemoStudentUser 从 homework 模块迁移至 users 模块 getCurrentStudentUser（通过 session + JOIN users/usersToRoles/roles 校验 student 角色）；6 个 student 页面（dashboard/assignments/assignments-[assignmentId]/courses/textbooks/textbooks-[id]/schedule）改用 users 模块，消除对 homework 的虚假依赖；student/elective 改用 getAuthContext()；homework 保留 re-export 向后兼容"
    }
  ],
  "routes": {
    "auth": {
      "/login": {
        "component": "LoginForm",
        "type": "client",
        "module": "auth"
      },
      "/register": {
        "component": "RegisterForm + registerAction",
        "type": "server",
        "module": "auth",
        "description": "注册页面（含未成年人信息保护、隐私政策/用户协议同意勾选）"
      },
      "/privacy": {
        "component": "PrivacyPage",
        "type": "server",
        "module": "auth",
        "description": "隐私政策页面（信息收集/使用/保护、用户权利、Cookie、未成年人保护条款、联系方式）"
      },
      "/terms": {
        "component": "TermsPage",
        "type": "server",
        "module": "auth",
        "description": "用户协议页面（服务说明、注册、行为规范、知识产权、免责、变更终止、法律适用）"
      }
    },
    "onboarding": {
      "/onboarding": {
        "component": "OnboardingPage + OnboardingStepper",
        "type": "server",
        "module": "onboarding",
        "method": "GET",
        "actions": [
          "getOnboardingStatusAction",
          "completeOnboardingAction"
        ],
        "dataAccess": [
          "onboarding/data-access.getOnboardingStatus",
          "onboarding/data-access.updateUserProfile",
          "onboarding/data-access.bindParentToChild",
          "onboarding/data-access.resolveDefaultPathByRoles",
          "classes/data-access.enrollStudentByInvitationCode",
          "classes/data-access.enrollTeacherByInvitationCode"
        ],
        "auth": "required",
        "description": "首次登录引导（独立路由 + middleware 重定向 + Server Action + Zod + 事务）"
      }
    },
    "admin": {
      "_layout": {
        "file": "app/(dashboard)/admin/layout.tsx",
        "type": "server",
        "purpose": "admin 路由组统一权限守卫：调用 getAuthContext() 校验已登录，未登录抛 PermissionDeniedError；细粒度权限由各页面 requirePermission() 自行检查",
        "authGuard": "shared/lib/auth-guard.getAuthContext",
        "auth": "required"
      },
      "/admin/dashboard": {
        "component": "AdminDashboardView",
        "type": "server",
        "dataAccess": [
          "dashboard/data-access.getAdminDashboardData"
        ],
        "permission": "school:manage"
      },
      "/admin/school": {
        "component": "重定向",
        "type": "server",
        "redirect": "/admin/school/classes",
        "permission": "school:manage"
      },
      "/admin/school/schools": {
        "component": "SchoolsClient",
        "type": "client",
        "module": "school",
        "permission": "school:manage"
      },
      "/admin/school/grades": {
        "component": "GradesClient",
        "type": "client",
        "module": "school",
        "permission": "school:manage"
      },
      "/admin/school/grades/insights": {
        "component": "年级作业洞察 + SchoolWideSummaryCard",
        "type": "server",
        "dataAccess": [
          "classes/data-access.getGradeHomeworkInsights",
          "grades/data-access-analytics.getSchoolWideGradeSummary"
        ],
        "permission": "school:manage",
        "description": "v3-P2 更新：顶部新增 SchoolWideSummaryCard 全校成绩汇总卡片（4 个统计卡片 + 各年级对比表格）"
      },
      "/admin/school/departments": {
        "component": "DepartmentsClient",
        "type": "client",
        "module": "school",
        "permission": "school:manage"
      },
      "/admin/school/classes": {
        "component": "AdminClassesClient",
        "type": "client",
        "module": "classes",
        "permission": "school:manage"
      },
      "/admin/school/academic-year": {
        "component": "AcademicYearClient",
        "type": "client",
        "module": "school",
        "permission": "school:manage"
      },
      "/admin/audit-logs": {
        "component": "AuditLogView",
        "type": "server",
        "module": "audit",
        "dataAccess": [
          "audit/data-access.getAuditLogs",
          "audit/data-access.getAuditModuleOptions"
        ],
        "permission": "audit_log:read"
      },
      "/admin/audit-logs/login-logs": {
        "component": "LoginLogView",
        "type": "server",
        "module": "audit",
        "dataAccess": [
          "audit/data-access.getLoginLogs"
        ],
        "permission": "audit_log:read"
      },
      "/admin/announcements": {
        "component": "AdminAnnouncementsView",
        "type": "server",
        "module": "announcements",
        "dataAccess": [
          "announcements/data-access.getAnnouncements",
          "school/data-access.getGrades",
          "classes/data-access.getAdminClasses"
        ],
        "actions": [
          "createAnnouncementAction"
        ],
        "permission": "announcement:manage"
      },
      "/admin/announcements/[id]": {
        "component": "AnnouncementForm (edit)",
        "type": "server",
        "module": "announcements",
        "dataAccess": [
          "announcements/data-access.getAnnouncementById",
          "school/data-access.getGrades"
        ],
        "actions": [
          "updateAnnouncementAction"
        ],
        "permission": "announcement:manage"
      },
      "/admin/files": {
        "component": "AdminFilesView",
        "type": "server",
        "module": "files",
        "dataAccess": [
          "files/data-access.getAllFileAttachments"
        ],
        "permission": "file:read"
      },
      "/admin/course-plans": {
        "component": "CoursePlanList",
        "type": "client",
        "module": "course-plans",
        "dataAccess": [
          "course-plans/data-access.getCoursePlans"
        ],
        "permission": "course_plan:manage"
      },
      "/admin/course-plans/create": {
        "component": "CoursePlanForm (create)",
        "type": "client",
        "module": "course-plans",
        "actions": [
          "createCoursePlanAction"
        ],
        "dataAccess": [
          "classes/data-access.getAdminClasses",
          "course-plans/data-access.getSubjectOptions",
          "classes/data-access.getStaffOptions",
          "school/data-access.getAcademicYears"
        ],
        "permission": "course_plan:manage"
      },
      "/admin/course-plans/[id]": {
        "component": "CoursePlanDetail",
        "type": "client",
        "module": "course-plans",
        "dataAccess": [
          "course-plans/data-access.getCoursePlanById"
        ],
        "actions": [
          "deleteCoursePlanAction",
          "createCoursePlanItemAction",
          "updateCoursePlanItemAction",
          "deleteCoursePlanItemAction",
          "toggleCoursePlanItemCompletedAction"
        ],
        "permission": "course_plan:manage"
      },
      "/admin/course-plans/[id]/edit": {
        "component": "CoursePlanForm (edit)",
        "type": "client",
        "module": "course-plans",
        "actions": [
          "updateCoursePlanAction"
        ],
        "dataAccess": [
          "course-plans/data-access.getCoursePlanById",
          "classes/data-access.getAdminClasses",
          "course-plans/data-access.getSubjectOptions",
          "classes/data-access.getStaffOptions",
          "school/data-access.getAcademicYears"
        ],
        "permission": "course_plan:manage"
      },
      "/admin/attendance": {
        "component": "AttendanceRecordList",
        "type": "server",
        "module": "attendance",
        "dataAccess": [
          "attendance/data-access.getAttendanceRecords (scope=all)",
          "classes/data-access.getAdminClasses"
        ],
        "permission": "attendance:manage",
        "description": "管理员考勤总览（权限：requirePermission(ATTENDANCE_MANAGE)）"
      },
      "/admin/users/import": {
        "component": "UserImportPage (含 UserImportDialog)",
        "type": "server",
        "module": "users",
        "actions": [
          "users/actions.downloadUserTemplateAction",
          "users/actions.importUsersAction"
        ],
        "permission": "user:manage",
        "description": "用户批量导入页面（说明卡片+字段文档表+导入对话框；权限：requirePermission(USER_MANAGE)）"
      },
      "/admin/users": {
        "component": "AdminUsersPage (含 AdminUsersView)",
        "type": "server",
        "module": "users",
        "dataAccess": [
          "users/data-access.getAdminUsers",
          "users/data-access.getAdminUserRoles"
        ],
        "actions": [
          "users/actions.updateUserRoleAction",
          "users/actions.deleteUserAction"
        ],
        "permission": "user:manage",
        "description": "管理员用户列表页面（搜索+角色筛选+分页表格+删除；权限：requirePermission(USER_MANAGE)）"
      },
      "/admin/scheduling/rules": {
        "component": "SchedulingRulesForm",
        "type": "server",
        "module": "scheduling",
        "dataAccess": [
          "scheduling/actions.getAdminClassesForScheduling",
          "scheduling/actions.getSchedulingRules"
        ],
        "actions": [
          "saveSchedulingRulesAction"
        ],
        "permission": "schedule:adjust",
        "description": "排课规则配置页面（权限：requirePermission(SCHEDULE_ADJUST)）"
      },
      "/admin/scheduling/auto": {
        "component": "AutoSchedulePanel + AutoScheduleResultView",
        "type": "server",
        "module": "scheduling",
        "dataAccess": [
          "scheduling/actions.getAdminClassesForScheduling"
        ],
        "actions": [
          "autoScheduleAction",
          "applyAutoScheduleAction"
        ],
        "permission": "schedule:auto",
        "description": "自动排课页面（预览+应用；权限：requirePermission(SCHEDULE_AUTO)）"
      },
      "/admin/scheduling/changes": {
        "component": "ScheduleChangeList + ScheduleConflictsView",
        "type": "server",
        "module": "scheduling",
        "dataAccess": [
          "scheduling/actions.getAdminClassesForScheduling",
          "scheduling/actions.getScheduleChanges"
        ],
        "actions": [
          "approveScheduleChangeAction",
          "rejectScheduleChangeAction",
          "getClassConflictsAction"
        ],
        "permission": "schedule:adjust",
        "description": "调课申请审批+冲突检测页面（权限：requirePermission(SCHEDULE_ADJUST)；审批操作需 SCHEDULE_AUTO）"
      },
      "/admin/elective": {
        "component": "ElectiveCourseList",
        "type": "server",
        "module": "elective",
        "dataAccess": [
          "elective/data-access.getElectiveCourses (scope=all)"
        ],
        "actions": [
          "deleteElectiveCourseAction",
          "openSelectionAction",
          "closeSelectionAction",
          "runLotteryAction"
        ],
        "permission": "elective:manage",
        "description": "管理员选修课程列表（权限：requirePermission(ELECTIVE_MANAGE)）"
      },
      "/admin/elective/create": {
        "component": "ElectiveCourseForm",
        "type": "client",
        "module": "elective",
        "actions": [
          "createElectiveCourseAction"
        ],
        "dataAccess": [
          "elective/data-access.getSubjectOptions"
        ],
        "permission": "elective:manage",
        "description": "创建选修课程（权限：requirePermission(ELECTIVE_MANAGE)）"
      },
      "/admin/elective/[id]/edit": {
        "component": "ElectiveCourseForm (edit)",
        "type": "client",
        "module": "elective",
        "actions": [
          "updateElectiveCourseAction"
        ],
        "dataAccess": [
          "elective/data-access.getElectiveCourseById",
          "elective/data-access.getSubjectOptions"
        ],
        "permission": "elective:manage",
        "description": "编辑选修课程（权限：requirePermission(ELECTIVE_MANAGE)）"
      },
      "/admin/settings": {
        "component": "AdminSettingsView",
        "type": "client",
        "module": "settings",
        "permission": "settings:admin",
        "description": "系统设置页面（学校信息/安全策略/文件上传/通知配置；权限：requirePermission(SETTINGS_ADMIN)）"
      },
      "/admin/error-book": {
        "component": "AdminErrorBookPage + ClassErrorOverview",
        "type": "server",
        "module": "error-book",
        "permission": "error_book:analytics_read",
        "description": "管理员错题分析页面（全校错题统计/薄弱知识点/学科分布/高频错题；权限：requirePermission(ERROR_BOOK_ANALYTICS_READ)）"
      }
    },
    "teacher": {
      "/teacher/dashboard": {
        "component": "TeacherDashboardView",
        "type": "server",
        "dataAccess": [
          "dashboard/data-access (teacher)",
          "homework/data-access.getTeacherGradeTrends",
          "classes/data-access.getTeacherClasses"
        ],
        "permission": "exam:read"
      },
      "/teacher/exams/all": {
        "component": "ExamDataTable",
        "type": "server",
        "dataAccess": [
          "exams/data-access.getExams"
        ],
        "permission": "exam:read"
      },
      "/teacher/exams/create": {
        "component": "ExamForm",
        "type": "client",
        "actions": [
          "createExamAction",
          "createAiExamAction",
          "previewAiExamAction"
        ],
        "permission": "exam:create"
      },
      "/teacher/questions": {
        "component": "QuestionDataTable",
        "type": "server",
        "dataAccess": [
          "questions/data-access.getQuestions"
        ],
        "permission": "question:read"
      },
      "/teacher/textbooks": {
        "component": "TextbookList",
        "type": "server",
        "dataAccess": [
          "textbooks/data-access.getTextbooks"
        ],
        "permission": "textbook:read"
      },
      "/teacher/textbooks/[id]": {
        "component": "TextbookReader",
        "type": "client",
        "dataAccess": [
          "textbooks/data-access.getTextbookById",
          "getChaptersByTextbookId",
          "getKnowledgePointsByTextbookId"
        ],
        "permission": "textbook:read"
      },
      "/teacher/classes/my": {
        "component": "ClassList",
        "type": "server",
        "dataAccess": [
          "classes/data-access.getTeacherClasses"
        ],
        "permission": "class:read"
      },
      "/teacher/classes/schedule": {
        "component": "ClassSchedule",
        "type": "server",
        "dataAccess": [
          "classes/data-access.getClassSchedule"
        ],
        "permission": "class:read"
      },
      "/teacher/classes/students": {
        "component": "ClassStudents",
        "type": "server",
        "dataAccess": [
          "classes/data-access.getClassStudents"
        ],
        "permission": "class:read"
      },
      "/teacher/classes": {
        "component": "重定向",
        "type": "server",
        "redirect": "/teacher/classes/my",
        "permission": "class:read"
      },
      "/teacher/classes/my/[id]": {
        "component": "班级详情",
        "type": "client",
        "module": "classes",
        "dataAccess": [
          "classes/data-access.getClassStudents",
          "classes/data-access.getClassSchedule",
          "classes/data-access.getClassHomeworkInsights"
        ],
        "permission": "class:read"
      },
      "/teacher/homework": {
        "component": "重定向",
        "type": "server",
        "redirect": "/teacher/homework/assignments",
        "permission": "homework:create"
      },
      "/teacher/homework/assignments": {
        "component": "作业列表",
        "type": "server",
        "module": "homework",
        "dataAccess": [
          "homework/data-access.getHomeworkAssignments"
        ],
        "permission": "homework:create"
      },
      "/teacher/homework/assignments/create": {
        "component": "HomeworkAssignmentForm",
        "type": "client",
        "module": "homework",
        "actions": [
          "createHomeworkAssignmentAction"
        ],
        "permission": "homework:create"
      },
      "/teacher/homework/assignments/[id]": {
        "component": "作业详情+错误分析",
        "type": "client",
        "module": "homework",
        "dataAccess": [
          "homework/data-access.getHomeworkAssignmentById",
          "homework/data-access.getHomeworkAssignmentAnalytics"
        ],
        "permission": "homework:create"
      },
      "/teacher/homework/assignments/[id]/submissions": {
        "component": "作业提交列表",
        "type": "server",
        "module": "homework",
        "dataAccess": [
          "homework/data-access.getHomeworkSubmissions"
        ],
        "permission": "homework:create"
      },
      "/teacher/homework/submissions": {
        "component": "批改列表",
        "type": "server",
        "module": "homework",
        "dataAccess": [
          "homework/data-access.getHomeworkAssignmentReviewList"
        ],
        "permission": "homework:grade"
      },
      "/teacher/homework/submissions/[submissionId]": {
        "component": "HomeworkGradingView",
        "type": "client",
        "module": "homework",
        "actions": [
          "gradeHomeworkSubmissionAction"
        ],
        "dataAccess": [
          "homework/data-access.getHomeworkSubmissionDetails"
        ],
        "permission": "homework:grade"
      },
      "/teacher/exams": {
        "component": "重定向",
        "type": "server",
        "redirect": "/teacher/exams/all",
        "permission": "exam:read"
      },
      "/teacher/exams/[id]/build": {
        "component": "ExamAssembly",
        "type": "client",
        "module": "exams",
        "permission": "exam:update"
      },
      "/teacher/exams/[id]/analytics": {
        "component": "ExamAnalyticsDashboard",
        "type": "server",
        "module": "exams",
        "dataAccess": [
          "exams/stats-service.getExamAnalytics"
        ],
        "permission": "exam:read",
        "description": "V3-8 新增：考试分析仪表盘页面（汇总卡片：应考人数/已批改份数/平均分/及格率 + 分数段分布 + 逐题分析表：错误率/难度等级）；权限：requirePermission(EXAM_READ)"
      },
      "/teacher/exams/grading": {
        "component": "重定向",
        "type": "server",
        "redirect": "/teacher/homework/submissions",
        "permission": "homework:grade"
      },
      "/teacher/exams/grading/[submissionId]": {
        "component": "重定向",
        "type": "server",
        "redirect": "/teacher/homework/submissions",
        "permission": "homework:grade"
      },
      "/teacher/grades": {
        "component": "成绩管理首页",
        "type": "server",
        "module": "grades",
        "dataAccess": [
          "grades/actions.getGradeRecordsAction"
        ],
        "permission": "grade_record:read"
      },
      "/teacher/grades/entry": {
        "component": "批量成绩录入",
        "type": "server",
        "module": "grades",
        "actions": [
          "grades/actions.batchCreateGradeRecordsAction",
          "grades/actions.createGradeRecordAction"
        ],
        "permission": "grade_record:manage"
      },
      "/teacher/grades/stats": {
        "component": "成绩统计报表",
        "type": "server",
        "module": "grades",
        "dataAccess": [
          "grades/actions.getClassGradeStatsAction",
          "grades/actions.getClassRankingAction"
        ],
        "permission": "grade_record:read"
      },
      "/teacher/grades/analytics": {
        "component": "成绩分析",
        "type": "server",
        "module": "grades",
        "dataAccess": [
          "grades/data-access-analytics.getGradeTrend",
          "grades/data-access-analytics.getClassComparison",
          "grades/data-access-analytics.getSubjectComparison",
          "grades/data-access-analytics.getGradeDistribution",
          "grades/data-access-analytics.getExamOptionsForGrades"
        ],
        "permission": "grade_record:read",
        "description": "v3-P2 更新：新增 semester/examId 搜索参数解析，调用 getExamOptionsForGrades 获取考试列表传给 AnalyticsFilters"
      },
      "/teacher/course-plans": {
        "component": "CoursePlanList (teacher)",
        "type": "client",
        "module": "course-plans",
        "dataAccess": [
          "course-plans/data-access.getCoursePlans (filtered by teacherId)"
        ],
        "permission": "course_plan:read"
      },
      "/teacher/course-plans/[id]": {
        "component": "CoursePlanDetail (teacher, read-only)",
        "type": "client",
        "module": "course-plans",
        "dataAccess": [
          "course-plans/data-access.getCoursePlanById"
        ],
        "permission": "course_plan:read"
      },
      "/teacher/attendance": {
        "component": "AttendanceRecordList + AttendanceFilters",
        "type": "server",
        "module": "attendance",
        "dataAccess": [
          "attendance/data-access.getAttendanceRecords",
          "classes/data-access.getTeacherClasses"
        ],
        "permission": "attendance:manage",
        "description": "教师考勤记录列表（权限：requirePermission(ATTENDANCE_MANAGE)）"
      },
      "/teacher/attendance/sheet": {
        "component": "AttendanceSheet",
        "type": "client",
        "module": "attendance",
        "actions": [
          "batchRecordAttendanceAction",
          "getClassAttendanceForDateAction"
        ],
        "dataAccess": [
          "attendance/data-access.getClassStudentsForAttendance"
        ],
        "permission": "attendance:manage",
        "description": "批量点名页面（权限：requirePermission(ATTENDANCE_MANAGE)）"
      },
      "/teacher/attendance/stats": {
        "component": "AttendanceStatsCard",
        "type": "server",
        "module": "attendance",
        "dataAccess": [
          "attendance/data-access-stats.getClassAttendanceStats",
          "classes/data-access.getTeacherClasses"
        ],
        "permission": "attendance:read",
        "description": "班级考勤统计（权限：requirePermission(ATTENDANCE_READ)）"
      },
      "/teacher/schedule-changes": {
        "component": "ScheduleChangeForm + ScheduleChangeList",
        "type": "server",
        "module": "scheduling",
        "dataAccess": [
          "scheduling/actions.getAdminClassesForScheduling",
          "scheduling/actions.getTeachersForScheduling",
          "scheduling/actions.getScheduleChanges (requesterId=ctx.userId)"
        ],
        "actions": [
          "requestScheduleChangeAction"
        ],
        "permission": "schedule:adjust",
        "description": "教师调课/代课申请页面（提交申请+查看本人申请列表；权限：requirePermission(SCHEDULE_ADJUST)；admin 角色查看全部申请）"
      },
      "/teacher/diagnostic": {
        "component": "ReportList",
        "type": "client",
        "module": "diagnostic",
        "dataAccess": [
          "diagnostic/data-access-reports.getDiagnosticReports"
        ],
        "actions": [
          "publishReportAction",
          "deleteReportAction"
        ],
        "permission": "diagnostic:read",
        "description": "学情诊断报告列表（reportType/status 过滤器；权限：requirePermission(DIAGNOSTIC_READ)；DataScope.class_members 仅查看自己报告；发布/删除操作需 DIAGNOSTIC_MANAGE）"
      },
      "/teacher/diagnostic/student/[studentId]": {
        "component": "StudentDiagnosticView",
        "type": "client",
        "module": "diagnostic",
        "dataAccess": [
          "diagnostic/data-access.getStudentMasterySummary",
          "diagnostic/data-access.getKnowledgePointStats (班级平均对比)",
          "diagnostic/data-access-reports.getDiagnosticReports"
        ],
        "actions": [
          "generateStudentReportAction"
        ],
        "permission": "diagnostic:read",
        "description": "学生学情诊断视图（概览卡片+雷达图+强项/弱项+生成报告[DIAGNOSTIC_MANAGE]+最新报告；权限：getAuthContext + DataScope 二次校验，class_members 仅自己，children 仅子女；v3-P2 更新：传入 practiceHrefBase=\"/teacher/questions\" 显示练习按钮）"
      },
      "/teacher/diagnostic/class/[classId]": {
        "component": "ClassDiagnosticView",
        "type": "client",
        "module": "diagnostic",
        "dataAccess": [
          "diagnostic/data-access.getClassMasterySummary"
        ],
        "actions": [
          "generateClassReportAction"
        ],
        "permission": "diagnostic:read",
        "description": "班级学情诊断视图（概览+知识点热力图+排名表+需重点关注学生+生成班级报告[DIAGNOSTIC_MANAGE]；权限：getAuthContext + DataScope 校验，class_taught 必须包含 classId，class_members/children notFound）"
      },
      "/teacher/elective": {
        "component": "ElectiveCourseList (teacher)",
        "type": "server",
        "module": "elective",
        "dataAccess": [
          "elective/data-access.getElectiveCourses (scope=class_taught/owned, currentUserId)"
        ],
        "actions": [
          "deleteElectiveCourseAction",
          "openSelectionAction",
          "closeSelectionAction",
          "runLotteryAction"
        ],
        "permission": "elective:manage",
        "description": "教师选修课程列表（权限：requirePermission(ELECTIVE_MANAGE)；DataScope.class_taught/owned 按 teacherId 过滤）"
      },
      "/teacher/lesson-plans": {
        "component": "LessonPlanList",
        "type": "server",
        "module": "lesson-preparation",
        "method": "GET",
        "dataAccess": [
          "lesson-preparation/data-access.getLessonPlans"
        ],
        "permission": "lesson_plan:read",
        "description": "教师课案列表（权限：requirePermission(LESSON_PLAN_READ)）"
      },
      "/teacher/lesson-plans/new": {
        "component": "LessonPlanEditor",
        "type": "client",
        "module": "lesson-preparation",
        "method": "GET",
        "actions": [
          "createLessonPlanAction",
          "getLessonPlanTemplatesAction",
          "getKnowledgePointOptionsAction"
        ],
        "permission": "lesson_plan:create",
        "description": "新建课案页面（权限：requirePermission(LESSON_PLAN_CREATE)）"
      },
      "/teacher/lesson-plans/[planId]/edit": {
        "component": "LessonPlanEditor",
        "type": "client",
        "module": "lesson-preparation",
        "method": "GET",
        "dataAccess": [
          "lesson-preparation/data-access.getLessonPlanById",
          "lesson-preparation/data-access-versions.getLessonPlanVersions"
        ],
        "actions": [
          "updateLessonPlanAction",
          "saveLessonPlanVersionAction",
          "revertLessonPlanVersionAction",
          "deleteLessonPlanAction",
          "duplicateLessonPlanAction",
          "saveAsTemplateAction",
          "suggestKnowledgePointsAction",
          "publishLessonPlanHomeworkAction"
        ],
        "permission": "lesson_plan:update",
        "permissionRead": "lesson_plan:read",
        "description": "编辑课案页面（权限：requirePermission(LESSON_PLAN_UPDATE)；只读访问需 LESSON_PLAN_READ）"
      },
      "/teacher/error-book": {
        "component": "TeacherErrorBookPage + ClassErrorOverview",
        "type": "server",
        "module": "error-book",
        "dataAccess": [
          "error-book/data-access.getStudentErrorBookSummaries",
          "error-book/data-access.getKnowledgePointWeakness",
          "error-book/data-access.getSubjectErrorDistribution",
          "error-book/data-access.getTopWrongQuestionsByStudentIds",
          "error-book/data-access.getStudentNameMap",
          "classes/data-access.getStudentIdsByClassIds"
        ],
        "permission": "error_book:analytics_read",
        "description": "教师错题分析页面（所教班级学生的错题统计/薄弱知识点/学科分布/高频错题；权限：requirePermission(ERROR_BOOK_ANALYTICS_READ)；DataScope.class_taught）"
      }
    },
    "student": {
      "/student/dashboard": {
        "component": "StudentDashboardView",
        "type": "server",
        "dataAccess": [
          "users/data-access.getCurrentStudentUser",
          "classes/data-access.getStudentClasses",
          "classes/data-access.getStudentSchedule",
          "homework/data-access.getStudentHomeworkAssignments",
          "homework/data-access.getStudentDashboardGrades"
        ],
        "permission": "homework:submit"
      },
      "/student/learning/assignments": {
        "component": "学生作业列表",
        "type": "server",
        "module": "homework",
        "dataAccess": [
          "users/data-access.getCurrentStudentUser",
          "homework/data-access.getStudentHomeworkAssignments"
        ],
        "permission": "homework:submit"
      },
      "/student/learning/assignments/[assignmentId]": {
        "component": "学生作答/复习",
        "type": "server",
        "module": "homework",
        "actions": [
          "startHomeworkSubmissionAction",
          "saveHomeworkAnswerAction",
          "submitHomeworkAction"
        ],
        "dataAccess": [
          "users/data-access.getCurrentStudentUser",
          "homework/data-access.getStudentHomeworkTakeData"
        ],
        "permission": "homework:submit"
      },
      "/student/learning/assignments/[assignmentId]/result": {
        "component": "HomeworkSubmissionResult",
        "type": "server",
        "module": "homework",
        "dataAccess": [
          "users/data-access.getCurrentStudentUser",
          "homework/data-access.getStudentSubmissionResult"
        ],
        "permission": "homework:submit",
        "description": "V3-9 新增：学生提交后即时反馈页（分数汇总+对错分布+错题预览）；homework-take-view.tsx 提交后 router.push 跳转至此页；权限：requirePermission(HOMEWORK_SUBMIT)"
      },
      "/student/learning/courses": {
        "component": "StudentCoursesView",
        "type": "server",
        "dataAccess": [
          "users/data-access.getCurrentStudentUser",
          "classes/data-access.getStudentClasses"
        ],
        "permission": "homework:submit"
      },
      "/student/learning/textbooks": {
        "component": "学生教材列表(只读)",
        "type": "server",
        "module": "textbooks",
        "dataAccess": [
          "users/data-access.getCurrentStudentUser",
          "textbooks/data-access.getTextbooks"
        ],
        "permission": "textbook:read"
      },
      "/student/learning/textbooks/[id]": {
        "component": "学生教材阅读(只读)",
        "type": "server",
        "module": "textbooks",
        "dataAccess": [
          "users/data-access.getCurrentStudentUser",
          "textbooks/data-access.getTextbookById",
          "textbooks/data-access.getChaptersByTextbookId",
          "textbooks/data-access.getKnowledgePointsByTextbookId"
        ],
        "permission": "textbook:read"
      },
      "/student/schedule": {
        "component": "学生课表",
        "type": "server",
        "module": "classes",
        "dataAccess": [
          "users/data-access.getCurrentStudentUser",
          "classes/data-access.getStudentClasses",
          "classes/data-access.getStudentSchedule"
        ],
        "permission": "homework:submit"
      },
      "/student/grades": {
        "component": "我的成绩",
        "type": "server",
        "module": "grades",
        "dataAccess": [
          "grades/data-access.getStudentGradeSummary"
        ],
        "permission": "grade_record:read"
      },
      "/student/attendance": {
        "component": "StudentAttendanceView",
        "type": "server",
        "module": "attendance",
        "dataAccess": [
          "attendance/data-access-stats.getStudentAttendanceSummary"
        ],
        "permission": "attendance:read",
        "description": "学生考勤视图（统计卡片 + 最近记录；权限：requirePermission(ATTENDANCE_READ)，DataScope.class_members 仅查自己）"
      },
      "/student/diagnostic": {
        "component": "StudentDiagnosticView",
        "type": "server",
        "module": "diagnostic",
        "dataAccess": [
          "diagnostic/data-access.getStudentMasterySummary (ctx.userId)",
          "diagnostic/data-access-reports.getDiagnosticReports (studentId=ctx.userId)"
        ],
        "permission": "diagnostic:read",
        "description": "学生本人学情诊断视图（概览+雷达图+强项/弱项+最新报告；权限：requirePermission(DIAGNOSTIC_READ)，DataScope.class_members 仅查自己）"
      },
      "/student/elective": {
        "component": "StudentSelectionView",
        "type": "server",
        "module": "elective",
        "dataAccess": [
          "elective/data-access-selections.getAvailableCoursesForStudent",
          "elective/data-access-selections.getStudentSelections"
        ],
        "actions": [
          "selectCourseAction",
          "dropCourseAction"
        ],
        "permission": "elective:select",
        "description": "学生选课页面（可选课程列表 + 我的选课记录；权限：requirePermission(ELECTIVE_SELECT)）"
      },
      "/student/error-book": {
        "component": "StudentErrorBookPage + ErrorBookStatsCards + ErrorBookFilters + ErrorBookList + AddErrorBookDialog",
        "type": "server",
        "module": "error-book",
        "dataAccess": [
          "error-book/data-access.getErrorBookStats (ctx.userId)",
          "error-book/data-access.getErrorBookItems (ctx.userId)"
        ],
        "actions": [
          "createErrorBookItemAction",
          "updateErrorBookNoteAction",
          "reviewErrorBookItemAction",
          "deleteErrorBookItemAction",
          "archiveErrorBookItemAction",
          "getErrorBookItemDetailAction"
        ],
        "permission": "error_book:read",
        "description": "学生错题本页面（统计卡片/筛选/列表/手动添加/详情复习；权限：requirePermission(ERROR_BOOK_READ)；DataScope.owned 仅查自己）"
      }
    },
    "management": {
      "/management/grade": {
        "component": "GradeManagementRedirect",
        "type": "server",
        "module": "grades",
        "redirect": "/management/grade/classes",
        "permission": "grade:manage"
      },
      "/management/grade/classes": {
        "component": "GradeClassesClient",
        "type": "client",
        "module": "classes",
        "permission": "grade:manage"
      },
      "/management/grade/insights": {
        "component": "年级作业洞察",
        "type": "server",
        "dataAccess": [
          "classes/data-access.getGradeHomeworkInsights"
        ],
        "permission": "grade:manage"
      }
    },
    "parent": {
      "/parent/dashboard": {
        "component": "ParentDashboard",
        "type": "server",
        "module": "parent",
        "dataAccess": [
          "parent/data-access.getParentDashboardData"
        ],
        "permission": "auth_required",
        "description": "家长仪表盘首页（问候语 + 子女卡片网格；权限：requireAuth()）"
      },
      "/parent/children/[studentId]": {
        "component": "ChildDetailHeader + ChildDetailPanel (Tab 布局 + SiblingSwitcher)",
        "type": "server",
        "module": "parent",
        "dataAccess": [
          "parent/data-access.getChildDashboardData",
          "parent/data-access.getChildNameList"
        ],
        "permission": "auth_required",
        "description": "v4 升级：子女详情页改为 6-Tab 布局（overview/homework/grades/schedule/attendance/diagnostic），支持 ?tab= URL 参数与多子女切换；权限：requireAuth() + 二次校验 ctx.dataScope.childrenIds 包含 studentId"
      },
      "/parent/grades": {
        "component": "子女成绩",
        "type": "server",
        "module": "grades",
        "dataAccess": [
          "grades/data-access.getStudentGradeSummary",
          "grades/data-access-analytics.getClassAverageTrend"
        ],
        "permission": "grade_record:read",
        "description": "家长成绩视图（按 DataScope.children 过滤；v4 新增 ParentExportButton 占位；v3-P2 更新：为每个子女并行查询 getClassAverageTrend，渲染 GradeTrendCard；v3-P3-4 更新：GradeTrendCard 新增日期范围选择器，通过 nuqs trendRange URL 参数持久化）"
      },
      "/parent/diagnostic": {
        "component": "子女学情诊断",
        "type": "server",
        "module": "diagnostic",
        "dataAccess": [
          "diagnostic/data-access.getStudentMasterySummary",
          "diagnostic/data-access-reports.getDiagnosticReports"
        ],
        "permission": "diagnostic:read",
        "description": "P2-5 新增：家长查看多子女学情诊断（按 DataScope.children 遍历，复用 StudentDiagnosticView 组件；含 loading.tsx + error.tsx；v3-P2 更新：传入 practiceHrefBase={null} 隐藏练习按钮）"
      },
      "/parent/attendance": {
        "component": "StudentAttendanceView (per child) + ParentAttendanceWarning",
        "type": "server",
        "module": "attendance",
        "dataAccess": [
          "parent/data-access.getChildren",
          "attendance/data-access-stats.getStudentAttendanceSummary"
        ],
        "permission": "attendance:read",
        "description": "v4 升级：家长考勤视图新增 ParentAttendanceWarning 异常预警横幅（absent>=3 high、absent>=1 medium、late>=3 medium、presentRate<90 high）；权限：requirePermission(ATTENDANCE_READ)，DataScope.children 仅查子女"
      },
      "/parent/leave": {
        "component": "LeaveRequestPage",
        "type": "server",
        "module": "parent",
        "permission": "auth_required",
        "description": "v4 新增：请假申请占位页（功能开发中，提供线下联系方式入口）"
      },
      "/parent/error-book": {
        "component": "ParentErrorBookPage + ErrorBookStatsCards + TopWrongQuestions",
        "type": "server",
        "module": "error-book",
        "dataAccess": [
          "error-book/data-access.getErrorBookStats (per child)",
          "error-book/data-access.getTopWrongQuestionsByStudentIds",
          "error-book/data-access.getKnowledgePointWeakness",
          "error-book/data-access.getStudentNameMap"
        ],
        "permission": "error_book:read",
        "description": "家长错题本页面（子女错题统计/薄弱知识点/高频错题；权限：requirePermission(ERROR_BOOK_READ)；DataScope.children 仅查子女）"
      }
    },
    "root": {
      "/": {
        "component": "重定向",
        "type": "server",
        "redirect": "/dashboard"
      }
    },
    "shared": {
      "/dashboard": {
        "component": "角色路由分发",
        "type": "server",
        "redirect": "按permissions判断→/admin|/teacher|/student|/parent"
      },
      "/profile": {
        "component": "ProfilePage",
        "type": "server",
        "permission": "auth_required",
        "description": "个人资料页面（展示头像 Avatar + Personal Information + Account Information + 学生/教师概览；头像从 userProfile.image 获取，无头像时显示用户名首字母 fallback）"
      },
      "/settings": {
        "component": "SettingsPage",
        "type": "server",
        "permission": "auth_required",
        "dataAccess": [
          "messaging/notification-preferences.getNotificationPreferences (re-export from notifications/preferences.ts)"
        ],
        "description": "设置页面（按角色分发 AdminSettingsView/TeacherSettingsView/StudentSettingsView/ParentSettingsView；含 General/Notifications/Appearance/Security/AI tab，Tab 通过 URL ?tab= 参数持久化，AI tab 需 AI_CONFIGURE 权限，Notifications 渲染 NotificationPreferencesForm，登出按钮 AlertDialog 二次确认）"
      },
      "/announcements": {
        "component": "AnnouncementList (published only, audience-filtered)",
        "type": "server",
        "module": "announcements",
        "dataAccess": [
          "announcements/data-access.getAnnouncements (status=published, audience={gradeId, classId})",
          "classes/data-access.getStudentActiveClassId",
          "classes/data-access.getStudentActiveGradeId",
          "classes/data-access.getClassGradeId"
        ],
        "permission": "announcement:read",
        "description": "用户端公告列表（根据用户身份过滤受众：school 全可见 / grade 按年级 / class 按班级）"
      },
      "/announcements/[id]": {
        "component": "AnnouncementDetail (read-only, canManage=false)",
        "type": "server",
        "module": "announcements",
        "dataAccess": [
          "announcements/data-access.getAnnouncementById"
        ],
        "permission": "announcement:read",
        "description": "用户端公告详情页（只读模式，无编辑/删除按钮）"
      }
    },
    "messages": {
      "/messages": {
        "component": "MessageList + NotificationList",
        "type": "server",
        "module": "messaging",
        "dataAccess": [
          "messaging/data-access.getMessages",
          "messaging/data-access.getNotifications"
        ],
        "permission": "message:read",
        "description": "消息首页（收件箱/已发送列表 + 通知列表；权限：requirePermission(MESSAGE_READ)）"
      },
      "/messages/[id]": {
        "component": "MessageDetail",
        "type": "server",
        "module": "messaging",
        "dataAccess": [
          "messaging/data-access.getMessageById",
          "messaging/data-access.getMessageThread"
        ],
        "actions": [
          "markMessageAsReadAction (自动已读)"
        ],
        "permission": "message:read",
        "description": "消息详情（含回复线程；权限：requirePermission(MESSAGE_READ)）"
      },
      "/messages/compose": {
        "component": "MessageCompose",
        "type": "server",
        "module": "messaging",
        "dataAccess": [
          "messaging/data-access.getRecipients"
        ],
        "permission": "message:send",
        "description": "写消息页面（支持 reply 模式 via searchParams: receiverId, subject, parentMessageId；权限：requirePermission(MESSAGE_SEND)）"
      }
    }
  },
  "apiRoutes": {
    "/api/auth/[...nextauth]": {
      "methods": [
        "GET",
        "POST"
      ],
      "handler": "auth.handlers",
      "auth": "public"
    },
    "/api/ai/chat": {
      "methods": [
        "POST"
      ],
      "handler": "createAiChatCompletion",
      "auth": "AI_CHAT",
      "validation": "parseAiChatPayload (Zod)"
    },
    "/api/ai/chat/stream": {
      "methods": [
        "POST"
      ],
      "handler": "createAiChatCompletionStream (SSE AsyncGenerator → ReadableStream)",
      "auth": "AI_CHAT",
      "validation": "parseAiChatPayload + filterUserInput (per message)",
      "protocol": "Server-Sent Events",
      "events": [
        "token — 流式 token 内容",
        "error — 错误信息",
        "filtered — 内容被安全过滤",
        "[DONE] — 流结束"
      ],
      "safety": [
        "checkDailyLimit — 调用前校验每日上限",
        "filterUserInput — 输入过滤",
        "filterAiOutput — 输出过滤（学生场景阻断直接答案）",
        "incrementDailyUsage — 调用后计数",
        "trackEvent — 埋点统计"
      ],
      "studentMode": "强制苏格拉底式引导系统提示"
    },
    "/api/notifications/stream": {
      "methods": [
        "GET"
      ],
      "handler": "通知实时推送 SSE 端点（ReadableStream + setInterval 定时推送）",
      "auth": "MESSAGE_READ",
      "validation": "requirePermission 权限校验",
      "protocol": "Server-Sent Events",
      "events": [
        "update — 未读数 + 最新通知列表（连接建立时立即推送，之后每 15 秒推送）",
        "error — 权限拒绝或内部错误",
        "[DONE] — 连接超时（5 分钟）自动关闭"
      ],
      "pushStrategy": "连接建立立即推送 + 15 秒间隔定时推送 + 5 分钟超时自动关闭",
      "module": "notifications",
      "deps": [
        "requirePermission",
        "data-access.getUnreadNotificationCount",
        "data-access.getNotifications"
      ],
      "usedBy": [
        "hooks/use-notification-stream.ts"
      ]
    },
    "/api/onboarding/complete": {
      "methods": [
        "POST"
      ],
      "handler": "onboarding complete（已废弃，迁移至 completeOnboardingAction Server Action）",
      "auth": "required",
      "validation": "Zod schema",
      "deprecated": true,
      "deprecatedReason": "存在角色自选越权、教师覆盖任课、无事务等漏洞，已迁移至 modules/onboarding/actions.ts"
    },
    "/api/onboarding/status": {
      "methods": [
        "GET"
      ],
      "handler": "onboarding status（已废弃，迁移至 getOnboardingStatusAction Server Action）",
      "auth": "required",
      "deprecated": true,
      "deprecatedReason": "引导流程改为独立路由 /onboarding + middleware 重定向，不再需要客户端拉取状态"
    },
    "/api/upload": {
      "methods": [
        "POST"
      ],
      "handler": "文件上传 (multipart/form-data)",
      "auth": "requireAuth",
      "module": "files",
      "validation": "isAllowedMimeType + MAX_FILE_SIZE (10MB)",
      "description": "保存文件到 public/uploads/YYYY-MM/cuid.ext，写入 fileAttachments 表，返回 FileUploadResult"
    },
    "/api/files/[id]": {
      "methods": [
        "GET",
        "DELETE"
      ],
      "handler": "文件元数据查询/删除",
      "auth": "GET: requireAuth, DELETE: requirePermission(FILE_DELETE)",
      "module": "files",
      "description": "GET 返回文件元数据；DELETE 删除 DB 记录并 unlink 磁盘文件（静默失败）"
    },
    "/api/files/batch-delete": {
      "methods": [
        "POST"
      ],
      "handler": "批量删除文件",
      "auth": "requirePermission(FILE_DELETE)",
      "module": "files",
      "validation": "JSON body { ids: string[] }，空数组返回 400",
      "description": "先通过 getFileAttachmentsByIds 查出文件记录，并行调用 storageProvider.delete 删除磁盘文件（静默失败），再调用 deleteFileAttachments 删除 DB 记录（失败时回退到逐条删除）；响应 { success, message, deletedCount, failedIds }"
    },
    "/api/search": {
      "methods": [
        "GET"
      ],
      "handler": "全局全文检索",
      "auth": "requireAuth",
      "module": "shared.db (questions/textbooks/exams/announcements)",
      "validation": "query params: q (关键词), type=all|question|textbook|exam|announcement, page=1, pageSize=10 (上限 50)",
      "description": "并行查询 questions/textbooks/exams/announcements（公告仅 status=published），按 createdAt 降序排序后分页；question content 字段 CAST AS CHAR 模糊匹配；返回 { success, query, type, results: [{ id, title, snippet, type, href, createdAt }], total, page, pageSize }"
    },
    "/api/export": {
      "methods": [
        "POST"
      ],
      "handler": "Excel 导出（grades/users/attendance/electiveCourses/courseSelections/audit/login/dataChange）",
      "auth": "requireAuth",
      "module": "shared.lib.excel + users/grades/attendance/elective/audit",
      "validation": "JSON body { type, params }",
      "description": "按 type 分发：grades→exportGradeRecordsToExcel, users→exportUsersToExcel, attendance→exportAttendanceRecordsToExcel(需 ATTENDANCE_READ), electiveCourses→exportElectiveCoursesToExcel(需 ELECTIVE_READ), courseSelections→exportCourseSelectionsToExcel(需 ELECTIVE_MANAGE), audit/login/dataChange→audit/actions 导出(需 AUDIT_LOG_READ)。返回 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 二进制流"
    },
    "/api/import": {
      "methods": [
        "POST"
      ],
      "handler": "Excel 解析预览（不写 DB）",
      "auth": "requirePermission(USER_MANAGE)",
      "module": "shared.lib.excel",
      "validation": "multipart/form-data file，限 .xlsx/.xls，10MB 上限",
      "description": "接收 Excel 文件，调用 parseExcel 返回 sheets 预览数据（实际导入由 users/actions.importUsersAction 完成）"
    }
  },
  "devops": {
    "ci": {
      "configFile": ".gitea/workflows/ci.yml",
      "triggers": [
        "push to main",
        "pull_request to main",
        "schedule cron 0 2 * * *"
      ],
      "jobs": {
        "build-deploy": {
          "runsOn": "CDCD",
          "container": "dockerreg.eazygame.cn/node-with-docker:22",
          "trigger": "push/PR to main",
          "steps": [
            "checkout",
            "cache npm",
            "configure npm proxy",
            "npm ci",
            "lint",
            "typecheck",
            "install playwright chromium",
            "integration tests",
            "e2e tests",
            "cache next.js build",
            "build",
            "prepare standalone",
            "deploy to docker"
          ]
        },
        "security-scan": {
          "runsOn": "ubuntu-latest",
          "trigger": "push/PR to main",
          "needs": "build-deploy",
          "continueOnError": true,
          "steps": [
            "checkout",
            "setup node 20",
            "npm ci",
            "npm audit --audit-level=moderate + 生成 audit-report.json (continue-on-error)",
            "Snyk scan --severity-threshold=high --sarif-file-output=snyk.sarif (env SNYK_TOKEN, continue-on-error)",
            "Trivy fs scan json+table (continue-on-error)",
            "OWASP ZAP baseline scan target=NEXTAUTH_URL||localhost:8015 cmd_options='-a -j' (continue-on-error)",
            "upload security-reports artifact (audit-report.json, trivy-fs-report.json, snyk.sarif)"
          ]
        },
        "scheduled-backup": {
          "runsOn": "ubuntu-latest",
          "trigger": "schedule cron 0 2 * * *",
          "condition": "github.event_name == 'schedule'",
          "steps": [
            "checkout",
            "run scripts/backup-db.sh (env DATABASE_URL, BACKUP_DIR)",
            "run scripts/backup-verify.sh (校验备份完整性)",
            "run scripts/backup-offsite-sync.sh (异地同步, env BACKUP_OFFSITE_*, 失败不阻塞)",
            "upload backups/ artifact (retention 30 days)"
          ]
        },
        "backup-verify": {
          "runsOn": "ubuntu-latest",
          "trigger": "schedule",
          "condition": "github.event_name == 'schedule'",
          "needs": "scheduled-backup",
          "steps": [
            "checkout",
            "download db-backup artifact",
            "run scripts/backup-verify.sh (独立校验)",
            "run scripts/health-check.sh > health-report.json",
            "upload backup-verify-report artifact (backups/, health-report.json, retention 7 days)"
          ]
        },
        "weekly-dr-drill": {
          "runsOn": "ubuntu-latest",
          "trigger": "schedule (每周触发, github.run_attempt % 7 == 0)",
          "condition": "github.event_name == 'schedule' && github.run_attempt % 7 == 0",
          "needs": "backup-verify",
          "steps": [
            "checkout",
            "run scripts/dr-drill.sh (env DATABASE_URL, DR_DRILL_TEST_DB=next_edu_dr_drill)",
            "upload dr-drill-report artifact (docs/dr/reports/, retention 90 days)"
          ]
        }
      }
    },
    "drDrillWorkflow": {
      "configFile": ".gitea/workflows/dr-drill.yml",
      "triggers": [
        "schedule cron 0 4 * * 1 (每周一凌晨 4 点)",
        "workflow_dispatch (inputs: backup_file, no_cleanup)"
      ],
      "job": "dr-drill",
      "runsOn": "ubuntu-latest",
      "timeoutMinutes": 30,
      "steps": [
        "checkout",
        "install mysql-client",
        "prepare backup directory (mkdir backups docs/dr/reports)",
        "download db-backup artifact (continue-on-error) 或现场执行 backup-db.sh",
        "run scripts/dr-drill.sh (支持 --backup/--no-cleanup 参数)",
        "upload dr-drill-report-${{ github.run_id }} artifact (docs/dr/reports/, retention 90 days)",
        "on failure: webhook 通知运维团队 (DR_NOTIFICATION_WEBHOOK)"
      ]
    },
    "securityWorkflow": {
      "configFile": ".gitea/workflows/security.yml",
      "triggers": [
        "schedule cron 0 3 * * 1 (每周一凌晨 3 点)",
        "workflow_dispatch (inputs: target_url, skip_dast)"
      ],
      "job": "deep-security-scan",
      "runsOn": "ubuntu-latest",
      "continueOnError": true,
      "steps": [
        "checkout",
        "setup node 20",
        "npm ci",
        "npm audit + 生成 audit-report.json (依赖扫描)",
        "Snyk scan --severity-threshold=medium --sarif-file-output=snyk.sarif (env SNYK_TOKEN, 深度依赖+静态分析)",
        "Trivy fs scan json+table (文件系统扫描, trivy-fs-report.json)",
        "Build Next.js standalone + docker build nextjs-app:scan + Trivy image scan (容器镜像扫描, trivy-image-report.json)",
        "OWASP ZAP baseline scan (DAST, target=inputs.target_url||NEXTAUTH_URL||localhost:8015, 可通过 skip_dast 跳过)",
        "Generate security-summary.md (jq 汇总各报告漏洞计数)",
        "upload security-reports-full artifact (audit-report.json, trivy-fs-report.json, trivy-image-report.json, snyk.sarif, security-summary.md)"
      ],
      "configFiles": {
        "suppressions": ".gitea/suppressions.json (Snyk 漏洞抑制, 每条含 id/package/severity/reason/expires/owner)",
        "trivyignore": ".trivyignore (Trivy CVE 忽略列表, 每行一个 CVE 带注释)"
      }
    },
    "scripts": {
      "scripts/audit.sh": {
        "type": "bash",
        "purpose": "依赖安全审计，运行 npm audit --audit-level=moderate，失败时生成 audit-report.json",
        "platform": "Linux/macOS"
      },
      "scripts/audit.ps1": {
        "type": "powershell",
        "purpose": "依赖安全审计（Windows 版本）",
        "platform": "Windows"
      },
      "scripts/backup-db.sh": {
        "type": "bash",
        "purpose": "MySQL 数据库备份，从 DATABASE_URL 解析连接信息，gzip 压缩，保留 RETENTION_DAYS 天（默认 30）",
        "env": [
          "DATABASE_URL",
          "BACKUP_DIR",
          "RETENTION_DAYS"
        ],
        "output": "${BACKUP_DIR}/db_backup_${TIMESTAMP}.sql.gz"
      },
      "scripts/restore-db.sh": {
        "type": "bash",
        "purpose": "MySQL 数据库恢复，从指定备份文件恢复",
        "env": [
          "DATABASE_URL"
        ],
        "usage": "./restore-db.sh <backup_file>"
      },
      "scripts/test-backup.sh": {
        "type": "bash",
        "purpose": "备份流程测试，执行一次备份并验证最新备份文件"
      },
      "scripts/backup-verify.sh": {
        "type": "bash",
        "purpose": "备份完整性校验：检查文件存在/大小/gzip 完整性/SQL 内容结构/SQL 语法（可选,需 DATABASE_URL）",
        "env": [
          "BACKUP_DIR",
          "DATABASE_URL",
          "BACKUP_VERIFY_MIN_SIZE"
        ],
        "exitCodes": {
          "0": "校验通过",
          "1": "校验失败"
        },
        "options": [
          "--min-size BYTES",
          "--no-sql-check",
          "--help"
        ]
      },
      "scripts/backup-offsite-sync.sh": {
        "type": "bash",
        "purpose": "异地备份同步：支持 S3/OSS/NFS 后端,同步后校验文件数量,清理远程过期备份（保留 90 天）",
        "env": [
          "BACKUP_DIR",
          "BACKUP_OFFSITE_BACKEND",
          "BACKUP_OFFSITE_REMOTE",
          "BACKUP_OFFSITE_BUCKET",
          "BACKUP_OFFSITE_ACCESS_KEY",
          "BACKUP_OFFSITE_SECRET_KEY",
          "BACKUP_OFFSITE_REGION",
          "BACKUP_OFFSITE_RETENTION_DAYS"
        ],
        "tools": [
          "aws-cli (s3)",
          "rclone (s3/oss)",
          "ossutil (oss)",
          "rsync (nfs)"
        ],
        "exitCodes": {
          "0": "同步成功",
          "1": "同步失败"
        },
        "options": [
          "--backend TYPE",
          "--no-cleanup",
          "--no-verify",
          "--help"
        ]
      },
      "scripts/dr-drill.sh": {
        "type": "bash",
        "purpose": "灾备演练：创建测试库→从备份恢复→数据完整性检查→冒烟测试→清理→生成报告到 docs/dr/reports/",
        "env": [
          "DATABASE_URL",
          "BACKUP_DIR",
          "DR_DRILL_TEST_DB",
          "DR_DRILL_REPORT_DIR"
        ],
        "exitCodes": {
          "0": "演练成功",
          "1": "演练失败"
        },
        "options": [
          "--backup FILE",
          "--test-db NAME",
          "--no-cleanup",
          "--report-dir DIR",
          "--help"
        ]
      },
      "scripts/dr-drill.ps1": {
        "type": "powershell",
        "purpose": "灾备演练（Windows PowerShell 5.1+ 版本），功能同 Bash 版本",
        "env": [
          "DATABASE_URL",
          "BACKUP_DIR",
          "DR_DRILL_TEST_DB",
          "DR_DRILL_REPORT_DIR"
        ],
        "platform": "Windows",
        "options": [
          "-BackupFile FILE",
          "-TestDb NAME",
          "-NoCleanup",
          "-ReportDir DIR",
          "-Help"
        ]
      },
      "scripts/failover.sh": {
        "type": "bash",
        "purpose": "故障切换：检测主库健康→提升备库→更新应用配置→重启应用→验证切换",
        "env": [
          "DATABASE_URL",
          "DATABASE_URL_STANDBY",
          "FAILOVER_APP_URL",
          "FAILOVER_APP_NAME",
          "FAILOVER_CONFIG_FILE",
          "FAILOVER_LOG_FILE"
        ],
        "exitCodes": {
          "0": "切换成功",
          "1": "切换失败"
        },
        "options": [
          "--auto",
          "--primary URL",
          "--standby URL",
          "--app-url URL",
          "--no-restart",
          "--dry-run",
          "--help"
        ]
      },
      "scripts/health-check.sh": {
        "type": "bash",
        "purpose": "健康检查：检查应用 HTTP/数据库连接/磁盘空间/备份新鲜度,输出 JSON 报告",
        "env": [
          "DATABASE_URL",
          "HEALTH_CHECK_URL",
          "BACKUP_DIR",
          "HEALTH_CHECK_DISK_THRESHOLD",
          "HEALTH_CHECK_BACKUP_MAX_AGE"
        ],
        "exitCodes": {
          "0": "健康",
          "1": "异常"
        },
        "options": [
          "--app-url URL",
          "--no-app",
          "--no-db",
          "--no-disk",
          "--no-backup",
          "--disk-threshold PCT",
          "--backup-max-age HRS",
          "--help"
        ]
      }
    },
    "packageJsonScripts": {
      "audit": "npm audit --audit-level=moderate",
      "audit:report": "npm audit --json > audit-report.json",
      "security:audit": "npm audit --audit-level=moderate",
      "security:scan": "bash scripts/security-scan.sh",
      "backup": "bash scripts/backup-db.sh",
      "restore": "bash scripts/restore-db.sh",
      "dr:backup-verify": "bash scripts/backup-verify.sh",
      "dr:offsite-sync": "bash scripts/backup-offsite-sync.sh",
      "dr:drill": "bash scripts/dr-drill.sh",
      "dr:drill:ps1": "powershell -ExecutionPolicy Bypass -File scripts/dr-drill.ps1",
      "dr:health-check": "bash scripts/health-check.sh",
      "dr:failover": "bash scripts/failover.sh"
    },
    "drDocs": {
      "docs/dr/dr-plan.md": "灾备计划文档：RTO/RPO 定义（4h/24h）、备份策略、故障切换流程、联系人列表、恢复步骤",
      "docs/dr/dr-runbook.md": "灾备操作手册：数据库故障/应用故障/备份失败/异地同步失败/演练失败/磁盘不足场景的诊断与处理",
      "docs/dr/reports/": "灾备演练报告存档目录（Markdown 格式,由 dr-drill.sh 生成）",
      "docs/dr/logs/": "故障切换日志目录（由 failover.sh 生成）"
    },
    "drEnvVars": {
      "BACKUP_OFFSITE_BACKEND": "异地备份后端类型: s3|oss|nfs|none",
      "BACKUP_OFFSITE_REMOTE": "远程存储路径",
      "BACKUP_OFFSITE_BUCKET": "存储桶名称（仅 s3/oss）",
      "BACKUP_OFFSITE_ACCESS_KEY": "访问密钥",
      "BACKUP_OFFSITE_SECRET_KEY": "秘密密钥",
      "BACKUP_OFFSITE_REGION": "区域（默认 us-east-1）",
      "BACKUP_OFFSITE_RETENTION_DAYS": "远程备份保留天数（默认 90）",
      "DR_DRILL_TEST_DB": "演练测试数据库名（默认 next_edu_dr_drill）",
      "HEALTH_CHECK_URL": "应用健康检查 URL（默认 http://localhost:8015）",
      "DATABASE_URL_STANDBY": "备库连接 URL（故障切换时使用）",
      "FAILOVER_APP_NAME": "应用容器名（默认 nextjs-app）"
    },
    "gitignore": {
      "added": [
        "/backups/",
        "/audit-report.json",
        "/trivy-fs-report.json",
        "/trivy-image-report.json",
        "/snyk.sarif",
        "/security-summary.md",
        "/playwright-report/",
        "/test-results/",
        "/tests/visual/.auth/"
      ],
      "exceptions": [
        ".env.example (灾备环境变量示例,允许提交)"
      ]
    }
  },
  "testing": {
    "e2e": {
      "configFile": "playwright.config.ts",
      "testDir": "./tests",
      "baseURL": "http://127.0.0.1:3000",
      "webServer": {
        "command": "npm run dev",
        "port": 3000,
        "timeout": 180000,
        "reuseExistingServer": "!process.env.CI",
        "env": {
          "SKIP_ENV_VALIDATION": "1",
          "NEXTAUTH_SECRET": "test-nextauth-secret",
          "NEXTAUTH_URL": "http://127.0.0.1:3000",
          "DATABASE_URL": "mysql://test:test@127.0.0.1:3306/test_db"
        }
      },
      "projects": [
        {
          "name": "chromium",
          "testDir": "./tests/e2e",
          "channel": "CI: undefined, local: chrome"
        },
        {
          "name": "visual-chromium",
          "testDir": "./tests/visual",
          "channel": "CI: undefined, local: chrome"
        }
      ],
      "snapshotPathTemplate": "{testDir}/__screenshots__/{testFilePath}/{arg}{ext}",
      "expect": {
        "toHaveScreenshot": {
          "maxDiffPixelRatio": 0.01,
          "animations": "disabled",
          "caret": "hide"
        },
        "toMatchSnapshot": {
          "maxDiffPixelRatio": 0.01
        }
      },
      "retries": "CI: 2, local: 0",
      "workers": "CI: 2, local: default",
      "testFiles": {
        "smoke-auth.spec.ts": {
          "coverage": "登录/注册页面控件渲染冒烟测试",
          "requiresDb": false
        },
        "auth-business-flow.spec.ts": {
          "coverage": "注册→登录→访问受保护区域完整流程",
          "requiresDb": true
        },
        "full-route-regression.spec.ts": {
          "coverage": "全路由清单完整性 + 公开/受保护路由守卫",
          "requiresDb": false
        },
        "auth.spec.ts": {
          "coverage": "认证页面（登录/注册/隐私/协议）渲染 + 未认证重定向",
          "requiresDb": false
        },
        "navigation.spec.ts": {
          "coverage": "admin/teacher/student 导航链接无 404",
          "requiresDb": true,
          "envVars": [
            "E2E_ADMIN_EMAIL",
            "E2E_TEACHER_EMAIL",
            "E2E_STUDENT_EMAIL"
          ]
        },
        "announcements.spec.ts": {
          "coverage": "公告页面未认证重定向 + 登录后渲染",
          "requiresDb": "partial"
        },
        "grades.spec.ts": {
          "coverage": "成绩页面未认证重定向 + 登录后渲染",
          "requiresDb": "partial"
        }
      }
    },
    "visual": {
      "configFile": "tests/visual/visual.config.ts",
      "snapshotDir": "tests/visual/__screenshots__",
      "storageStateDir": "tests/visual/.auth/",
      "viewports": {
        "desktop": {
          "width": 1920,
          "height": 1080
        },
        "tablet": {
          "width": 768,
          "height": 1024
        },
        "mobile": {
          "width": 375,
          "height": 812
        }
      },
      "themes": [
        "light",
        "dark"
      ],
      "defaultMaxDiffPixelRatio": 0.01,
      "testAccounts": {
        "admin": {
          "email": "admin@xiaoxue.edu.cn",
          "envVars": [
            "VISUAL_ADMIN_EMAIL",
            "VISUAL_ADMIN_PASSWORD"
          ]
        },
        "teacher": {
          "email": "admin@xiaoxue.edu.cn",
          "envVars": [
            "VISUAL_TEACHER_EMAIL",
            "VISUAL_TEACHER_PASSWORD"
          ]
        },
        "student": {
          "email": "admin@xiaoxue.edu.cn",
          "envVars": [
            "VISUAL_STUDENT_EMAIL",
            "VISUAL_STUDENT_PASSWORD"
          ]
        }
      },
      "testFiles": {
        "homepage.spec.ts": {
          "coverage": "登录页在 desktop/tablet/mobile × light/dark 下的快照",
          "requiresDb": false
        },
        "admin-dashboard.spec.ts": {
          "coverage": "管理员仪表盘整页 + 侧边栏/主内容区组件快照",
          "requiresDb": true,
          "role": "admin"
        },
        "teacher-dashboard.spec.ts": {
          "coverage": "教师仪表盘整页 + 侧边栏/主内容区组件快照",
          "requiresDb": true,
          "role": "teacher"
        },
        "student-dashboard.spec.ts": {
          "coverage": "学生仪表盘整页 + 侧边栏/主内容区组件快照",
          "requiresDb": true,
          "role": "student"
        }
      },
      "helpers": {
        "auth.ts": [
          "setupAuthState(role)",
          "loginByUI(page, role)",
          "storageStatePath(role)"
        ],
        "visual-helpers.ts": [
          "setViewport(page, size)",
          "setTheme(page, theme)",
          "waitForPageReady(page)",
          "maskDynamicElements(page, selectors)",
          "buildMaskOption(masks)"
        ]
      },
      "scripts": {
        "test:visual": "playwright test --project=visual-chromium",
        "test:visual:update": "playwright test --project=visual-chromium --update-snapshots"
      }
    }
  }
}
