@studious-lms/server 1.11.0 → 1.13.0

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 (188) hide show
  1. package/.env.example +3 -0
  2. package/dist/index.js +40 -4
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/config/cors.d.ts.map +1 -1
  5. package/dist/lib/config/cors.js +7 -2
  6. package/dist/lib/config/cors.js.map +1 -1
  7. package/dist/lib/config/env.d.ts +8 -0
  8. package/dist/lib/config/env.d.ts.map +1 -1
  9. package/dist/lib/config/env.js +5 -2
  10. package/dist/lib/config/env.js.map +1 -1
  11. package/dist/lib/pusher.d.ts +2 -1
  12. package/dist/lib/pusher.d.ts.map +1 -1
  13. package/dist/lib/pusher.js +4 -3
  14. package/dist/lib/pusher.js.map +1 -1
  15. package/dist/models/agenda.d.ts +6 -0
  16. package/dist/models/agenda.d.ts.map +1 -1
  17. package/dist/models/announcement.d.ts +3 -0
  18. package/dist/models/announcement.d.ts.map +1 -1
  19. package/dist/models/assignment.d.ts +61 -0
  20. package/dist/models/assignment.d.ts.map +1 -1
  21. package/dist/models/auth.d.ts +6 -0
  22. package/dist/models/auth.d.ts.map +1 -1
  23. package/dist/models/class.d.ts +28 -0
  24. package/dist/models/class.d.ts.map +1 -1
  25. package/dist/models/class.js +12 -3
  26. package/dist/models/class.js.map +1 -1
  27. package/dist/models/comment.d.ts +13 -0
  28. package/dist/models/comment.d.ts.map +1 -1
  29. package/dist/models/comment.js +3 -2
  30. package/dist/models/comment.js.map +1 -1
  31. package/dist/models/conversation.d.ts +17 -0
  32. package/dist/models/conversation.d.ts.map +1 -1
  33. package/dist/models/conversation.js +13 -2
  34. package/dist/models/conversation.js.map +1 -1
  35. package/dist/models/event.d.ts +12 -0
  36. package/dist/models/event.d.ts.map +1 -1
  37. package/dist/models/file.d.ts +38 -0
  38. package/dist/models/file.d.ts.map +1 -1
  39. package/dist/models/folder.d.ts +6 -0
  40. package/dist/models/folder.d.ts.map +1 -1
  41. package/dist/models/labChat.d.ts +6 -0
  42. package/dist/models/labChat.d.ts.map +1 -1
  43. package/dist/models/newtonChat.d.ts +3 -0
  44. package/dist/models/newtonChat.d.ts.map +1 -1
  45. package/dist/models/school.d.ts +3 -0
  46. package/dist/models/school.d.ts.map +1 -1
  47. package/dist/models/section.d.ts +3 -0
  48. package/dist/models/section.d.ts.map +1 -1
  49. package/dist/models/worksheet.d.ts +62 -0
  50. package/dist/models/worksheet.d.ts.map +1 -1
  51. package/dist/models/worksheet.js +37 -7
  52. package/dist/models/worksheet.js.map +1 -1
  53. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  54. package/dist/pipelines/gradeWorksheet.js +59 -14
  55. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  56. package/dist/routers/_app.d.ts +4641 -2285
  57. package/dist/routers/_app.d.ts.map +1 -1
  58. package/dist/routers/_app.js +6 -2
  59. package/dist/routers/_app.js.map +1 -1
  60. package/dist/routers/admin.d.ts +190 -0
  61. package/dist/routers/admin.d.ts.map +1 -0
  62. package/dist/routers/admin.js +50 -0
  63. package/dist/routers/admin.js.map +1 -0
  64. package/dist/routers/agenda.d.ts +6 -0
  65. package/dist/routers/agenda.d.ts.map +1 -1
  66. package/dist/routers/announcement.d.ts +6 -0
  67. package/dist/routers/announcement.d.ts.map +1 -1
  68. package/dist/routers/announcement.js +6 -5
  69. package/dist/routers/announcement.js.map +1 -1
  70. package/dist/routers/assignment.d.ts +58 -0
  71. package/dist/routers/assignment.d.ts.map +1 -1
  72. package/dist/routers/assignment.js +4 -4
  73. package/dist/routers/assignment.js.map +1 -1
  74. package/dist/routers/class.d.ts +54 -0
  75. package/dist/routers/class.d.ts.map +1 -1
  76. package/dist/routers/class.js +5 -2
  77. package/dist/routers/class.js.map +1 -1
  78. package/dist/routers/comment.d.ts +26 -0
  79. package/dist/routers/comment.d.ts.map +1 -1
  80. package/dist/routers/comment.js +6 -3
  81. package/dist/routers/comment.js.map +1 -1
  82. package/dist/routers/discussion.d.ts +823 -0
  83. package/dist/routers/discussion.d.ts.map +1 -0
  84. package/dist/routers/discussion.js +151 -0
  85. package/dist/routers/discussion.js.map +1 -0
  86. package/dist/routers/event.d.ts +3 -0
  87. package/dist/routers/event.d.ts.map +1 -1
  88. package/dist/routers/file.d.ts +4 -0
  89. package/dist/routers/file.d.ts.map +1 -1
  90. package/dist/routers/school.d.ts +6 -0
  91. package/dist/routers/school.d.ts.map +1 -1
  92. package/dist/routers/worksheet.d.ts +6 -0
  93. package/dist/routers/worksheet.d.ts.map +1 -1
  94. package/dist/routers/worksheet.js +4 -2
  95. package/dist/routers/worksheet.js.map +1 -1
  96. package/dist/seedDatabase.d.ts.map +1 -1
  97. package/dist/seedDatabase.js +1490 -1307
  98. package/dist/seedDatabase.js.map +1 -1
  99. package/dist/services/adminInstitution.d.ts +102 -0
  100. package/dist/services/adminInstitution.d.ts.map +1 -0
  101. package/dist/services/adminInstitution.js +117 -0
  102. package/dist/services/adminInstitution.js.map +1 -0
  103. package/dist/services/adminRunJob.d.ts +45 -0
  104. package/dist/services/adminRunJob.d.ts.map +1 -0
  105. package/dist/services/adminRunJob.js +41 -0
  106. package/dist/services/adminRunJob.js.map +1 -0
  107. package/dist/services/agenda.d.ts +6 -0
  108. package/dist/services/agenda.d.ts.map +1 -1
  109. package/dist/services/assignment.d.ts +50 -0
  110. package/dist/services/assignment.d.ts.map +1 -1
  111. package/dist/services/assignment.js +16 -9
  112. package/dist/services/assignment.js.map +1 -1
  113. package/dist/services/class.d.ts +54 -0
  114. package/dist/services/class.d.ts.map +1 -1
  115. package/dist/services/class.js +20 -3
  116. package/dist/services/class.js.map +1 -1
  117. package/dist/services/comment.d.ts +20 -0
  118. package/dist/services/comment.d.ts.map +1 -1
  119. package/dist/services/comment.js +23 -2
  120. package/dist/services/comment.js.map +1 -1
  121. package/dist/services/conversation.d.ts.map +1 -1
  122. package/dist/services/conversation.js +9 -7
  123. package/dist/services/conversation.js.map +1 -1
  124. package/dist/services/discussion.d.ts +762 -0
  125. package/dist/services/discussion.d.ts.map +1 -0
  126. package/dist/services/discussion.js +625 -0
  127. package/dist/services/discussion.js.map +1 -0
  128. package/dist/services/event.d.ts +3 -0
  129. package/dist/services/event.d.ts.map +1 -1
  130. package/dist/services/file.d.ts +8 -0
  131. package/dist/services/file.d.ts.map +1 -1
  132. package/dist/services/school.d.ts +6 -0
  133. package/dist/services/school.d.ts.map +1 -1
  134. package/dist/services/studentProgress.d.ts.map +1 -1
  135. package/dist/services/studentProgress.js +27 -5
  136. package/dist/services/studentProgress.js.map +1 -1
  137. package/dist/services/studentProgressInferenceCache.d.ts +16 -0
  138. package/dist/services/studentProgressInferenceCache.d.ts.map +1 -0
  139. package/dist/services/studentProgressInferenceCache.js +43 -0
  140. package/dist/services/studentProgressInferenceCache.js.map +1 -0
  141. package/dist/services/worksheet.d.ts +6 -0
  142. package/dist/services/worksheet.d.ts.map +1 -1
  143. package/dist/services/worksheet.js +5 -5
  144. package/dist/services/worksheet.js.map +1 -1
  145. package/dist/trpc.d.ts +12 -0
  146. package/dist/trpc.d.ts.map +1 -1
  147. package/dist/trpc.js +16 -3
  148. package/dist/trpc.js.map +1 -1
  149. package/dist/utils/adminPanelAccess.d.ts +8 -0
  150. package/dist/utils/adminPanelAccess.d.ts.map +1 -0
  151. package/dist/utils/adminPanelAccess.js +44 -0
  152. package/dist/utils/adminPanelAccess.js.map +1 -0
  153. package/package.json +1 -1
  154. package/prisma/migrations/20260510183000_add_discussions/migration.sql +151 -0
  155. package/prisma/schema.prisma +98 -0
  156. package/src/index.ts +39 -2
  157. package/src/lib/config/cors.ts +5 -0
  158. package/src/lib/config/env.ts +3 -0
  159. package/src/lib/pusher.ts +2 -1
  160. package/src/models/class.ts +10 -1
  161. package/src/models/comment.ts +1 -0
  162. package/src/models/conversation.ts +12 -0
  163. package/src/models/worksheet.ts +39 -5
  164. package/src/pipelines/gradeWorksheet.ts +66 -15
  165. package/src/routers/_app.ts +4 -0
  166. package/src/routers/admin.ts +70 -0
  167. package/src/routers/announcement.ts +4 -3
  168. package/src/routers/assignment.ts +2 -2
  169. package/src/routers/class.ts +3 -0
  170. package/src/routers/comment.ts +5 -0
  171. package/src/routers/discussion.ts +205 -0
  172. package/src/routers/worksheet.ts +2 -0
  173. package/src/seedDatabase.ts +1713 -1361
  174. package/src/services/adminInstitution.ts +140 -0
  175. package/src/services/adminRunJob.ts +46 -0
  176. package/src/services/assignment.ts +19 -8
  177. package/src/services/class.ts +28 -1
  178. package/src/services/comment.ts +22 -0
  179. package/src/services/conversation.ts +9 -4
  180. package/src/services/discussion.ts +701 -0
  181. package/src/services/studentProgress.ts +80 -3
  182. package/src/services/studentProgressInferenceCache.ts +64 -0
  183. package/src/services/worksheet.ts +5 -2
  184. package/src/trpc.ts +15 -0
  185. package/src/utils/adminPanelAccess.ts +46 -0
  186. package/tests/integration/routers/conversation.test.ts +9 -7
  187. package/tests/integration/routers/discussion.test.ts +253 -0
  188. package/tests/unit/services/discussion.test.ts +356 -0
@@ -1,68 +1,115 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4e5c4562-c500-5409-8250-b98dc8416587")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a764ff67-df4a-5c42-b133-d6b43f06c40e")}catch(e){}}();
3
3
  import { prisma } from "./lib/prisma.js";
4
4
  import { hash } from "bcryptjs";
5
5
  import { logger } from "./utils/logger.js";
6
+ import { ensureAIUserExists, getAIUserId } from "./utils/aiUser.js";
7
+ import { AssignmentType, ConversationRole, ConversationType, DiscussionType, FolderScope, JobStatus, PermissionLevel, PermissionPrincipalType, ReactionType, SchoolMembershipRole, SchoolMembershipStatus, UploadStatus, WorksheetQuestionType, } from "@prisma/client";
8
+ // -----------------------------------------------------------------------------
9
+ // Clear
10
+ // -----------------------------------------------------------------------------
6
11
  export async function clearDatabase() {
7
- // Delete in order to respect foreign key constraints
8
- // Delete notifications first (they reference users)
9
- logger.info('Clearing database');
12
+ logger.info("Clearing database");
13
+ // Top-of-the-graph children first.
14
+ await prisma.aiUsageLog.deleteMany();
10
15
  await prisma.notification.deleteMany();
11
- // Delete worksheet-related records
16
+ // Worksheet + question progress (referenced by submissions).
12
17
  await prisma.studentQuestionProgress.deleteMany();
13
18
  await prisma.studentWorksheetResponse.deleteMany();
14
19
  await prisma.worksheetQuestion.deleteMany();
15
20
  await prisma.worksheet.deleteMany();
16
- // Delete reactions (they reference announcements and comments)
21
+ // Reactions and reads (reference announcements/comments/discussions).
17
22
  await prisma.reaction.deleteMany();
18
- // Delete comments (they reference announcements and users)
23
+ await prisma.discussionRead.deleteMany();
24
+ // Discussion tree.
25
+ await prisma.discussionReply.deleteMany();
26
+ await prisma.discussion.deleteMany();
27
+ await prisma.discussionTag.deleteMany();
28
+ // Comments after their reactions are gone.
19
29
  await prisma.comment.deleteMany();
20
- // Delete NewtonChat (they reference submissions and conversations)
30
+ // Newton + Lab chats sit on conversations + submissions.
21
31
  await prisma.newtonChat.deleteMany();
22
- // Delete chat-related records
32
+ await prisma.labChat.deleteMany();
33
+ // Conversation graph.
23
34
  await prisma.mention.deleteMany();
24
35
  await prisma.message.deleteMany();
25
36
  await prisma.conversationMember.deleteMany();
26
- await prisma.labChat.deleteMany();
27
37
  await prisma.conversation.deleteMany();
28
- // Delete other records that reference users
38
+ // Submissions / assignments / announcements / events.
29
39
  await prisma.submission.deleteMany();
30
40
  await prisma.assignment.deleteMany();
31
41
  await prisma.announcement.deleteMany();
32
- await prisma.event.deleteMany();
33
42
  await prisma.attendance.deleteMany();
34
- // Delete class-related records
43
+ await prisma.event.deleteMany();
44
+ // RunJob is referenced by worksheets / messages / progress, all cleared above.
45
+ await prisma.runJob.deleteMany();
46
+ // Class scaffolding.
35
47
  await prisma.section.deleteMany();
36
48
  await prisma.markScheme.deleteMany();
37
49
  await prisma.gradingBoundary.deleteMany();
50
+ // Permissions live on folders; clear before folders.
51
+ await prisma.permission.deleteMany();
52
+ // Folders + files. Files reference folders/assignments/etc. so flush both.
53
+ await prisma.file.deleteMany();
38
54
  await prisma.folder.deleteMany();
55
+ // Classes (need to be gone before departments + tags they reference).
39
56
  await prisma.class.deleteMany();
40
- // Delete user-related records
57
+ // School scaffolding.
58
+ await prisma.tag.deleteMany();
59
+ await prisma.department.deleteMany();
60
+ await prisma.schoolInvite.deleteMany();
61
+ await prisma.schoolMembership.deleteMany();
62
+ // Sessions + profiles.
41
63
  await prisma.session.deleteMany();
42
64
  await prisma.userProfile.deleteMany();
43
- // Delete users first
65
+ // Users.
44
66
  await prisma.user.deleteMany();
45
- // Delete schools (which reference files for logos) - this will cascade delete the file references
67
+ // Schools (cascades remaining school-scoped rows).
46
68
  await prisma.school.deleteMany();
47
- // Delete marketing-related records
69
+ // Marketing forms.
48
70
  await prisma.schoolDevelopementProgram.deleteMany();
49
71
  await prisma.earlyAccessRequest.deleteMany();
50
- // Finally delete all files
51
- await prisma.file.deleteMany();
52
72
  }
