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