227 lines
9.5 KiB
TypeScript
227 lines
9.5 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 = ["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 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
|
|
});
|
|
|
|
// 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("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(`- 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();
|