53
- // Helper function to generate DiceBear avatar URL
73
+ // -----------------------------------------------------------------------------
74
+ // Tiny helpers
75
+ // -----------------------------------------------------------------------------
54
76
  function getDiceBearAvatar(seed) {
55
77
  return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(seed)}`;
56
78
  }
79
+ function pick(arr) {
80
+ return arr[Math.floor(Math.random() * arr.length)];
81
+ }
82
+ /**
83
+ * Run async work in capped batches. Supabase session-mode poolers cap clients
84
+ * very low (often ≤15), so unbounded `Promise.all` over a list will trip
85
+ * `MaxClientsInSessionMode`. Keep this small.
86
+ */
87
+ async function runInBatches(items, fn, batchSize = 4, delayMs = 50) {
88
+ const results = [];
89
+ for (let i = 0; i < items.length; i += batchSize) {
90
+ const slice = items.slice(i, i + batchSize);
91
+ const slot = await Promise.all(slice.map((item, j) => fn(item, i + j)));
92
+ results.push(...slot);
93
+ if (i + batchSize < items.length && delayMs > 0) {
94
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
95
+ }
96
+ }
97
+ return results;
98
+ }
99
+ function daysFromNow(days, base = new Date()) {
100
+ return new Date(base.getTime() + days * 24 * 60 * 60 * 1000);
101
+ }
102
+ function hoursFromNow(hours, base = new Date()) {
103
+ return new Date(base.getTime() + hours * 60 * 60 * 1000);
104
+ }
57
105
  export async function createUser(email, password, username) {
58
- logger.debug("Creating user", { email, username, password });
59
106
  const hashedPassword = await hash(password, 10);
60
- return await prisma.user.create({
107
+ return prisma.user.create({
61
108
  data: { email, password: hashedPassword, username, verified: true },
62
109
  });
63
110
  }
64
111
  export async function addNotification(userId, title, content) {
65
- return await prisma.notification.create({
112
+ return prisma.notification.create({
66
113
  data: {
67
114
  receiverId: userId,
68
115
  title,
@@ -70,1463 +117,1599 @@ export async function addNotification(userId, title, content) {
70
117
  },
71
118
  });
72
119
  }
120
+ /**
121
+ * Create a fake "uploaded" file record. We don't actually push bytes anywhere —
122
+ * this is just enough to make attachments render in the UI.
123
+ */
124
+ async function createFakeFile(opts) {
125
+ const type = opts.type ?? guessMime(opts.name);
126
+ const size = opts.size ?? Math.floor(50000 + Math.random() * 950000);
127
+ return prisma.file.create({
128
+ data: {
129
+ name: opts.name,
130
+ path: `seed/${crypto.randomUUID()}-${opts.name}`,
131
+ type,
132
+ size,
133
+ uploadStatus: UploadStatus.COMPLETED,
134
+ uploadedAt: new Date(),
135
+ ...(opts.userId && { user: { connect: { id: opts.userId } } }),
136
+ ...(opts.folderId && { folder: { connect: { id: opts.folderId } } }),
137
+ ...(opts.assignmentId && { assignment: { connect: { id: opts.assignmentId } } }),
138
+ ...(opts.submissionId && { submission: { connect: { id: opts.submissionId } } }),
139
+ ...(opts.annotationId && { annotations: { connect: { id: opts.annotationId } } }),
140
+ ...(opts.messageId && { message: { connect: { id: opts.messageId } } }),
141
+ ...(opts.announcementId && { announcement: { connect: { id: opts.announcementId } } }),
142
+ ...(opts.discussionId && { discussion: { connect: { id: opts.discussionId } } }),
143
+ ...(opts.discussionReplyId && {
144
+ discussionReply: { connect: { id: opts.discussionReplyId } },
145
+ }),
146
+ ...(opts.classDraftId && { classDraft: { connect: { id: opts.classDraftId } } }),
147
+ },
148
+ });
149
+ }
150
+ function guessMime(name) {
151
+ const ext = name.split(".").pop()?.toLowerCase();
152
+ switch (ext) {
153
+ case "pdf":
154
+ return "application/pdf";
155
+ case "png":
156
+ return "image/png";
157
+ case "jpg":
158
+ case "jpeg":
159
+ return "image/jpeg";
160
+ case "gif":
161
+ return "image/gif";
162
+ case "webp":
163
+ return "image/webp";
164
+ case "docx":
165
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
166
+ case "xlsx":
167
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
168
+ case "pptx":
169
+ return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
170
+ case "md":
171
+ return "text/markdown";
172
+ case "txt":
173
+ return "text/plain";
174
+ case "csv":
175
+ return "text/csv";
176
+ case "mp4":
177
+ return "video/mp4";
178
+ case "mov":
179
+ return "video/quicktime";
180
+ default:
181
+ return "application/octet-stream";
182
+ }
183
+ }
184
+ // -----------------------------------------------------------------------------
185
+ // Rubric + boundary structures (kept as JSON strings to match `structured`)
186
+ // -----------------------------------------------------------------------------
187
+ const fourLevelRubricLevels = [
188
+ { id: "excellent", name: "Excellent", points: 4, color: "#10B981" },
189
+ { id: "proficient", name: "Proficient", points: 3, color: "#3B82F6" },
190
+ { id: "developing", name: "Developing", points: 2, color: "#F59E0B" },
191
+ { id: "beginning", name: "Beginning", points: 1, color: "#EF4444" },
192
+ ];
193
+ function buildRubric(name, criteria) {
194
+ return JSON.stringify({
195
+ name,
196
+ criteria: criteria.map((c) => ({
197
+ ...c,
198
+ levels: fourLevelRubricLevels.map((l) => ({
199
+ ...l,
200
+ description: `${l.name} performance on ${c.title.toLowerCase()}.`,
201
+ })),
202
+ })),
203
+ });
204
+ }
205
+ const apGradingScale = JSON.stringify({
206
+ name: "AP Grading Scale",
207
+ boundaries: [
208
+ { id: "a-plus", grade: "A+", minPercentage: 97, maxPercentage: 100, color: "#059669" },
209
+ { id: "a", grade: "A", minPercentage: 93, maxPercentage: 96, color: "#10B981" },
210
+ { id: "a-minus", grade: "A-", minPercentage: 90, maxPercentage: 92, color: "#34D399" },
211
+ { id: "b-plus", grade: "B+", minPercentage: 87, maxPercentage: 89, color: "#1D4ED8" },
212
+ { id: "b", grade: "B", minPercentage: 83, maxPercentage: 86, color: "#3B82F6" },
213
+ { id: "b-minus", grade: "B-", minPercentage: 80, maxPercentage: 82, color: "#60A5FA" },
214
+ { id: "c-plus", grade: "C+", minPercentage: 77, maxPercentage: 79, color: "#D97706" },
215
+ { id: "c", grade: "C", minPercentage: 73, maxPercentage: 76, color: "#F59E0B" },
216
+ { id: "c-minus", grade: "C-", minPercentage: 70, maxPercentage: 72, color: "#FBBF24" },
217
+ { id: "d", grade: "D", minPercentage: 60, maxPercentage: 69, color: "#F87171" },
218
+ { id: "f", grade: "F", minPercentage: 0, maxPercentage: 59, color: "#EF4444" },
219
+ ],
220
+ });
221
+ // -----------------------------------------------------------------------------
222
+ // Seed
223
+ // -----------------------------------------------------------------------------
73
224
  export const seedDatabase = async () => {
74
225
  await clearDatabase();
75
- logger.info('Cleared database');
76
- // Create comprehensive demo data for teacher demo video
77
- // 1. Create School
78
- const demoFile = await prisma.file.create({
226
+ logger.info("Cleared database");
227
+ // The AI assistant uses a fixed user id (`AI_ASSISTANT`) and is the sender
228
+ // on every AI-generated message. clearDatabase wipes it along with everyone
229
+ // else, so recreate it before we start producing AI-authored content.
230
+ await ensureAIUserExists();
231
+ const aiUserId = getAIUserId();
232
+ const now = new Date();
233
+ // ---------------------------------------------------------------------
234
+ // 1. School + logo
235
+ // ---------------------------------------------------------------------
236
+ const schoolLogo = await prisma.file.create({
79
237
  data: {
80
- name: 'school_logo.png',
81
- path: 'logos/demo_school.png',
82
- type: 'image/png',
83
- size: 15000,
84
- }
238
+ name: "school_logo.png",
239
+ path: "logos/riverside.png",
240
+ type: "image/png",
241
+ size: 18240,
242
+ uploadStatus: UploadStatus.COMPLETED,
243
+ },
85
244
  });
86
245
  const school = await prisma.school.create({
87
246
  data: {
88
- name: 'Riverside High School',
89
- logoId: demoFile.id,
90
- subdomain: 'riverside',
91
- }
247
+ name: "Riverside High School",
248
+ description: "A demo school used to showcase Studious end-to-end.",
249
+ subdomain: "riverside",
250
+ logoId: schoolLogo.id,
251
+ },
92
252
  });
93
- // 2. Create Teachers
94
- const teachers = await Promise.all([
95
- createUser('sarah.johnson@riverside.edu', 'demo123', 'sarah.johnson'),
96
- createUser('michael.chen@riverside.edu', 'demo123', 'michael.chen'),
97
- createUser('emma.davis@riverside.edu', 'demo123', 'emma.davis'),
253
+ // ---------------------------------------------------------------------
254
+ // 2. Departments
255
+ // ---------------------------------------------------------------------
256
+ const [scienceDept, mathDept, englishDept] = await Promise.all([
257
+ prisma.department.create({
258
+ data: {
259
+ schoolId: school.id,
260
+ name: "Science",
261
+ description: "Biology, Chemistry, Physics, Environmental Science.",
262
+ color: "#10B981",
263
+ },
264
+ }),
265
+ prisma.department.create({
266
+ data: {
267
+ schoolId: school.id,
268
+ name: "Mathematics",
269
+ description: "Algebra, Geometry, Calculus, Statistics.",
270
+ color: "#3B82F6",
271
+ },
272
+ }),
273
+ prisma.department.create({
274
+ data: {
275
+ schoolId: school.id,
276
+ name: "English",
277
+ description: "Literature, Composition, Creative Writing.",
278
+ color: "#8B5CF6",
279
+ },
280
+ }),
98
281
  ]);
99
- // 3. Create Students (realistic names)
100
- const students = await Promise.all([
101
- createUser('alex.martinez@student.rverside.eidu', 'student123', 'alex.martinez'),
102
- createUser('sophia.williams@student.riverside.edu', 'student123', 'sophia.williams'),
103
- createUser('james.brown@student.riverside.edu', 'student123', 'james.brown'),
104
- createUser('olivia.taylor@student.riverside.edu', 'student123', 'olivia.taylor'),
105
- createUser('ethan.anderson@student.riverside.edu', 'student123', 'ethan.anderson'),
106
- createUser('ava.thomas@student.riverside.edu', 'student123', 'ava.thomas'),
107
- createUser('noah.jackson@student.riverside.edu', 'student123', 'noah.jackson'),
108
- createUser('isabella.white@student.riverside.edu', 'student123', 'isabella.white'),
109
- createUser('liam.harris@student.riverside.edu', 'student123', 'liam.harris'),
110
- createUser('mia.clark@student.riverside.edu', 'student123', 'mia.clark'),
111
- createUser('lucas.lewis@student.riverside.edu', 'student123', 'lucas.lewis'),
112
- createUser('charlotte.walker@student.riverside.edu', 'student123', 'charlotte.walker'),
282
+ // ---------------------------------------------------------------------
283
+ // 3. Tags (school-wide + department-scoped)
284
+ // ---------------------------------------------------------------------
285
+ const [tagAP, tagHonors, tagNeedsSupport, tagGifted, tagSeniorClass] = await Promise.all([
286
+ prisma.tag.create({
287
+ data: { schoolId: school.id, name: "AP", color: "#DC2626" },
288
+ }),
289
+ prisma.tag.create({
290
+ data: { schoolId: school.id, name: "Honors", color: "#7C3AED" },
291
+ }),
292
+ prisma.tag.create({
293
+ data: { schoolId: school.id, name: "Needs Support", color: "#F59E0B" },
294
+ }),
295
+ prisma.tag.create({
296
+ data: { schoolId: school.id, name: "Gifted & Talented", color: "#0EA5E9" },
297
+ }),
298
+ prisma.tag.create({
299
+ data: { schoolId: school.id, name: "Class of 2026", color: "#10B981" },
300
+ }),
113
301
  ]);
114
- // 4. Create User Profiles with DiceBear avatars
302
+ // ---------------------------------------------------------------------
303
+ // 4. Users — teachers + admin + students
304
+ // ---------------------------------------------------------------------
305
+ const admin = await createUser("admin@riverside.edu", "admin123", "principal.rivera");
306
+ const teachers = await runInBatches([
307
+ { email: "sarah.johnson@riverside.edu", username: "sarah.johnson" },
308
+ { email: "michael.chen@riverside.edu", username: "michael.chen" },
309
+ { email: "emma.davis@riverside.edu", username: "emma.davis" },
310
+ ], (t) => createUser(t.email, "demo123", t.username));
311
+ const studentSeeds = [
312
+ { email: "alex.martinez@student.riverside.edu", username: "alex.martinez", name: "Alex Martinez" },
313
+ { email: "sophia.williams@student.riverside.edu", username: "sophia.williams", name: "Sophia Williams" },
314
+ { email: "james.brown@student.riverside.edu", username: "james.brown", name: "James Brown" },
315
+ { email: "olivia.taylor@student.riverside.edu", username: "olivia.taylor", name: "Olivia Taylor" },
316
+ { email: "ethan.anderson@student.riverside.edu", username: "ethan.anderson", name: "Ethan Anderson" },
317
+ { email: "ava.thomas@student.riverside.edu", username: "ava.thomas", name: "Ava Thomas" },
318
+ { email: "noah.jackson@student.riverside.edu", username: "noah.jackson", name: "Noah Jackson" },
319
+ { email: "isabella.white@student.riverside.edu", username: "isabella.white", name: "Isabella White" },
320
+ { email: "liam.harris@student.riverside.edu", username: "liam.harris", name: "Liam Harris" },
321
+ { email: "mia.clark@student.riverside.edu", username: "mia.clark", name: "Mia Clark" },
322
+ { email: "lucas.lewis@student.riverside.edu", username: "lucas.lewis", name: "Lucas Lewis" },
323
+ { email: "charlotte.walker@student.riverside.edu", username: "charlotte.walker", name: "Charlotte Walker" },
324
+ ];
325
+ const students = await runInBatches(studentSeeds, (s) => createUser(s.email, "student123", s.username));
326
+ // Attach everyone to the school via the canonical schoolId pointer.
327
+ await prisma.user.updateMany({
328
+ where: { id: { in: [admin.id, ...teachers.map((t) => t.id), ...students.map((s) => s.id)] } },
329
+ data: { schoolId: school.id },
330
+ });
331
+ // ---------------------------------------------------------------------
332
+ // 5. SchoolMemberships
333
+ // ---------------------------------------------------------------------
334
+ await prisma.schoolMembership.createMany({
335
+ data: [
336
+ {
337
+ schoolId: school.id,
338
+ userId: admin.id,
339
+ role: SchoolMembershipRole.ADMIN,
340
+ status: SchoolMembershipStatus.ACTIVE,
341
+ },
342
+ ...teachers.map((t) => ({
343
+ schoolId: school.id,
344
+ userId: t.id,
345
+ role: SchoolMembershipRole.TEACHER,
346
+ status: SchoolMembershipStatus.ACTIVE,
347
+ })),
348
+ ...students.map((s) => ({
349
+ schoolId: school.id,
350
+ userId: s.id,
351
+ role: SchoolMembershipRole.STUDENT,
352
+ status: SchoolMembershipStatus.ACTIVE,
353
+ })),
354
+ ],
355
+ });
356
+ // A couple of pending invites for the demo.
115
357
  await Promise.all([
116
- prisma.userProfile.create({
358
+ prisma.schoolInvite.create({
117
359
  data: {
118
- userId: teachers[0].id,
119
- displayName: 'Dr. Sarah Johnson',
120
- bio: 'Biology teacher with 15 years of experience. Passionate about making science accessible to all students.',
121
- location: 'Riverside, CA',
122
- website: 'https://sarahjohnson-bio.com',
123
- profilePicture: getDiceBearAvatar(teachers[0].username),
124
- }
360
+ schoolId: school.id,
361
+ invitedById: admin.id,
362
+ email: "new.teacher@riverside.edu",
363
+ role: SchoolMembershipRole.TEACHER,
364
+ token: `invite-${crypto.randomUUID()}`,
365
+ expiresAt: daysFromNow(7, now),
366
+ },
125
367
  }),
126
- prisma.userProfile.create({
368
+ prisma.schoolInvite.create({
127
369
  data: {
128
- userId: teachers[1].id,
129
- displayName: 'Mr. Michael Chen',
130
- bio: 'Mathematics teacher and department head. Specializes in AP Calculus and Statistics.',
131
- location: 'Riverside, CA',
132
- profilePicture: getDiceBearAvatar(teachers[1].username),
133
- }
370
+ schoolId: school.id,
371
+ invitedById: admin.id,
372
+ email: "transfer.student@riverside.edu",
373
+ role: SchoolMembershipRole.STUDENT,
374
+ token: `invite-${crypto.randomUUID()}`,
375
+ expiresAt: daysFromNow(14, now),
376
+ },
134
377
  }),
135
- prisma.userProfile.create({
378
+ ]);
379
+ // Hook teachers into their departments + add a few student tags.
380
+ await prisma.user.update({
381
+ where: { id: teachers[0].id },
382
+ data: { departments: { connect: { id: scienceDept.id } } },
383
+ });
384
+ await prisma.user.update({
385
+ where: { id: teachers[1].id },
386
+ data: { departments: { connect: { id: mathDept.id } } },
387
+ });
388
+ await prisma.user.update({
389
+ where: { id: teachers[2].id },
390
+ data: { departments: { connect: { id: englishDept.id } } },
391
+ });
392
+ // Tag a slice of students for the demo.
393
+ await Promise.all([
394
+ prisma.user.update({
395
+ where: { id: students[0].id },
396
+ data: { tags: { connect: [{ id: tagAP.id }, { id: tagSeniorClass.id }] } },
397
+ }),
398
+ prisma.user.update({
399
+ where: { id: students[1].id },
136
400
  data: {
137
- userId: teachers[2].id,
138
- displayName: 'Ms. Emma Davis',
139
- bio: 'English Literature teacher. Loves fostering creative writing and critical thinking.',
140
- location: 'Riverside, CA',
141
- profilePicture: getDiceBearAvatar(teachers[2].username),
142
- }
401
+ tags: { connect: [{ id: tagAP.id }, { id: tagGifted.id }, { id: tagSeniorClass.id }] },
402
+ },
403
+ }),
404
+ prisma.user.update({
405
+ where: { id: students[4].id },
406
+ data: { tags: { connect: [{ id: tagNeedsSupport.id }] } },
407
+ }),
408
+ prisma.user.update({
409
+ where: { id: students[6].id },
410
+ data: { tags: { connect: [{ id: tagHonors.id }] } },
143
411
  }),
144
412
  ]);
145
- // Add profiles for all students with DiceBear avatars
146
- await Promise.all(students.map((student, index) => {
147
- const names = [
148
- 'Alex Martinez', 'Sophia Williams', 'James Brown', 'Olivia Taylor',
149
- 'Ethan Anderson', 'Ava Thomas', 'Noah Jackson', 'Isabella White',
150
- 'Liam Harris', 'Mia Clark', 'Lucas Lewis', 'Charlotte Walker'
151
- ];
152
- return prisma.userProfile.create({
153
- data: {
413
+ // ---------------------------------------------------------------------
414
+ // 6. Profiles. createMany is cheap and avoids 16 parallel inserts.
415
+ // ---------------------------------------------------------------------
416
+ await prisma.userProfile.createMany({
417
+ data: [
418
+ {
419
+ userId: admin.id,
420
+ displayName: "Principal Rivera",
421
+ bio: "School administrator, oversees academic operations at Riverside High.",
422
+ location: "Riverside, CA",
423
+ profilePicture: getDiceBearAvatar(admin.username),
424
+ },
425
+ {
426
+ userId: teachers[0].id,
427
+ displayName: "Dr. Sarah Johnson",
428
+ bio: "Biology teacher with 15 years of experience.",
429
+ location: "Riverside, CA",
430
+ website: "https://sarahjohnson-bio.example",
431
+ profilePicture: getDiceBearAvatar(teachers[0].username),
432
+ },
433
+ {
434
+ userId: teachers[1].id,
435
+ displayName: "Mr. Michael Chen",
436
+ bio: "Math department head. AP Calculus + Statistics.",
437
+ location: "Riverside, CA",
438
+ profilePicture: getDiceBearAvatar(teachers[1].username),
439
+ },
440
+ {
441
+ userId: teachers[2].id,
442
+ displayName: "Ms. Emma Davis",
443
+ bio: "English Literature. Loves creative writing.",
444
+ location: "Riverside, CA",
445
+ profilePicture: getDiceBearAvatar(teachers[2].username),
446
+ },
447
+ ...students.map((student, i) => ({
154
448
  userId: student.id,
155
- displayName: names[index] || student.username,
156
- bio: index < 6 ? `Grade 11 student at Riverside High School.` : undefined,
449
+ displayName: studentSeeds[i].name,
450
+ bio: i < 6 ? "Grade 11 student at Riverside High." : undefined,
157
451
  profilePicture: getDiceBearAvatar(student.username),
158
- }
159
- });
160
- }));
161
- // 5. Create Classes with realistic subjects
452
+ })),
453
+ ],
454
+ });
455
+ // ---------------------------------------------------------------------
456
+ // 7. Storage roots (school + personal). Class roots get created with classes.
457
+ // ---------------------------------------------------------------------
458
+ const schoolStorageRoot = await prisma.folder.create({
459
+ data: {
460
+ name: "Cloud Storage",
461
+ scope: FolderScope.SCHOOL_STORAGE,
462
+ color: "#3B82F6",
463
+ schoolStorage: { connect: { id: school.id } },
464
+ },
465
+ });
466
+ // Personal storage for each teacher.
467
+ const personalRoots = await Promise.all([admin, ...teachers].map((u) => prisma.folder.create({
468
+ data: {
469
+ name: "My Files",
470
+ scope: FolderScope.PERSONAL,
471
+ color: "#6366F1",
472
+ personalOwner: { connect: { id: u.id } },
473
+ },
474
+ })));
475
+ // Some files in the school-wide storage.
476
+ await createFakeFile({
477
+ name: "Faculty Handbook 2026.pdf",
478
+ userId: admin.id,
479
+ folderId: schoolStorageRoot.id,
480
+ size: 1240000,
481
+ });
482
+ await createFakeFile({
483
+ name: "Emergency Procedures.pdf",
484
+ userId: admin.id,
485
+ folderId: schoolStorageRoot.id,
486
+ size: 520000,
487
+ });
488
+ await createFakeFile({
489
+ name: "Bell Schedule (2026-2027).pdf",
490
+ userId: admin.id,
491
+ folderId: schoolStorageRoot.id,
492
+ size: 84000,
493
+ });
494
+ // A personal subfolder + files for Sarah.
495
+ const sarahLessonPlans = await prisma.folder.create({
496
+ data: {
497
+ name: "Lesson Plans",
498
+ scope: FolderScope.PERSONAL,
499
+ color: "#10B981",
500
+ parentFolder: { connect: { id: personalRoots[1].id } },
501
+ },
502
+ });
503
+ await Promise.all([
504
+ createFakeFile({
505
+ name: "Cell Membrane Lecture.pptx",
506
+ userId: teachers[0].id,
507
+ folderId: sarahLessonPlans.id,
508
+ }),
509
+ createFakeFile({
510
+ name: "Mitosis vs Meiosis.md",
511
+ userId: teachers[0].id,
512
+ folderId: sarahLessonPlans.id,
513
+ }),
514
+ createFakeFile({
515
+ name: "Photosynthesis Diagram.png",
516
+ userId: teachers[0].id,
517
+ folderId: sarahLessonPlans.id,
518
+ }),
519
+ ]);
520
+ // Sharing the faculty handbook with all teachers via a permission grant.
521
+ await prisma.permission.create({
522
+ data: {
523
+ folderId: schoolStorageRoot.id,
524
+ principalType: PermissionPrincipalType.SCHOOL,
525
+ schoolId: school.id,
526
+ level: PermissionLevel.READ,
527
+ grantedById: admin.id,
528
+ },
529
+ });
530
+ // ---------------------------------------------------------------------
531
+ // 8. Classes + their class-storage roots
532
+ // ---------------------------------------------------------------------
162
533
  const biologyClass = await prisma.class.create({
163
534
  data: {
164
- name: 'AP Biology',
165
- subject: 'Biology',
166
- section: 'Period 3',
167
- color: '#10B981',
168
- syllabus: 'Advanced Placement Biology covering molecular biology, genetics, evolution, ecology, and more.',
535
+ name: "AP Biology",
536
+ subject: "Biology",
537
+ section: "Period 3",
538
+ color: "#10B981",
539
+ syllabus: "Molecular biology, genetics, evolution, ecology.",
169
540
  schoolId: school.id,
541
+ departmentId: scienceDept.id,
542
+ tags: { connect: [{ id: tagAP.id }] },
170
543
  teachers: { connect: { id: teachers[0].id } },
171
- students: { connect: students.slice(0, 8).map(s => ({ id: s.id })) },
544
+ students: { connect: students.slice(0, 8).map((s) => ({ id: s.id })) },
172
545
  },
173
546
  });
174
547
  const mathClass = await prisma.class.create({
175
548
  data: {
176
- name: 'AP Calculus BC',
177
- subject: 'Mathematics',
178
- section: 'Period 2',
179
- color: '#3B82F6',
180
- syllabus: 'Advanced calculus including limits, derivatives, integrals, and series.',
549
+ name: "AP Calculus BC",
550
+ subject: "Mathematics",
551
+ section: "Period 2",
552
+ color: "#3B82F6",
553
+ syllabus: "Limits, derivatives, integrals, series, parametric and polar.",
181
554
  schoolId: school.id,
555
+ departmentId: mathDept.id,
556
+ tags: { connect: [{ id: tagAP.id }, { id: tagHonors.id }] },
182
557
  teachers: { connect: { id: teachers[1].id } },
183
- students: { connect: students.slice(4, 12).map(s => ({ id: s.id })) },
558
+ students: { connect: students.slice(4, 12).map((s) => ({ id: s.id })) },
184
559
  },
185
560
  });
186
561
  const englishClass = await prisma.class.create({
187
562
  data: {
188
- name: 'English Literature',
189
- subject: 'English',
190
- section: 'Period 1',
191
- color: '#8B5CF6',
192
- syllabus: 'Study of classic and contemporary literature with focus on analysis and writing.',
563
+ name: "English Literature",
564
+ subject: "English",
565
+ section: "Period 1",
566
+ color: "#8B5CF6",
567
+ syllabus: "Close reading, rhetorical analysis, and academic writing.",
193
568
  schoolId: school.id,
569
+ departmentId: englishDept.id,
570
+ tags: { connect: [{ id: tagHonors.id }] },
194
571
  teachers: { connect: { id: teachers[2].id } },
195
- students: { connect: students.slice(0, 10).map(s => ({ id: s.id })) },
572
+ students: { connect: students.slice(0, 10).map((s) => ({ id: s.id })) },
196
573
  },
197
574
  });
198
- // 6. Create Sections for organization
199
- const bioSections = await Promise.all([
200
- prisma.section.create({
201
- data: { name: 'Unit 1: Chemistry of Life', classId: biologyClass.id, color: '#EF4444', order: 1 }
575
+ const classes = [biologyClass, mathClass, englishClass];
576
+ // Per-class storage root + a few subfolders + draft attachments.
577
+ const classFolders = await Promise.all(classes.map((c) => prisma.folder.create({
578
+ data: {
579
+ name: "Class Files",
580
+ scope: FolderScope.CLASS,
581
+ color: c.color ?? "#3B82F6",
582
+ class: { connect: { id: c.id } },
583
+ },
584
+ })));
585
+ // Biology subfolders + files.
586
+ const bioHandouts = await prisma.folder.create({
587
+ data: {
588
+ name: "Handouts",
589
+ scope: FolderScope.CLASS,
590
+ color: "#10B981",
591
+ parentFolder: { connect: { id: classFolders[0].id } },
592
+ },
593
+ });
594
+ const bioLabs = await prisma.folder.create({
595
+ data: {
596
+ name: "Labs",
597
+ scope: FolderScope.CLASS,
598
+ color: "#0EA5E9",
599
+ parentFolder: { connect: { id: classFolders[0].id } },
600
+ },
601
+ });
602
+ await Promise.all([
603
+ createFakeFile({
604
+ name: "Cell Structure Reading.pdf",
605
+ userId: teachers[0].id,
606
+ folderId: bioHandouts.id,
607
+ size: 740000,
202
608
  }),
203
- prisma.section.create({
204
- data: { name: 'Unit 2: Cell Structure', classId: biologyClass.id, color: '#F97316', order: 2 }
609
+ createFakeFile({
610
+ name: "Microscope Setup.pdf",
611
+ userId: teachers[0].id,
612
+ folderId: bioLabs.id,
205
613
  }),
206
- prisma.section.create({
207
- data: { name: 'Unit 3: Cellular Processes', classId: biologyClass.id, color: '#EAB308', order: 3 }
614
+ createFakeFile({
615
+ name: "Onion Cell Slides.jpg",
616
+ userId: teachers[0].id,
617
+ folderId: bioLabs.id,
208
618
  }),
209
- prisma.section.create({
210
- data: { name: 'Unit 4: Genetics', classId: biologyClass.id, color: '#22C55E', order: 4 }
619
+ createFakeFile({
620
+ name: "Syllabus AP Bio 2026.pdf",
621
+ userId: teachers[0].id,
622
+ folderId: classFolders[0].id,
211
623
  }),
212
624
  ]);
213
- const mathSections = await Promise.all([
214
- prisma.section.create({
215
- data: { name: 'Limits and Continuity', classId: mathClass.id, color: '#3B82F6', order: 1 }
625
+ // Math subfolders + files.
626
+ const mathPracticeFolder = await prisma.folder.create({
627
+ data: {
628
+ name: "Practice Problems",
629
+ scope: FolderScope.CLASS,
630
+ color: "#6366F1",
631
+ parentFolder: { connect: { id: classFolders[1].id } },
632
+ },
633
+ });
634
+ await Promise.all([
635
+ createFakeFile({
636
+ name: "AP Calc BC Syllabus.pdf",
637
+ userId: teachers[1].id,
638
+ folderId: classFolders[1].id,
216
639
  }),
217
- prisma.section.create({
218
- data: { name: 'Derivatives', classId: mathClass.id, color: '#6366F1', order: 2 }
640
+ createFakeFile({
641
+ name: "Integration Cheatsheet.pdf",
642
+ userId: teachers[1].id,
643
+ folderId: mathPracticeFolder.id,
219
644
  }),
220
- prisma.section.create({
221
- data: { name: 'Integrals', classId: mathClass.id, color: '#8B5CF6', order: 3 }
645
+ createFakeFile({
646
+ name: "Past AP Exam 2024.pdf",
647
+ userId: teachers[1].id,
648
+ folderId: mathPracticeFolder.id,
649
+ size: 2100000,
222
650
  }),
223
651
  ]);
224
- // 7. Create mark schemes first
225
- const bioMarkScheme = await prisma.markScheme.create({
226
- data: {
227
- classId: biologyClass.id,
228
- structured: JSON.stringify({
229
- name: 'Biology Assessment Rubric',
230
- criteria: [
231
- {
232
- id: 'scientific-accuracy',
233
- title: 'Scientific Accuracy',
234
- description: 'Correct use of scientific terminology and concepts',
235
- levels: [
236
- {
237
- id: 'excellent',
238
- name: 'Excellent',
239
- description: 'All scientific terms and concepts used correctly with precision',
240
- points: 4,
241
- color: '#10B981'
242
- },
243
- {
244
- id: 'proficient',
245
- name: 'Proficient',
246
- description: 'Most scientific terms and concepts used correctly',
247
- points: 3,
248
- color: '#3B82F6'
249
- },
250
- {
251
- id: 'developing',
252
- name: 'Developing',
253
- description: 'Some scientific terms used correctly, minor errors present',
254
- points: 2,
255
- color: '#F59E0B'
256
- },
257
- {
258
- id: 'beginning',
259
- name: 'Beginning',
260
- description: 'Limited use of scientific terminology, significant errors',
261
- points: 1,
262
- color: '#EF4444'
263
- }
264
- ]
265
- },
266
- {
267
- id: 'analysis-reasoning',
268
- title: 'Analysis & Reasoning',
269
- description: 'Quality of analysis and logical reasoning',
270
- levels: [
271
- {
272
- id: 'excellent',
273
- name: 'Excellent',
274
- description: 'Clear, sophisticated analysis with logical connections',
275
- points: 4,
276
- color: '#10B981'
277
- },
278
- {
279
- id: 'proficient',
280
- name: 'Proficient',
281
- description: 'Good analysis with mostly logical reasoning',
282
- points: 3,
283
- color: '#3B82F6'
284
- },
285
- {
286
- id: 'developing',
287
- name: 'Developing',
288
- description: 'Basic analysis with some logical gaps',
289
- points: 2,
290
- color: '#F59E0B'
291
- },
292
- {
293
- id: 'beginning',
294
- name: 'Beginning',
295
- description: 'Limited analysis, unclear reasoning',
296
- points: 1,
297
- color: '#EF4444'
298
- }
299
- ]
300
- },
301
- {
302
- id: 'communication',
303
- title: 'Communication',
304
- description: 'Clear writing and proper formatting',
305
- levels: [
306
- {
307
- id: 'excellent',
308
- name: 'Excellent',
309
- description: 'Clear, well-organized writing with proper format',
310
- points: 4,
311
- color: '#10B981'
312
- },
313
- {
314
- id: 'proficient',
315
- name: 'Proficient',
316
- description: 'Generally clear writing with good organization',
317
- points: 3,
318
- color: '#3B82F6'
319
- },
320
- {
321
- id: 'developing',
322
- name: 'Developing',
323
- description: 'Adequate writing with some organizational issues',
324
- points: 2,
325
- color: '#F59E0B'
326
- },
327
- {
328
- id: 'beginning',
329
- name: 'Beginning',
330
- description: 'Unclear writing, poor organization',
331
- points: 1,
332
- color: '#EF4444'
333
- }
334
- ]
335
- },
336
- {
337
- id: 'evidence-examples',
338
- title: 'Evidence & Examples',
339
- description: 'Use of relevant examples and evidence',
340
- levels: [
341
- {
342
- id: 'excellent',
343
- name: 'Excellent',
344
- description: 'Strong, relevant examples that enhance understanding',
345
- points: 4,
346
- color: '#10B981'
347
- },
348
- {
349
- id: 'proficient',
350
- name: 'Proficient',
351
- description: 'Good examples that support main points',
352
- points: 3,
353
- color: '#3B82F6'
354
- },
355
- {
356
- id: 'developing',
357
- name: 'Developing',
358
- description: 'Some examples provided, may lack relevance',
359
- points: 2,
360
- color: '#F59E0B'
361
- },
362
- {
363
- id: 'beginning',
364
- name: 'Beginning',
365
- description: 'Few or no relevant examples provided',
366
- points: 1,
367
- color: '#EF4444'
368
- }
369
- ]
370
- }
371
- ]
372
- })
373
- }
652
+ // English class folder file.
653
+ await createFakeFile({
654
+ name: "Reading List Q1.pdf",
655
+ userId: teachers[2].id,
656
+ folderId: classFolders[2].id,
374
657
  });
375
- const mathMarkScheme = await prisma.markScheme.create({
376
- data: {
377
- classId: mathClass.id,
378
- structured: JSON.stringify({
379
- name: 'Mathematics Assessment Rubric',
380
- criteria: [
381
- {
382
- id: 'mathematical-accuracy',
383
- title: 'Mathematical Accuracy',
384
- description: 'Correct calculations and mathematical procedures',
385
- levels: [
386
- {
387
- id: 'excellent',
388
- name: 'Excellent',
389
- description: 'All calculations correct, proper mathematical procedures',
390
- points: 4,
391
- color: '#10B981'
392
- },
393
- {
394
- id: 'proficient',
395
- name: 'Proficient',
396
- description: 'Most calculations correct, minor computational errors',
397
- points: 3,
398
- color: '#3B82F6'
399
- },
400
- {
401
- id: 'developing',
402
- name: 'Developing',
403
- description: 'Some calculations correct, several errors present',
404
- points: 2,
405
- color: '#F59E0B'
406
- },
407
- {
408
- id: 'beginning',
409
- name: 'Beginning',
410
- description: 'Many calculation errors, incorrect procedures',
411
- points: 1,
412
- color: '#EF4444'
413
- }
414
- ]
415
- },
416
- {
417
- id: 'problem-solving',
418
- title: 'Problem-Solving Strategy',
419
- description: 'Appropriate method selection and setup',
420
- levels: [
421
- {
422
- id: 'excellent',
423
- name: 'Excellent',
424
- description: 'Optimal strategy chosen, excellent problem setup',
425
- points: 4,
426
- color: '#10B981'
427
- },
428
- {
429
- id: 'proficient',
430
- name: 'Proficient',
431
- description: 'Good strategy, appropriate problem approach',
432
- points: 3,
433
- color: '#3B82F6'
434
- },
435
- {
436
- id: 'developing',
437
- name: 'Developing',
438
- description: 'Adequate strategy, some setup issues',
439
- points: 2,
440
- color: '#F59E0B'
441
- },
442
- {
443
- id: 'beginning',
444
- name: 'Beginning',
445
- description: 'Poor strategy choice, unclear setup',
446
- points: 1,
447
- color: '#EF4444'
448
- }
449
- ]
450
- },
451
- {
452
- id: 'work-shown',
453
- title: 'Work Shown',
454
- description: 'Clear step-by-step work and explanations',
455
- levels: [
456
- {
457
- id: 'excellent',
458
- name: 'Excellent',
459
- description: 'All work clearly shown with detailed explanations',
460
- points: 4,
461
- color: '#10B981'
462
- },
463
- {
464
- id: 'proficient',
465
- name: 'Proficient',
466
- description: 'Most work shown, good explanations',
467
- points: 3,
468
- color: '#3B82F6'
469
- },
470
- {
471
- id: 'developing',
472
- name: 'Developing',
473
- description: 'Some work shown, limited explanations',
474
- points: 2,
475
- color: '#F59E0B'
476
- },
477
- {
478
- id: 'beginning',
479
- name: 'Beginning',
480
- description: 'Little work shown, unclear process',
481
- points: 1,
482
- color: '#EF4444'
483
- }
484
- ]
485
- },
486
- {
487
- id: 'final-answer',
488
- title: 'Final Answer',
489
- description: 'Correct final answer with proper units/notation',
490
- levels: [
491
- {
492
- id: 'excellent',
493
- name: 'Excellent',
494
- description: 'Correct answer with proper units and notation',
495
- points: 4,
496
- color: '#10B981'
497
- },
498
- {
499
- id: 'proficient',
500
- name: 'Proficient',
501
- description: 'Correct answer, minor notation issues',
502
- points: 3,
503
- color: '#3B82F6'
504
- },
505
- {
506
- id: 'developing',
507
- name: 'Developing',
508
- description: 'Answer close to correct, some notation errors',
509
- points: 2,
510
- color: '#F59E0B'
511
- },
512
- {
513
- id: 'beginning',
514
- name: 'Beginning',
515
- description: 'Incorrect answer or missing units',
516
- points: 1,
517
- color: '#EF4444'
518
- }
519
- ]
520
- }
521
- ]
522
- })
523
- }
658
+ // A class draft file (attached to class drafts list, not yet on an assignment).
659
+ await createFakeFile({
660
+ name: "Draft - Genetics Worksheet.docx",
661
+ userId: teachers[0].id,
662
+ classDraftId: biologyClass.id,
524
663
  });
525
- // 8. Create grading boundaries
526
- const mathGradingBoundary = await prisma.gradingBoundary.create({
664
+ // Grant the science department READ on Bio class folder.
665
+ await prisma.permission.create({
527
666
  data: {
528
- classId: mathClass.id,
529
- structured: JSON.stringify({
530
- name: 'AP Calculus BC Grading Scale',
531
- boundaries: [
532
- {
533
- id: 'a-plus',
534
- grade: 'A+',
535
- minPercentage: 97,
536
- maxPercentage: 100,
537
- description: 'Exceptional mastery of all concepts',
538
- color: '#059669'
539
- },
540
- {
541
- id: 'a',
542
- grade: 'A',
543
- minPercentage: 93,
544
- maxPercentage: 96,
545
- description: 'Strong mastery of concepts',
546
- color: '#10B981'
547
- },
548
- {
549
- id: 'a-minus',
550
- grade: 'A-',
551
- minPercentage: 90,
552
- maxPercentage: 92,
553
- description: 'Good mastery with minor gaps',
554
- color: '#34D399'
555
- },
556
- {
557
- id: 'b-plus',
558
- grade: 'B+',
559
- minPercentage: 87,
560
- maxPercentage: 89,
561
- description: 'Above average understanding',
562
- color: '#1D4ED8'
563
- },
564
- {
565
- id: 'b',
566
- grade: 'B',
567
- minPercentage: 83,
568
- maxPercentage: 86,
569
- description: 'Solid understanding of most concepts',
570
- color: '#3B82F6'
571
- },
572
- {
573
- id: 'b-minus',
574
- grade: 'B-',
575
- minPercentage: 80,
576
- maxPercentage: 82,
577
- description: 'Adequate understanding with some gaps',
578
- color: '#60A5FA'
579
- },
580
- {
581
- id: 'c-plus',
582
- grade: 'C+',
583
- minPercentage: 77,
584
- maxPercentage: 79,
585
- description: 'Basic understanding, needs improvement',
586
- color: '#D97706'
587
- },
588
- {
589
- id: 'c',
590
- grade: 'C',
591
- minPercentage: 73,
592
- maxPercentage: 76,
593
- description: 'Minimal acceptable understanding',
594
- color: '#F59E0B'
595
- },
596
- {
597
- id: 'c-minus',
598
- grade: 'C-',
599
- minPercentage: 70,
600
- maxPercentage: 72,
601
- description: 'Below average, significant gaps',
602
- color: '#FBBF24'
603
- },
604
- {
605
- id: 'd',
606
- grade: 'D',
607
- minPercentage: 60,
608
- maxPercentage: 69,
609
- description: 'Poor understanding, major deficiencies',
610
- color: '#F87171'
611
- },
612
- {
613
- id: 'f',
614
- grade: 'F',
615
- minPercentage: 0,
616
- maxPercentage: 59,
617
- description: 'Inadequate understanding, does not meet standards',
618
- color: '#EF4444'
619
- }
620
- ]
621
- })
622
- }
667
+ folderId: classFolders[0].id,
668
+ principalType: PermissionPrincipalType.DEPARTMENT,
669
+ departmentId: scienceDept.id,
670
+ level: PermissionLevel.READ,
671
+ grantedById: teachers[0].id,
672
+ },
623
673
  });
624
- // 9. Create realistic assignments (more for Calc BC)
625
- const now = new Date();
626
- const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
627
- const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
628
- const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
629
- const nextMonth = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
630
- const lastWeek = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
631
- const assignments = await Promise.all([
632
- // Biology assignments
633
- prisma.assignment.create({
634
- data: {
635
- title: 'Cell Structure Lab Report',
636
- instructions: 'Complete the microscopy lab and write a detailed report on your observations of plant and animal cells. Include labeled diagrams and compare/contrast the structures you observed.',
637
- dueDate: nextWeek,
638
- teacherId: teachers[0].id,
639
- classId: biologyClass.id,
640
- sectionId: bioSections[1].id,
641
- type: 'LAB',
642
- maxGrade: 100,
643
- weight: 1.5,
644
- markSchemeId: bioMarkScheme.id,
645
- }
646
- }),
647
- prisma.assignment.create({
648
- data: {
649
- title: 'Genetics Problem Set',
650
- instructions: 'Solve the genetics problems on pages 234-237. Show all work including Punnett squares and probability calculations.',
651
- dueDate: tomorrow,
652
- teacherId: teachers[0].id,
653
- classId: biologyClass.id,
654
- sectionId: bioSections[3].id,
655
- type: 'HOMEWORK',
656
- maxGrade: 50,
657
- weight: 1.0,
658
- markSchemeId: bioMarkScheme.id,
659
- }
660
- }),
661
- prisma.assignment.create({
674
+ // ---------------------------------------------------------------------
675
+ // 9. Sections inside each class
676
+ // ---------------------------------------------------------------------
677
+ const bioSections = await Promise.all([
678
+ { name: "Unit 1: Chemistry of Life", color: "#EF4444" },
679
+ { name: "Unit 2: Cell Structure", color: "#F97316" },
680
+ { name: "Unit 3: Cellular Processes", color: "#EAB308" },
681
+ { name: "Unit 4: Genetics", color: "#22C55E" },
682
+ ].map((s, i) => prisma.section.create({
683
+ data: { name: s.name, color: s.color, order: i + 1, classId: biologyClass.id },
684
+ })));
685
+ const mathSections = await Promise.all([
686
+ { name: "Limits and Continuity", color: "#3B82F6" },
687
+ { name: "Derivatives", color: "#6366F1" },
688
+ { name: "Integrals", color: "#8B5CF6" },
689
+ { name: "Series", color: "#A855F7" },
690
+ ].map((s, i) => prisma.section.create({
691
+ data: { name: s.name, color: s.color, order: i + 1, classId: mathClass.id },
692
+ })));
693
+ await Promise.all([
694
+ { name: "Short Stories", color: "#8B5CF6" },
695
+ { name: "Poetry", color: "#EC4899" },
696
+ { name: "Novels", color: "#A855F7" },
697
+ ].map((s, i) => prisma.section.create({
698
+ data: { name: s.name, color: s.color, order: i + 1, classId: englishClass.id },
699
+ })));
700
+ // ---------------------------------------------------------------------
701
+ // 10. MarkSchemes + GradingBoundaries
702
+ // Mix of class-bound (legacy) and department-level (template) rows.
703
+ // ---------------------------------------------------------------------
704
+ const bioRubricStructured = buildRubric("Biology Assessment Rubric", [
705
+ { id: "scientific-accuracy", title: "Scientific Accuracy", description: "Correct use of scientific terminology and concepts" },
706
+ { id: "analysis", title: "Analysis & Reasoning", description: "Quality of analysis and logical reasoning" },
707
+ { id: "communication", title: "Communication", description: "Clear writing and proper formatting" },
708
+ { id: "evidence", title: "Evidence & Examples", description: "Use of relevant examples and evidence" },
709
+ ]);
710
+ const mathRubricStructured = buildRubric("Mathematics Assessment Rubric", [
711
+ { id: "accuracy", title: "Mathematical Accuracy", description: "Correct calculations and procedures" },
712
+ { id: "strategy", title: "Problem-Solving Strategy", description: "Appropriate method selection and setup" },
713
+ { id: "work-shown", title: "Work Shown", description: "Clear step-by-step work and explanations" },
714
+ { id: "final-answer", title: "Final Answer", description: "Correct final answer with proper units/notation" },
715
+ ]);
716
+ const [bioMarkScheme, mathMarkScheme] = await Promise.all([
717
+ prisma.markScheme.create({
662
718
  data: {
663
- title: 'Evolution Essay',
664
- instructions: 'Write a 5-page essay on the evidence for evolution. Use at least 5 scientific sources and cite them properly.',
665
- dueDate: nextMonth,
666
- teacherId: teachers[0].id,
667
719
  classId: biologyClass.id,
668
- type: 'ESSAY',
669
- maxGrade: 100,
670
- weight: 2.0,
671
- markSchemeId: bioMarkScheme.id,
672
- }
720
+ name: "Biology Assessment Rubric",
721
+ structured: bioRubricStructured,
722
+ tags: { connect: [{ id: tagAP.id }] },
723
+ },
673
724
  }),
674
- // Math assignments (expanded for demo)
675
- prisma.assignment.create({
725
+ prisma.markScheme.create({
676
726
  data: {
677
- title: 'Limits and Continuity Test',
678
- instructions: 'Comprehensive test covering limits, continuity, and the Intermediate Value Theorem. Calculators allowed for Section B only.',
679
- dueDate: lastWeek, // Already completed
680
- teacherId: teachers[1].id,
681
727
  classId: mathClass.id,
682
- sectionId: mathSections[0].id,
683
- type: 'TEST',
684
- maxGrade: 100,
685
- weight: 2.5,
686
- markSchemeId: mathMarkScheme.id,
687
- gradingBoundaryId: mathGradingBoundary.id,
688
- graded: true,
689
- }
728
+ name: "Mathematics Assessment Rubric",
729
+ structured: mathRubricStructured,
730
+ tags: { connect: [{ id: tagAP.id }] },
731
+ },
690
732
  }),
691
- prisma.assignment.create({
733
+ // Department-level templates so the rubric library has content too.
734
+ prisma.markScheme.create({
692
735
  data: {
693
- title: 'Limits Practice Problems',
694
- instructions: 'Complete problems 1-25 on page 89. Focus on algebraic manipulation and graphical analysis.',
695
- dueDate: yesterday, // Just submitted
696
- teacherId: teachers[1].id,
697
- classId: mathClass.id,
698
- sectionId: mathSections[0].id,
699
- type: 'HOMEWORK',
700
- maxGrade: 25,
701
- weight: 1.0,
702
- markSchemeId: mathMarkScheme.id,
703
- gradingBoundaryId: mathGradingBoundary.id,
704
- }
736
+ departmentId: mathDept.id,
737
+ name: "Math Department Standard Rubric",
738
+ structured: mathRubricStructured,
739
+ },
705
740
  }),
706
- prisma.assignment.create({
741
+ prisma.gradingBoundary.create({
707
742
  data: {
708
- title: 'Derivative Applications Quiz',
709
- instructions: 'Quiz covering related rates, optimization, and curve sketching.',
710
- dueDate: new Date(now.getTime() + 5 * 24 * 60 * 60 * 1000),
711
- teacherId: teachers[1].id,
712
- classId: mathClass.id,
713
- sectionId: mathSections[1].id,
714
- type: 'QUIZ',
715
- maxGrade: 75,
716
- weight: 1.5,
717
- markSchemeId: mathMarkScheme.id,
718
- gradingBoundaryId: mathGradingBoundary.id,
719
- }
720
- }),
721
- prisma.assignment.create({
722
- data: {
723
- title: 'Integration Techniques Homework',
724
- instructions: 'Complete problems 1-30 on pages 156-158. Practice integration by parts, substitution, and partial fractions.',
725
- dueDate: new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000),
726
- teacherId: teachers[1].id,
727
- classId: mathClass.id,
728
- sectionId: mathSections[2].id,
729
- type: 'HOMEWORK',
730
- maxGrade: 30,
731
- weight: 1.0,
732
- markSchemeId: mathMarkScheme.id,
733
- gradingBoundaryId: mathGradingBoundary.id,
734
- }
735
- }),
736
- prisma.assignment.create({
737
- data: {
738
- title: 'AP Practice Test - Derivatives',
739
- instructions: 'Complete this practice AP exam focusing on derivatives and their applications. Time limit: 90 minutes.',
740
- dueDate: new Date(now.getTime() + 10 * 24 * 60 * 60 * 1000),
741
- teacherId: teachers[1].id,
742
- classId: mathClass.id,
743
- sectionId: mathSections[1].id,
744
- type: 'TEST',
745
- maxGrade: 108, // AP style scoring
746
- weight: 2.5,
747
- markSchemeId: mathMarkScheme.id,
748
- gradingBoundaryId: mathGradingBoundary.id,
749
- }
750
- }),
751
- prisma.assignment.create({
752
- data: {
753
- title: 'Calculus Project: Real-World Applications',
754
- instructions: 'Choose a real-world scenario and create a presentation showing how calculus is used. Include derivatives, integrals, and optimization. 10-minute presentation required.',
755
- dueDate: new Date(now.getTime() + 21 * 24 * 60 * 60 * 1000),
756
- teacherId: teachers[1].id,
757
- classId: mathClass.id,
758
- type: 'PROJECT',
759
- maxGrade: 150,
760
- weight: 3.0,
761
- markSchemeId: mathMarkScheme.id,
762
- gradingBoundaryId: mathGradingBoundary.id,
763
- }
743
+ departmentId: mathDept.id,
744
+ name: "AP Grading Scale (Template)",
745
+ structured: apGradingScale,
746
+ },
764
747
  }),
765
748
  ]);
766
- // 10. Create comprehensive submissions for all students and assignments
767
- const submissionsToCreate = [];
768
- // Helper function to generate realistic grades based on assignment type and student performance
769
- const generateGrade = (maxGrade, studentIndex, assignmentType) => {
770
- // Create some variation in student performance
771
- const basePerformance = [0.92, 0.88, 0.85, 0.90, 0.82, 0.87, 0.91, 0.84, 0.89, 0.86, 0.83, 0.88]; // Different performance levels
772
- const performance = basePerformance[studentIndex % basePerformance.length];
773
- // Adjust based on assignment type
774
- let multiplier = performance;
775
- if (assignmentType === 'TEST')
776
- multiplier -= 0.05; // Tests are harder
777
- if (assignmentType === 'QUIZ')
778
- multiplier -= 0.02; // Quizzes slightly harder
779
- if (assignmentType === 'HOMEWORK')
780
- multiplier += 0.03; // Homework gets better grades
781
- // Add some randomness
782
- const randomFactor = (Math.random() - 0.5) * 0.1; // ±5%
783
- const finalGrade = Math.round(maxGrade * (multiplier + randomFactor));
784
- return Math.max(0, Math.min(maxGrade, finalGrade)); // Clamp between 0 and maxGrade
785
- };
786
- // Generate teacher comments based on grade
787
- const generateComment = (grade, maxGrade, assignmentType) => {
788
- const percentage = (grade / maxGrade) * 100;
789
- const comments = {
790
- excellent: [
791
- 'Excellent work! Your understanding of the concepts is clear.',
792
- 'Outstanding performance. Keep up the great work!',
793
- 'Impressive analysis and problem-solving approach.',
794
- 'Perfect execution. Your work demonstrates mastery of the material.'
795
- ],
796
- good: [
797
- 'Good work overall. Minor areas for improvement noted.',
798
- 'Solid understanding demonstrated. Watch calculation accuracy.',
799
- 'Well done! A few small errors but good conceptual grasp.',
800
- 'Nice job. Consider reviewing the highlighted sections.'
801
- ],
802
- average: [
803
- 'Adequate work. Please review the concepts we discussed in class.',
804
- 'Shows basic understanding but needs more practice.',
805
- 'Fair attempt. Come see me during office hours for extra help.',
806
- 'Meets expectations. Focus on showing more detailed work.'
807
- ],
808
- needs_improvement: [
809
- 'Needs significant improvement. Please schedule a meeting to discuss.',
810
- 'Below expectations. Additional practice and review required.',
811
- 'Struggling with key concepts. Let\'s work together to improve.',
812
- 'Requires more effort and attention to detail.'
813
- ]
814
- };
815
- let category;
816
- if (percentage >= 90)
817
- category = 'excellent';
818
- else if (percentage >= 80)
819
- category = 'good';
820
- else if (percentage >= 70)
821
- category = 'average';
822
- else
823
- category = 'needs_improvement';
824
- return comments[category][Math.floor(Math.random() * comments[category].length)];
825
- };
826
- // Create submissions for Biology assignments (students 0-7)
827
- for (let i = 0; i < 3; i++) { // First 3 assignments are Biology
828
- const assignment = assignments[i];
829
- const maxGrade = assignment.maxGrade || 100; // Default to 100 if null
830
- for (let j = 0; j < 8; j++) { // 8 students in Biology
831
- const student = students[j];
832
- const grade = generateGrade(maxGrade, j, assignment.type);
833
- const isSubmitted = Math.random() > 0.1; // 90% submission rate
834
- const isGraded = i < 2; // First 2 assignments are graded
835
- submissionsToCreate.push({
836
- assignmentId: assignment.id,
837
- studentId: student.id,
838
- submitted: isSubmitted,
839
- submittedAt: isSubmitted ? new Date(assignment.dueDate.getTime() - Math.random() * 24 * 60 * 60 * 1000) : null,
840
- gradeReceived: isGraded && isSubmitted ? grade : null,
841
- teacherComments: isGraded && isSubmitted ? generateComment(grade, maxGrade, assignment.type) : null,
842
- returned: isGraded && isSubmitted,
843
- });
844
- }
845
- }
846
- // Create submissions for Math assignments (students 4-11) - 8 students
847
- for (let i = 3; i < assignments.length; i++) { // Math assignments start at index 3
848
- const assignment = assignments[i];
849
- const maxGrade = assignment.maxGrade || 100; // Default to 100 if null
850
- for (let j = 4; j < 12; j++) { // Students 4-11 are in Math class
851
- const student = students[j];
852
- const grade = generateGrade(maxGrade, j, assignment.type);
853
- // Determine submission and grading status based on due date
854
- const isOverdue = assignment.dueDate < now;
855
- const isDueSoon = assignment.dueDate < tomorrow;
856
- const isSubmitted = isOverdue || (isDueSoon && Math.random() > 0.2) || Math.random() > 0.15; // Higher submission rate for math
857
- const isGraded = isOverdue && assignment.graded; // Only grade overdue assignments
858
- submissionsToCreate.push({
859
- assignmentId: assignment.id,
860
- studentId: student.id,
861
- submitted: isSubmitted,
862
- submittedAt: isSubmitted ? new Date(assignment.dueDate.getTime() - Math.random() * 48 * 60 * 60 * 1000) : null,
863
- gradeReceived: isGraded && isSubmitted ? grade : null,
864
- teacherComments: isGraded && isSubmitted ? generateComment(grade, maxGrade, assignment.type) : null,
865
- returned: isGraded && isSubmitted,
866
- rubricState: isGraded && isSubmitted ? JSON.stringify({
867
- criteria: [
868
- { name: 'Mathematical Accuracy', score: Math.round(grade * 0.5), maxScore: Math.round(maxGrade * 0.5) },
869
- { name: 'Problem-Solving Strategy', score: Math.round(grade * 0.25), maxScore: Math.round(maxGrade * 0.25) },
870
- { name: 'Work Shown', score: Math.round(grade * 0.15), maxScore: Math.round(maxGrade * 0.15) },
871
- { name: 'Final Answer', score: Math.round(grade * 0.1), maxScore: Math.round(maxGrade * 0.1) }
872
- ]
873
- }) : null,
874
- });
875
- }
876
- }
877
- // Create all submissions in batches to avoid connection pool exhaustion
878
- const BATCH_SIZE = 15; // Create 15 at a time to avoid overwhelming Supabase connection pool
879
- logger.info(`Creating ${submissionsToCreate.length} submissions in batches of ${BATCH_SIZE}`);
880
- for (let i = 0; i < submissionsToCreate.length; i += BATCH_SIZE) {
881
- const batch = submissionsToCreate.slice(i, i + BATCH_SIZE);
882
- await Promise.all(batch.map(submission => prisma.submission.create({ data: submission })));
883
- // Small delay between batches to avoid overwhelming the connection pool
884
- if (i + BATCH_SIZE < submissionsToCreate.length) {
885
- await new Promise(resolve => setTimeout(resolve, 100)); // 100ms delay
886
- }
887
- // Log progress every 30 submissions or at the end
888
- if ((i + BATCH_SIZE) % 30 === 0 || i + BATCH_SIZE >= submissionsToCreate.length) {
889
- logger.info(`Created ${Math.min(i + BATCH_SIZE, submissionsToCreate.length)}/${submissionsToCreate.length} submissions`);
890
- }
891
- }
892
- logger.info(`Successfully created all ${submissionsToCreate.length} submissions`);
893
- // 11. Create announcements
894
- await Promise.all([
895
- prisma.announcement.create({
896
- data: {
897
- remarks: 'Reminder: Lab safety quiz next Tuesday. Please review the safety protocols we discussed in class.',
898
- teacherId: teachers[0].id,
899
- classId: biologyClass.id,
900
- createdAt: new Date(now.getTime() - 24 * 60 * 60 * 1000),
901
- }
902
- }),
903
- prisma.announcement.create({
904
- data: {
905
- remarks: 'Great job on the recent quiz, everyone! The class average was 87%. Keep up the excellent work!',
906
- teacherId: teachers[1].id,
907
- classId: mathClass.id,
908
- createdAt: new Date(now.getTime() - 6 * 60 * 60 * 1000),
909
- }
910
- }),
911
- prisma.announcement.create({
912
- data: {
913
- remarks: 'Don\'t forget: Parent-teacher conferences are next week. Sign-up sheets are available in the main office.',
914
- teacherId: teachers[2].id,
915
- classId: englishClass.id,
916
- createdAt: new Date(now.getTime() - 12 * 60 * 60 * 1000),
917
- }
918
- }),
919
- ]);
920
- // 12. Create events/calendar items (lots for Michael Chen's week of Oct 7)
921
- const oct7 = new Date('2025-10-07T08:00:00');
922
- const oct8 = new Date('2025-10-08T08:00:00');
923
- const oct9 = new Date('2025-10-09T08:00:00');
924
- const oct10 = new Date('2025-10-10T08:00:00');
925
- const oct11 = new Date('2025-10-11T08:00:00');
926
- // Build events array first, then batch create them
927
- const eventsToCreate = [
928
- // Monday Oct 7 - Michael Chen's busy day
929
- {
930
- name: 'AP Calculus BC - Period 2',
931
- startTime: new Date('2025-10-07T09:15:00'),
932
- endTime: new Date('2025-10-07T10:05:00'),
933
- location: 'Room 156',
934
- remarks: 'Derivatives unit test review',
935
- userId: teachers[1].id,
749
+ const mathGradingBoundary = await prisma.gradingBoundary.create({
750
+ data: {
936
751
  classId: mathClass.id,
937
- color: '#3B82F6',
938
- },
939
- {
940
- name: 'Department Head Meeting',
941
- startTime: new Date('2025-10-07T10:30:00'),
942
- endTime: new Date('2025-10-07T11:30:00'),
943
- location: 'Conference Room A',
944
- remarks: 'Monthly math department meeting - curriculum planning',
945
- userId: teachers[1].id,
946
- color: '#8B5CF6',
752
+ name: "AP Calculus BC Grading Scale",
753
+ structured: apGradingScale,
947
754
  },
755
+ });
756
+ // ---------------------------------------------------------------------
757
+ // 11. Assignments + attachments
758
+ // ---------------------------------------------------------------------
759
+ const assignmentSpecs = [
948
760
  {
949
- name: 'AP Statistics - Period 5',
950
- startTime: new Date('2025-10-07T13:45:00'),
951
- endTime: new Date('2025-10-07T14:35:00'),
952
- location: 'Room 156',
953
- remarks: 'Hypothesis testing introduction',
954
- userId: teachers[1].id,
955
- color: '#3B82F6',
761
+ title: "Cell Structure Lab Report",
762
+ instructions: "Complete the microscopy lab and write a detailed report on your observations of plant and animal cells. Include labeled diagrams.",
763
+ dueDate: daysFromNow(7, now),
764
+ teacherId: teachers[0].id,
765
+ classId: biologyClass.id,
766
+ sectionId: bioSections[1].id,
767
+ type: AssignmentType.LAB,
768
+ maxGrade: 100,
769
+ weight: 1.5,
770
+ markSchemeId: bioMarkScheme.id,
771
+ acceptFiles: true,
772
+ acceptExtendedResponse: true,
773
+ attachments: ["Lab Report Template.docx", "Rubric.pdf"],
956
774
  },
957
775
  {
958
- name: 'Parent Conference - Williams Family',
959
- startTime: new Date('2025-10-07T15:00:00'),
960
- endTime: new Date('2025-10-07T15:30:00'),
961
- location: 'Room 156',
962
- remarks: 'Discuss Sophia\'s progress in AP Calculus',
963
- userId: teachers[1].id,
964
- color: '#F59E0B',
776
+ title: "Genetics Problem Set",
777
+ instructions: "Solve problems on pages 234-237. Show all work including Punnett squares and probability.",
778
+ dueDate: daysFromNow(1, now),
779
+ teacherId: teachers[0].id,
780
+ classId: biologyClass.id,
781
+ sectionId: bioSections[3].id,
782
+ type: AssignmentType.HOMEWORK,
783
+ maxGrade: 50,
784
+ weight: 1.0,
785
+ markSchemeId: bioMarkScheme.id,
786
+ acceptFiles: true,
787
+ acceptWorksheet: true,
788
+ attachments: ["Genetics Worksheet.pdf"],
965
789
  },
966
790
  {
967
- name: 'Math Tutoring Session',
968
- startTime: new Date('2025-10-07T15:45:00'),
969
- endTime: new Date('2025-10-07T16:45:00'),
970
- location: 'Room 156',
971
- remarks: 'Extra help for struggling students',
972
- userId: teachers[1].id,
973
- color: '#10B981',
791
+ title: "Evolution Essay",
792
+ instructions: "Write a 5-page essay on the evidence for evolution. Use at least 5 scientific sources and cite properly.",
793
+ dueDate: daysFromNow(30, now),
794
+ teacherId: teachers[0].id,
795
+ classId: biologyClass.id,
796
+ type: AssignmentType.ESSAY,
797
+ maxGrade: 100,
798
+ weight: 2.0,
799
+ markSchemeId: bioMarkScheme.id,
800
+ acceptFiles: true,
801
+ acceptExtendedResponse: true,
802
+ gradeWithAI: true,
803
+ attachments: ["Essay Guidelines.pdf"],
974
804
  },
975
- // Tuesday Oct 8
976
805
  {
977
- name: 'AP Calculus BC - Period 2',
978
- startTime: new Date('2025-10-08T09:15:00'),
979
- endTime: new Date('2025-10-08T10:05:00'),
980
- location: 'Room 156',
981
- remarks: 'Derivatives unit test',
982
- userId: teachers[1].id,
806
+ title: "Limits and Continuity Test",
807
+ instructions: "Comprehensive test covering limits, continuity, and the Intermediate Value Theorem.",
808
+ dueDate: daysFromNow(-7, now),
809
+ teacherId: teachers[1].id,
983
810
  classId: mathClass.id,
984
- color: '#3B82F6',
811
+ sectionId: mathSections[0].id,
812
+ type: AssignmentType.TEST,
813
+ maxGrade: 100,
814
+ weight: 2.5,
815
+ markSchemeId: mathMarkScheme.id,
816
+ gradingBoundaryId: mathGradingBoundary.id,
817
+ graded: true,
818
+ acceptFiles: true,
819
+ attachments: ["Practice Test.pdf", "Formula Sheet.pdf"],
985
820
  },
986
821
  {
987
- name: 'Faculty Meeting',
988
- startTime: new Date('2025-10-08T12:00:00'),
989
- endTime: new Date('2025-10-08T13:00:00'),
990
- location: 'Main Auditorium',
991
- remarks: 'All-school faculty meeting',
992
- userId: teachers[1].id,
993
- color: '#6B7280',
822
+ title: "Limits Practice Problems",
823
+ instructions: "Complete problems 1-25 on page 89.",
824
+ dueDate: daysFromNow(-1, now),
825
+ teacherId: teachers[1].id,
826
+ classId: mathClass.id,
827
+ sectionId: mathSections[0].id,
828
+ type: AssignmentType.HOMEWORK,
829
+ maxGrade: 25,
830
+ weight: 1.0,
831
+ markSchemeId: mathMarkScheme.id,
832
+ gradingBoundaryId: mathGradingBoundary.id,
833
+ acceptWorksheet: true,
994
834
  },
995
835
  {
996
- name: 'AP Statistics - Period 5',
997
- startTime: new Date('2025-10-08T13:45:00'),
998
- endTime: new Date('2025-10-08T14:35:00'),
999
- location: 'Room 156',
1000
- remarks: 'Hypothesis testing practice problems',
1001
- userId: teachers[1].id,
1002
- color: '#3B82F6',
836
+ title: "Derivative Applications Quiz",
837
+ instructions: "Quiz covering related rates, optimization, and curve sketching.",
838
+ dueDate: daysFromNow(5, now),
839
+ teacherId: teachers[1].id,
840
+ classId: mathClass.id,
841
+ sectionId: mathSections[1].id,
842
+ type: AssignmentType.QUIZ,
843
+ maxGrade: 75,
844
+ weight: 1.5,
845
+ markSchemeId: mathMarkScheme.id,
846
+ gradingBoundaryId: mathGradingBoundary.id,
847
+ acceptWorksheet: true,
848
+ gradeWithAI: true,
1003
849
  },
1004
850
  {
1005
- name: 'Grade Level Team Meeting',
1006
- startTime: new Date('2025-10-08T15:00:00'),
1007
- endTime: new Date('2025-10-08T16:00:00'),
1008
- location: 'Room 201',
1009
- remarks: '11th grade team coordination',
1010
- userId: teachers[1].id,
1011
- color: '#8B5CF6',
851
+ title: "Integration Techniques Homework",
852
+ instructions: "Complete problems 1-30 on pages 156-158.",
853
+ dueDate: daysFromNow(3, now),
854
+ teacherId: teachers[1].id,
855
+ classId: mathClass.id,
856
+ sectionId: mathSections[2].id,
857
+ type: AssignmentType.HOMEWORK,
858
+ maxGrade: 30,
859
+ weight: 1.0,
860
+ markSchemeId: mathMarkScheme.id,
861
+ gradingBoundaryId: mathGradingBoundary.id,
862
+ acceptFiles: true,
1012
863
  },
1013
- // Wednesday Oct 9
1014
864
  {
1015
- name: 'AP Calculus BC - Period 2',
1016
- startTime: new Date('2025-10-09T09:15:00'),
1017
- endTime: new Date('2025-10-09T10:05:00'),
1018
- location: 'Room 156',
1019
- remarks: 'Integration introduction',
1020
- userId: teachers[1].id,
865
+ title: "Calculus Project: Real-World Applications",
866
+ instructions: "Choose a real-world scenario and create a presentation showing how calculus is used.",
867
+ dueDate: daysFromNow(21, now),
868
+ teacherId: teachers[1].id,
1021
869
  classId: mathClass.id,
1022
- color: '#3B82F6',
870
+ type: AssignmentType.PROJECT,
871
+ maxGrade: 150,
872
+ weight: 3.0,
873
+ markSchemeId: mathMarkScheme.id,
874
+ gradingBoundaryId: mathGradingBoundary.id,
875
+ acceptFiles: true,
876
+ acceptExtendedResponse: true,
877
+ attachments: ["Project Rubric.pdf", "Past Examples.pdf"],
1023
878
  },
1024
879
  {
1025
- name: 'Professional Development',
1026
- startTime: new Date('2025-10-09T11:00:00'),
1027
- endTime: new Date('2025-10-09T12:30:00'),
1028
- location: 'Library Conference Room',
1029
- remarks: 'Technology in Mathematics Education workshop',
1030
- userId: teachers[1].id,
1031
- color: '#059669',
880
+ title: "Macbeth — Themes Essay",
881
+ instructions: "Choose a theme and trace its development across the play. 4-6 pages, MLA format.",
882
+ dueDate: daysFromNow(10, now),
883
+ teacherId: teachers[2].id,
884
+ classId: englishClass.id,
885
+ type: AssignmentType.ESSAY,
886
+ maxGrade: 100,
887
+ weight: 2.0,
888
+ acceptFiles: true,
889
+ acceptExtendedResponse: true,
890
+ gradeWithAI: true,
891
+ attachments: ["Macbeth Annotation Guide.pdf"],
1032
892
  },
1033
- {
1034
- name: 'AP Statistics - Period 5',
1035
- startTime: new Date('2025-10-09T13:45:00'),
1036
- endTime: new Date('2025-10-09T14:35:00'),
1037
- location: 'Room 156',
1038
- remarks: 'Chi-square tests',
1039
- userId: teachers[1].id,
1040
- color: '#3B82F6',
893
+ ];
894
+ const assignments = await runInBatches(assignmentSpecs, (spec) => prisma.assignment.create({
895
+ data: {
896
+ title: spec.title,
897
+ instructions: spec.instructions,
898
+ dueDate: spec.dueDate,
899
+ teacherId: spec.teacherId,
900
+ classId: spec.classId,
901
+ sectionId: spec.sectionId,
902
+ type: spec.type,
903
+ maxGrade: spec.maxGrade,
904
+ weight: spec.weight,
905
+ markSchemeId: spec.markSchemeId,
906
+ gradingBoundaryId: spec.gradingBoundaryId,
907
+ graded: spec.graded ?? false,
908
+ acceptFiles: spec.acceptFiles ?? false,
909
+ acceptExtendedResponse: spec.acceptExtendedResponse ?? false,
910
+ acceptWorksheet: spec.acceptWorksheet ?? false,
911
+ gradeWithAI: spec.gradeWithAI ?? false,
912
+ aiPolicyLevel: spec.gradeWithAI ? 2 : 0,
1041
913
  },
1042
- {
1043
- name: 'Student Council Advisory',
1044
- startTime: new Date('2025-10-09T15:00:00'),
1045
- endTime: new Date('2025-10-09T16:00:00'),
1046
- location: 'Room 105',
1047
- remarks: 'Advisor meeting for student council',
1048
- userId: teachers[1].id,
1049
- color: '#DC2626',
914
+ }));
915
+ // Now attach files to assignments (after the assignment IDs exist).
916
+ for (let i = 0; i < assignments.length; i++) {
917
+ const spec = assignmentSpecs[i];
918
+ if (!spec.attachments?.length)
919
+ continue;
920
+ await Promise.all(spec.attachments.map((name) => createFakeFile({
921
+ name,
922
+ userId: spec.teacherId,
923
+ assignmentId: assignments[i].id,
924
+ })));
925
+ }
926
+ // ---------------------------------------------------------------------
927
+ // 12. Worksheets — attach the worksheet-style assignments to a worksheet
928
+ // ---------------------------------------------------------------------
929
+ const geneticsAssignment = assignments[1];
930
+ const geneticsWorksheet = await prisma.worksheet.create({
931
+ data: {
932
+ classId: biologyClass.id,
933
+ name: "Genetics Problem Set",
934
+ status: JobStatus.COMPLETED,
935
+ assignments: { connect: { id: geneticsAssignment.id } },
1050
936
  },
1051
- // Thursday Oct 10
937
+ });
938
+ const geneticsQuestions = await runInBatches([
1052
939
  {
1053
- name: 'AP Calculus BC - Period 2',
1054
- startTime: new Date('2025-10-10T09:15:00'),
1055
- endTime: new Date('2025-10-10T10:05:00'),
1056
- location: 'Room 156',
1057
- remarks: 'Integration by substitution',
1058
- userId: teachers[1].id,
1059
- classId: mathClass.id,
1060
- color: '#3B82F6',
940
+ type: WorksheetQuestionType.MULTIPLE_CHOICE,
941
+ question: "A heterozygous tall pea plant (Tt) is crossed with a homozygous short pea plant (tt). What percentage of offspring will be tall?",
942
+ answer: "50%",
943
+ options: { choices: ["0%", "25%", "50%", "75%", "100%"] },
944
+ points: 5,
945
+ order: 1,
1061
946
  },
1062
947
  {
1063
- name: 'Curriculum Committee',
1064
- startTime: new Date('2025-10-10T11:00:00'),
1065
- endTime: new Date('2025-10-10T12:00:00'),
1066
- location: 'Principal\'s Office',
1067
- remarks: 'Review new AP curriculum standards',
1068
- userId: teachers[1].id,
1069
- color: '#8B5CF6',
948
+ type: WorksheetQuestionType.SHORT_ANSWER,
949
+ question: "Define genotype and phenotype, and give an example of each.",
950
+ answer: "Genotype is the genetic makeup (e.g. Tt). Phenotype is the observable trait (e.g. tall).",
951
+ points: 10,
952
+ order: 2,
1070
953
  },
1071
954
  {
1072
- name: 'AP Statistics - Period 5',
1073
- startTime: new Date('2025-10-10T13:45:00'),
1074
- endTime: new Date('2025-10-10T14:35:00'),
1075
- location: 'Room 156',
1076
- remarks: 'ANOVA introduction',
1077
- userId: teachers[1].id,
1078
- color: '#3B82F6',
955
+ type: WorksheetQuestionType.TRUE_FALSE,
956
+ question: "A Punnett square predicts the probability of genotypes in offspring.",
957
+ answer: "true",
958
+ options: { choices: ["true", "false"] },
959
+ points: 5,
960
+ order: 3,
1079
961
  },
1080
962
  {
1081
- name: 'Parent Conference - Anderson Family',
1082
- startTime: new Date('2025-10-10T15:00:00'),
1083
- endTime: new Date('2025-10-10T15:30:00'),
1084
- location: 'Room 156',
1085
- remarks: 'Discuss Ethan\'s improvement strategies',
1086
- userId: teachers[1].id,
1087
- color: '#F59E0B',
963
+ type: WorksheetQuestionType.LONG_ANSWER,
964
+ question: "Explain how independent assortment contributes to genetic variation.",
965
+ answer: "During meiosis, homologous chromosome pairs sort independently into gametes, producing many different combinations of alleles.",
966
+ points: 15,
967
+ order: 4,
1088
968
  },
1089
969
  {
1090
- name: 'Math Club Meeting',
1091
- startTime: new Date('2025-10-10T15:45:00'),
1092
- endTime: new Date('2025-10-10T16:45:00'),
1093
- location: 'Room 156',
1094
- remarks: 'Preparing for state math competition',
1095
- userId: teachers[1].id,
1096
- color: '#10B981',
970
+ type: WorksheetQuestionType.MATH_EXPRESSION,
971
+ question: "If two heterozygous parents (Aa x Aa) produce 100 offspring, how many are expected to be homozygous recessive?",
972
+ answer: "25",
973
+ points: 5,
974
+ order: 5,
1097
975
  },
1098
- // Friday Oct 11
1099
- {
1100
- name: 'AP Calculus BC - Period 2',
1101
- startTime: new Date('2025-10-11T09:15:00'),
1102
- endTime: new Date('2025-10-11T10:05:00'),
1103
- location: 'Room 156',
1104
- remarks: 'Integration by parts',
1105
- userId: teachers[1].id,
976
+ ], (q) => prisma.worksheetQuestion.create({
977
+ data: { worksheetId: geneticsWorksheet.id, ...q },
978
+ }));
979
+ // A separate worksheet for the limits homework.
980
+ const limitsAssignment = assignments[4];
981
+ const limitsWorksheet = await prisma.worksheet.create({
982
+ data: {
1106
983
  classId: mathClass.id,
1107
- color: '#3B82F6',
984
+ name: "Limits Practice",
985
+ status: JobStatus.COMPLETED,
986
+ assignments: { connect: { id: limitsAssignment.id } },
1108
987
  },
988
+ });
989
+ await runInBatches([
1109
990
  {
1110
- name: 'IEP Meeting - Student Support',
1111
- startTime: new Date('2025-10-11T10:30:00'),
1112
- endTime: new Date('2025-10-11T11:30:00'),
1113
- location: 'Special Services Office',
1114
- remarks: 'Individualized Education Plan review',
1115
- userId: teachers[1].id,
1116
- color: '#F59E0B',
991
+ type: WorksheetQuestionType.MATH_EXPRESSION,
992
+ question: "Evaluate lim_{x→2} (x^2 - 4) / (x - 2).",
993
+ answer: "4",
994
+ points: 10,
995
+ order: 1,
1117
996
  },
1118
997
  {
1119
- name: 'AP Statistics - Period 5',
1120
- startTime: new Date('2025-10-11T13:45:00'),
1121
- endTime: new Date('2025-10-11T14:35:00'),
1122
- location: 'Room 156',
1123
- remarks: 'ANOVA practice and review',
1124
- userId: teachers[1].id,
1125
- color: '#3B82F6',
998
+ type: WorksheetQuestionType.MATH_EXPRESSION,
999
+ question: "Evaluate lim_{x→0} sin(x) / x.",
1000
+ answer: "1",
1001
+ points: 10,
1002
+ order: 2,
1126
1003
  },
1127
1004
  {
1128
- name: 'Weekend Prep Session',
1129
- startTime: new Date('2025-10-11T15:00:00'),
1130
- endTime: new Date('2025-10-11T17:00:00'),
1131
- location: 'Room 156',
1132
- remarks: 'Voluntary AP exam prep session',
1133
- userId: teachers[1].id,
1134
- color: '#059669',
1005
+ type: WorksheetQuestionType.SHORT_ANSWER,
1006
+ question: "State the Intermediate Value Theorem.",
1007
+ answer: "If f is continuous on [a, b] and N lies between f(a) and f(b), then there exists c in (a, b) with f(c) = N.",
1008
+ points: 5,
1009
+ order: 3,
1135
1010
  },
1136
- // Some events for other teachers too
1137
- {
1138
- name: 'Cell Biology Lab',
1139
- startTime: new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000),
1140
- endTime: new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000 + 90 * 60 * 1000), // 90 minutes
1141
- location: 'Science Lab Room 204',
1142
- remarks: 'Bring lab notebooks and safety goggles',
1143
- userId: teachers[0].id,
1011
+ ], (q) => prisma.worksheetQuestion.create({
1012
+ data: { worksheetId: limitsWorksheet.id, ...q },
1013
+ }));
1014
+ // ---------------------------------------------------------------------
1015
+ // 13. Submissions (+ attachments, worksheet responses, annotations)
1016
+ // ---------------------------------------------------------------------
1017
+ const generateGrade = (maxGrade, studentIndex, type) => {
1018
+ const base = [0.92, 0.88, 0.85, 0.9, 0.82, 0.87, 0.91, 0.84, 0.89, 0.86, 0.83, 0.88];
1019
+ let multiplier = base[studentIndex % base.length];
1020
+ if (type === AssignmentType.TEST)
1021
+ multiplier -= 0.05;
1022
+ if (type === AssignmentType.QUIZ)
1023
+ multiplier -= 0.02;
1024
+ if (type === AssignmentType.HOMEWORK)
1025
+ multiplier += 0.03;
1026
+ const variance = (Math.random() - 0.5) * 0.1;
1027
+ return Math.max(0, Math.min(maxGrade, Math.round(maxGrade * (multiplier + variance))));
1028
+ };
1029
+ const teacherFeedback = (pct) => {
1030
+ if (pct >= 90)
1031
+ return pick([
1032
+ "Excellent work! Your understanding of the concepts is clear.",
1033
+ "Outstanding performance. Keep it up.",
1034
+ "Impressive analysis and reasoning throughout.",
1035
+ ]);
1036
+ if (pct >= 80)
1037
+ return pick([
1038
+ "Good work overall. A few small slips but nothing structural.",
1039
+ "Solid understanding — watch calculation accuracy on tricky cases.",
1040
+ "Nice job. Review the highlighted sections.",
1041
+ ]);
1042
+ if (pct >= 70)
1043
+ return pick([
1044
+ "Adequate work — please revisit the concepts we covered in class.",
1045
+ "Shows basic understanding but needs more practice.",
1046
+ "Fair attempt. Come to office hours and we'll work through it.",
1047
+ ]);
1048
+ return pick([
1049
+ "Below expectations. Let's schedule a meeting to talk through it.",
1050
+ "Struggling with the key concepts — we'll get you there.",
1051
+ "Needs significant improvement. Try the supplementary set.",
1052
+ ]);
1053
+ };
1054
+ const studentsInBio = students.slice(0, 8);
1055
+ const studentsInMath = students.slice(4, 12);
1056
+ const studentsInEnglish = students.slice(0, 10);
1057
+ const studentsForClass = (classId) => {
1058
+ if (classId === biologyClass.id)
1059
+ return studentsInBio;
1060
+ if (classId === mathClass.id)
1061
+ return studentsInMath;
1062
+ return studentsInEnglish;
1063
+ };
1064
+ // Submissions: small batches + a tiny delay between them.
1065
+ const submissionRecords = [];
1066
+ for (const assignment of assignments) {
1067
+ const enrolled = studentsForClass(assignment.classId);
1068
+ const max = assignment.maxGrade ?? 100;
1069
+ const overdue = assignment.dueDate < now;
1070
+ const created = await runInBatches(enrolled, (student) => {
1071
+ const studentIndex = students.findIndex((s) => s.id === student.id);
1072
+ const isSubmitted = overdue ? Math.random() > 0.05 : Math.random() > 0.4;
1073
+ const isGraded = overdue && isSubmitted && (assignment.graded || Math.random() > 0.4);
1074
+ const grade = generateGrade(max, studentIndex, assignment.type);
1075
+ const submittedAt = isSubmitted
1076
+ ? new Date(assignment.dueDate.getTime() - Math.random() * 48 * 60 * 60 * 1000)
1077
+ : null;
1078
+ return prisma.submission.create({
1079
+ data: {
1080
+ assignmentId: assignment.id,
1081
+ studentId: student.id,
1082
+ submitted: isSubmitted,
1083
+ submittedAt,
1084
+ gradeReceived: isGraded ? grade : null,
1085
+ teacherComments: isGraded ? teacherFeedback((grade / max) * 100) : null,
1086
+ returned: isGraded,
1087
+ extendedResponse: assignment.acceptExtendedResponse && isSubmitted
1088
+ ? sampleExtendedResponse(assignment.title)
1089
+ : null,
1090
+ rubricState: isGraded
1091
+ ? JSON.stringify({
1092
+ criteria: [
1093
+ { name: "Accuracy", score: Math.round(grade * 0.4), maxScore: Math.round(max * 0.4) },
1094
+ { name: "Reasoning", score: Math.round(grade * 0.3), maxScore: Math.round(max * 0.3) },
1095
+ { name: "Communication", score: Math.round(grade * 0.2), maxScore: Math.round(max * 0.2) },
1096
+ { name: "Evidence", score: Math.round(grade * 0.1), maxScore: Math.round(max * 0.1) },
1097
+ ],
1098
+ })
1099
+ : null,
1100
+ },
1101
+ });
1102
+ });
1103
+ for (const sub of created) {
1104
+ submissionRecords.push({
1105
+ id: sub.id,
1106
+ assignmentId: assignment.id,
1107
+ studentId: sub.studentId,
1108
+ submitted: sub.submitted ?? false,
1109
+ isGraded: Boolean(sub.returned),
1110
+ });
1111
+ }
1112
+ }
1113
+ logger.info(`Created ${submissionRecords.length} submissions`);
1114
+ // Attach a fake "submitted document" to submitted submissions, and a
1115
+ // teacher "annotations" PDF on graded ones.
1116
+ const submittedOnes = submissionRecords.filter((s) => s.submitted);
1117
+ await runInBatches(submittedOnes, async (sub) => {
1118
+ await createFakeFile({
1119
+ name: pick(["Submission.pdf", "Final Draft.docx", "Worked Solutions.pdf", "Notes.png"]),
1120
+ userId: sub.studentId,
1121
+ submissionId: sub.id,
1122
+ });
1123
+ if (sub.isGraded && Math.random() > 0.4) {
1124
+ await createFakeFile({
1125
+ name: "Teacher Annotations.pdf",
1126
+ userId: assignmentTeacherFor(sub.assignmentId, assignments),
1127
+ annotationId: sub.id,
1128
+ });
1129
+ }
1130
+ });
1131
+ // Worksheet responses for the genetics worksheet (one per Bio student that
1132
+ // submitted). Sequential to keep the pool happy.
1133
+ for (const student of studentsInBio) {
1134
+ const submission = submissionRecords.find((s) => s.assignmentId === geneticsAssignment.id && s.studentId === student.id);
1135
+ if (!submission?.submitted)
1136
+ continue;
1137
+ const response = await prisma.studentWorksheetResponse.create({
1138
+ data: {
1139
+ studentId: student.id,
1140
+ worksheetId: geneticsWorksheet.id,
1141
+ submissionId: submission.id,
1142
+ submitted: true,
1143
+ submittedAt: new Date(now.getTime() - Math.random() * 24 * 60 * 60 * 1000),
1144
+ },
1145
+ });
1146
+ await runInBatches(geneticsQuestions, (q) => {
1147
+ const isCorrect = Math.random() > 0.25;
1148
+ return prisma.studentQuestionProgress.create({
1149
+ data: {
1150
+ studentId: student.id,
1151
+ questionId: q.id,
1152
+ studentWorksheetResponseId: response.id,
1153
+ response: isCorrect ? q.answer : "I'm not sure — guessing here.",
1154
+ isCorrect,
1155
+ points: isCorrect ? q.points : Math.floor(q.points / 2),
1156
+ feedback: isCorrect
1157
+ ? "Nice — correct reasoning."
1158
+ : "Re-read the section on Punnett squares and try again.",
1159
+ status: JobStatus.COMPLETED,
1160
+ },
1161
+ });
1162
+ });
1163
+ }
1164
+ // ---------------------------------------------------------------------
1165
+ // 14. Announcements + attachments + reactions + comments
1166
+ // ---------------------------------------------------------------------
1167
+ const bioAnnouncement = await prisma.announcement.create({
1168
+ data: {
1169
+ remarks: "Reminder: lab safety quiz next Tuesday. Please review the protocols from Unit 2 — there'll be a short review at the start of class.",
1170
+ teacherId: teachers[0].id,
1144
1171
  classId: biologyClass.id,
1145
- color: '#10B981',
1172
+ createdAt: hoursFromNow(-24, now),
1146
1173
  },
1147
- {
1148
- name: 'Poetry Reading',
1149
- startTime: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000),
1150
- endTime: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000 + 60 * 60 * 1000),
1151
- location: 'Library',
1152
- remarks: 'Students will present their original poetry',
1153
- userId: teachers[2].id,
1174
+ });
1175
+ await createFakeFile({
1176
+ name: "Lab Safety Protocols.pdf",
1177
+ userId: teachers[0].id,
1178
+ announcementId: bioAnnouncement.id,
1179
+ });
1180
+ const mathAnnouncement = await prisma.announcement.create({
1181
+ data: {
1182
+ remarks: "Great job on the recent quiz, everyone! Class average was 87%. Solutions are attached. Office hours are Wednesday after school.",
1183
+ teacherId: teachers[1].id,
1184
+ classId: mathClass.id,
1185
+ createdAt: hoursFromNow(-6, now),
1186
+ },
1187
+ });
1188
+ await createFakeFile({
1189
+ name: "Quiz Solutions.pdf",
1190
+ userId: teachers[1].id,
1191
+ announcementId: mathAnnouncement.id,
1192
+ });
1193
+ const englishAnnouncement = await prisma.announcement.create({
1194
+ data: {
1195
+ remarks: "Parent-teacher conferences are next week. Sign-ups are in the main office. Bring questions about your Macbeth essay drafts.",
1196
+ teacherId: teachers[2].id,
1154
1197
  classId: englishClass.id,
1155
- color: '#8B5CF6',
1198
+ createdAt: hoursFromNow(-12, now),
1156
1199
  },
1157
- ];
1158
- // Create events in batches to avoid connection pool exhaustion
1159
- const EVENT_BATCH_SIZE = 10;
1160
- logger.info(`Creating ${eventsToCreate.length} events in batches of ${EVENT_BATCH_SIZE}`);
1161
- for (let i = 0; i < eventsToCreate.length; i += EVENT_BATCH_SIZE) {
1162
- const batch = eventsToCreate.slice(i, i + EVENT_BATCH_SIZE);
1163
- await Promise.all(batch.map(event => prisma.event.create({ data: event })));
1164
- // Small delay between batches
1165
- if (i + EVENT_BATCH_SIZE < eventsToCreate.length) {
1166
- await new Promise(resolve => setTimeout(resolve, 100));
1167
- }
1168
- }
1169
- logger.info(`Successfully created all ${eventsToCreate.length} events`);
1170
- // 13. Create attendance records
1171
- const attendanceDate = new Date(now.getTime() - 24 * 60 * 60 * 1000);
1172
- await Promise.all([
1173
- prisma.attendance.create({
1174
- data: {
1175
- date: attendanceDate,
1176
- classId: biologyClass.id,
1177
- present: { connect: students.slice(0, 6).map(s => ({ id: s.id })) },
1178
- late: { connect: [{ id: students[6].id }] },
1179
- absent: { connect: [{ id: students[7].id }] },
1180
- }
1181
- }),
1182
- prisma.attendance.create({
1183
- data: {
1184
- date: attendanceDate,
1185
- classId: mathClass.id,
1186
- present: { connect: students.slice(4, 11).map(s => ({ id: s.id })) },
1187
- late: { connect: [{ id: students[11].id }] },
1188
- }
1189
- }),
1190
- ]);
1191
- // 14. Create notifications for realistic teacher experience
1192
- await Promise.all([
1193
- // For main teacher (Sarah Johnson)
1194
- addNotification(teachers[0].id, 'New Submission', 'Alex Martinez submitted Genetics Problem Set'),
1195
- addNotification(teachers[0].id, 'Grade Reminder', 'You have 3 assignments pending grading'),
1196
- addNotification(teachers[0].id, 'Lab Equipment', 'Microscopes for tomorrow\'s lab are ready in Room 204'),
1197
- addNotification(teachers[0].id, 'Parent Contact', 'Mrs. Williams requested a meeting about Sophia\'s progress'),
1198
- // For students
1199
- addNotification(students[0].id, 'Assignment Graded', 'Your Genetics Problem Set has been graded: 45/50'),
1200
- addNotification(students[1].id, 'Assignment Graded', 'Your Genetics Problem Set has been graded: 48/50'),
1201
- addNotification(students[2].id, 'Assignment Due', 'Cell Structure Lab Report is due in 3 days'),
1202
- addNotification(students[3].id, 'Class Announcement', 'New announcement in AP Biology'),
1203
- ]);
1204
- // 15. Create some lab chats for AI demo
1205
- const labChatContext = JSON.stringify({
1206
- subject: 'Biology',
1207
- topic: 'Cell Structure and Function',
1208
- gradeLevel: '11th Grade AP',
1209
- difficulty: 'Advanced',
1210
- objectives: [
1211
- 'Understand prokaryotic vs eukaryotic cell structures',
1212
- 'Identify organelles and their functions',
1213
- 'Compare plant and animal cells'
1200
+ });
1201
+ // Reactions to announcements.
1202
+ await prisma.reaction.createMany({
1203
+ data: [
1204
+ { type: ReactionType.THUMBSUP, userId: students[0].id, announcementId: bioAnnouncement.id },
1205
+ { type: ReactionType.HEART, userId: students[1].id, announcementId: bioAnnouncement.id },
1206
+ { type: ReactionType.CELEBRATE, userId: students[4].id, announcementId: mathAnnouncement.id },
1207
+ { type: ReactionType.CELEBRATE, userId: students[5].id, announcementId: mathAnnouncement.id },
1208
+ { type: ReactionType.HAPPY, userId: students[6].id, announcementId: mathAnnouncement.id },
1214
1209
  ],
1215
- standards: ['NGSS HS-LS1-1', 'NGSS HS-LS1-2'],
1216
- duration: '2 weeks',
1217
- assessmentType: 'Lab report and quiz'
1218
1210
  });
1219
- // Create conversation for lab chat
1220
- const labConversation = await prisma.conversation.create({
1211
+ // A comment thread on the math announcement.
1212
+ const topComment = await prisma.comment.create({
1221
1213
  data: {
1222
- type: 'GROUP',
1223
- name: 'Lab: Cell Structure Materials',
1224
- displayInChat: false,
1225
- }
1214
+ content: "Will the quiz solutions help with the upcoming test as well?",
1215
+ authorId: students[4].id,
1216
+ announcementId: mathAnnouncement.id,
1217
+ },
1226
1218
  });
1227
- // Add teacher to conversation
1228
- await prisma.conversationMember.create({
1219
+ const replyComment = await prisma.comment.create({
1229
1220
  data: {
1230
- userId: teachers[0].id,
1231
- conversationId: labConversation.id,
1232
- role: 'ADMIN',
1233
- }
1221
+ content: "Yes — the test reuses the same concepts. Use these to self-check first.",
1222
+ authorId: teachers[1].id,
1223
+ announcementId: mathAnnouncement.id,
1224
+ parentCommentId: topComment.id,
1225
+ },
1234
1226
  });
1235
- // Create lab chat
1236
- await prisma.labChat.create({
1227
+ await prisma.reaction.create({
1228
+ data: { type: ReactionType.THUMBSUP, userId: students[4].id, commentId: replyComment.id },
1229
+ });
1230
+ // A comment directly on an assignment (asking for clarification).
1231
+ await prisma.comment.create({
1232
+ data: {
1233
+ content: "For question 4, are we expected to include a diagram or is text sufficient?",
1234
+ authorId: students[1].id,
1235
+ assignmentId: assignments[1].id, // Genetics Problem Set
1236
+ },
1237
+ });
1238
+ // ---------------------------------------------------------------------
1239
+ // 15. Discussions (with replies, tags, reads, reactions)
1240
+ // ---------------------------------------------------------------------
1241
+ const bioTag = await prisma.discussionTag.create({
1242
+ data: { classId: biologyClass.id, name: "Lab", color: "#10B981" },
1243
+ });
1244
+ const bioTagHomework = await prisma.discussionTag.create({
1245
+ data: { classId: biologyClass.id, name: "Homework Help", color: "#F59E0B" },
1246
+ });
1247
+ const mathTagAP = await prisma.discussionTag.create({
1248
+ data: { classId: mathClass.id, name: "AP Prep", color: "#DC2626" },
1249
+ });
1250
+ const labQuestion = await prisma.discussion.create({
1237
1251
  data: {
1238
- title: 'Cell Structure Teaching Materials',
1239
- context: labChatContext,
1240
1252
  classId: biologyClass.id,
1241
- conversationId: labConversation.id,
1242
- createdById: teachers[0].id,
1243
- }
1253
+ authorId: students[2].id,
1254
+ title: "What stain works best for onion cells?",
1255
+ body: "I tried iodine but the nuclei were hard to see. Anyone have better luck with methylene blue?",
1256
+ type: DiscussionType.QUESTION,
1257
+ tags: { connect: [{ id: bioTag.id }, { id: bioTagHomework.id }] },
1258
+ lastActivityAt: hoursFromNow(-2, now),
1259
+ },
1260
+ });
1261
+ await createFakeFile({
1262
+ name: "onion-cells-iodine.jpg",
1263
+ userId: students[2].id,
1264
+ discussionId: labQuestion.id,
1244
1265
  });
1245
- // 16. Create chat conversations and messages
1246
- // Teacher-to-teacher conversations
1247
- const teacherConversation1 = await prisma.conversation.create({
1266
+ const reply1 = await prisma.discussionReply.create({
1248
1267
  data: {
1249
- type: 'GROUP',
1250
- name: 'Math Department Chat',
1251
- displayInChat: true,
1252
- }
1268
+ discussionId: labQuestion.id,
1269
+ authorId: students[5].id,
1270
+ body: "Methylene blue is way clearer for nuclei. We used it last week.",
1271
+ },
1253
1272
  });
1254
- // Add all teachers to department chat
1255
- await Promise.all([
1256
- prisma.conversationMember.create({
1257
- data: {
1258
- userId: teachers[0].id, // Sarah (Biology)
1259
- conversationId: teacherConversation1.id,
1260
- role: 'MEMBER',
1261
- }
1262
- }),
1263
- prisma.conversationMember.create({
1264
- data: {
1265
- userId: teachers[1].id, // Michael (Math)
1266
- conversationId: teacherConversation1.id,
1267
- role: 'ADMIN',
1268
- }
1269
- }),
1270
- prisma.conversationMember.create({
1271
- data: {
1272
- userId: teachers[2].id, // Emma (English)
1273
- conversationId: teacherConversation1.id,
1274
- role: 'MEMBER',
1275
- }
1276
- }),
1277
- ]);
1278
- // Teacher-student conversations
1279
- const studentConversation1 = await prisma.conversation.create({
1273
+ const reply2 = await prisma.discussionReply.create({
1280
1274
  data: {
1281
- type: 'DM',
1282
- name: null,
1283
- displayInChat: true,
1284
- }
1275
+ discussionId: labQuestion.id,
1276
+ authorId: teachers[0].id,
1277
+ body: "Either works — focus on getting a thin sample. I'll bring more methylene blue tomorrow.",
1278
+ isAcceptedAnswer: true,
1279
+ },
1285
1280
  });
1286
- await Promise.all([
1287
- prisma.conversationMember.create({
1288
- data: {
1289
- userId: teachers[1].id, // Michael Chen
1290
- conversationId: studentConversation1.id,
1291
- role: 'MEMBER',
1292
- }
1293
- }),
1294
- prisma.conversationMember.create({
1295
- data: {
1296
- userId: students[4].id, // Ethan Anderson
1297
- conversationId: studentConversation1.id,
1298
- role: 'MEMBER',
1299
- }
1300
- }),
1301
- ]);
1302
- const studentConversation2 = await prisma.conversation.create({
1281
+ await prisma.reaction.createMany({
1282
+ data: [
1283
+ { type: ReactionType.THUMBSUP, userId: students[2].id, discussionReplyId: reply2.id },
1284
+ { type: ReactionType.IDEA, userId: students[1].id, discussionReplyId: reply1.id },
1285
+ ],
1286
+ });
1287
+ const announcementDiscussion = await prisma.discussion.create({
1303
1288
  data: {
1304
- type: 'DM',
1305
- name: null,
1306
- displayInChat: true,
1307
- }
1289
+ classId: mathClass.id,
1290
+ authorId: teachers[1].id,
1291
+ title: "AP Calculus BC Mock Exam — Saturday 10am",
1292
+ body: "Reminder: full-length mock exam this Saturday at 10am in Room 156. Bring two pencils and a calculator.",
1293
+ type: DiscussionType.ANNOUNCEMENT,
1294
+ isPinned: true,
1295
+ tags: { connect: [{ id: mathTagAP.id }] },
1296
+ lastActivityAt: hoursFromNow(-4, now),
1297
+ },
1308
1298
  });
1309
- await Promise.all([
1310
- prisma.conversationMember.create({
1311
- data: {
1312
- userId: teachers[1].id, // Michael Chen
1313
- conversationId: studentConversation2.id,
1314
- role: 'MEMBER',
1315
- }
1316
- }),
1317
- prisma.conversationMember.create({
1318
- data: {
1319
- userId: students[1].id, // Sophia Williams
1320
- conversationId: studentConversation2.id,
1321
- role: 'MEMBER',
1322
- }
1323
- }),
1324
- ]);
1325
- // Create messages in conversations
1326
- const chatMessages = [
1327
- // Teacher department chat messages
1328
- {
1329
- content: 'Hey everyone! Hope you all had a great weekend. Quick reminder about the faculty meeting tomorrow at noon.',
1330
- senderId: teachers[1].id,
1331
- conversationId: teacherConversation1.id,
1332
- createdAt: new Date('2024-10-07T07:30:00'),
1299
+ const generalDiscussion = await prisma.discussion.create({
1300
+ data: {
1301
+ classId: mathClass.id,
1302
+ authorId: students[8].id,
1303
+ title: "Any tips for memorizing integration formulas?",
1304
+ body: "Flashcards aren't sticking. Curious what's worked for everyone.",
1305
+ type: DiscussionType.DISCUSSION,
1306
+ lastActivityAt: hoursFromNow(-1, now),
1333
1307
  },
1334
- {
1335
- content: 'Thanks for the reminder, Michael! I\'ll be there. Are we discussing the new curriculum standards?',
1336
- senderId: teachers[0].id,
1337
- conversationId: teacherConversation1.id,
1338
- createdAt: new Date('2024-10-07T07:45:00'),
1308
+ });
1309
+ const genReply = await prisma.discussionReply.create({
1310
+ data: {
1311
+ discussionId: generalDiscussion.id,
1312
+ authorId: students[1].id,
1313
+ body: "I rewrite the formulas every morning for a week. Sounds annoying but it works.",
1339
1314
  },
1340
- {
1341
- content: 'Yes, among other things. Also wanted to coordinate on the parent-teacher conferences next week.',
1342
- senderId: teachers[1].id,
1343
- conversationId: teacherConversation1.id,
1344
- createdAt: new Date('2024-10-07T08:00:00'),
1315
+ });
1316
+ await prisma.discussionReply.create({
1317
+ data: {
1318
+ discussionId: generalDiscussion.id,
1319
+ authorId: students[10].id,
1320
+ parentId: genReply.id,
1321
+ body: "Going to try this — thanks!",
1345
1322
  },
1346
- {
1347
- content: 'Perfect timing! I have several parents asking about their kids\' progress in my English classes.',
1348
- senderId: teachers[2].id,
1349
- conversationId: teacherConversation1.id,
1350
- createdAt: new Date('2024-10-07T08:15:00'),
1323
+ });
1324
+ // A few discussion reads so unread counts look realistic.
1325
+ await prisma.discussionRead.createMany({
1326
+ data: [
1327
+ { userId: students[0].id, discussionId: labQuestion.id, lastReadAt: hoursFromNow(-1, now) },
1328
+ { userId: students[1].id, discussionId: labQuestion.id, lastReadAt: hoursFromNow(-3, now) },
1329
+ { userId: teachers[0].id, discussionId: labQuestion.id, lastReadAt: now },
1330
+ {
1331
+ userId: students[4].id,
1332
+ discussionId: announcementDiscussion.id,
1333
+ lastReadAt: hoursFromNow(-2, now),
1334
+ },
1335
+ ],
1336
+ });
1337
+ // ---------------------------------------------------------------------
1338
+ // 16. Events + attendance
1339
+ // ---------------------------------------------------------------------
1340
+ const teachingWeekStart = new Date("2026-10-12T08:00:00");
1341
+ const eventsToCreate = buildWeeklyTeacherEvents(teachingWeekStart, teachers[1].id, mathClass.id);
1342
+ eventsToCreate.push({
1343
+ name: "Cell Biology Lab",
1344
+ startTime: daysFromNow(2, now),
1345
+ endTime: hoursFromNow(2 * 24 + 1.5, now),
1346
+ location: "Science Lab Room 204",
1347
+ remarks: "Bring lab notebooks and safety goggles",
1348
+ userId: teachers[0].id,
1349
+ classId: biologyClass.id,
1350
+ color: "#10B981",
1351
+ }, {
1352
+ name: "Poetry Reading",
1353
+ startTime: daysFromNow(7, now),
1354
+ endTime: hoursFromNow(7 * 24 + 1, now),
1355
+ location: "Library",
1356
+ remarks: "Students will present their original poetry",
1357
+ userId: teachers[2].id,
1358
+ classId: englishClass.id,
1359
+ color: "#8B5CF6",
1360
+ });
1361
+ await runInBatches(eventsToCreate, (event) => prisma.event.create({ data: event }));
1362
+ logger.info(`Created ${eventsToCreate.length} events`);
1363
+ const attendanceDate = hoursFromNow(-24, now);
1364
+ await prisma.attendance.create({
1365
+ data: {
1366
+ date: attendanceDate,
1367
+ classId: biologyClass.id,
1368
+ present: { connect: students.slice(0, 6).map((s) => ({ id: s.id })) },
1369
+ late: { connect: [{ id: students[6].id }] },
1370
+ absent: { connect: [{ id: students[7].id }] },
1351
1371
  },
1352
- {
1353
- content: 'The integration unit is going well in AP Calc. Students seem to be grasping the concepts better this year.',
1354
- senderId: teachers[1].id,
1355
- conversationId: teacherConversation1.id,
1356
- createdAt: new Date('2024-10-08T16:30:00'),
1372
+ });
1373
+ await prisma.attendance.create({
1374
+ data: {
1375
+ date: attendanceDate,
1376
+ classId: mathClass.id,
1377
+ present: { connect: students.slice(4, 11).map((s) => ({ id: s.id })) },
1378
+ late: { connect: [{ id: students[11].id }] },
1357
1379
  },
1358
- {
1359
- content: 'That\'s great to hear! I\'ve been incorporating more hands-on labs in biology and seeing similar improvements.',
1380
+ });
1381
+ await prisma.attendance.create({
1382
+ data: {
1383
+ date: attendanceDate,
1384
+ classId: englishClass.id,
1385
+ present: { connect: students.slice(0, 9).map((s) => ({ id: s.id })) },
1386
+ absent: { connect: [{ id: students[9].id }] },
1387
+ },
1388
+ });
1389
+ // ---------------------------------------------------------------------
1390
+ // 17. Notifications
1391
+ // ---------------------------------------------------------------------
1392
+ await prisma.notification.createMany({
1393
+ data: [
1394
+ { receiverId: teachers[0].id, title: "New Submission", content: "Alex Martinez submitted Genetics Problem Set" },
1395
+ { receiverId: teachers[0].id, title: "Grade Reminder", content: "You have 3 assignments pending grading" },
1396
+ { receiverId: teachers[0].id, title: "Parent Contact", content: "Mrs. Williams requested a meeting about Sophia's progress" },
1397
+ { receiverId: teachers[1].id, title: "AI Grading Ready", content: "Derivative Applications Quiz AI feedback is ready to review" },
1398
+ { receiverId: teachers[1].id, title: "New Discussion Reply", content: "A student replied to 'Integration tips' in AP Calculus BC" },
1399
+ { receiverId: students[0].id, title: "Assignment Graded", content: "Your Genetics Problem Set has been graded: 45/50" },
1400
+ { receiverId: students[1].id, title: "Assignment Graded", content: "Your Genetics Problem Set has been graded: 48/50" },
1401
+ { receiverId: students[2].id, title: "Assignment Due", content: "Cell Structure Lab Report is due in 3 days" },
1402
+ { receiverId: students[3].id, title: "Class Announcement", content: "New announcement in AP Biology" },
1403
+ { receiverId: students[4].id, title: "Worksheet Returned", content: "Mr. Chen returned your Limits Practice Worksheet" },
1404
+ ],
1405
+ });
1406
+ // ---------------------------------------------------------------------
1407
+ // 18. Conversations / Messages / Mentions / LabChat / NewtonChat
1408
+ // ---------------------------------------------------------------------
1409
+ const labConversation = await prisma.conversation.create({
1410
+ data: {
1411
+ type: ConversationType.GROUP,
1412
+ name: "Lab: Cell Structure Materials",
1413
+ displayInChat: false,
1414
+ members: {
1415
+ create: [
1416
+ { userId: teachers[0].id, role: ConversationRole.ADMIN },
1417
+ { userId: aiUserId, role: ConversationRole.MEMBER },
1418
+ ],
1419
+ },
1420
+ },
1421
+ });
1422
+ await prisma.labChat.create({
1423
+ data: {
1424
+ title: "Cell Structure Teaching Materials",
1425
+ classId: biologyClass.id,
1426
+ conversationId: labConversation.id,
1427
+ createdById: teachers[0].id,
1428
+ context: JSON.stringify({
1429
+ subject: "Biology",
1430
+ topic: "Cell Structure and Function",
1431
+ gradeLevel: "11th Grade AP",
1432
+ difficulty: "Advanced",
1433
+ objectives: [
1434
+ "Understand prokaryotic vs eukaryotic cell structures",
1435
+ "Identify organelles and their functions",
1436
+ "Compare plant and animal cells",
1437
+ ],
1438
+ standards: ["NGSS HS-LS1-1", "NGSS HS-LS1-2"],
1439
+ duration: "2 weeks",
1440
+ assessmentType: "Lab report and quiz",
1441
+ }),
1442
+ },
1443
+ });
1444
+ // A short Lab Chat exchange so the AI surface has content.
1445
+ await prisma.message.create({
1446
+ data: {
1447
+ content: "Can you draft a 10-question multiple choice quiz on organelle functions for my AP Bio students?",
1360
1448
  senderId: teachers[0].id,
1361
- conversationId: teacherConversation1.id,
1362
- createdAt: new Date('2024-10-08T16:45:00'),
1449
+ conversationId: labConversation.id,
1450
+ createdAt: hoursFromNow(-3, now),
1363
1451
  },
1364
- {
1365
- content: 'Anyone free for coffee after school tomorrow? Would love to chat about cross-curricular projects.',
1366
- senderId: teachers[2].id,
1367
- conversationId: teacherConversation1.id,
1368
- createdAt: new Date('2024-10-09T14:00:00'),
1452
+ });
1453
+ await prisma.message.create({
1454
+ data: {
1455
+ content: "Sure! Here are 10 AP-level MCQs on organelle functions, with explanations on each. Want me to also generate a 4-criterion rubric for grading short-answer follow-ups?",
1456
+ senderId: aiUserId,
1457
+ conversationId: labConversation.id,
1458
+ createdAt: hoursFromNow(-2.95, now),
1459
+ status: JobStatus.COMPLETED,
1460
+ },
1461
+ });
1462
+ // A teacher-to-teacher group chat.
1463
+ const deptChat = await prisma.conversation.create({
1464
+ data: {
1465
+ type: ConversationType.GROUP,
1466
+ name: "Faculty Lounge",
1467
+ displayInChat: true,
1468
+ members: {
1469
+ create: [
1470
+ { userId: teachers[0].id, role: ConversationRole.MEMBER },
1471
+ { userId: teachers[1].id, role: ConversationRole.ADMIN },
1472
+ { userId: teachers[2].id, role: ConversationRole.MEMBER },
1473
+ { userId: admin.id, role: ConversationRole.MEMBER },
1474
+ ],
1475
+ },
1476
+ },
1477
+ });
1478
+ // DMs.
1479
+ const dmChenEthan = await prisma.conversation.create({
1480
+ data: {
1481
+ type: ConversationType.DM,
1482
+ members: {
1483
+ create: [
1484
+ { userId: teachers[1].id },
1485
+ { userId: students[4].id },
1486
+ ],
1487
+ },
1488
+ },
1489
+ });
1490
+ const dmChenSophia = await prisma.conversation.create({
1491
+ data: {
1492
+ type: ConversationType.DM,
1493
+ members: {
1494
+ create: [
1495
+ { userId: teachers[1].id },
1496
+ { userId: students[1].id },
1497
+ ],
1498
+ },
1369
1499
  },
1500
+ });
1501
+ const messages = [
1370
1502
  {
1371
- content: 'I\'m in! Math Club ends at 4:45, so anytime after 5 works for me.',
1503
+ content: "Quick reminder about the faculty meeting tomorrow at noon.",
1372
1504
  senderId: teachers[1].id,
1373
- conversationId: teacherConversation1.id,
1374
- createdAt: new Date('2024-10-09T14:15:00'),
1505
+ conversationId: deptChat.id,
1506
+ createdAt: new Date("2026-10-12T07:30:00"),
1375
1507
  },
1376
- // Michael Chen with Ethan Anderson (struggling student)
1377
1508
  {
1378
- content: 'Hi Mr. Chen, I\'m really struggling with the integration problems from today\'s class. Could you help explain substitution method again?',
1379
- senderId: students[4].id,
1380
- conversationId: studentConversation1.id,
1381
- createdAt: new Date('2024-10-09T15:30:00'),
1509
+ content: "Thanks for the heads up. Are we discussing the new curriculum standards?",
1510
+ senderId: teachers[0].id,
1511
+ conversationId: deptChat.id,
1512
+ createdAt: new Date("2026-10-12T07:45:00"),
1382
1513
  },
1383
1514
  {
1384
- content: 'Of course, Ethan! I\'m glad you reached out. The key is identifying what to substitute. Can you tell me which problem you\'re stuck on specifically?',
1515
+ content: "Yes, plus coordination for parent-teacher conferences. I'll bring the draft agenda.",
1385
1516
  senderId: teachers[1].id,
1386
- conversationId: studentConversation1.id,
1387
- createdAt: new Date('2024-10-09T15:45:00'),
1517
+ conversationId: deptChat.id,
1518
+ createdAt: new Date("2026-10-12T08:00:00"),
1519
+ attachmentNames: ["Draft Agenda.pdf"],
1388
1520
  },
1389
1521
  {
1390
- content: 'Problem #7 from the homework. I don\'t know how to choose what u should equal.',
1391
- senderId: students[4].id,
1392
- conversationId: studentConversation1.id,
1393
- createdAt: new Date('2024-10-09T16:00:00'),
1522
+ content: "Perfect — several parents are already asking about progress in my classes.",
1523
+ senderId: teachers[2].id,
1524
+ conversationId: deptChat.id,
1525
+ createdAt: new Date("2026-10-12T08:15:00"),
1394
1526
  },
1395
1527
  {
1396
- content: 'Great question! For u-substitution, look for a function whose derivative also appears in the integral. In #7, try setting u equal to the expression inside the parentheses. What do you get when you differentiate that?',
1528
+ content: "@sarah.johnson can you bring the Bio progress reports too?",
1397
1529
  senderId: teachers[1].id,
1398
- conversationId: studentConversation1.id,
1399
- createdAt: new Date('2024-10-09T16:15:00'),
1530
+ conversationId: deptChat.id,
1531
+ createdAt: new Date("2026-10-12T08:20:00"),
1532
+ mentions: [teachers[0].id],
1400
1533
  },
1534
+ // Ethan struggling student DM
1401
1535
  {
1402
- content: 'Oh! So u = 2x + 1, and du = 2dx? That means I need to adjust for the 2...',
1536
+ content: "Hi Mr. Chen, I'm really struggling with the integration problems. Could you explain substitution again?",
1403
1537
  senderId: students[4].id,
1404
- conversationId: studentConversation1.id,
1405
- createdAt: new Date('2024-10-09T17:00:00'),
1538
+ conversationId: dmChenEthan.id,
1539
+ createdAt: new Date("2026-10-13T15:30:00"),
1406
1540
  },
1407
1541
  {
1408
- content: 'Exactly! You\'ve got it. Remember to substitute back at the end. Feel free to come to tutoring tomorrow if you need more practice.',
1542
+ content: "Of course Ethan — glad you reached out. Which problem are you stuck on?",
1409
1543
  senderId: teachers[1].id,
1410
- conversationId: studentConversation1.id,
1411
- createdAt: new Date('2024-10-09T17:15:00'),
1544
+ conversationId: dmChenEthan.id,
1545
+ createdAt: new Date("2026-10-13T15:45:00"),
1412
1546
  },
1413
1547
  {
1414
- content: 'Thank you so much! This makes way more sense now. I\'ll definitely come to tutoring.',
1548
+ content: "Problem #7 from the homework.",
1415
1549
  senderId: students[4].id,
1416
- conversationId: studentConversation1.id,
1417
- createdAt: new Date('2024-10-09T17:30:00'),
1550
+ conversationId: dmChenEthan.id,
1551
+ createdAt: new Date("2026-10-13T16:00:00"),
1552
+ attachmentNames: ["IMG_4421.jpg"],
1418
1553
  },
1419
- // Michael Chen with Sophia Williams (high-achieving student)
1420
1554
  {
1421
- content: 'Hi Mr. Chen! I finished the integration homework early. Are there any extra challenge problems I could work on?',
1422
- senderId: students[1].id,
1423
- conversationId: studentConversation2.id,
1424
- createdAt: new Date('2024-10-10T19:00:00'),
1425
- },
1426
- {
1427
- content: 'Sophia, I love your enthusiasm! Try problems 45-50 from chapter 6. They involve integration by parts, which we\'ll cover next week.',
1555
+ content: "For u-sub, look for a function whose derivative also appears. Try u = the inside of the parentheses.",
1428
1556
  senderId: teachers[1].id,
1429
- conversationId: studentConversation2.id,
1430
- createdAt: new Date('2024-10-10T19:30:00'),
1557
+ conversationId: dmChenEthan.id,
1558
+ createdAt: new Date("2026-10-13T16:15:00"),
1431
1559
  },
1560
+ // Sophia gifted student DM
1432
1561
  {
1433
- content: 'Perfect! I looked ahead in the textbook and the integration by parts formula looks interesting. Is it similar to the product rule for derivatives?',
1562
+ content: "Mr. Chen! I finished the integration homework — are there any extra challenge problems I could try?",
1434
1563
  senderId: students[1].id,
1435
- conversationId: studentConversation2.id,
1436
- createdAt: new Date('2024-10-10T20:00:00'),
1564
+ conversationId: dmChenSophia.id,
1565
+ createdAt: new Date("2026-10-13T19:00:00"),
1437
1566
  },
1438
1567
  {
1439
- content: 'You\'re absolutely right! Integration by parts is essentially the reverse of the product rule. You have a great mathematical intuition.',
1568
+ content: "Try problems 45-50 from chapter 6 — they cover integration by parts.",
1440
1569
  senderId: teachers[1].id,
1441
- conversationId: studentConversation2.id,
1442
- createdAt: new Date('2024-10-10T20:15:00'),
1570
+ conversationId: dmChenSophia.id,
1571
+ createdAt: new Date("2026-10-13T19:30:00"),
1572
+ attachmentNames: ["Chapter 6 Challenge.pdf"],
1443
1573
  },
1444
1574
  {
1445
- content: 'Also, I wanted to ask about the Math Club competition. What topics should I focus on for preparation?',
1575
+ content: "Perfect, thanks! Integration by parts looks similar to the product rule for derivatives.",
1446
1576
  senderId: students[1].id,
1447
- conversationId: studentConversation2.id,
1448
- createdAt: new Date('2024-10-11T12:00:00'),
1449
- },
1450
- {
1451
- content: 'Great question! Focus on algebra, geometry, and basic calculus. I\'ll have practice problems ready for tomorrow\'s Math Club meeting.',
1452
- senderId: teachers[1].id,
1453
- conversationId: studentConversation2.id,
1454
- createdAt: new Date('2024-10-11T12:30:00'),
1577
+ conversationId: dmChenSophia.id,
1578
+ createdAt: new Date("2026-10-13T20:00:00"),
1455
1579
  },
1456
1580
  ];
1457
- // Create all messages in batches to avoid connection pool exhaustion
1458
- const MESSAGE_BATCH_SIZE = 10;
1459
- logger.info(`Creating ${chatMessages.length} chat messages in batches of ${MESSAGE_BATCH_SIZE}`);
1460
- for (let i = 0; i < chatMessages.length; i += MESSAGE_BATCH_SIZE) {
1461
- const batch = chatMessages.slice(i, i + MESSAGE_BATCH_SIZE);
1462
- await Promise.all(batch.map(message => prisma.message.create({ data: message })));
1581
+ for (const msg of messages) {
1582
+ const created = await prisma.message.create({
1583
+ data: {
1584
+ content: msg.content,
1585
+ senderId: msg.senderId,
1586
+ conversationId: msg.conversationId,
1587
+ createdAt: msg.createdAt,
1588
+ },
1589
+ });
1590
+ if (msg.mentions?.length) {
1591
+ await prisma.mention.createMany({
1592
+ data: msg.mentions.map((userId) => ({ messageId: created.id, userId })),
1593
+ });
1594
+ }
1595
+ if (msg.attachmentNames?.length) {
1596
+ await Promise.all(msg.attachmentNames.map((name) => createFakeFile({ name, userId: msg.senderId, messageId: created.id })));
1597
+ }
1463
1598
  }
1464
- logger.info(`Successfully created all ${chatMessages.length} chat messages`);
1465
- // 16. Create file structure for classes
1466
- await Promise.all([
1467
- prisma.folder.create({
1599
+ // Newton tutor chat tied to a submission.
1600
+ const newtonSubmission = submissionRecords.find((s) => s.assignmentId === limitsAssignment.id && s.submitted);
1601
+ if (newtonSubmission) {
1602
+ const newtonConv = await prisma.conversation.create({
1468
1603
  data: {
1469
- name: 'Class Files',
1470
- classId: biologyClass.id,
1471
- color: '#10B981',
1472
- }
1473
- }),
1474
- prisma.folder.create({
1604
+ type: ConversationType.GROUP,
1605
+ name: "Session with Newton Tutor",
1606
+ displayInChat: true,
1607
+ members: {
1608
+ create: [
1609
+ { userId: newtonSubmission.studentId, role: ConversationRole.MEMBER },
1610
+ { userId: aiUserId, role: ConversationRole.MEMBER },
1611
+ ],
1612
+ },
1613
+ },
1614
+ });
1615
+ await prisma.newtonChat.create({
1475
1616
  data: {
1476
- name: 'Class Files',
1477
- classId: mathClass.id,
1478
- color: '#3B82F6',
1479
- }
1480
- }),
1481
- prisma.folder.create({
1617
+ submissionId: newtonSubmission.id,
1618
+ conversationId: newtonConv.id,
1619
+ title: "Limits Practice — Newton Tutor",
1620
+ },
1621
+ });
1622
+ await prisma.message.create({
1482
1623
  data: {
1483
- name: 'Class Files',
1484
- classId: englishClass.id,
1485
- color: '#8B5CF6',
1486
- }
1487
- }),
1488
- ]);
1489
- logger.info('✅ Comprehensive demo database seeded successfully!');
1490
- logger.info('📚 Created:');
1491
- logger.info(' - 1 School (Riverside High School)');
1492
- logger.info(' - 3 Teachers with detailed profiles');
1493
- logger.info(' - 12 Students with profiles');
1494
- logger.info(' - 3 Classes (AP Biology, AP Calculus BC, English Literature)');
1495
- logger.info(' - 7 Course sections with color coding');
1496
- logger.info(' - 2 Mark schemes with detailed rubrics');
1497
- logger.info(' - 1 Grading boundary with weighted categories');
1498
- logger.info(' - 9 Assignments (6 for Calc BC demo class)');
1499
- logger.info(' - 64+ Student submissions with realistic grades & comments');
1500
- logger.info(' - 3 Announcements from different teachers');
1501
- logger.info(' - 3 Calendar events');
1502
- logger.info(' - Attendance records with present/late/absent tracking');
1503
- logger.info(' - 1 Lab chat for AI demo');
1504
- logger.info(' - Multiple realistic notifications');
1505
- logger.info(' - File organization structure');
1506
- logger.info('');
1507
- logger.info('🎯 AP Calculus BC Class (Demo Focus):');
1508
- logger.info(' - 6 assignments with varied due dates');
1509
- logger.info(' - Complete mark scheme & grading boundaries');
1510
- logger.info(' - All 8 students have submissions to all assignments');
1511
- logger.info(' - Realistic grade distribution & teacher feedback');
1512
- logger.info(' - Rubric-based grading with detailed breakdowns');
1513
- logger.info('');
1514
- logger.info('🎬 Demo accounts:');
1515
- logger.info(' Main Teacher: sarah.johnson@riverside.edu / demo123');
1516
- logger.info(' Math Teacher: michael.chen@riverside.edu / demo123');
1517
- logger.info(' English Teacher: emma.davis@riverside.edu / demo123');
1518
- logger.info(' Student: alex.martinez@student.riverside.edu / student123');
1624
+ content: "Hi! I'm Newton, your study buddy. I can see you're working on the Limits Practice worksheet. Want to go over Question 1?",
1625
+ senderId: aiUserId,
1626
+ conversationId: newtonConv.id,
1627
+ createdAt: now,
1628
+ status: JobStatus.COMPLETED,
1629
+ },
1630
+ });
1631
+ await prisma.message.create({
1632
+ data: {
1633
+ content: "Yes please — I got stuck on the sin(x)/x limit.",
1634
+ senderId: newtonSubmission.studentId,
1635
+ conversationId: newtonConv.id,
1636
+ createdAt: hoursFromNow(0.05, now),
1637
+ },
1638
+ });
1639
+ await prisma.message.create({
1640
+ data: {
1641
+ content: "Great pick. Try thinking about what sin(x) looks like near 0 — what does its slope look like there? That's the key intuition behind lim_{x→0} sin(x)/x = 1.",
1642
+ senderId: aiUserId,
1643
+ conversationId: newtonConv.id,
1644
+ createdAt: hoursFromNow(0.1, now),
1645
+ status: JobStatus.COMPLETED,
1646
+ },
1647
+ });
1648
+ }
1649
+ // ---------------------------------------------------------------------
1650
+ // 19. Summary
1651
+ // ---------------------------------------------------------------------
1652
+ logger.info("Demo database seeded.");
1653
+ logger.info(`Created: 1 school, ${[admin, ...teachers].length} staff, ${students.length} students, ${classes.length} classes, ${assignments.length} assignments, ${submissionRecords.length} submissions.`);
1654
+ logger.info("Demo accounts:");
1655
+ logger.info(" Admin: admin@riverside.edu / admin123");
1656
+ logger.info(" Bio: sarah.johnson@riverside.edu / demo123");
1657
+ logger.info(" Math: michael.chen@riverside.edu / demo123");
1658
+ logger.info(" English: emma.davis@riverside.edu / demo123");
1659
+ logger.info(" Student: alex.martinez@student.riverside.edu / student123");
1519
1660
  };
1520
- // Only run seedDatabase when executed directly (npm run seed)
1521
- // Not when imported by other modules
1522
- const isSeedScript = process.argv[1]?.includes('seedDatabase') ||
1523
- process.argv.includes('seed');
1661
+ // -----------------------------------------------------------------------------
1662
+ // Local helpers (small enough to keep at the bottom)
1663
+ // -----------------------------------------------------------------------------
1664
+ function assignmentTeacherFor(assignmentId, assignments) {
1665
+ return assignments.find((a) => a.id === assignmentId)?.teacherId ?? assignments[0].teacherId;
1666
+ }
1667
+ function sampleExtendedResponse(assignmentTitle) {
1668
+ return `Response for "${assignmentTitle}".\n\nThis essay addresses the prompt by walking through the key points covered in class, supported with examples and a short reflection on what surprised me. (Demo seed text — would be student-written in production.)`;
1669
+ }
1670
+ function buildWeeklyTeacherEvents(weekStart, teacherId, classId) {
1671
+ const dayOffset = (n) => daysFromNow(n, weekStart);
1672
+ const at = (offset, h, m) => {
1673
+ const d = dayOffset(offset);
1674
+ d.setHours(h, m, 0, 0);
1675
+ return d;
1676
+ };
1677
+ return [
1678
+ // Monday
1679
+ { name: "AP Calculus BC", startTime: at(0, 9, 15), endTime: at(0, 10, 5), location: "Room 156", remarks: "Derivatives unit test review", userId: teacherId, classId, color: "#3B82F6" },
1680
+ { name: "Department Head Meeting", startTime: at(0, 10, 30), endTime: at(0, 11, 30), location: "Conference Room A", remarks: "Curriculum planning", userId: teacherId, color: "#8B5CF6" },
1681
+ { name: "AP Statistics", startTime: at(0, 13, 45), endTime: at(0, 14, 35), location: "Room 156", remarks: "Hypothesis testing introduction", userId: teacherId, color: "#3B82F6" },
1682
+ { name: "Parent Conference — Williams Family", startTime: at(0, 15, 0), endTime: at(0, 15, 30), location: "Room 156", remarks: "Discuss Sophia's progress", userId: teacherId, color: "#F59E0B" },
1683
+ { name: "Math Tutoring", startTime: at(0, 15, 45), endTime: at(0, 16, 45), location: "Room 156", remarks: "Extra help for struggling students", userId: teacherId, color: "#10B981" },
1684
+ // Tuesday
1685
+ { name: "AP Calculus BC", startTime: at(1, 9, 15), endTime: at(1, 10, 5), location: "Room 156", remarks: "Derivatives unit test", userId: teacherId, classId, color: "#3B82F6" },
1686
+ { name: "Faculty Meeting", startTime: at(1, 12, 0), endTime: at(1, 13, 0), location: "Main Auditorium", userId: teacherId, color: "#6B7280" },
1687
+ { name: "AP Statistics", startTime: at(1, 13, 45), endTime: at(1, 14, 35), location: "Room 156", remarks: "Hypothesis testing practice", userId: teacherId, color: "#3B82F6" },
1688
+ // Wednesday
1689
+ { name: "AP Calculus BC", startTime: at(2, 9, 15), endTime: at(2, 10, 5), location: "Room 156", remarks: "Integration introduction", userId: teacherId, classId, color: "#3B82F6" },
1690
+ { name: "Professional Development", startTime: at(2, 11, 0), endTime: at(2, 12, 30), location: "Library Conference Room", remarks: "Tech in Math Education", userId: teacherId, color: "#059669" },
1691
+ { name: "AP Statistics", startTime: at(2, 13, 45), endTime: at(2, 14, 35), location: "Room 156", remarks: "Chi-square tests", userId: teacherId, color: "#3B82F6" },
1692
+ // Thursday
1693
+ { name: "AP Calculus BC", startTime: at(3, 9, 15), endTime: at(3, 10, 5), location: "Room 156", remarks: "Integration by substitution", userId: teacherId, classId, color: "#3B82F6" },
1694
+ { name: "Curriculum Committee", startTime: at(3, 11, 0), endTime: at(3, 12, 0), location: "Principal's Office", userId: teacherId, color: "#8B5CF6" },
1695
+ { name: "AP Statistics", startTime: at(3, 13, 45), endTime: at(3, 14, 35), location: "Room 156", remarks: "ANOVA introduction", userId: teacherId, color: "#3B82F6" },
1696
+ { name: "Math Club", startTime: at(3, 15, 45), endTime: at(3, 16, 45), location: "Room 156", remarks: "Prepping for state competition", userId: teacherId, color: "#10B981" },
1697
+ // Friday
1698
+ { name: "AP Calculus BC", startTime: at(4, 9, 15), endTime: at(4, 10, 5), location: "Room 156", remarks: "Integration by parts", userId: teacherId, classId, color: "#3B82F6" },
1699
+ { name: "AP Statistics", startTime: at(4, 13, 45), endTime: at(4, 14, 35), location: "Room 156", remarks: "ANOVA practice", userId: teacherId, color: "#3B82F6" },
1700
+ { name: "Weekend Prep Session", startTime: at(4, 15, 0), endTime: at(4, 17, 0), location: "Room 156", remarks: "Voluntary AP exam prep", userId: teacherId, color: "#059669" },
1701
+ ];
1702
+ }
1703
+ // -----------------------------------------------------------------------------
1704
+ // Entry point
1705
+ // -----------------------------------------------------------------------------
1706
+ const isSeedScript = process.argv[1]?.includes("seedDatabase") || process.argv.includes("seed");
1524
1707
  if (isSeedScript) {
1525
1708
  (async () => {
1526
- logger.info('Seeding database');
1709
+ logger.info("Seeding database");
1527
1710
  await seedDatabase();
1528
- logger.info('Database seeded');
1711
+ logger.info("Database seeded");
1529
1712
  })();
1530
1713
  }
1531
1714
  //# sourceMappingURL=seedDatabase.js.map
1532
- //# debugId=4e5c4562-c500-5409-8250-b98dc8416587
1715
+ //# debugId=a764ff67-df4a-5c42-b133-d6b43f06c40e