Files
CICD/docs/scripts/seed-exams.ts
2025-12-30 17:48:22 +08:00

352 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import "dotenv/config"
import { db } from "@/shared/db"
import { users, exams, questions, knowledgePoints, examSubmissions, examQuestions, submissionAnswers } from "@/shared/db/schema"
import { createId } from "@paralleldrive/cuid2"
import { faker } from "@faker-js/faker"
import { eq } from "drizzle-orm"
/**
* Seed Script for Next_Edu
*
* Usage:
* 1. Ensure DATABASE_URL is set in .env
* 2. Run with tsx: npx tsx docs/scripts/seed-exams.ts
*/
const SUBJECTS = ["Mathematics", "Physics", "English", "Chemistry", "Biology"]
const GRADES = ["Grade 10", "Grade 11", "Grade 12"]
const DIFFICULTY = [1, 2, 3, 4, 5]
async function seed() {
console.log("🌱 Starting seed process...")
// 1. Create a Teacher User if not exists
const teacherEmail = "teacher@example.com"
let teacherId = "user_teacher_123"
const existingTeacher = await db.query.users.findFirst({
where: eq(users.email, teacherEmail)
})
if (!existingTeacher) {
console.log("Creating teacher user...")
await db.insert(users).values({
id: teacherId,
name: "Senior Teacher",
email: teacherEmail,
role: "teacher",
image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Teacher",
})
} else {
teacherId = existingTeacher.id
console.log("Teacher user exists:", teacherId)
}
// 1b. Create Students
console.log("Creating students...")
const studentIds: string[] = []
for (let i = 0; i < 5; i++) {
const sId = createId()
studentIds.push(sId)
await db.insert(users).values({
id: sId,
name: faker.person.fullName(),
email: faker.internet.email(),
role: "student",
image: `https://api.dicebear.com/7.x/avataaars/svg?seed=${sId}`,
})
}
// 2. Create Knowledge Points
console.log("Creating knowledge points...")
const kpIds: string[] = []
for (const subject of SUBJECTS) {
for (let i = 0; i < 3; i++) {
const kpId = createId()
kpIds.push(kpId)
await db.insert(knowledgePoints).values({
id: kpId,
name: `${subject} - ${faker.science.unit()}`,
description: faker.lorem.sentence(),
level: 1,
})
}
}
// 3. Create Questions
console.log("Creating questions...")
const questionIds: string[] = []
for (let i = 0; i < 50; i++) {
const qId = createId()
questionIds.push(qId)
const type = faker.helpers.arrayElement([
"single_choice",
"multiple_choice",
"text",
"judgment",
] as const)
await db.insert(questions).values({
id: qId,
content: {
text: faker.lorem.paragraph(),
options: type.includes("choice") ? [
{ id: "A", text: faker.lorem.sentence(), isCorrect: true },
{ id: "B", text: faker.lorem.sentence(), isCorrect: false },
{ id: "C", text: faker.lorem.sentence(), isCorrect: false },
{ id: "D", text: faker.lorem.sentence(), isCorrect: false },
] : undefined
},
type,
difficulty: faker.helpers.arrayElement(DIFFICULTY),
authorId: teacherId,
})
}
// 4. Create Exams & Submissions
console.log("Creating exams and submissions...")
for (let i = 0; i < 15; i++) {
const examId = createId()
const subject = faker.helpers.arrayElement(SUBJECTS)
const grade = faker.helpers.arrayElement(GRADES)
const status = faker.helpers.arrayElement(["draft", "published", "archived"] as const)
const scheduledAt = faker.date.soon({ days: 30 })
const meta = {
subject,
grade,
difficulty: faker.helpers.arrayElement(DIFFICULTY),
totalScore: 100,
durationMin: faker.helpers.arrayElement([45, 60, 90, 120]),
questionCount: faker.number.int({ min: 10, max: 30 }),
tags: [faker.word.sample(), faker.word.sample()],
scheduledAt: scheduledAt.toISOString()
}
await db.insert(exams).values({
id: examId,
title: `${subject} ${faker.helpers.arrayElement(["Midterm", "Final", "Quiz", "Unit Test"])}`,
description: JSON.stringify(meta),
creatorId: teacherId,
startTime: scheduledAt,
status,
})
// Link some questions to this exam (random 5 questions)
const selectedQuestions = faker.helpers.arrayElements(questionIds, 5)
await db.insert(examQuestions).values(
selectedQuestions.map((qId, idx) => ({
examId,
questionId: qId,
score: 20, // 5 * 20 = 100
order: idx
}))
)
// Create submissions for published exams
if (status === "published") {
const submittingStudents = faker.helpers.arrayElements(studentIds, faker.number.int({ min: 1, max: 3 }))
for (const studentId of submittingStudents) {
const submissionId = createId()
const submissionStatus = faker.helpers.arrayElement(["submitted", "graded"])
await db.insert(examSubmissions).values({
id: submissionId,
examId,
studentId,
score: submissionStatus === "graded" ? faker.number.int({ min: 60, max: 100 }) : null,
status: submissionStatus,
submittedAt: faker.date.recent(),
})
// Generate answers for this submission
for (const qId of selectedQuestions) {
await db.insert(submissionAnswers).values({
id: createId(),
submissionId: submissionId,
questionId: qId,
answerContent: { answer: faker.lorem.sentence() }, // Mock answer
score: submissionStatus === "graded" ? faker.number.int({ min: 0, max: 20 }) : null,
feedback: submissionStatus === "graded" ? faker.lorem.sentence() : null,
})
}
}
}
}
// 5. Create a specific Primary School Chinese Exam (小学语文)
console.log("Creating Primary School Chinese Exam...")
const chineseExamId = createId()
const chineseQuestions = []
// 5a. Pinyin Questions
const pinyinQ1 = createId()
const pinyinQ2 = createId()
chineseQuestions.push({ id: pinyinQ1, score: 5 }, { id: pinyinQ2, score: 5 })
await db.insert(questions).values([
{
id: pinyinQ1,
content: { text: "看拼音写词语chūn tiān ( )" },
type: "text",
difficulty: 1,
authorId: teacherId,
},
{
id: pinyinQ2,
content: { text: "看拼音写词语huā duǒ ( )" },
type: "text",
difficulty: 1,
authorId: teacherId,
}
])
// 5b. Vocabulary Questions
const vocabQ1 = createId()
const vocabQ2 = createId()
chineseQuestions.push({ id: vocabQ1, score: 5 }, { id: vocabQ2, score: 5 })
await db.insert(questions).values([
{
id: vocabQ1,
content: {
text: "选词填空:今天天气真( )。",
options: [
{ id: "A", text: "美好", isCorrect: false },
{ id: "B", text: "晴朗", isCorrect: true },
{ id: "C", text: "快乐", isCorrect: false }
]
},
type: "single_choice",
difficulty: 2,
authorId: teacherId,
},
{
id: vocabQ2,
content: {
text: "下列词语中,书写正确的是( )。",
options: [
{ id: "A", text: "漂扬", isCorrect: false },
{ id: "B", text: "飘扬", isCorrect: true },
{ id: "C", text: "票扬", isCorrect: false }
]
},
type: "single_choice",
difficulty: 2,
authorId: teacherId,
}
])
// 5c. Reading Comprehension Questions
const readingQ1 = createId()
const readingQ2 = createId()
chineseQuestions.push({ id: readingQ1, score: 10 }, { id: readingQ2, score: 10 })
await db.insert(questions).values([
{
id: readingQ1,
content: {
text: "阅读短文《小兔子乖乖》,回答问题:\n\n小兔子乖乖把门儿开开...\n\n文中提到的动物是",
options: [
{ id: "A", text: "大灰狼", isCorrect: false },
{ id: "B", text: "小兔子", isCorrect: true },
{ id: "C", text: "小花猫", isCorrect: false }
]
},
type: "single_choice",
difficulty: 3,
authorId: teacherId,
},
{
id: readingQ2,
content: { text: "请用一句话形容小兔子。" },
type: "text",
difficulty: 3,
authorId: teacherId,
}
])
// 5d. Construct Exam Structure
const chineseExamStructure = [
{
id: createId(),
type: "group",
title: "第一部分:基础知识",
children: [
{
id: createId(),
type: "group",
title: "一、看拼音写词语",
children: [
{ id: createId(), type: "question", questionId: pinyinQ1, score: 5 },
{ id: createId(), type: "question", questionId: pinyinQ2, score: 5 }
]
},
{
id: createId(),
type: "group",
title: "二、词语积累",
children: [
{ id: createId(), type: "question", questionId: vocabQ1, score: 5 },
{ id: createId(), type: "question", questionId: vocabQ2, score: 5 }
]
}
]
},
{
id: createId(),
type: "group",
title: "第二部分:阅读理解",
children: [
{
id: createId(),
type: "group",
title: "三、短文阅读",
children: [
{ id: createId(), type: "question", questionId: readingQ1, score: 10 },
{ id: createId(), type: "question", questionId: readingQ2, score: 10 }
]
}
]
}
]
await db.insert(exams).values({
id: chineseExamId,
title: "小学语文三年级上册期末考试",
description: JSON.stringify({
subject: "Chinese",
grade: "Grade 3",
difficulty: 3,
totalScore: 40,
durationMin: 90,
questionCount: 6,
tags: ["期末", "语文", "三年级"]
}),
structure: chineseExamStructure,
creatorId: teacherId,
status: "published",
startTime: new Date(),
})
// Link questions to exam
await db.insert(examQuestions).values(
chineseQuestions.map((q, idx) => ({
examId: chineseExamId,
questionId: q.id,
score: q.score,
order: idx
}))
)
console.log("✅ Seed completed successfully!")
process.exit(0)
}
seed().catch((err) => {
console.error("❌ Seed failed:", err)
process.exit(1)
})