From 6b5be5696d8ad9ef1c8482b5487ac0d1ffaf159b Mon Sep 17 00:00:00 2001 From: Torben Haack Date: Mon, 11 Nov 2024 07:01:32 +0100 Subject: [PATCH] fix data generation --- .../scripts/generate-data.js | 733 +++++++++--------- .../scripts/generate-data.ts | 357 --------- 2 files changed, 358 insertions(+), 732 deletions(-) delete mode 100644 packages/reservation-platform/scripts/generate-data.ts diff --git a/packages/reservation-platform/scripts/generate-data.js b/packages/reservation-platform/scripts/generate-data.js index 2ee6c79..59b72b4 100644 --- a/packages/reservation-platform/scripts/generate-data.js +++ b/packages/reservation-platform/scripts/generate-data.js @@ -1,384 +1,367 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); +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, + }); }; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); - return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } + +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; }; -var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { - if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { - if (ar || !(i in from)) { - if (!ar) ar = Array.prototype.slice.call(from, 0, i); - ar[i] = from[i]; - } - } - return to.concat(ar || Array.prototype.slice.call(from)); + +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; }; -Object.defineProperty(exports, "__esModule", { value: true }); -var sqlite3 = require("sqlite3"); -var faker_1 = require("@faker-js/faker"); -var lodash_1 = require("lodash"); -var luxon_1 = require("luxon"); -var sqlite_1 = require("sqlite"); -var uuid_1 = require("uuid"); -var dbPath = "./db/sqlite.db"; -// Configurations for test data generation -var startDate = luxon_1.DateTime.fromISO("2024-11-01"); -var endDate = luxon_1.DateTime.fromISO("2024-11-30"); -var holidays = []; // Example holidays -var existingJobs = []; -// Calendar week usage configs -var weekUsage = { - 44: { minJobs: 4, maxJobs: 6 }, - 45: { minJobs: 10, maxJobs: 15 }, - 46: { minJobs: 10, maxJobs: 15 }, - 47: { minJobs: 3, maxJobs: 5 }, - 48: { minJobs: 10, maxJobs: 15 }, + +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)) + ); + }); }; -var printerUsageBias = { - fastPrints: ["Printer A", "Printer B"], - largePrints: ["Printer C", "Printer D", "Printer E"], + +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; }; -var initDB = function () { return __awaiter(void 0, void 0, void 0, function () { - return __generator(this, function (_a) { - console.log("Initializing database connection..."); - return [2 /*return*/, (0, sqlite_1.open)({ - filename: dbPath, - driver: sqlite3.Database, - })]; - }); -}); }; -var createUser = function (isPowerUser) { - if (isPowerUser === void 0) { isPowerUser = false; } - var name = [faker_1.faker.person.firstName(), faker_1.faker.person.lastName()]; - var user = { - id: (0, uuid_1.v4)(), - github_id: faker_1.faker.number.int(), - username: "".concat(name[0].slice(0, 2)).concat(name[1].slice(0, 6)).toUpperCase(), - displayName: "".concat(name[0], " ").concat(name[1]).toUpperCase(), - email: "".concat(name[0], ".").concat(name[1], "@mercedes-benz.com"), - role: (0, lodash_1.sample)(["user", "admin"]), - isPowerUser: isPowerUser, - }; - console.log("Created user:", user); - return user; + +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); + } + } }; -var createPrinter = function () { - var printer = { - id: (0, uuid_1.v4)(), - name: "Printer ".concat(faker_1.faker.number.int({ max: 9 })), - description: faker_1.faker.lorem.sentence(), - status: (0, lodash_1.random)(0, 2), - }; - console.log("Created printer:", printer); - return printer; + +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."); + } }; -var isPrinterAvailable = function (printer, startAt, duration) { - var endAt = startAt + duration * 60 * 1000; // Convert minutes to milliseconds - return !existingJobs.some(function (job) { - var jobStart = job.startAt; - var 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 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; }; -var weightedSampleUser = function (users) { - var weights = users.map(function (user) { return (user.isPowerUser ? 3 : 1); }); - var weightedUsers = users.flatMap(function (user, index) { return Array(weights[index]).fill(user); }); - return (0, lodash_1.sample)(weightedUsers); -}; -var normalRandom = function (mean, stdDev) { - var u = 0; - var v = 0; - while (u === 0) - u = Math.random(); - while (v === 0) - v = Math.random(); - return mean + stdDev * Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v); -}; -var createPrintJob = function (users, printers, startAt) { - var user = weightedSampleUser(users); - var printer; - // Probabilistic determination for large prints - var isLargePrint = (0, lodash_1.random)(0, 100) < 30; // 30% chance for a large print - if (isLargePrint) { - printer = (0, lodash_1.sample)(printers.filter(function (p) { return printerUsageBias.largePrints.includes(p.name); })); - } - else { - printer = (0, lodash_1.sample)(printers.filter(function (p) { return printerUsageBias.fastPrints.includes(p.name); })); - } - if (!printer) { - printer = (0, lodash_1.sample)(printers); - } - // Variable duration for print jobs with realistic limits - var duration = Math.round(normalRandom(isLargePrint ? 240 : 75, 30)); - var minDuration = 15; - var maxDuration = isLargePrint ? 5760 : 3540; // Maximum duration of 96 hours or 59 minutes in minutes - if (isLargePrint) { - duration = Math.min(duration, maxDuration); - } - duration = Math.max(duration, minDuration); - duration = Math.min(duration, maxDuration); - // Ensure printer availability - if (!isPrinterAvailable(printer, startAt, duration)) { - console.log("Printer not available, skipping job creation."); - return null; - } - // Dynamic error probability based on printer status, duration, and other factors - var aborted = false; - var abortReason = null; - var baseErrorRate = Math.max(0, Math.min(5, 100)); // Ensure error rate is between 0% and 100% - var userErrorModifier = user.isPowerUser ? -2 : 2; // Power users make fewer errors - var timeErrorModifier = startAt >= luxon_1.DateTime.fromObject({ hour: 14 }).toMillis() && startAt <= luxon_1.DateTime.fromObject({ hour: 17 }).toMillis() - ? 1 - : 0; // More errors in the afternoon - var errorRate = baseErrorRate + userErrorModifier + timeErrorModifier; - if ((0, lodash_1.random)(0, 100) < Math.max(0, Math.min(errorRate, 100))) { - aborted = true; - abortReason = generateDynamicAbortReason(); - } - var printJob = { - id: (0, uuid_1.v4)(), - printerId: printer.id, - userId: user.id, - startAt: startAt, - durationInMinutes: duration, - comments: faker_1.faker.lorem.sentence(), - aborted: aborted, - abortReason: abortReason, - }; - console.log("Created print job:", printJob); - return printJob; -}; -var generateDynamicAbortReason = function () { - var reasons = [ - "Filament gerissen", - "Drucker überhitzt", - "Schichtversatz festgestellt", - "Düse verstopft", - "Kalibrierung fehlgeschlagen", - "E".concat((0, lodash_1.random)(500, 599)), - ]; - var reason = (0, lodash_1.sample)(reasons); - // Add typos to simulate human variability - if ((0, lodash_1.random)(0, 1)) { - reason = reason.replace("e", (0, lodash_1.random)(0, 1) ? "é" : "e"); - } - return reason; -}; -var generatePrintJobsForWeek = function (users, printers, weekNumber, -// biome-ignore lint/suspicious/noExplicitAny: -db, dryRun) { return __awaiter(void 0, void 0, void 0, function () { - var weekConfig, totalJobs, startOfWeek, jobsRemaining, day, dayDate, _i, printers_1, printer, jobsForDay, i, timeSlots, selectedSlot, startAt, printJob; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - console.log("Generating print jobs for week ".concat(weekNumber, "...")); - weekConfig = weekUsage[weekNumber]; - totalJobs = (0, lodash_1.random)(weekConfig.minJobs, weekConfig.maxJobs); - startOfWeek = startDate.plus({ weeks: weekNumber - 44 }); - jobsRemaining = totalJobs; - day = 0; - _a.label = 1; - case 1: - if (!(day < 7)) return [3 /*break*/, 7]; - dayDate = startOfWeek.plus({ days: day }); - if (dayDate > endDate || jobsRemaining <= 0) - return [3 /*break*/, 7]; - if (holidays.includes(dayDate.toISODate()) || dayDate.weekday === 6 || dayDate.weekday === 7) { - console.log("Skipping holiday or weekend: ".concat(dayDate.toISODate())); - return [3 /*break*/, 6]; - } - // Update printer status to simulate maintenance or breakdowns - for (_i = 0, printers_1 = printers; _i < printers_1.length; _i++) { - printer = printers_1[_i]; - if ((0, lodash_1.random)(0, 100) < 5) { - // 5% chance per day that a printer goes out of service - printer.status = 2; // Status 2 means "out of service" - console.log("Printer ".concat(printer.name, " is out of service on ").concat(dayDate.toISODate())); - } - else if (printer.status === 2) { - printer.status = 0; // Printer becomes available again - } - } - jobsForDay = Math.min(jobsRemaining, (0, lodash_1.random)(1, 3)); - jobsRemaining -= jobsForDay; - console.log("Generating ".concat(jobsForDay, " print jobs for day ").concat(dayDate.toISODate(), "...")); - i = 0; - _a.label = 2; - case 2: - if (!(i < jobsForDay)) return [3 /*break*/, 6]; - timeSlots = [ - { hour: 7, minute: 0 }, - { hour: 11, minute: 0 }, - { hour: 13, minute: 0 }, - { hour: 15, minute: 0 }, - ]; - selectedSlot = (0, lodash_1.sample)(timeSlots); - startAt = luxon_1.DateTime.fromISO("".concat(dayDate.toISODate(), "T").concat(String(selectedSlot.hour).padStart(2, "0"), ":").concat(String(selectedSlot.minute + (0, lodash_1.random)(0, 30)).padStart(2, "0"), ":00")); - printJob = createPrintJob(users, printers, startAt.toMillis()); - if (!printJob) return [3 /*break*/, 5]; - if (!!dryRun) return [3 /*break*/, 4]; - return [4 /*yield*/, db.run("INSERT INTO printJob (id, printerId, userId, startAt, durationInMinutes, comments, aborted, abortReason)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [ - printJob.id, - printJob.printerId, - printJob.userId, - printJob.startAt, - printJob.durationInMinutes, - printJob.comments, - printJob.aborted ? 1 : 0, - printJob.abortReason, - ])]; - case 3: - _a.sent(); - _a.label = 4; - case 4: - existingJobs.push(printJob); - console.log("Inserted print job into database:", printJob.id); - console.log(JSON.stringify({ - event: "PrintJobCreated", - jobId: printJob.id, - printerId: printJob.printerId, - userId: printJob.userId, - startAt: new Date(printJob.startAt).toISOString(), - duration: printJob.durationInMinutes, - aborted: printJob.aborted, - })); - _a.label = 5; - case 5: - i++; - return [3 /*break*/, 2]; - case 6: - day++; - return [3 /*break*/, 1]; - case 7: return [2 /*return*/]; - } - }); -}); }; -var generateTestData = function () { - var args_1 = []; - for (var _i = 0; _i < arguments.length; _i++) { - args_1[_i] = arguments[_i]; - } - return __awaiter(void 0, __spreadArray([], args_1, true), void 0, function (dryRun) { - var db, users, printers, _a, users_1, user, _b, printers_2, printer, validateData, _c, _d, weekNumber; - if (dryRun === void 0) { dryRun = false; } - return __generator(this, function (_e) { - switch (_e.label) { - case 0: - console.log("Starting test data generation..."); - return [4 /*yield*/, initDB()]; - case 1: - db = _e.sent(); - users = __spreadArray(__spreadArray([], Array.from({ length: 7 }, function () { return createUser(false); }), true), Array.from({ length: 3 }, function () { return createUser(true); }), true); - printers = Array.from({ length: 5 }, createPrinter); - if (!!dryRun) return [3 /*break*/, 9]; - _a = 0, users_1 = users; - _e.label = 2; - case 2: - if (!(_a < users_1.length)) return [3 /*break*/, 5]; - user = users_1[_a]; - return [4 /*yield*/, db.run("INSERT INTO user (id, github_id, name, displayName, email, role)\n VALUES (?, ?, ?, ?, ?, ?)", [user.id, user.github_id, user.username, user.displayName, user.email, user.role])]; - case 3: - _e.sent(); - console.log("Inserted user into database:", user.id); - _e.label = 4; - case 4: - _a++; - return [3 /*break*/, 2]; - case 5: - _b = 0, printers_2 = printers; - _e.label = 6; - case 6: - if (!(_b < printers_2.length)) return [3 /*break*/, 9]; - printer = printers_2[_b]; - return [4 /*yield*/, db.run("INSERT INTO printer (id, name, description, status)\n VALUES (?, ?, ?, ?)", [printer.id, printer.name, printer.description, printer.status])]; - case 7: - _e.sent(); - console.log("Inserted printer into database:", printer.id); - _e.label = 8; - case 8: - _b++; - return [3 /*break*/, 6]; - case 9: - validateData = function (printJobs, users, printers) { - var _loop_1 = function (job) { - var userExists = users.some(function (user) { return user.id === job.userId; }); - var printerExists = printers.some(function (printer) { return printer.id === job.printerId; }); - if (!userExists || !printerExists) { - console.error("Invalid job detected: ".concat(job.id)); - } - }; - for (var _i = 0, printJobs_1 = printJobs; _i < printJobs_1.length; _i++) { - var job = printJobs_1[_i]; - _loop_1(job); - } - }; - _c = 0, _d = Object.keys(weekUsage); - _e.label = 10; - case 10: - if (!(_c < _d.length)) return [3 /*break*/, 13]; - weekNumber = _d[_c]; - return [4 /*yield*/, generatePrintJobsForWeek(users, printers, Number.parseInt("".concat(weekNumber)), db, dryRun)]; - case 11: - _e.sent(); - console.log("======> \uD83D\uDCC5 Week ".concat(weekNumber)); - validateData(existingJobs, users, printers); - _e.label = 12; - case 12: - _c++; - return [3 /*break*/, 10]; - case 13: - if (!!dryRun) return [3 /*break*/, 15]; - return [4 /*yield*/, db.close()]; - case 14: - _e.sent(); - console.log("Database connection closed. Test data generation complete."); - return [3 /*break*/, 16]; - case 15: - console.log("Dry run complete. No data was written to the database."); - _e.label = 16; - case 16: return [2 /*return*/]; - } - }); - }); -}; -generateTestData(process.argv.includes("--dry-run")) - .then(function () { - console.log("Test data generation script finished."); -}) - .catch(function (err) { - console.error("Error generating test data:", err); + +// 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); + }); diff --git a/packages/reservation-platform/scripts/generate-data.ts b/packages/reservation-platform/scripts/generate-data.ts deleted file mode 100644 index a65ab8a..0000000 --- a/packages/reservation-platform/scripts/generate-data.ts +++ /dev/null @@ -1,357 +0,0 @@ -import * as sqlite3 from "sqlite3"; -type User = { - id: string; - github_id: number; - username: string; - displayName: string; - email: string; - role: string | undefined; - isPowerUser: boolean; -}; - -type Printer = { - id: string; - name: string; - description: string; - status: number; -}; - -type PrintJob = { - id: string; - printerId: string; - userId: string; - startAt: number; - durationInMinutes: number; - comments: string; - aborted: boolean; - abortReason: string | null; -}; - -import { faker } from "@faker-js/faker"; -import { random, sample } from "lodash"; -import { DateTime } from "luxon"; -import { open } from "sqlite"; -import { v4 as uuidv4 } from "uuid"; - -const dbPath = "./db/sqlite.db"; - -// Configurations for test data generation -const startDate = DateTime.fromISO("2024-11-01"); -const endDate = DateTime.fromISO("2024-11-30"); -const holidays: string[] = []; // Example holidays - -const existingJobs: PrintJob[] = []; - -// Calendar week usage configs -const weekUsage = { - 44: { minJobs: 4, maxJobs: 6 }, - 45: { minJobs: 10, maxJobs: 15 }, - 46: { minJobs: 10, maxJobs: 15 }, - 47: { minJobs: 3, maxJobs: 5 }, - 48: { minJobs: 10, maxJobs: 15 }, -}; - -const printerUsageBias = { - fastPrints: ["Printer A", "Printer B"], - largePrints: ["Printer C", "Printer D", "Printer E"], -}; - -const initDB = async () => { - console.log("Initializing database connection..."); - return open({ - filename: dbPath, - driver: sqlite3.Database, - }); -}; - -const createUser = (isPowerUser = false): User => { - 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]}@mercedes-benz.com`, - role: sample(["user", "admin"]), - isPowerUser, - }; - console.log("Created user:", user); - return user; -}; - -const createPrinter = (): Printer => { - const printer = { - id: uuidv4(), - name: `Printer ${faker.number.int({ max: 9 })}`, - description: faker.lorem.sentence(), - status: random(0, 2), - }; - console.log("Created printer:", printer); - return printer; -}; - -const isPrinterAvailable = (printer: Printer, startAt: number, duration: number): boolean => { - 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 weightedSampleUser = (users: User[]): User | undefined => { - const weights = users.map((user) => (user.isPowerUser ? 3 : 1)); - const weightedUsers = users.flatMap((user, index) => Array(weights[index]).fill(user)); - return sample(weightedUsers); -}; - -const normalRandom = (mean: number, stdDev: number): number => { - let u = 0; - let v = 0; - while (u === 0) u = Math.random(); - while (v === 0) v = Math.random(); - return mean + stdDev * Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v); -}; - -const createPrintJob = (users: User[], printers: Printer[], startAt: number): PrintJob | null => { - const user = weightedSampleUser(users) as User; - let printer: Printer; - - // Probabilistic determination for large prints - const isLargePrint = random(0, 100) < 30; // 30% chance for a large print - - if (isLargePrint) { - printer = sample(printers.filter((p) => printerUsageBias.largePrints.includes(p.name))) as Printer; - } else { - printer = sample(printers.filter((p) => printerUsageBias.fastPrints.includes(p.name))) as Printer; - } - - if (!printer) { - printer = sample(printers) as Printer; - } - - // Variable duration for print jobs with realistic limits - let duration = Math.round(normalRandom(isLargePrint ? 240 : 75, 30)); - const minDuration = 15; - const maxDuration = isLargePrint ? 5760 : 3540; // Maximum duration of 96 hours or 59 minutes in minutes - if (isLargePrint) { - duration = Math.min(duration, maxDuration); - } - duration = Math.max(duration, minDuration); - duration = Math.min(duration, maxDuration); - - // Ensure printer availability - if (!isPrinterAvailable(printer, startAt, duration)) { - console.log("Printer not available, skipping job creation."); - return null; - } - - // Dynamic error probability based on printer status, duration, and other factors - let aborted = false; - let abortReason = null; - const baseErrorRate = Math.max(0, Math.min(5, 100)); // Ensure error rate is between 0% and 100% - const userErrorModifier = user.isPowerUser ? -2 : 2; // Power users make fewer errors - const timeErrorModifier = - startAt >= DateTime.fromObject({ hour: 14 }).toMillis() && startAt <= DateTime.fromObject({ hour: 17 }).toMillis() - ? 1 - : 0; // More errors in the afternoon - const errorRate = baseErrorRate + userErrorModifier + timeErrorModifier; - - if (random(0, 100) < Math.max(0, Math.min(errorRate, 100))) { - aborted = true; - abortReason = generateDynamicAbortReason(); - } - - 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 generateDynamicAbortReason = (): string => { - const reasons = [ - "Filament gerissen", - "Drucker überhitzt", - "Schichtversatz festgestellt", - "Düse verstopft", - "Kalibrierung fehlgeschlagen", - `E${random(500, 599)}`, // Error codes - ]; - let reason = sample(reasons) as string; - - // Add typos to simulate human variability - if (random(0, 1)) { - reason = reason.replace("e", random(0, 1) ? "é" : "e"); - } - return reason; -}; - -const generatePrintJobsForWeek = async ( - users: User[], - printers: Printer[], - weekNumber: number, - // biome-ignore lint/suspicious/noExplicitAny: - db: any, - dryRun: boolean, -) => { - console.log(`Generating print jobs for week ${weekNumber}...`); - const weekConfig = weekUsage[weekNumber as keyof typeof weekUsage]; - const totalJobs = random(weekConfig.minJobs, weekConfig.maxJobs); - const startOfWeek = startDate.plus({ weeks: weekNumber - 44 }); - - let jobsRemaining = totalJobs; - for (let day = 0; day < 7; day++) { - const dayDate = startOfWeek.plus({ days: day }); - if (dayDate > endDate || jobsRemaining <= 0) break; - - if (holidays.includes(dayDate.toISODate() as string) || dayDate.weekday === 6 || dayDate.weekday === 7) { - console.log(`Skipping holiday or weekend: ${dayDate.toISODate()}`); - continue; - } - - // Update printer status to simulate maintenance or breakdowns - for (const printer of printers) { - if (random(0, 100) < 5) { - // 5% chance per day that a printer goes out of service - printer.status = 2; // Status 2 means "out of service" - console.log(`Printer ${printer.name} is out of service on ${dayDate.toISODate()}`); - } else if (printer.status === 2) { - printer.status = 0; // Printer becomes available again - } - } - - const jobsForDay = Math.min(jobsRemaining, random(1, 3)); - jobsRemaining -= jobsForDay; - - console.log(`Generating ${jobsForDay} print jobs for day ${dayDate.toISODate()}...`); - for (let i = 0; i < jobsForDay; i++) { - // Simulate peak usage in the morning and after lunch - const timeSlots = [ - { hour: 7, minute: 0 }, - { hour: 11, minute: 0 }, - { hour: 13, minute: 0 }, - { hour: 15, minute: 0 }, - ]; - const selectedSlot = sample(timeSlots) as { - hour: number; - minute: number; - }; - const startAt = DateTime.fromISO( - `${dayDate.toISODate()}T${String(selectedSlot.hour).padStart(2, "0")}:${String(selectedSlot.minute + random(0, 30)).padStart(2, "0")}:00`, - ); - - const printJob = createPrintJob(users, printers, startAt.toMillis()); - 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); - console.log( - JSON.stringify({ - event: "PrintJobCreated", - jobId: printJob.id, - printerId: printJob.printerId, - userId: printJob.userId, - startAt: new Date(printJob.startAt).toISOString(), - duration: printJob.durationInMinutes, - aborted: printJob.aborted, - }), - ); - } - } - } -}; - -const generateTestData = async (dryRun = false): Promise => { - 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: 5 }, createPrinter); - - 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); - } - } - - const validateData = (printJobs: PrintJob[], users: User[], printers: Printer[]): void => { - for (const job of printJobs) { - const userExists = users.some((user) => user.id === job.userId); - const printerExists = printers.some((printer) => printer.id === job.printerId); - if (!userExists || !printerExists) { - console.error(`Invalid job detected: ${job.id}`); - } - } - }; - - // Generate print jobs for each week - for (const weekNumber of Object.keys(weekUsage) as unknown as number[]) { - await generatePrintJobsForWeek(users, printers, Number.parseInt(`${weekNumber}`), db, dryRun); - console.log(`======> 📅 Week ${weekNumber}`); - validateData(existingJobs, users, printers); - } - - 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."); - } -}; - -generateTestData(process.argv.includes("--dry-run")) - .then(() => { - console.log("Test data generation script finished."); - }) - .catch((err) => { - console.error("Error generating test data:", err); - });