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 = { 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(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, defaultSchoolId } = 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})`, startTime: startTime.toISOString(), endTime: endTime.toISOString(), subjectId: subjectId, classId: classInfo.id, teacherId: teacherId, schoolId: defaultSchoolId }); 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, schoolId: defaultSchoolId }); // 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, schoolId: defaultSchoolId }); } 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, schoolId: defaultSchoolId }); } // 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, schoolId: defaultSchoolId }); } } 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); // Keep Lesson id sequence in sync so "Generate lessons" from templates does not hit duplicate key const { error: syncErr } = await supabase.rpc("sync_lesson_id_sequence"); if (syncErr) { console.warn("Lesson sequence sync skipped (run migrations if you use Generate lessons):", syncErr.message); } 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();