完整性更新
现在已经实现了大部分基础功能
This commit is contained in:
567
scripts/seed.ts
567
scripts/seed.ts
@@ -3,8 +3,16 @@ import { db } from "../src/shared/db";
|
||||
import {
|
||||
users, roles, usersToRoles,
|
||||
questions, knowledgePoints, questionsToKnowledgePoints,
|
||||
exams, examQuestions, examSubmissions, submissionAnswers,
|
||||
textbooks, chapters
|
||||
exams, examQuestions,
|
||||
homeworkAssignments,
|
||||
homeworkAssignmentQuestions,
|
||||
homeworkAssignmentTargets,
|
||||
homeworkSubmissions,
|
||||
homeworkAnswers,
|
||||
textbooks, chapters,
|
||||
schools,
|
||||
grades,
|
||||
classes, classEnrollments, classSchedule
|
||||
} from "../src/shared/db/schema";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { faker } from "@faker-js/faker";
|
||||
@@ -30,9 +38,12 @@ async function seed() {
|
||||
try {
|
||||
await db.execute(sql`SET FOREIGN_KEY_CHECKS = 0;`);
|
||||
const tables = [
|
||||
"class_schedule", "class_enrollments", "classes",
|
||||
"homework_answers", "homework_submissions", "homework_assignment_targets", "homework_assignment_questions", "homework_assignments",
|
||||
"submission_answers", "exam_submissions", "exam_questions", "exams",
|
||||
"questions_to_knowledge_points", "questions", "knowledge_points",
|
||||
"chapters", "textbooks",
|
||||
"grades", "schools",
|
||||
"users_to_roles", "roles", "users", "accounts", "sessions"
|
||||
];
|
||||
for (const table of tables) {
|
||||
@@ -52,14 +63,16 @@ async function seed() {
|
||||
admin: "role_admin",
|
||||
teacher: "role_teacher",
|
||||
student: "role_student",
|
||||
grade_head: "role_grade_head"
|
||||
grade_head: "role_grade_head",
|
||||
teaching_head: "role_teaching_head"
|
||||
};
|
||||
|
||||
await db.insert(roles).values([
|
||||
{ id: roleMap.admin, name: "admin", description: "System Administrator" },
|
||||
{ id: roleMap.teacher, name: "teacher", description: "Academic Instructor" },
|
||||
{ id: roleMap.student, name: "student", description: "Learner" },
|
||||
{ id: roleMap.grade_head, name: "grade_head", description: "Head of Grade Year" }
|
||||
{ id: roleMap.grade_head, name: "grade_head", description: "Head of Grade Year" },
|
||||
{ id: roleMap.teaching_head, name: "teaching_head", description: "Teaching Research Lead" }
|
||||
]);
|
||||
|
||||
// Users
|
||||
@@ -98,6 +111,107 @@ async function seed() {
|
||||
{ userId: "user_student_1", roleId: roleMap.student },
|
||||
]);
|
||||
|
||||
const extraStudentIds: string[] = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const studentId = createId();
|
||||
extraStudentIds.push(studentId);
|
||||
await db.insert(users).values({
|
||||
id: studentId,
|
||||
name: faker.person.fullName(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
role: "student",
|
||||
image: `https://api.dicebear.com/7.x/avataaars/svg?seed=${studentId}`,
|
||||
});
|
||||
await db.insert(usersToRoles).values({ userId: studentId, roleId: roleMap.student });
|
||||
}
|
||||
|
||||
const schoolId = "school_nextedu"
|
||||
const grade10Id = "grade_10"
|
||||
|
||||
await db.insert(schools).values([
|
||||
{ id: schoolId, name: "Next_Edu School", code: "NEXTEDU" },
|
||||
{ id: "school_demo_2", name: "Demo School No.2", code: "DEMO2" },
|
||||
])
|
||||
|
||||
await db.insert(grades).values([
|
||||
{
|
||||
id: grade10Id,
|
||||
schoolId,
|
||||
name: "Grade 10",
|
||||
order: 10,
|
||||
gradeHeadId: "user_teacher_math",
|
||||
teachingHeadId: "user_teacher_math",
|
||||
},
|
||||
])
|
||||
|
||||
await db.insert(classes).values([
|
||||
{
|
||||
id: "class_10_3",
|
||||
schoolName: "Next_Edu School",
|
||||
schoolId,
|
||||
name: "Grade 10 · Class 3",
|
||||
grade: "Grade 10",
|
||||
gradeId: grade10Id,
|
||||
homeroom: "10-3",
|
||||
room: "Room 304",
|
||||
invitationCode: "100003",
|
||||
teacherId: "user_teacher_math",
|
||||
},
|
||||
{
|
||||
id: "class_10_7",
|
||||
schoolName: "Next_Edu School",
|
||||
schoolId,
|
||||
name: "Grade 10 · Class 7",
|
||||
grade: "Grade 10",
|
||||
gradeId: grade10Id,
|
||||
homeroom: "10-7",
|
||||
room: "Room 201",
|
||||
invitationCode: "100007",
|
||||
teacherId: "user_teacher_math",
|
||||
},
|
||||
]);
|
||||
|
||||
await db.insert(classEnrollments).values([
|
||||
{ classId: "class_10_3", studentId: "user_student_1", status: "active" },
|
||||
...extraStudentIds.slice(0, 8).map((studentId) => ({ classId: "class_10_3", studentId, status: "active" as const })),
|
||||
...extraStudentIds.slice(8, 12).map((studentId) => ({ classId: "class_10_7", studentId, status: "active" as const })),
|
||||
]);
|
||||
|
||||
await db.insert(classSchedule).values([
|
||||
{ id: "cs_001", classId: "class_10_3", weekday: 1, startTime: "09:00", endTime: "09:45", course: "Mathematics", location: "Room 304" },
|
||||
{ id: "cs_002", classId: "class_10_3", weekday: 3, startTime: "14:00", endTime: "14:45", course: "Physics", location: "Lab A" },
|
||||
{ id: "cs_003", classId: "class_10_7", weekday: 2, startTime: "11:00", endTime: "11:45", course: "Mathematics", location: "Room 201" },
|
||||
]);
|
||||
|
||||
await db.insert(textbooks).values([
|
||||
{
|
||||
id: "tb_01",
|
||||
title: "Advanced Mathematics Grade 10",
|
||||
subject: "Mathematics",
|
||||
grade: "Grade 10",
|
||||
publisher: "Next Education Press",
|
||||
},
|
||||
])
|
||||
|
||||
await db.insert(chapters).values([
|
||||
{
|
||||
id: "ch_01",
|
||||
textbookId: "tb_01",
|
||||
title: "Chapter 1: Real Numbers",
|
||||
order: 1,
|
||||
parentId: null,
|
||||
content: "# Chapter 1: Real Numbers\n\nIn this chapter, we will explore the properties of real numbers...",
|
||||
},
|
||||
{
|
||||
id: "ch_01_01",
|
||||
textbookId: "tb_01",
|
||||
title: "1.1 Introduction to Real Numbers",
|
||||
order: 1,
|
||||
parentId: "ch_01",
|
||||
content: "## 1.1 Introduction\n\nReal numbers include rational and irrational numbers.",
|
||||
},
|
||||
])
|
||||
|
||||
// --- 2. Knowledge Graph (Tree) ---
|
||||
console.log("🧠 Seeding Knowledge Graph...");
|
||||
|
||||
@@ -111,103 +225,273 @@ async function seed() {
|
||||
{ id: kpLinearId, name: "Linear Equations", parentId: kpAlgebraId, level: 2 },
|
||||
]);
|
||||
|
||||
await db.insert(knowledgePoints).values([
|
||||
{
|
||||
id: "kp_01",
|
||||
name: "Real Numbers",
|
||||
description: "Definition and properties of real numbers",
|
||||
level: 1,
|
||||
order: 1,
|
||||
chapterId: "ch_01",
|
||||
},
|
||||
{
|
||||
id: "kp_02",
|
||||
name: "Rational Numbers",
|
||||
description: "Numbers that can be expressed as a fraction",
|
||||
level: 2,
|
||||
order: 1,
|
||||
chapterId: "ch_01_01",
|
||||
},
|
||||
])
|
||||
|
||||
// --- 3. Question Bank (Rich Content) ---
|
||||
console.log("📚 Seeding Question Bank...");
|
||||
|
||||
// 3.1 Simple Single Choice
|
||||
const qSimpleId = createId();
|
||||
await db.insert(questions).values({
|
||||
id: qSimpleId,
|
||||
authorId: "user_teacher_math",
|
||||
type: "single_choice",
|
||||
difficulty: 1,
|
||||
content: {
|
||||
text: "What is 2 + 2?",
|
||||
options: [
|
||||
{ id: "A", text: "3", isCorrect: false },
|
||||
{ id: "B", text: "4", isCorrect: true },
|
||||
{ id: "C", text: "5", isCorrect: false }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Link to KP
|
||||
await db.insert(questionsToKnowledgePoints).values({
|
||||
questionId: qSimpleId,
|
||||
knowledgePointId: kpLinearId // Just for demo
|
||||
});
|
||||
|
||||
// 3.2 Composite Question (Reading Comprehension)
|
||||
const qParentId = createId();
|
||||
const qChild1Id = createId();
|
||||
const qChild2Id = createId();
|
||||
|
||||
// Parent (Passage)
|
||||
await db.insert(questions).values({
|
||||
id: qParentId,
|
||||
authorId: "user_teacher_math",
|
||||
type: "composite",
|
||||
difficulty: 3,
|
||||
content: {
|
||||
text: "Read the following passage about Algebra...\n(Long text here)...",
|
||||
assets: []
|
||||
}
|
||||
});
|
||||
|
||||
// Children
|
||||
await db.insert(questions).values([
|
||||
const mathExamQuestions: Array<{
|
||||
id: string;
|
||||
type: "single_choice" | "text" | "judgment";
|
||||
difficulty: number;
|
||||
content: unknown;
|
||||
score: number;
|
||||
}> = [
|
||||
{
|
||||
id: qChild1Id,
|
||||
authorId: "user_teacher_math",
|
||||
parentId: qParentId, // <--- Key: Nested
|
||||
id: createId(),
|
||||
type: "single_choice",
|
||||
difficulty: 2,
|
||||
difficulty: 1,
|
||||
score: 4,
|
||||
content: {
|
||||
text: "What is the main topic?",
|
||||
text: "1) What is 2 + 2?",
|
||||
options: [
|
||||
{ id: "A", text: "Geometry", isCorrect: false },
|
||||
{ id: "B", text: "Algebra", isCorrect: true }
|
||||
]
|
||||
}
|
||||
{ id: "A", text: "3", isCorrect: false },
|
||||
{ id: "B", text: "4", isCorrect: true },
|
||||
{ id: "C", text: "5", isCorrect: false },
|
||||
{ id: "D", text: "6", isCorrect: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: qChild2Id,
|
||||
authorId: "user_teacher_math",
|
||||
parentId: qParentId,
|
||||
type: "text",
|
||||
difficulty: 4,
|
||||
id: createId(),
|
||||
type: "single_choice",
|
||||
difficulty: 2,
|
||||
score: 4,
|
||||
content: {
|
||||
text: "Explain the concept of variables.",
|
||||
}
|
||||
}
|
||||
]);
|
||||
text: "2) If f(x) = 2x + 1, then f(3) = ?",
|
||||
options: [
|
||||
{ id: "A", text: "5", isCorrect: false },
|
||||
{ id: "B", text: "7", isCorrect: true },
|
||||
{ id: "C", text: "8", isCorrect: false },
|
||||
{ id: "D", text: "10", isCorrect: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: "single_choice",
|
||||
difficulty: 2,
|
||||
score: 4,
|
||||
content: {
|
||||
text: "3) Solve 3x - 5 = 7. What is x?",
|
||||
options: [
|
||||
{ id: "A", text: "3", isCorrect: false },
|
||||
{ id: "B", text: "4", isCorrect: true },
|
||||
{ id: "C", text: "5", isCorrect: false },
|
||||
{ id: "D", text: "6", isCorrect: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: "single_choice",
|
||||
difficulty: 3,
|
||||
score: 4,
|
||||
content: {
|
||||
text: "4) Which is a factor of x^2 - 9?",
|
||||
options: [
|
||||
{ id: "A", text: "(x - 3)", isCorrect: true },
|
||||
{ id: "B", text: "(x + 9)", isCorrect: false },
|
||||
{ id: "C", text: "(x - 9)", isCorrect: false },
|
||||
{ id: "D", text: "(x^2 + 9)", isCorrect: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: "single_choice",
|
||||
difficulty: 2,
|
||||
score: 4,
|
||||
content: {
|
||||
text: "5) If a^2 = 49 and a > 0, then a = ?",
|
||||
options: [
|
||||
{ id: "A", text: "-7", isCorrect: false },
|
||||
{ id: "B", text: "0", isCorrect: false },
|
||||
{ id: "C", text: "7", isCorrect: true },
|
||||
{ id: "D", text: "49", isCorrect: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: "single_choice",
|
||||
difficulty: 3,
|
||||
score: 4,
|
||||
content: {
|
||||
text: "6) Simplify (x^2 y)(x y^3).",
|
||||
options: [
|
||||
{ id: "A", text: "x^2 y^3", isCorrect: false },
|
||||
{ id: "B", text: "x^3 y^4", isCorrect: true },
|
||||
{ id: "C", text: "x^3 y^3", isCorrect: false },
|
||||
{ id: "D", text: "x^4 y^4", isCorrect: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: "single_choice",
|
||||
difficulty: 2,
|
||||
score: 4,
|
||||
content: {
|
||||
text: "7) The slope of the line y = -3x + 2 is:",
|
||||
options: [
|
||||
{ id: "A", text: "2", isCorrect: false },
|
||||
{ id: "B", text: "-3", isCorrect: true },
|
||||
{ id: "C", text: "3", isCorrect: false },
|
||||
{ id: "D", text: "-2", isCorrect: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: "single_choice",
|
||||
difficulty: 1,
|
||||
score: 4,
|
||||
content: {
|
||||
text: "8) The probability of getting heads in one fair coin toss is:",
|
||||
options: [
|
||||
{ id: "A", text: "0", isCorrect: false },
|
||||
{ id: "B", text: "1/4", isCorrect: false },
|
||||
{ id: "C", text: "1/2", isCorrect: true },
|
||||
{ id: "D", text: "1", isCorrect: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: "single_choice",
|
||||
difficulty: 2,
|
||||
score: 4,
|
||||
content: {
|
||||
text: "9) In an arithmetic sequence with a1 = 2 and d = 3, a5 = ?",
|
||||
options: [
|
||||
{ id: "A", text: "11", isCorrect: false },
|
||||
{ id: "B", text: "12", isCorrect: false },
|
||||
{ id: "C", text: "14", isCorrect: true },
|
||||
{ id: "D", text: "17", isCorrect: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
type: "single_choice",
|
||||
difficulty: 2,
|
||||
score: 4,
|
||||
content: {
|
||||
text: "10) The solution set of x^2 = 0 is:",
|
||||
options: [
|
||||
{ id: "A", text: "{0}", isCorrect: true },
|
||||
{ id: "B", text: "{1}", isCorrect: false },
|
||||
{ id: "C", text: "{-1, 1}", isCorrect: false },
|
||||
{ id: "D", text: "Empty set", isCorrect: false },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{ id: createId(), type: "text", difficulty: 1, score: 4, content: { text: "11) Fill in the blank: √81 = ____.", correctAnswer: "9" } },
|
||||
{ id: createId(), type: "text", difficulty: 2, score: 4, content: { text: "12) Fill in the blank: (a - b)^2 = ____.", correctAnswer: ["a^2 - 2ab + b^2", "a² - 2ab + b²"] } },
|
||||
{ id: createId(), type: "text", difficulty: 1, score: 4, content: { text: "13) Fill in the blank: 2^5 = ____.", correctAnswer: "32" } },
|
||||
{ id: createId(), type: "text", difficulty: 2, score: 4, content: { text: "14) Fill in the blank: The area of a circle with radius r is ____.", correctAnswer: ["πr^2", "pi r^2", "πr²"] } },
|
||||
{ id: createId(), type: "text", difficulty: 1, score: 4, content: { text: "15) Fill in the blank: If x = -2, then x^3 = ____.", correctAnswer: "-8" } },
|
||||
|
||||
{ id: createId(), type: "judgment", difficulty: 1, score: 2, content: { text: "16) If x > y, then x + 1 > y + 1.", correctAnswer: true } },
|
||||
{ id: createId(), type: "judgment", difficulty: 1, score: 2, content: { text: "17) The graph of y = 2x is a parabola.", correctAnswer: false } },
|
||||
{ id: createId(), type: "judgment", difficulty: 1, score: 2, content: { text: "18) The sum of interior angles of a triangle is 180°.", correctAnswer: true } },
|
||||
{ id: createId(), type: "judgment", difficulty: 2, score: 2, content: { text: "19) (x + y)^2 = x^2 + y^2.", correctAnswer: false } },
|
||||
{ id: createId(), type: "judgment", difficulty: 1, score: 2, content: { text: "20) 0 is a positive number.", correctAnswer: false } },
|
||||
|
||||
{ id: createId(), type: "text", difficulty: 3, score: 10, content: { text: "21) Solve the system: x + y = 5, x - y = 1." } },
|
||||
{ id: createId(), type: "text", difficulty: 3, score: 10, content: { text: "22) Expand and simplify: (2x - 3)(x + 4)." } },
|
||||
{ id: createId(), type: "text", difficulty: 3, score: 10, content: { text: "23) In a right triangle with legs 6 and 8, find the hypotenuse and the area." } },
|
||||
];
|
||||
|
||||
await db.insert(questions).values(
|
||||
mathExamQuestions.map((q) => ({
|
||||
id: q.id,
|
||||
type: q.type,
|
||||
difficulty: q.difficulty,
|
||||
content: q.content,
|
||||
authorId: "user_teacher_math",
|
||||
}))
|
||||
);
|
||||
|
||||
await db.insert(questionsToKnowledgePoints).values({
|
||||
questionId: mathExamQuestions[0].id,
|
||||
knowledgePointId: kpLinearId
|
||||
});
|
||||
|
||||
// --- 4. Exams (New Structure) ---
|
||||
console.log("📝 Seeding Exams...");
|
||||
|
||||
const examId = createId();
|
||||
|
||||
const makeGroup = (title: string, children: unknown[]) => ({
|
||||
id: createId(),
|
||||
type: "group",
|
||||
title,
|
||||
children,
|
||||
});
|
||||
|
||||
const makeQuestionNode = (questionId: string, score: number) => ({
|
||||
id: createId(),
|
||||
type: "question",
|
||||
questionId,
|
||||
score,
|
||||
});
|
||||
|
||||
const choiceIds = mathExamQuestions.slice(0, 10).map((q) => q.id);
|
||||
const fillIds = mathExamQuestions.slice(10, 15).map((q) => q.id);
|
||||
const judgmentIds = mathExamQuestions.slice(15, 20).map((q) => q.id);
|
||||
const shortAnswerIds = mathExamQuestions.slice(20, 23).map((q) => q.id);
|
||||
|
||||
const examStructure = [
|
||||
{
|
||||
type: "group",
|
||||
title: "Part 1: Basics",
|
||||
children: [
|
||||
{ type: "question", questionId: qSimpleId, score: 10 }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "group",
|
||||
title: "Part 2: Reading",
|
||||
children: [
|
||||
// For composite questions, we usually add the parent, and the system fetches children
|
||||
{ type: "question", questionId: qParentId, score: 20 }
|
||||
]
|
||||
}
|
||||
makeGroup(
|
||||
"第一部分:单项选择题(共10题,每题4分,共40分)",
|
||||
choiceIds.map((id) => makeQuestionNode(id, 4))
|
||||
),
|
||||
makeGroup(
|
||||
"第二部分:填空题(共5题,每题4分,共20分)",
|
||||
fillIds.map((id) => makeQuestionNode(id, 4))
|
||||
),
|
||||
makeGroup(
|
||||
"第三部分:判断题(共5题,每题2分,共10分)",
|
||||
judgmentIds.map((id) => makeQuestionNode(id, 2))
|
||||
),
|
||||
makeGroup(
|
||||
"第四部分:解答题(共3题,每题10分,共30分)",
|
||||
shortAnswerIds.map((id) => makeQuestionNode(id, 10))
|
||||
),
|
||||
];
|
||||
|
||||
await db.insert(exams).values({
|
||||
id: examId,
|
||||
title: "Algebra Mid-Term 2025",
|
||||
description: "Comprehensive assessment",
|
||||
title: "Grade 10 Mathematics Final Exam (Seed)",
|
||||
description: JSON.stringify({
|
||||
subject: "Mathematics",
|
||||
grade: "Grade 10",
|
||||
difficulty: 3,
|
||||
totalScore: 100,
|
||||
durationMin: 120,
|
||||
questionCount: 23,
|
||||
tags: ["seed", "math", "grade10", "final"],
|
||||
}),
|
||||
creatorId: "user_teacher_math",
|
||||
status: "published",
|
||||
startTime: new Date(),
|
||||
@@ -215,15 +499,118 @@ async function seed() {
|
||||
});
|
||||
|
||||
// Link questions physically (Source of Truth)
|
||||
await db.insert(examQuestions).values([
|
||||
{ examId, questionId: qSimpleId, score: 10, order: 0 },
|
||||
{ examId, questionId: qParentId, score: 20, order: 1 },
|
||||
// Note: Child questions are often implicitly included or explicitly added depending on logic.
|
||||
// For this seed, we assume linking Parent is enough for the relation,
|
||||
// but let's link children too for completeness if the query strategy requires it.
|
||||
{ examId, questionId: qChild1Id, score: 0, order: 2 },
|
||||
{ examId, questionId: qChild2Id, score: 0, order: 3 },
|
||||
]);
|
||||
const orderedQuestionIds = [...choiceIds, ...fillIds, ...judgmentIds, ...shortAnswerIds];
|
||||
const scoreById = new Map(mathExamQuestions.map((q) => [q.id, q.score] as const));
|
||||
|
||||
await db.insert(examQuestions).values(
|
||||
orderedQuestionIds.map((questionId, order) => ({
|
||||
examId,
|
||||
questionId,
|
||||
score: scoreById.get(questionId) ?? 0,
|
||||
order,
|
||||
}))
|
||||
);
|
||||
|
||||
console.log("📌 Seeding Homework Assignments...");
|
||||
|
||||
const assignmentId = createId();
|
||||
const now = new Date();
|
||||
const dueAt = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
|
||||
const lateDueAt = new Date(now.getTime() + 9 * 24 * 60 * 60 * 1000);
|
||||
|
||||
await db.insert(homeworkAssignments).values({
|
||||
id: assignmentId,
|
||||
sourceExamId: examId,
|
||||
title: "Grade 10 Mathematics Final - Homework (Seed)",
|
||||
description: "Auto-generated homework assignment from seeded math exam.",
|
||||
structure: examStructure as unknown,
|
||||
status: "published",
|
||||
creatorId: "user_teacher_math",
|
||||
availableAt: now,
|
||||
dueAt,
|
||||
allowLate: true,
|
||||
lateDueAt,
|
||||
maxAttempts: 2,
|
||||
});
|
||||
|
||||
await db.insert(homeworkAssignmentQuestions).values(
|
||||
orderedQuestionIds.map((questionId, order) => ({
|
||||
assignmentId,
|
||||
questionId,
|
||||
score: scoreById.get(questionId) ?? 0,
|
||||
order,
|
||||
}))
|
||||
);
|
||||
|
||||
const targetStudentIds = ["user_student_1", ...extraStudentIds.slice(0, 4)];
|
||||
await db.insert(homeworkAssignmentTargets).values(
|
||||
targetStudentIds.map((studentId) => ({
|
||||
assignmentId,
|
||||
studentId,
|
||||
}))
|
||||
);
|
||||
|
||||
const scoreForQuestion = (questionId: string) => scoreById.get(questionId) ?? 0;
|
||||
const buildAnswer = (questionId: string, type: string) => {
|
||||
if (type === "single_choice") return { answer: "B" };
|
||||
if (type === "judgment") return { answer: true };
|
||||
return { answer: "Seed answer" };
|
||||
};
|
||||
|
||||
const questionTypeById = new Map(mathExamQuestions.map((q) => [q.id, q.type] as const));
|
||||
|
||||
const submissionIds: string[] = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const studentId = targetStudentIds[i];
|
||||
const submissionId = createId();
|
||||
submissionIds.push(submissionId);
|
||||
const submittedAt = new Date(now.getTime() - (i + 1) * 24 * 60 * 60 * 1000);
|
||||
const status = i === 0 ? "graded" : i === 1 ? "graded" : "submitted";
|
||||
|
||||
const perQuestionScores = orderedQuestionIds.map((qid, idx) => {
|
||||
const max = scoreForQuestion(qid);
|
||||
if (status !== "graded") return null;
|
||||
if (max <= 0) return 0;
|
||||
if (idx % 7 === 0) return Math.max(0, max - 1);
|
||||
return max;
|
||||
});
|
||||
|
||||
const totalScore =
|
||||
status === "graded"
|
||||
? perQuestionScores.reduce<number>((sum, s) => sum + Number(s ?? 0), 0)
|
||||
: null;
|
||||
|
||||
await db.insert(homeworkSubmissions).values({
|
||||
id: submissionId,
|
||||
assignmentId,
|
||||
studentId,
|
||||
attemptNo: 1,
|
||||
score: totalScore,
|
||||
status,
|
||||
startedAt: submittedAt,
|
||||
submittedAt,
|
||||
isLate: false,
|
||||
createdAt: submittedAt,
|
||||
updatedAt: submittedAt,
|
||||
});
|
||||
|
||||
await db.insert(homeworkAnswers).values(
|
||||
orderedQuestionIds.map((questionId, idx) => {
|
||||
const questionType = questionTypeById.get(questionId) ?? "text";
|
||||
const score = status === "graded" ? (perQuestionScores[idx] ?? 0) : null;
|
||||
return {
|
||||
id: createId(),
|
||||
submissionId,
|
||||
questionId,
|
||||
answerContent: buildAnswer(questionId, questionType),
|
||||
score,
|
||||
feedback: status === "graded" ? (score === scoreForQuestion(questionId) ? "Good" : "Check calculation") : null,
|
||||
createdAt: submittedAt,
|
||||
updatedAt: submittedAt,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const end = performance.now();
|
||||
console.log(`✅ Seed completed in ${(end - start).toFixed(2)}ms`);
|
||||
|
||||
Reference in New Issue
Block a user