full-stack-school/scripts/seed_schedule.ts

234 lines
9.8 KiB
TypeScript

import "dotenv/config";
import { createClient } from "@supabase/supabase-js";
import * as fs from "fs";
import * as path from "path";
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
"sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz",
{ auth: { persistSession: false } }
);
const SUBJECTS: Record<string, number> = {
Math: 1,
Science: 2,
English: 3,
History: 4,
Art: 5,
Music: 6,
PE: 7,
Computer: 8,
Geography: 9,
Biology: 10
};
// -------------------------------------------------------------
// CONFIGURATION: Adjust the timeline spread and generation odds
// -------------------------------------------------------------
const CONFIG = {
monthsPast: 3, // How many months backward to generate lessons
monthsFuture: 9, // How many months forward to generate lessons
baseDate: new Date("2026-02-23T00:00:00.000Z"), // "Present" date
examProbability: 0.05, // 5% chance of an Exam per lesson
assignmentProbability: 0.1, // 10% chance of an Assignment per lesson
attendanceProbability: 0.95, // 95% attendance rate for past lessons
};
// Helper to chunk large arrays for Supabase batch inserts
function chunkArray<T>(array: T[], size: number): T[][] {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
async function insertInChunks(table: string, data: any[]) {
if (data.length === 0) return;
console.log(`Inserting ${data.length} records into ${table}...`);
const chunks = chunkArray(data, 1000);
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const { error } = await supabase.from(table).insert(chunk);
if (error) {
console.error(`Error inserting into ${table} (chunk ${i}):`, error);
}
}
}
async function main() {
try {
console.log("Loading user IDs from seed-data.json...");
const seedDataPath = path.join(process.cwd(), "scripts", "seed-data.json");
if (!fs.existsSync(seedDataPath)) {
throw new Error("seed-data.json not found! Please run `npm run seed:users` first.");
}
const data = JSON.parse(fs.readFileSync(seedDataPath, "utf-8"));
const { teacherMap, studentMap, classes } = data;
console.log("Cleaning old Schedule & Attendance data...");
const tablesToClean = ["LessonWhiteboard", "Attendance", "Result", "Assignment", "Exam", "Lesson"];
for (const table of tablesToClean) {
await supabase.from(table).delete().neq("id", "0" as any);
}
console.log(`Building full year timetable...`);
console.log(`- Past months: ${CONFIG.monthsPast}`);
console.log(`- Future months: ${CONFIG.monthsFuture}`);
const days = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"];
const periods = [8, 9, 10, 11, 13, 14]; // Expanded periods (8 AM - 2 PM, skipping 12 PM lunch)
let lessonIdCounter = 1;
let examIdCounter = 1;
let assignmentIdCounter = 1;
let resultIdCounter = 1;
let attendanceIdCounter = 1;
const lessonsData = [];
const whiteboardsData = [];
const examsData = [];
const assignmentsData = [];
const resultsData = [];
const attendancesData = [];
// Compute start of generation (aligned to a Monday)
const startDate = new Date(CONFIG.baseDate);
startDate.setMonth(startDate.getMonth() - CONFIG.monthsPast);
const startDayOfWeek = startDate.getDay();
const daysToMonday = startDayOfWeek === 0 ? -6 : 1 - startDayOfWeek;
startDate.setDate(startDate.getDate() + daysToMonday);
// Compute end of generation
const endDate = new Date(CONFIG.baseDate);
endDate.setMonth(endDate.getMonth() + CONFIG.monthsFuture);
let currentWeekStart = new Date(startDate);
while (currentWeekStart < endDate) {
for (let classIndex = 0; classIndex < classes.length; classIndex++) {
const classInfo = classes[classIndex];
// Gather students belonging to this class based on seed logic
const studentList: string[] = [];
for (let i = 1; i <= 50; i++) {
const studentBase = classIndex === 0 ? 6 : classIndex;
const studentIdx = studentBase + 6 * (i % 8);
if (studentMap[studentIdx] && !studentList.includes(studentMap[studentIdx])) {
studentList.push(studentMap[studentIdx]);
}
}
for (let dayOffset = 0; dayOffset < days.length; dayOffset++) {
const dayName = days[dayOffset];
const currentDate = new Date(currentWeekStart);
currentDate.setDate(currentDate.getDate() + dayOffset);
for (let period = 0; period < periods.length; period++) {
const startHour = periods[period];
const subjectIdx = (classIndex + dayOffset + period) % 10;
const subjectKey = Object.keys(SUBJECTS)[subjectIdx];
const subjectId = SUBJECTS[subjectKey];
const teacherId = teacherMap[subjectId]; // Authorized teacher
const startTime = new Date(currentDate);
startTime.setUTCHours(startHour, 0, 0, 0);
const endTime = new Date(currentDate);
endTime.setUTCHours(startHour + 1, 0, 0, 0);
// 1. Insert Lesson
lessonsData.push({
id: lessonIdCounter,
name: `${classInfo.name} ${subjectKey} (${dayName})`,
day: dayName,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
subjectId: subjectId,
classId: classInfo.id,
teacherId: teacherId
});
whiteboardsData.push({
lessonId: lessonIdCounter
});
// 2. Insert Exam / Results (Random Probability)
if (Math.random() < CONFIG.examProbability) {
examsData.push({
id: examIdCounter,
title: `${subjectKey} Assessment`,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
lessonId: lessonIdCounter
});
// Only give results to a subset to prevent table explosions
for (const sId of studentList.slice(0, 4)) {
resultsData.push({
id: resultIdCounter++,
score: 60 + Math.floor(Math.random() * 41), // 60-100
studentId: sId,
examId: examIdCounter
});
}
examIdCounter++;
}
// 3. Insert Assignment
if (Math.random() < CONFIG.assignmentProbability) {
assignmentsData.push({
id: assignmentIdCounter++,
title: `${subjectKey} Practice`,
startDate: startTime.toISOString(),
dueDate: new Date(startTime.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
lessonId: lessonIdCounter
});
}
// 4. Insert Attendance (ONLY for dates occurring in the past)
if (startTime < CONFIG.baseDate) {
for (const sId of studentList) {
attendancesData.push({
id: attendanceIdCounter++,
date: startTime.toISOString(),
present: Math.random() < CONFIG.attendanceProbability,
studentId: sId,
lessonId: lessonIdCounter
});
}
}
lessonIdCounter++;
}
}
}
// Advance to the next week safely
currentWeekStart.setDate(currentWeekStart.getDate() + 7);
}
// Send payload in bulk batches to Supabase to prevent network/memory bottlenecks
await insertInChunks("Lesson", lessonsData);
await insertInChunks("LessonWhiteboard", whiteboardsData);
await insertInChunks("Exam", examsData);
await insertInChunks("Assignment", assignmentsData);
await insertInChunks("Result", resultsData);
await insertInChunks("Attendance", attendancesData);
console.log(`\n✅ Timeline successfully generated!`);
console.log(`- Lessons: ${lessonsData.length}`);
console.log(`- Whiteboards: ${whiteboardsData.length}`);
console.log(`- Exams: ${examsData.length}`);
console.log(`- Assignments: ${assignmentsData.length}`);
console.log(`- Results: ${resultsData.length}`);
console.log(`- Attendance Records: ${attendancesData.length}`);
} catch (err) {
console.error("Schedule Seeding Failed:", err);
}
}
main();