openlearn-next 0.4.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +26 -21
  2. package/cli-data.mjs +535 -40
  3. package/cli-doctor.mjs +456 -17
  4. package/cli.mjs +214 -12
  5. package/dist/assets/AcademicYearFilter-DUUGpDKt.js +1 -0
  6. package/dist/assets/ActivityWorkspaceWidget-5PzyxF6a.js +1 -0
  7. package/dist/assets/AdminDirectoryView-BCtmQTEK.js +5 -0
  8. package/dist/assets/AssessmentCenter-B43ojHG-.js +1 -0
  9. package/dist/assets/BatchPickerModal-CbmjInp7.js +1 -0
  10. package/dist/assets/ClassesView-DEnMaDOt.js +32 -0
  11. package/dist/assets/ClassroomCountdownWidget-CHq4983w.js +2 -0
  12. package/dist/assets/CloudDriveModal-D2t3Uyzr.js +1 -0
  13. package/dist/assets/ComputerLabView-YtnoT5Rb.js +1 -0
  14. package/dist/assets/CourseManagement-DOXyJ_j-.js +1 -0
  15. package/dist/assets/CourseWizardModal-Dbi6Caw2.js +3 -0
  16. package/dist/assets/CoursewareHubPanel-CyxDIJAF.js +1 -0
  17. package/dist/assets/Dashboard-2rLu_ZBy.js +8 -0
  18. package/dist/assets/ExportWeightModal-BrmAh6VX.js +1 -0
  19. package/dist/assets/GLTFLoader-CZJbo_y1.js +1 -0
  20. package/dist/assets/HelpTour-D_q1z_MI.js +1 -0
  21. package/dist/assets/HelpView-CVBrKDrd.js +204 -0
  22. package/dist/assets/ImportLessonsModal-PhHtX31R.js +1 -0
  23. package/dist/assets/ImportModal-DqjqNeH4.js +2 -0
  24. package/dist/assets/InteractiveCoursewareViewer-Vi6lLz7a.js +1 -0
  25. package/dist/assets/InteractiveWhiteboard-lTzeayX5.js +65 -0
  26. package/dist/assets/LazyCourseware-KIt0rGtW.js +2 -0
  27. package/dist/assets/LazyWhiteboard-CCN3pYcW.js +2 -0
  28. package/dist/assets/LessonEditorView-D_GVhHuM.js +1 -0
  29. package/dist/assets/LiveClassroomView-BRsZ6QEq.js +61 -0
  30. package/dist/assets/NotificationDetailModal-tY5rxYCN.js +1 -0
  31. package/dist/assets/OrbitControls-CQjN_uwS.js +1 -0
  32. package/dist/assets/PluginView-CedpAW6d.js +77 -0
  33. package/dist/assets/ProcessLogsModal-Cfr-T9Gd.js +1 -0
  34. package/dist/assets/QuizGeneratorModal-bUA002kP.js +1 -0
  35. package/dist/assets/StudentAssignmentView-BhAz6Hfk.js +1 -0
  36. package/dist/assets/StudentLessonView-DC1yX4kG.js +1 -0
  37. package/dist/assets/StudentView-PHKxIV9T.js +2 -0
  38. package/dist/assets/SystemResourceLibraryModal-DgXYe6_x.js +1 -0
  39. package/dist/assets/TeacherView-DscJtOJ9.js +2 -0
  40. package/dist/assets/TimetableView-D2ozoj_D.js +6 -0
  41. package/dist/assets/UTIF-FfCsrHhe.js +1 -0
  42. package/dist/assets/__vite-optional-peer-dep_ai_ooxml-ui-BIHI7g3E.js +1 -0
  43. package/dist/assets/browser-DEI0QxZC.js +1 -0
  44. package/dist/assets/html2canvas-pro.esm-Bo50djkB.js +12 -0
  45. package/dist/assets/index-2676X8YB.js +131 -0
  46. package/dist/assets/index-C6hlh8pw.css +1 -0
  47. package/dist/assets/{index.es-C5eWEO4o.js → index.es-DyEF0CZ9.js} +1 -1
  48. package/dist/assets/pluginParsers-e4PSm2Iy.js +1 -0
  49. package/dist/assets/react_pptx-react-viewer-BIHI7g3E.js +1 -0
  50. package/dist/assets/teaching-modes-client-BeVaDdJ-.js +1 -0
  51. package/dist/assets/three.module-iOYB3rD4.js +4228 -0
  52. package/dist/assets/timeline-duration-BgdJCSrW.js +1 -0
  53. package/dist/assets/{vendor-charts-qLbgF1eL.js → vendor-charts-SrpBsvM3.js} +2 -2
  54. package/dist/assets/{vendor-icons-BRmhY6fg.js → vendor-icons-CXrZ3R2V.js} +2 -2
  55. package/dist/assets/{vendor-konva-CEOq4dcl.js → vendor-konva-Dne0Z-WC.js} +1 -1
  56. package/dist/assets/{vendor-pdf-D7rGevI1.js → vendor-pdf-C1Vh2Aqq.js} +56 -56
  57. package/dist/assets/vendor-pptx-Bzn6whfK.js +11311 -0
  58. package/dist/assets/vendor-pptx-DIWdXF1L.css +1 -0
  59. package/dist/assets/{vendor-react-B0t7H3hh.js → vendor-react-Kv_JATwr.js} +1 -1
  60. package/dist/assets/vendor-utils-aM7aYvPP.js +1 -0
  61. package/dist/cli-migrator.cjs +310 -0
  62. package/dist/env-validation.cjs +351 -0
  63. package/dist/index.html +13 -10
  64. package/dist/plugin-net-preload.cjs +263 -0
  65. package/dist/server.cjs +51140 -27229
  66. package/migrations/000_initial_schema.sql +410 -0
  67. package/migrations/001_add_execution_mode.sql +6 -0
  68. package/migrations/002_add_client_session_expiry.sql +6 -0
  69. package/migrations/003_classroom_runtime.sql +33 -0
  70. package/migrations/004_courseware_score_config.sql +26 -0
  71. package/migrations/005_assignment_hub.sql +155 -0
  72. package/migrations/006_classroom_event_bus.sql +28 -0
  73. package/migrations/007_interactive_classroom.sql +119 -0
  74. package/migrations/008_teaching_modes.sql +36 -0
  75. package/migrations/009_classroom_peer_review.sql +159 -0
  76. package/migrations/010_class_groups.sql +33 -0
  77. package/migrations/011_classroom_feed.sql +34 -0
  78. package/migrations/012_auto_record_score.sql +48 -0
  79. package/migrations/013_score_source_strategy.sql +81 -0
  80. package/migrations/014_performance_indexes.sql +21 -0
  81. package/migrations/015_demo_data_registry.sql +26 -0
  82. package/migrations/016_preset_polls_and_passcode_expiry.sql +33 -0
  83. package/migrations/017_pacing_signals_segment.sql +6 -0
  84. package/migrations/018_processes_plugin_owner.sql +26 -0
  85. package/migrations/019_plugins_version.sql +61 -0
  86. package/migrations/020_upload_max_file_mb.sql +14 -0
  87. package/migrations/021_class_owner_and_password_gate.sql +11 -0
  88. package/migrations/022_plugin_public_routes.sql +14 -0
  89. package/migrations/023_encrypt_class_passcodes.sql +18 -0
  90. package/migrations/024_segment_gates.sql +37 -0
  91. package/migrations/025_assignment_lookup_indexes.sql +11 -0
  92. package/migrations/026_plugin_grants.sql +21 -0
  93. package/migrations/027_academic_year.sql +26 -0
  94. package/migrations/028_classroom_indexes.sql +14 -0
  95. package/migrations/029_clean_lesson_think_artifacts.sql +13 -0
  96. package/migrations/030_backfill_academic_year.sql +30 -0
  97. package/migrations/031_assessment_core.sql +158 -0
  98. package/migrations/032_progress_verification.sql +17 -0
  99. package/migrations/033_student_lock_mode.sql +9 -0
  100. package/migrations/034_plugin_orphan_data.sql +27 -0
  101. package/migrations/035_icebreaker_checkins.sql +26 -0
  102. package/migrations/036_student_read_notifications_created_at.sql +19 -0
  103. package/migrations/037_plugin_config_scopes.sql +40 -0
  104. package/migrations/038_assessment_ai_grading_suggestion.sql +10 -0
  105. package/migrations/039_question_bank_sharing_and_papers.sql +14 -0
  106. package/migrations/040_assignment_grade_history.sql +37 -0
  107. package/migrations/041_course_package_idempotency.sql +33 -0
  108. package/migrations/042_assignment_grade_resubmit_audit.sql +12 -0
  109. package/migrations/043_mark_sha256_passwords.sql +31 -0
  110. package/migrations/README.md +61 -0
  111. package/migrations/schema-golden.json +640 -0
  112. package/package.json +54 -28
  113. package/dist/assets/AdminDirectoryView-DY--WM5Z.js +0 -1
  114. package/dist/assets/BatchPickerModal-BpWu0Lil.js +0 -1
  115. package/dist/assets/ClassesView-KbHEqJto.js +0 -22
  116. package/dist/assets/ClassroomCountdownWidget-96WFRO9A.js +0 -2
  117. package/dist/assets/CloudDriveModal-DEQ1nkNk.js +0 -1
  118. package/dist/assets/ComputerLabView-1qWDNRQ2.js +0 -1
  119. package/dist/assets/CourseManagement-D3ZpxY0l.js +0 -1
  120. package/dist/assets/CourseWizardModal-Ck_-M8pk.js +0 -3
  121. package/dist/assets/CoursewareHubPanel-CaEizhxl.js +0 -1
  122. package/dist/assets/Dashboard-CYbpbofV.js +0 -8
  123. package/dist/assets/ExportWeightModal-DW2bu9Zz.js +0 -1
  124. package/dist/assets/HelpTour-CMJbbGne.js +0 -1
  125. package/dist/assets/HelpView-CtzdqhBq.js +0 -204
  126. package/dist/assets/ImportLessonsModal-jBuIK453.js +0 -1
  127. package/dist/assets/ImportModal-D7L-KNhZ.js +0 -1
  128. package/dist/assets/InteractiveCoursewareViewer-Bj7ahXMs.js +0 -1
  129. package/dist/assets/InteractiveWhiteboard-B7JcJvDu.js +0 -77
  130. package/dist/assets/LazyCourseware-CuiPxhDg.js +0 -2
  131. package/dist/assets/LazyWhiteboard-BJwCEZAU.js +0 -2
  132. package/dist/assets/LessonEditorView-DFrOp2qp.js +0 -1
  133. package/dist/assets/LiveClassroomView-BZ5nGee3.js +0 -59
  134. package/dist/assets/NotificationDetailModal-Baqnc5pU.js +0 -1
  135. package/dist/assets/PluginView-CCbIuZ24.js +0 -66
  136. package/dist/assets/ProcessLogsModal-DmFXg9mG.js +0 -1
  137. package/dist/assets/QuizGeneratorModal-BBEDssp7.js +0 -1
  138. package/dist/assets/StudentAssignmentView-DgIjUsXz.js +0 -1
  139. package/dist/assets/StudentLessonView-CBJbI5x6.js +0 -1
  140. package/dist/assets/StudentPreviewModal-Bh8OZs0t.js +0 -1
  141. package/dist/assets/StudentView--uGxc5TJ.js +0 -2
  142. package/dist/assets/SystemResourceLibraryModal-BVCclcqA.js +0 -1
  143. package/dist/assets/TeacherView-D4Zsc0eZ.js +0 -2
  144. package/dist/assets/TimetableView-BQG-rEb5.js +0 -6
  145. package/dist/assets/index-BKBCBKvy.js +0 -217
  146. package/dist/assets/index-CvGu7LSj.css +0 -1
  147. package/dist/assets/purify.es-CYR4BTuT.js +0 -3
  148. package/dist/assets/vendor-pptx-C5-U7ieS.js +0 -39
  149. package/dist/assets/vendor-utils-m0Urvtws.js +0 -1
