const sqlite3 = require("sqlite3"); const faker = require("@faker-js/faker").faker; const { random, sample, sampleSize, sum } = require("lodash"); const { DateTime } = require("luxon"); const { open } = require("sqlite"); const { v4: uuidv4 } = require("uuid"); const dbPath = "./db/sqlite.db"; // Configuration for test data generation let startDate = DateTime.fromISO("2024-10-08"); let endDate = DateTime.fromISO("2024-11-08"); let numberOfPrinters = 5; // Use weekday names for better readability and ease of setting trends let avgPrintTimesPerDay = { Monday: 4, Tuesday: 2, Wednesday: 5, Thursday: 2, Friday: 3, Saturday: 0, Sunday: 0, }; // Average number of prints for each weekday let avgPrintDurationPerDay = { Monday: 240, // Total average duration in minutes for Monday Tuesday: 30, Wednesday: 45, Thursday: 40, Friday: 120, Saturday: 0, Sunday: 0, }; // Average total duration of prints for each weekday let printerUsage = { "Drucker 1": 0.5, "Drucker 2": 0.7, "Drucker 3": 0.6, "Drucker 4": 0.3, "Drucker 5": 0.4, }; // Usage percentages for each printer // **New Configurations for Error Rates** let generalErrorRate = 0.05; // 5% chance any print job may fail let printerErrorRates = { "Drucker 1": 0.02, // 2% error rate for Printer 1 "Drucker 2": 0.03, "Drucker 3": 0.01, "Drucker 4": 0.05, "Drucker 5": 0.04, }; // Error rates for each printer const holidays = []; // Example holidays const existingJobs = []; const initDB = async () => { console.log("Initializing database connection..."); return open({ filename: dbPath, driver: sqlite3.Database, }); }; const createUser = (isPowerUser = false) => { const name = [faker.person.firstName(), faker.person.lastName()]; const user = { id: uuidv4(), github_id: faker.number.int(), username: `${name[0].slice(0, 2)}${name[1].slice(0, 6)}`.toUpperCase(), displayName: `${name[0]} ${name[1]}`.toUpperCase(), email: `${name[0]}.${name[1]}@example.com`, role: sample(["user", "admin"]), isPowerUser, }; console.log("Created user:", user); return user; }; const createPrinter = (index) => { const printer = { id: uuidv4(), name: `Drucker ${index}`, description: faker.lorem.sentence(), status: random(0, 2), }; console.log("Created printer:", printer); return printer; }; const isPrinterAvailable = (printer, startAt, duration) => { const endAt = startAt + duration * 60 * 1000; // Convert minutes to milliseconds return !existingJobs.some((job) => { const jobStart = job.startAt; const jobEnd = job.startAt + job.durationInMinutes * 60 * 1000; return ( printer.id === job.printerId && ((startAt >= jobStart && startAt < jobEnd) || (endAt > jobStart && endAt <= jobEnd) || (startAt <= jobStart && endAt >= jobEnd)) ); }); }; const createPrintJob = (users, printers, startAt, duration) => { const user = sample(users); let printer; // Weighted selection based on printer usage const printerNames = Object.keys(printerUsage); const weightedPrinters = printers.filter((p) => printerNames.includes(p.name)); // Create a weighted array of printers based on usage percentages const printerWeights = weightedPrinters.map((p) => ({ printer: p, weight: printerUsage[p.name], })); const totalWeight = sum(printerWeights.map((pw) => pw.weight)); const randomWeight = Math.random() * totalWeight; let accumulatedWeight = 0; for (const pw of printerWeights) { accumulatedWeight += pw.weight; if (randomWeight <= accumulatedWeight) { printer = pw.printer; break; } } if (!printer) { printer = sample(printers); } if (!isPrinterAvailable(printer, startAt, duration)) { console.log("Printer not available, skipping job creation."); return null; } // **Determine if the job should be aborted based on error rates** let aborted = false; let abortReason = null; // Calculate the combined error rate const printerErrorRate = printerErrorRates[printer.name] || 0; const combinedErrorRate = 1 - (1 - generalErrorRate) * (1 - printerErrorRate); if (Math.random() < combinedErrorRate) { aborted = true; const errorMessages = [ "Unbekannt", "Keine Ahnung", "Falsch gebucht", "Filament gelöst", "Druckabbruch", "Düsenverstopfung", "Schichthaftung fehlgeschlagen", "Materialmangel", "Dateifehler", "Temperaturproblem", "Mechanischer Fehler", "Softwarefehler", "Kalibrierungsfehler", "Überhitzung", ]; abortReason = sample(errorMessages); // Generate a random abort reason } const printJob = { id: uuidv4(), printerId: printer.id, userId: user.id, startAt, durationInMinutes: duration, comments: faker.lorem.sentence(), aborted, abortReason, }; console.log("Created print job:", printJob); return printJob; }; const generatePrintJobsForDay = async (users, printers, dayDate, totalJobsForDay, totalDurationForDay, db, dryRun) => { console.log(`Generating print jobs for ${dayDate.toISODate()}...`); // Generate random durations that sum up approximately to totalDurationForDay const durations = []; let remainingDuration = totalDurationForDay; for (let i = 0; i < totalJobsForDay; i++) { const avgJobDuration = remainingDuration / (totalJobsForDay - i); const jobDuration = Math.max( Math.round(random(avgJobDuration * 0.8, avgJobDuration * 1.2)), 5, // Minimum duration of 5 minutes ); durations.push(jobDuration); remainingDuration -= jobDuration; } // Shuffle durations to randomize job lengths const shuffledDurations = sampleSize(durations, durations.length); for (let i = 0; i < totalJobsForDay; i++) { const duration = shuffledDurations[i]; // Random start time between 8 AM and 6 PM, adjusted to avoid overlapping durations const possibleStartHours = Array.from({ length: 10 }, (_, idx) => idx + 8); // 8 AM to 6 PM let startAt; let attempts = 0; do { const hour = sample(possibleStartHours); const minute = random(0, 59); startAt = dayDate.set({ hour, minute, second: 0, millisecond: 0 }).toMillis(); attempts++; if (attempts > 10) { console.log("Unable to find available time slot, skipping job."); break; } } while (!isPrinterAvailable(sample(printers), startAt, duration)); if (attempts > 10) continue; const printJob = createPrintJob(users, printers, startAt, duration); if (printJob) { if (!dryRun) { await db.run( `INSERT INTO printJob (id, printerId, userId, startAt, durationInMinutes, comments, aborted, abortReason) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ printJob.id, printJob.printerId, printJob.userId, printJob.startAt, printJob.durationInMinutes, printJob.comments, printJob.aborted ? 1 : 0, printJob.abortReason, ], ); } existingJobs.push(printJob); console.log("Inserted print job into database:", printJob.id); } } }; const generateTestData = async (dryRun = false) => { console.log("Starting test data generation..."); const db = await initDB(); // Generate users and printers const users = [ ...Array.from({ length: 7 }, () => createUser(false)), ...Array.from({ length: 3 }, () => createUser(true)), ]; const printers = Array.from({ length: numberOfPrinters }, (_, index) => createPrinter(index + 1)); if (!dryRun) { // Insert users into the database for (const user of users) { await db.run( `INSERT INTO user (id, github_id, name, displayName, email, role) VALUES (?, ?, ?, ?, ?, ?)`, [user.id, user.github_id, user.username, user.displayName, user.email, user.role], ); console.log("Inserted user into database:", user.id); } // Insert printers into the database for (const printer of printers) { await db.run( `INSERT INTO printer (id, name, description, status) VALUES (?, ?, ?, ?)`, [printer.id, printer.name, printer.description, printer.status], ); console.log("Inserted printer into database:", printer.id); } } // Generate print jobs for each day within the specified date range let currentDay = startDate; while (currentDay <= endDate) { const weekdayName = currentDay.toFormat("EEEE"); // Get weekday name (e.g., 'Monday') if (holidays.includes(currentDay.toISODate()) || avgPrintTimesPerDay[weekdayName] === 0) { console.log(`Skipping holiday or no jobs scheduled: ${currentDay.toISODate()}`); currentDay = currentDay.plus({ days: 1 }); continue; } const totalJobsForDay = avgPrintTimesPerDay[weekdayName]; const totalDurationForDay = avgPrintDurationPerDay[weekdayName]; await generatePrintJobsForDay(users, printers, currentDay, totalJobsForDay, totalDurationForDay, db, dryRun); currentDay = currentDay.plus({ days: 1 }); } if (!dryRun) { await db.close(); console.log("Database connection closed. Test data generation complete."); } else { console.log("Dry run complete. No data was written to the database."); } }; const setConfigurations = (config) => { if (config.startDate) startDate = DateTime.fromISO(config.startDate); if (config.endDate) endDate = DateTime.fromISO(config.endDate); if (config.numberOfPrinters) numberOfPrinters = config.numberOfPrinters; if (config.avgPrintTimesPerDay) avgPrintTimesPerDay = config.avgPrintTimesPerDay; if (config.avgPrintDurationPerDay) avgPrintDurationPerDay = config.avgPrintDurationPerDay; if (config.printerUsage) printerUsage = config.printerUsage; if (config.generalErrorRate !== undefined) generalErrorRate = config.generalErrorRate; if (config.printerErrorRates) printerErrorRates = config.printerErrorRates; }; // Example usage setConfigurations({ startDate: "2024-10-08", endDate: "2024-11-08", numberOfPrinters: 6, avgPrintTimesPerDay: { Monday: 4, // High usage Tuesday: 2, // Low usage Wednesday: 3, // Low usage Thursday: 2, // Low usage Friday: 8, // High usage Saturday: 0, Sunday: 0, }, avgPrintDurationPerDay: { Monday: 300, // High total duration Tuesday: 60, // Low total duration Wednesday: 90, Thursday: 60, Friday: 240, Saturday: 0, Sunday: 0, }, printerUsage: { "Drucker 1": 2.3, "Drucker 2": 1.7, "Drucker 3": 0.1, "Drucker 4": 1.5, "Drucker 5": 2.4, "Drucker 6": 0.3, "Drucker 7": 0.9, "Drucker 8": 0.1, }, generalErrorRate: 0.05, // 5% general error rate printerErrorRates: { "Drucker 1": 0.02, "Drucker 2": 0.03, "Drucker 3": 0.1, "Drucker 4": 0.05, "Drucker 5": 0.04, "Drucker 6": 0.02, "Drucker 7": 0.01, "PrinteDrucker 8": 0.03, }, }); generateTestData(process.argv.includes("--dry-run")) .then(() => { console.log("Test data generation script finished."); }) .catch((err) => { console.error("Error generating test data:", err); });