@@ -0,0 +1,119 @@
1
+ -- UP
2
+ -- Interactive Classroom & Lesson Lifecycle Runtime Tables
3
+
4
+ CREATE TABLE IF NOT EXISTS lesson_quiz_submissions (
5
+ id TEXT PRIMARY KEY,
6
+ lesson_id TEXT NOT NULL,
7
+ element_id TEXT NOT NULL,
8
+ student_id TEXT NOT NULL,
9
+ student_name TEXT,
10
+ answer TEXT NOT NULL,
11
+ score REAL DEFAULT 0,
12
+ is_correct INTEGER DEFAULT 0,
13
+ time_spent_ms INTEGER DEFAULT 0,
14
+ submitted_at INTEGER NOT NULL,
15
+ -- CONCUR-01:唯一键必须含 lesson_id。否则同一 element_id 被多个课节
16
+ -- 复用时(课程复制 / 同模板多节课),后一节课的作答会覆盖前一节课的记录。
17
+ UNIQUE(lesson_id, element_id, student_id)
18
+ );
19
+
20
+ CREATE INDEX IF NOT EXISTS idx_lqs_lesson_element ON lesson_quiz_submissions(lesson_id, element_id);
21
+ CREATE INDEX IF NOT EXISTS idx_lqs_lesson_student ON lesson_quiz_submissions(lesson_id, student_id);
22
+ CREATE INDEX IF NOT EXISTS idx_lqs_student ON lesson_quiz_submissions(student_id);
23
+
24
+ CREATE TABLE IF NOT EXISTS classroom_sessions (
25
+ id TEXT PRIMARY KEY,
26
+ lesson_id TEXT NOT NULL,
27
+ class_id TEXT,
28
+ teacher_id TEXT NOT NULL,
29
+ stage TEXT DEFAULT 'PRE_CLASS_READY',
30
+ current_segment_id TEXT,
31
+ checkin_code TEXT,
32
+ focus_mode INTEGER DEFAULT 0,
33
+ started_at INTEGER,
34
+ ended_at INTEGER,
35
+ settings_json TEXT DEFAULT '{}',
36
+ created_at INTEGER NOT NULL
37
+ );
38
+
39
+ CREATE INDEX IF NOT EXISTS idx_cs_lesson ON classroom_sessions(lesson_id, stage);
40
+
41
+ CREATE TABLE IF NOT EXISTS classroom_quick_polls (
42
+ id TEXT PRIMARY KEY,
43
+ session_id TEXT NOT NULL,
44
+ lesson_id TEXT NOT NULL,
45
+ question_type TEXT NOT NULL,
46
+ title TEXT,
47
+ options_json TEXT NOT NULL,
48
+ correct_option TEXT,
49
+ status TEXT DEFAULT 'ACTIVE',
50
+ created_at INTEGER NOT NULL
51
+ );
52
+
53
+ CREATE INDEX IF NOT EXISTS idx_cqp_session ON classroom_quick_polls(session_id, status);
54
+
55
+ CREATE TABLE IF NOT EXISTS classroom_poll_votes (
56
+ id TEXT PRIMARY KEY,
57
+ poll_id TEXT NOT NULL,
58
+ student_id TEXT NOT NULL,
59
+ student_name TEXT,
60
+ selected_option TEXT NOT NULL,
61
+ voted_at INTEGER NOT NULL,
62
+ UNIQUE(poll_id, student_id)
63
+ );
64
+
65
+ CREATE INDEX IF NOT EXISTS idx_cpv_poll ON classroom_poll_votes(poll_id);
66
+
67
+ CREATE TABLE IF NOT EXISTS classroom_buzzers (
68
+ id TEXT PRIMARY KEY,
69
+ session_id TEXT NOT NULL,
70
+ lesson_id TEXT NOT NULL,
71
+ title TEXT,
72
+ status TEXT DEFAULT 'READY',
73
+ winner_student_id TEXT,
74
+ winner_student_name TEXT,
75
+ winner_response_time_ms INTEGER,
76
+ created_at INTEGER NOT NULL
77
+ );
78
+
79
+ CREATE TABLE IF NOT EXISTS classroom_exit_tickets (
80
+ id TEXT PRIMARY KEY,
81
+ session_id TEXT NOT NULL,
82
+ lesson_id TEXT NOT NULL,
83
+ student_id TEXT NOT NULL,
84
+ student_name TEXT,
85
+ rating INTEGER,
86
+ puzzled_concept TEXT,
87
+ feedback TEXT,
88
+ created_at INTEGER NOT NULL,
89
+ UNIQUE(session_id, student_id)
90
+ );
91
+
92
+ CREATE INDEX IF NOT EXISTS idx_cet_session ON classroom_exit_tickets(session_id);
93
+
94
+ CREATE TABLE IF NOT EXISTS classroom_pacing_signals (
95
+ id TEXT PRIMARY KEY,
96
+ session_id TEXT NOT NULL,
97
+ student_id TEXT NOT NULL,
98
+ signal_type TEXT NOT NULL,
99
+ created_at INTEGER NOT NULL
100
+ );
101
+
102
+ CREATE INDEX IF NOT EXISTS idx_cps_session ON classroom_pacing_signals(session_id, created_at);
103
+
104
+ -- DOWN
105
+ DROP INDEX IF EXISTS idx_cps_session;
106
+ DROP TABLE IF EXISTS classroom_pacing_signals;
107
+ DROP INDEX IF EXISTS idx_cet_session;
108
+ DROP TABLE IF EXISTS classroom_exit_tickets;
109
+ DROP TABLE IF EXISTS classroom_buzzers;
110
+ DROP INDEX IF EXISTS idx_cpv_poll;
111
+ DROP TABLE IF EXISTS classroom_poll_votes;
112
+ DROP INDEX IF EXISTS idx_cqp_session;
113
+ DROP TABLE IF EXISTS classroom_quick_polls;
114
+ DROP INDEX IF EXISTS idx_cs_lesson;
115
+ DROP TABLE IF EXISTS classroom_sessions;
116
+ DROP INDEX IF EXISTS idx_lqs_lesson_student;
117
+ DROP INDEX IF EXISTS idx_lqs_student;
118
+ DROP INDEX IF EXISTS idx_lqs_lesson_element;
119
+ DROP TABLE IF EXISTS lesson_quiz_submissions;
@@ -0,0 +1,36 @@
1
+ -- UP
2
+ -- 教学模式(Teaching Modes)
3
+ --
4
+ -- 课堂启动门户(Classroom Entry Portal)的「教学模式选择器」数据源。
5
+ -- 教师为当次课堂选择一种教学模式,用于匹配合适的课堂节奏与互动策略。
6
+ --
7
+ -- 设计取舍:内置模式定义在代码常量中(server/routes/classroom.ts 的
8
+ -- BUILTIN_TEACHING_MODES),不在此处 seed。原因是内置模式的文案需要跟随
9
+ -- i18n 与产品迭代变化,写进迁移会造成「迁移与业务常量两处维护」。
10
+ -- API 读取时以「数据库记录优先 + 内置常量兜底」合并返回;
11
+ -- is_builtin 用于禁止删除内置模式。
12
+
13
+ CREATE TABLE IF NOT EXISTS teaching_modes (
14
+ id TEXT PRIMARY KEY,
15
+ name TEXT NOT NULL,
16
+ name_en TEXT,
17
+ description TEXT,
18
+ description_en TEXT,
19
+ icon TEXT,
20
+ color TEXT,
21
+ is_builtin INTEGER DEFAULT 0,
22
+ sort_order INTEGER DEFAULT 100,
23
+ created_at INTEGER NOT NULL,
24
+ updated_at INTEGER
25
+ );
26
+
27
+ CREATE INDEX IF NOT EXISTS idx_teaching_modes_sort ON teaching_modes(sort_order, created_at);
28
+
29
+ -- 当次课堂选用的教学模式。单独成列而非写入 settings_json,便于后续按模式
30
+ -- 聚合教学行为分布(如「不同模式下的互动次数 / 专注率」对比分析)。
31
+ ALTER TABLE classroom_sessions ADD COLUMN teaching_mode_id TEXT;
32
+
33
+ -- DOWN
34
+ DROP INDEX IF EXISTS idx_teaching_modes_sort;
35
+ DROP TABLE IF EXISTS teaching_modes;
36
+ ALTER TABLE classroom_sessions DROP COLUMN teaching_mode_id;
@@ -0,0 +1,159 @@
1
+ -- UP
2
+ -- ═══════════════════════════════════════════════════════════════════════════
3
+ -- 009_classroom_peer_review.sql — 课中全班大屏互评(Peer Review Showcase)
4
+ --
5
+ -- 背景:`src/features/classroom/peer-review/*`(Stitch 21e2dac1 全班大屏作业互评)
6
+ -- 此前只有前端 UI,无任何后端表;界面靠内置 mock 学生/作品/评分渲染。
7
+ -- 本迁移补齐课中互评的真实数据模型,使互评秀场可用真实数据驱动。
8
+ --
9
+ -- 与既有「作业级互评」(005_assignment_hub.sql 的 plugin_peer_review_*)的区别:
10
+ -- plugin_peer_review_* 作业为中心,跨课时生效,带双盲/量规/截止
11
+ -- classroom_peer_review_* 课堂为中心,随 session 生命周期,服务课中大屏场景
12
+ -- 两者互不影响,可并存。
13
+ --
14
+ -- 设计要点:
15
+ -- 1. 所有表以 session_id 为主归属(课堂实时数据随会话结束即冻结);
16
+ -- 2. 分配任务唯一键 (session_id, reviewer_id, target_attempt_id) —— 防止
17
+ -- 同一评阅人对同一份作品重复分配;
18
+ -- 3. 评语唯一键 (session_id, reviewer_id, target_attempt_id) —— 支持重复修改
19
+ -- (同一条记录被 UPDATE),避免刷出多条互评记录;
20
+ -- 4. 目标作品用 courseware_attempt.id 引用(不冗余作品内容),便于大屏展示
21
+ -- 时按需联表取真实成绩与完成度;
22
+ -- 5. 徽章/提名/弹幕均为真实课堂互动事件,供大屏实时展示与统计。
23
+ -- ═══════════════════════════════════════════════════════════════════════════
24
+
25
+ -- ── 1. 互评任务分配 ────────────────────────────────────────────────────────
26
+ -- 教师一键「1 生评 2 份」时写入;学生端按此拉取自己的待评列表。
27
+ CREATE TABLE IF NOT EXISTS classroom_peer_review_tasks (
28
+ id TEXT PRIMARY KEY,
29
+ session_id TEXT NOT NULL,
30
+ lesson_id TEXT NOT NULL,
31
+ class_id TEXT,
32
+ -- 评阅人(学生)
33
+ reviewer_id TEXT NOT NULL,
34
+ reviewer_name TEXT,
35
+ -- 被评对象:提交作品的学生 + 具体 attempt(真实课件作答)
36
+ target_student_id TEXT NOT NULL,
37
+ target_student_name TEXT,
38
+ target_attempt_id TEXT,
39
+ -- 展示口径(来自 courseware_attempt 快照,避免大屏每帧联表)
40
+ target_work_title TEXT,
41
+ target_score REAL,
42
+ -- 分层对调:'benchmark'(标杆范本)| 'improve'(攻坚作业)
43
+ tier TEXT DEFAULT 'benchmark',
44
+ status TEXT DEFAULT 'pending', -- pending | in_progress | submitted
45
+ created_at INTEGER NOT NULL,
46
+ submitted_at INTEGER,
47
+ UNIQUE(session_id, reviewer_id, target_attempt_id)
48
+ );
49
+
50
+ CREATE INDEX IF NOT EXISTS idx_cprt_session ON classroom_peer_review_tasks(session_id, status);
51
+ CREATE INDEX IF NOT EXISTS idx_cprt_reviewer ON classroom_peer_review_tasks(session_id, reviewer_id);
52
+
53
+ -- ── 2. 互评评语(学生提交的评分 + 评语 + 量规维度分) ─────────────────────
54
+ -- 唯一键含 session/reviewer/target_attempt,保证「同一人评同一份作品」只有一条,
55
+ -- 重复提交走 UPDATE(学生可反复改分直到教师锁榜)。
56
+ CREATE TABLE IF NOT EXISTS classroom_peer_reviews (
57
+ id TEXT PRIMARY KEY,
58
+ session_id TEXT NOT NULL,
59
+ lesson_id TEXT NOT NULL,
60
+ reviewer_id TEXT NOT NULL,
61
+ reviewer_name TEXT,
62
+ target_student_id TEXT NOT NULL,
63
+ target_attempt_id TEXT,
64
+ score REAL NOT NULL,
65
+ max_score REAL DEFAULT 5,
66
+ comment TEXT,
67
+ -- 量规维度分:{"algorithm":4,"style":5,...}(JSON,维度定义见 rubric 表)
68
+ dimension_scores_json TEXT DEFAULT '{}',
69
+ created_at INTEGER NOT NULL,
70
+ updated_at INTEGER NOT NULL,
71
+ UNIQUE(session_id, reviewer_id, target_attempt_id)
72
+ );
73
+
74
+ CREATE INDEX IF NOT EXISTS idx_cpr_session ON classroom_peer_reviews(session_id, target_student_id);
75
+
76
+ -- ── 3. 随堂互评微勋章(学生互赠,用于大屏徽章流) ────────────────────────
77
+ CREATE TABLE IF NOT EXISTS classroom_peer_badges (
78
+ id TEXT PRIMARY KEY,
79
+ session_id TEXT NOT NULL,
80
+ lesson_id TEXT NOT NULL,
81
+ sender_id TEXT NOT NULL,
82
+ sender_name TEXT,
83
+ receiver_id TEXT NOT NULL,
84
+ receiver_name TEXT,
85
+ -- 勋章类型键(对应前端徽章定义,如 'brilliant_idea' | 'self_heal' | 'best_solution')
86
+ badge_key TEXT NOT NULL,
87
+ created_at INTEGER NOT NULL,
88
+ UNIQUE(session_id, sender_id, receiver_id, badge_key)
89
+ );
90
+
91
+ CREATE INDEX IF NOT EXISTS idx_cpb_session ON classroom_peer_badges(session_id, receiver_id);
92
+
93
+ -- ── 4. 先锋榜提名(按票数排名,供大屏领奖台) ────────────────────────────
94
+ -- 每次提名一行(而非计数器列),便于去重与追溯;票数由 COUNT 聚合得到。
95
+ CREATE TABLE IF NOT EXISTS classroom_peer_nominations (
96
+ id TEXT PRIMARY KEY,
97
+ session_id TEXT NOT NULL,
98
+ lesson_id TEXT NOT NULL,
99
+ nominator_id TEXT NOT NULL,
100
+ nominated_student_id TEXT NOT NULL,
101
+ nominated_student_name TEXT,
102
+ honor_key TEXT, -- 荣誉标签键(如 'best_open_source' | 'best_progress')
103
+ created_at INTEGER NOT NULL,
104
+ UNIQUE(session_id, nominator_id, nominated_student_id)
105
+ );
106
+
107
+ CREATE INDEX IF NOT EXISTS idx_cpn_session ON classroom_peer_nominations(session_id, nominated_student_id);
108
+
109
+ -- ── 5. 大屏弹幕(文字 / 语音弹幕) ───────────────────────────────────────
110
+ CREATE TABLE IF NOT EXISTS classroom_danmaku (
111
+ id TEXT PRIMARY KEY,
112
+ session_id TEXT NOT NULL,
113
+ lesson_id TEXT NOT NULL,
114
+ sender_id TEXT,
115
+ sender_name TEXT,
116
+ text TEXT NOT NULL,
117
+ -- 'text' | 'voice'(voice 附带时长秒数)
118
+ type TEXT DEFAULT 'text',
119
+ voice_duration_seconds INTEGER,
120
+ -- 弹幕纵向位置百分比(0-100),由前端随机分配后落库,保证多端位置一致
121
+ top_percent INTEGER DEFAULT 20,
122
+ created_at INTEGER NOT NULL
123
+ );
124
+
125
+ CREATE INDEX IF NOT EXISTS idx_cd_session ON classroom_danmaku(session_id, created_at DESC);
126
+
127
+ -- ── 6. 互评量规维度定义(教师可自定义,服务 rubric 看板) ─────────────────
128
+ CREATE TABLE IF NOT EXISTS classroom_peer_rubric_dimensions (
129
+ id TEXT PRIMARY KEY,
130
+ session_id TEXT NOT NULL,
131
+ lesson_id TEXT NOT NULL,
132
+ label TEXT NOT NULL,
133
+ -- 展示用色(Tailwind 类名,随主题无关的固定色板)
134
+ color_class TEXT,
135
+ bar_color_class TEXT,
136
+ max_score REAL DEFAULT 5,
137
+ weight REAL DEFAULT 1,
138
+ sort_order INTEGER DEFAULT 0,
139
+ created_at INTEGER NOT NULL,
140
+ UNIQUE(session_id, label)
141
+ );
142
+
143
+ CREATE INDEX IF NOT EXISTS idx_cprd_session ON classroom_peer_rubric_dimensions(session_id, sort_order);
144
+
145
+ -- DOWN
146
+ -- 回滚 009:删除课中全班大屏互评(Peer Review)相关表与索引
147
+ DROP INDEX IF EXISTS idx_cprt_session;
148
+ DROP INDEX IF EXISTS idx_cprt_reviewer;
149
+ DROP INDEX IF EXISTS idx_cpr_session;
150
+ DROP INDEX IF EXISTS idx_cpb_session;
151
+ DROP INDEX IF EXISTS idx_cpn_session;
152
+ DROP INDEX IF EXISTS idx_cd_session;
153
+ DROP INDEX IF EXISTS idx_cprd_session;
154
+ DROP TABLE IF EXISTS classroom_peer_rubric_dimensions;
155
+ DROP TABLE IF EXISTS classroom_peer_nominations;
156
+ DROP TABLE IF EXISTS classroom_peer_badges;
157
+ DROP TABLE IF EXISTS classroom_danmaku;
158
+ DROP TABLE IF EXISTS classroom_peer_reviews;
159
+ DROP TABLE IF EXISTS classroom_peer_review_tasks;
@@ -0,0 +1,33 @@
1
+ -- UP
2
+ -- 班级固定分组(Class Seating Groups)
3
+ --
4
+ -- 机房管理中为班级学生建立持久化学习小组:支持自动分组、指定组长、
5
+ -- 手动调整成员归属;上课需要分组活动时默认按该分组进行,
6
+ -- 同时课堂内可临时新建分组或调整(临时分组只写 classroom_sessions 侧,不改本表)。
7
+ --
8
+ -- 设计取舍:
9
+ -- - is_default 标记「上课默认分组方案」,同一班级至多一套;
10
+ -- - member_ids / leader_id 用 JSON 保存整组成员与组长,
11
+ -- 组数通常为个位数、成员为几十人,单行读取即可渲染整组面板,
12
+ -- 避免额外的成员关系表与多查询往返;
13
+ -- - name_en 记录可选英文组名,跟随平台的 i18n 口径。
14
+
15
+ CREATE TABLE IF NOT EXISTS class_groups (
16
+ id TEXT PRIMARY KEY,
17
+ class_id TEXT NOT NULL,
18
+ name TEXT NOT NULL,
19
+ name_en TEXT,
20
+ color TEXT DEFAULT 'bg-indigo-500',
21
+ member_ids TEXT NOT NULL DEFAULT '[]',
22
+ leader_id TEXT,
23
+ is_default INTEGER DEFAULT 0,
24
+ sort_order INTEGER DEFAULT 100,
25
+ created_at INTEGER NOT NULL,
26
+ updated_at INTEGER
27
+ );
28
+
29
+ CREATE INDEX IF NOT EXISTS idx_class_groups_class ON class_groups(class_id, sort_order, created_at);
30
+
31
+ -- DOWN
32
+ DROP INDEX IF EXISTS idx_class_groups_class;
33
+ DROP TABLE IF EXISTS class_groups;
@@ -0,0 +1,34 @@
1
+ -- UP
2
+ -- 011: 课堂动态流持久化(课堂会话保存与恢复)
3
+ --
4
+ -- 背景:liveClassFeed 此前是纯前端内存态(appStore 上限 50 条),教师离开
5
+ -- 课堂再回来后动态流清零。本表把课堂动态(随堂作答、签到、表彰、作业提交/
6
+ -- 批改、进度里程碑等)落库为会话级事实,重进课堂时按 lesson 回放。
7
+ --
8
+ -- 写入方:server/services/classroom-feed-service.ts(订阅内核事件总线,
9
+ -- 与 server/event-routing.ts 的声明式路由同款模式)。
10
+ -- 读取方:GET /api/classroom/sessions/:lessonId 的 feedReplay 字段。
11
+
12
+ CREATE TABLE IF NOT EXISTS classroom_feed (
13
+ id TEXT PRIMARY KEY,
14
+ session_id TEXT,
15
+ lesson_id TEXT NOT NULL,
16
+ class_id TEXT,
17
+ -- 动态类型:quiz_answered / checkin / achievement / assignment_submitted /
18
+ -- assignment_graded / progress / courseware_submitted / info
19
+ type TEXT NOT NULL,
20
+ message TEXT NOT NULL,
21
+ actor_id TEXT,
22
+ actor_name TEXT,
23
+ source TEXT,
24
+ created_at INTEGER NOT NULL
25
+ );
26
+
27
+ CREATE INDEX IF NOT EXISTS idx_classroom_feed_lesson ON classroom_feed(lesson_id, created_at);
28
+ CREATE INDEX IF NOT EXISTS idx_classroom_feed_session ON classroom_feed(session_id, created_at);
29
+
30
+ -- DOWN
31
+ -- 回滚 011:删除课堂动态流表与索引
32
+ DROP INDEX IF EXISTS idx_classroom_feed_lesson;
33
+ DROP INDEX IF EXISTS idx_classroom_feed_session;
34
+ DROP TABLE IF EXISTS classroom_feed;
@@ -0,0 +1,48 @@
1
+ -- UP
2
+ -- Courseware Auto Grade Recording Rule (auto-record)
3
+ --
4
+ -- 把「学生提交数据」页的**逐条手动「录入成绩」**升级为**按规则自动录入**:
5
+ -- 教师先设定规则(开关 + 完成度门槛),之后课件被提交时由服务端自动把成绩
6
+ -- 写入学期成绩(assignment_submissions),无需逐条点击。
7
+ --
8
+ -- 设计要点:
9
+ -- 1. 字段挂在既有 courseware_score_config 上,复用它「课件专属 → 全局 '*' → 内置默认」
10
+ -- 的三级继承(见 packages/plugins/courseware-score.ts 的 resolveScoreConfig),
11
+ -- 不另立配置表,避免出现两套成绩配置。
12
+ -- 2. auto_record_enabled 默认 0(关闭):不改变现有行为,教师显式开启才生效。
13
+ -- 3. auto_record_min_completion 为完成度门槛(0~1)。完成度低于门槛的提交不自动录入,
14
+ -- 留在「学生提交数据」页由教师人工判定 —— 规则只自动化"确定性足够"的记录。
15
+ -- 4. 分数始终取课件聚合分(submission_result.score)。**没有分数一律不录**,
16
+ -- 绝不凭空造分(历史上 promote 的 finalScore 默认 100 会给满分,已改为拒绝)。
17
+
18
+ ALTER TABLE courseware_score_config ADD COLUMN auto_record_enabled INTEGER NOT NULL DEFAULT 0;
19
+ ALTER TABLE courseware_score_config ADD COLUMN auto_record_min_completion REAL NOT NULL DEFAULT 0;
20
+
21
+ -- DOWN
22
+ -- 不使用 ALTER TABLE ... DROP COLUMN(旧版 SQLite 内核不支持,且迁移执行器只对
23
+ -- "duplicate column name" 容错,失败会中断回滚)。改为标准的「重建表」回滚。
24
+ CREATE TABLE courseware_score_config__rollback (
25
+ courseware_id TEXT PRIMARY KEY,
26
+ courseware_name TEXT,
27
+ score_policy TEXT NOT NULL DEFAULT 'LATEST',
28
+ score_fields TEXT NOT NULL DEFAULT '',
29
+ raw_full_score REAL NOT NULL DEFAULT 100,
30
+ target_full_score REAL NOT NULL DEFAULT 100,
31
+ weight_percentage REAL NOT NULL DEFAULT 100,
32
+ lesson_id TEXT,
33
+ updated_at INTEGER NOT NULL DEFAULT 0
34
+ );
35
+
36
+ INSERT INTO courseware_score_config__rollback
37
+ (courseware_id, courseware_name, score_policy, score_fields,
38
+ raw_full_score, target_full_score, weight_percentage, lesson_id, updated_at)
39
+ SELECT
40
+ courseware_id, courseware_name, score_policy, score_fields,
41
+ raw_full_score, target_full_score, weight_percentage, lesson_id, updated_at
42
+ FROM courseware_score_config;
43
+
44
+ DROP TABLE courseware_score_config;
45
+
46
+ ALTER TABLE courseware_score_config__rollback RENAME TO courseware_score_config;
47
+
48
+ CREATE INDEX IF NOT EXISTS idx_courseware_score_config_lesson ON courseware_score_config(lesson_id);
@@ -0,0 +1,81 @@
1
+ -- UP
2
+ -- 成绩来源(source)与自动录入更新策略(auto_record_strategy)
3
+ --
4
+ -- 背景(2026-09-30 审计教学缺口 2):
5
+ -- 1. 自动录入(autoRecordAttempt,submit_attempt 实时触发)无条件 upsert 覆盖
6
+ -- —— 教师手动调整过的分数会被课件分冲掉。
7
+ -- 2. 批量补录(autoRecordForLesson)只要已有成绩行就跳过
8
+ -- —— 学生重做课件后,自动录入的旧分永不更新。
9
+ -- 根因是 assignment_submissions 无法区分「手动录入」与「自动录入」。
10
+ --
11
+ -- 设计要点:
12
+ -- 1. source 列区分成绩来源:'manual'(教师录入/改判,受保护)与 'auto'(规则自动录入,可刷新)。
13
+ -- auto 路径遇 source='manual' 跳过(manual-protected),auto 行按策略刷新。
14
+ -- 2. 存量回填:自动路径写入的 feedback 固定以「由自动录入规则。」开头
15
+ -- (见 auto-record-score.ts 的 sourceLabel),据此回填 source='auto';其余一律 'manual'
16
+ -- (含历史自动录入但 feedback 文案不符合的行 —— 宁可多保护,不可误覆盖)。
17
+ -- 3. status='absent'(缺考)行在 auto 路径同样受保护(absent-protected),见 commit 3。
18
+ -- 4. auto_record_strategy 挂在既有 courseware_score_config 上,复用其三级继承:
19
+ -- 'latest'(默认,最新一次 attempt 分数覆盖)/ 'highest'(仅新分更高才覆盖,鼓励重做)。
20
+
21
+ ALTER TABLE assignment_submissions ADD COLUMN source TEXT;
22
+
23
+ UPDATE assignment_submissions SET source =
24
+ CASE WHEN feedback LIKE '由自动录入规则。%' THEN 'auto' ELSE 'manual' END;
25
+
26
+ ALTER TABLE courseware_score_config ADD COLUMN auto_record_strategy TEXT NOT NULL DEFAULT 'latest';
27
+
28
+ -- DOWN
29
+ -- 「重建表」回滚(同 012:不用 ALTER TABLE ... DROP COLUMN,旧版 SQLite 内核不支持)。
30
+
31
+ CREATE TABLE assignment_submissions__rollback (
32
+ assignment_id TEXT NOT NULL,
33
+ student_id TEXT NOT NULL,
34
+ content TEXT,
35
+ score INTEGER,
36
+ feedback TEXT,
37
+ submitted_at INTEGER NOT NULL,
38
+ graded_at INTEGER,
39
+ status TEXT NOT NULL DEFAULT 'submitted',
40
+ PRIMARY KEY (assignment_id, student_id)
41
+ );
42
+
43
+ INSERT INTO assignment_submissions__rollback
44
+ (assignment_id, student_id, content, score, feedback, submitted_at, graded_at, status)
45
+ SELECT
46
+ assignment_id, student_id, content, score, feedback, submitted_at, graded_at, status
47
+ FROM assignment_submissions;
48
+
49
+ DROP TABLE assignment_submissions;
50
+
51
+ ALTER TABLE assignment_submissions__rollback RENAME TO assignment_submissions;
52
+
53
+ CREATE TABLE courseware_score_config__rollback (
54
+ courseware_id TEXT PRIMARY KEY,
55
+ courseware_name TEXT,
56
+ score_policy TEXT NOT NULL DEFAULT 'LATEST',
57
+ score_fields TEXT NOT NULL DEFAULT '',
58
+ raw_full_score REAL NOT NULL DEFAULT 100,
59
+ target_full_score REAL NOT NULL DEFAULT 100,
60
+ weight_percentage REAL NOT NULL DEFAULT 100,
61
+ lesson_id TEXT,
62
+ updated_at INTEGER NOT NULL DEFAULT 0,
63
+ auto_record_enabled INTEGER NOT NULL DEFAULT 0,
64
+ auto_record_min_completion REAL NOT NULL DEFAULT 0
65
+ );
66
+
67
+ INSERT INTO courseware_score_config__rollback
68
+ (courseware_id, courseware_name, score_policy, score_fields,
69
+ raw_full_score, target_full_score, weight_percentage, lesson_id, updated_at,
70
+ auto_record_enabled, auto_record_min_completion)
71
+ SELECT
72
+ courseware_id, courseware_name, score_policy, score_fields,
73
+ raw_full_score, target_full_score, weight_percentage, lesson_id, updated_at,
74
+ auto_record_enabled, auto_record_min_completion
75
+ FROM courseware_score_config;
76
+
77
+ DROP TABLE courseware_score_config;
78
+
79
+ ALTER TABLE courseware_score_config__rollback RENAME TO courseware_score_config;
80
+
81
+ CREATE INDEX IF NOT EXISTS idx_courseware_score_config_lesson ON courseware_score_config(lesson_id);
@@ -0,0 +1,21 @@
1
+ -- UP
2
+ -- 性能索引补充(DATA-INT-02 / 路线图 A6)
3
+ --
4
+ -- 高频查询路径缺失索引,数据量增长后退化为全表扫描:
5
+ -- 1. vfs_nodes(parent_id):VFS 目录树逐级下钻(workspace GET /files/* 每层一次 parent 查询)
6
+ -- 2. plugin_submissions(lesson_id):插件提交按课节检索
7
+ -- 3. student_point_logs(student_id):积分台账按学生检索
8
+ -- 4. student_rollcalls(class_id) / (student_id):点名记录按班级/学生检索
9
+
10
+ CREATE INDEX IF NOT EXISTS idx_vfs_nodes_parent ON vfs_nodes(parent_id);
11
+ CREATE INDEX IF NOT EXISTS idx_plugin_submissions_lesson ON plugin_submissions(lesson_id);
12
+ CREATE INDEX IF NOT EXISTS idx_student_point_logs_student ON student_point_logs(student_id);
13
+ CREATE INDEX IF NOT EXISTS idx_student_rollcalls_class ON student_rollcalls(class_id);
14
+ CREATE INDEX IF NOT EXISTS idx_student_rollcalls_student ON student_rollcalls(student_id);
15
+
16
+ -- DOWN
17
+ DROP INDEX IF EXISTS idx_student_rollcalls_student;
18
+ DROP INDEX IF EXISTS idx_student_rollcalls_class;
19
+ DROP INDEX IF EXISTS idx_student_point_logs_student;
20
+ DROP INDEX IF EXISTS idx_plugin_submissions_lesson;
21
+ DROP INDEX IF EXISTS idx_vfs_nodes_parent;
@@ -0,0 +1,26 @@
1
+ -- UP
2
+ -- 演示数据登记表(DEMO-SEED-01)
3
+ --
4
+ -- 一键演示数据的**精确作用域**保障。
5
+ --
6
+ -- 背景:既有 `POST /api/admin/seed-demo` 依赖标题模糊匹配来识别演示数据
7
+ -- (`title LIKE 'E2E %'`),无法区分"演示数据"与"恰好同名的真实数据",
8
+ -- 清理时存在误伤风险。本表记录播种时**实际创建的每一行**,清理时据此
9
+ -- 精确删除,不再依赖任何命名约定。
10
+ --
11
+ -- 约束:
12
+ -- 1. 清理只删除本表登记的行;未登记的一律不碰。
13
+ -- 2. 管理员账号(users.role='administrator')永不删除,代码侧另有兜底断言。
14
+
15
+ CREATE TABLE IF NOT EXISTS demo_data_registry (
16
+ entity_type TEXT NOT NULL,
17
+ entity_id TEXT NOT NULL,
18
+ created_at INTEGER NOT NULL,
19
+ PRIMARY KEY (entity_type, entity_id)
20
+ );
21
+
22
+ CREATE INDEX IF NOT EXISTS idx_demo_data_registry_type ON demo_data_registry(entity_type);
23
+
24
+ -- DOWN
25
+ DROP INDEX IF EXISTS idx_demo_data_registry_type;
26
+ DROP TABLE IF EXISTS demo_data_registry;
@@ -0,0 +1,33 @@
1
+ -- UP
2
+ -- 016_preset_polls_and_passcode_expiry.sql
3
+ -- 补齐课程预设投票表与班级口令有效期、梯级下课条、随机点名奖赏字段
4
+
5
+ CREATE TABLE IF NOT EXISTS lesson_preset_polls (
6
+ id TEXT PRIMARY KEY,
7
+ lesson_id TEXT NOT NULL,
8
+ title TEXT NOT NULL,
9
+ question_type TEXT NOT NULL DEFAULT 'ABCD',
10
+ options_json TEXT NOT NULL,
11
+ correct_option TEXT,
12
+ sort_order INTEGER DEFAULT 0,
13
+ created_at INTEGER NOT NULL,
14
+ updated_at INTEGER NOT NULL
15
+ );
16
+
17
+ CREATE INDEX IF NOT EXISTS idx_lpp_lesson ON lesson_preset_polls(lesson_id);
18
+
19
+ ALTER TABLE classes ADD COLUMN class_passcode_expires_at INTEGER;
20
+
21
+ ALTER TABLE student_rollcalls ADD COLUMN rating TEXT;
22
+ ALTER TABLE student_rollcalls ADD COLUMN score INTEGER DEFAULT 0;
23
+ ALTER TABLE student_rollcalls ADD COLUMN reward_coins INTEGER DEFAULT 0;
24
+ ALTER TABLE student_rollcalls ADD COLUMN difficulty TEXT;
25
+
26
+ ALTER TABLE classroom_exit_tickets ADD COLUMN core_answer TEXT;
27
+ ALTER TABLE classroom_exit_tickets ADD COLUMN is_correct INTEGER DEFAULT 0;
28
+ ALTER TABLE classroom_exit_tickets ADD COLUMN tier_level TEXT DEFAULT 'passed';
29
+ ALTER TABLE classroom_exit_tickets ADD COLUMN challenge_answer TEXT;
30
+
31
+ -- DOWN
32
+ DROP INDEX IF EXISTS idx_lpp_lesson;
33
+ DROP TABLE IF EXISTS lesson_preset_polls;
@@ -0,0 +1,6 @@
1
+ -- UP
2
+ ALTER TABLE classroom_pacing_signals ADD COLUMN segment_id TEXT;
3
+ CREATE INDEX IF NOT EXISTS idx_cps_session_segment ON classroom_pacing_signals(session_id, segment_id, student_id);
4
+
5
+ -- DOWN
6
+ DROP INDEX IF EXISTS idx_cps_session_segment;
@@ -0,0 +1,26 @@
1
+ -- UP
2
+ -- 018_processes_plugin_owner.sql
3
+ -- 审计项 B-5:processes 表补 plugin_id,让后台任务的归属可判定。
4
+ --
5
+ -- 背景:ProcessManager.spawn / registerInterval 把任务写进全局 processes 表,
6
+ -- 且 processId 经全局事件总线 `process.spawned` 广播(任意插件可订阅拿到)。
7
+ -- 但 kill(processId) 只按 id UPDATE、无归属校验 —— 插件可以杀掉他人后台任务。
8
+ -- 没有 plugin_id 列就无法判定归属,只能靠内存 Map(重启即丢,且 restore()
9
+ -- 从 DB 重建的任务会失去归属信息),故落成列。
10
+ --
11
+ -- 历史行为:存量行的 plugin_id 为 NULL,表示「非插件进程」(内核自身任务)。
12
+ -- kill 校验对 NULL 的处理见 context-builder.wrapProcessManager 的 isOwnProcess:
13
+ -- 查不到归属时不拦截,避免误杀内核任务与存量行。
14
+
15
+ ALTER TABLE processes ADD COLUMN plugin_id TEXT;
16
+
17
+ -- 归属查询的热路径:按 owner 列出其全部进程
18
+ CREATE INDEX IF NOT EXISTS idx_processes_plugin_id ON processes(plugin_id);
19
+
20
+ -- 按 owner + 状态筛选(restore 只需处理 running,但内核自身任务 owner 为 NULL)
21
+ CREATE INDEX IF NOT EXISTS idx_processes_plugin_status ON processes(plugin_id, status);
22
+ -- DOWN
23
+ DROP INDEX IF EXISTS idx_processes_plugin_status;
24
+ DROP INDEX IF EXISTS idx_processes_plugin_id;
25
+ -- SQLite 3.35+ 支持 DROP COLUMN;旧版走表重建兜底由 migrate 层处理
26
+ ALTER TABLE processes DROP COLUMN plugin_id;