mirror of
https://github.com/Oak-and-Sprout/sprout-track.git
synced 2026-02-09 09:09:16 -06:00
920 lines
31 KiB
JavaScript
920 lines
31 KiB
JavaScript
/**
|
|
* Test data generation script for Sprout Track
|
|
* Generates realistic families, caretakers, babies, and log entries
|
|
* Run with: node scripts/generate-test-data.js
|
|
*/
|
|
|
|
const { PrismaClient } = require('@prisma/client');
|
|
const { randomUUID } = require('crypto');
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
// Get parameters from environment variables set by bash script
|
|
const familyCount = parseInt(process.env.FAMILY_COUNT) || 1;
|
|
const daysCount = parseInt(process.env.DAYS_COUNT) || 7;
|
|
const clearData = process.env.CLEAR_DATA === 'true';
|
|
|
|
// Random name arrays
|
|
const maleFirstNames = [
|
|
'James', 'John', 'Robert', 'Michael', 'William', 'David', 'Richard', 'Joseph', 'Thomas', 'Charles',
|
|
'Christopher', 'Daniel', 'Matthew', 'Anthony', 'Mark', 'Donald', 'Steven', 'Paul', 'Andrew', 'Joshua',
|
|
'Kenneth', 'Kevin', 'Brian', 'George', 'Timothy', 'Ronald', 'Jason', 'Edward', 'Jeffrey', 'Ryan',
|
|
'Jacob', 'Gary', 'Nicholas', 'Eric', 'Jonathan', 'Stephen', 'Larry', 'Justin', 'Scott', 'Brandon',
|
|
'Benjamin', 'Samuel', 'Gregory', 'Alexander', 'Patrick', 'Frank', 'Raymond', 'Jack', 'Dennis', 'Jerry',
|
|
'Tyler', 'Aaron', 'Jose', 'Henry', 'Adam', 'Douglas', 'Nathan', 'Peter', 'Zachary', 'Kyle',
|
|
'Noah', 'Alan', 'Ethan', 'Jeremy', 'Lionel', 'Angel', 'Jordan', 'Wayne', 'Arthur', 'Sean',
|
|
'Felix', 'Carl', 'Harold', 'Jose', 'Ralph', 'Mason', 'Roy', 'Eugene', 'Louis', 'Philip'
|
|
];
|
|
|
|
const femaleFirstNames = [
|
|
'Mary', 'Patricia', 'Jennifer', 'Linda', 'Elizabeth', 'Barbara', 'Susan', 'Jessica', 'Sarah', 'Karen',
|
|
'Nancy', 'Lisa', 'Betty', 'Helen', 'Sandra', 'Donna', 'Carol', 'Ruth', 'Sharon', 'Michelle',
|
|
'Laura', 'Sarah', 'Kimberly', 'Deborah', 'Dorothy', 'Amy', 'Angela', 'Ashley', 'Brenda', 'Emma',
|
|
'Olivia', 'Cynthia', 'Marie', 'Janet', 'Catherine', 'Frances', 'Christine', 'Samantha', 'Debra', 'Rachel',
|
|
'Carolyn', 'Janet', 'Maria', 'Heather', 'Diane', 'Julie', 'Joyce', 'Virginia', 'Victoria', 'Kelly',
|
|
'Christina', 'Joan', 'Evelyn', 'Lauren', 'Judith', 'Megan', 'Cheryl', 'Andrea', 'Hannah', 'Jacqueline',
|
|
'Martha', 'Gloria', 'Teresa', 'Sara', 'Janice', 'Marie', 'Julia', 'Heather', 'Diane', 'Carolyn',
|
|
'Ruth', 'Sharon', 'Michelle', 'Laura', 'Sarah', 'Kimberly', 'Deborah', 'Dorothy', 'Lisa', 'Nancy'
|
|
];
|
|
|
|
const lastNames = [
|
|
'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez',
|
|
'Hernandez', 'Lopez', 'Gonzalez', 'Wilson', 'Anderson', 'Thomas', 'Taylor', 'Moore', 'Jackson', 'Martin',
|
|
'Lee', 'Perez', 'Thompson', 'White', 'Harris', 'Sanchez', 'Clark', 'Ramirez', 'Lewis', 'Robinson',
|
|
'Walker', 'Young', 'Allen', 'King', 'Wright', 'Scott', 'Torres', 'Nguyen', 'Hill', 'Flores',
|
|
'Green', 'Adams', 'Nelson', 'Baker', 'Hall', 'Rivera', 'Campbell', 'Mitchell', 'Carter', 'Roberts',
|
|
'Gomez', 'Phillips', 'Evans', 'Turner', 'Diaz', 'Parker', 'Cruz', 'Edwards', 'Collins', 'Reyes',
|
|
'Stewart', 'Morris', 'Morales', 'Murphy', 'Cook', 'Rogers', 'Gutierrez', 'Ortiz', 'Morgan', 'Cooper',
|
|
'Peterson', 'Bailey', 'Reed', 'Kelly', 'Howard', 'Ramos', 'Kim', 'Cox', 'Ward', 'Richardson'
|
|
];
|
|
|
|
const caretakerTypes = [
|
|
'Parent', 'Mother', 'Father', 'Grandmother', 'Grandfather', 'Nanny', 'Babysitter',
|
|
'Daycare Provider', 'Aunt', 'Uncle', 'Family Friend', 'Caregiver'
|
|
];
|
|
|
|
// Note content examples
|
|
const noteTemplates = [
|
|
"Baby was very fussy during feeding time today",
|
|
"Slept through the night for the first time!",
|
|
"Pediatrician appointment scheduled for next week",
|
|
"Started showing interest in solid foods",
|
|
"Had a great day at the park",
|
|
"Trying new sleep routine tonight",
|
|
"Baby seemed extra giggly today",
|
|
"Running low on diapers - need to buy more",
|
|
"Grandmother visited and baby was so happy",
|
|
"First time rolling over from back to tummy!",
|
|
"Teething seems to be starting",
|
|
"Baby loves the new toy we got",
|
|
"Had to change clothes 3 times today - lots of spit up",
|
|
"Daycare said baby played well with other children",
|
|
"Trying to establish better feeding schedule",
|
|
"Baby's first laugh was so precious",
|
|
"Noticed baby tracking objects with eyes",
|
|
"Temperature was a bit high, monitoring closely",
|
|
"Great nap schedule today",
|
|
"Baby's grip is getting stronger"
|
|
];
|
|
|
|
// Milestone examples
|
|
const milestoneTemplates = {
|
|
MOTOR: [
|
|
"First time holding head up",
|
|
"Rolling over from tummy to back",
|
|
"Rolling over from back to tummy",
|
|
"Sitting without support",
|
|
"First crawling movements",
|
|
"Pulling up to standing",
|
|
"First steps with support",
|
|
"Walking independently",
|
|
"Climbing stairs",
|
|
"Running"
|
|
],
|
|
COGNITIVE: [
|
|
"First social smile",
|
|
"Recognizing familiar faces",
|
|
"Following objects with eyes",
|
|
"Reaching for toys",
|
|
"Understanding cause and effect",
|
|
"Object permanence awareness",
|
|
"Problem solving skills",
|
|
"Imitating actions",
|
|
"Understanding simple commands",
|
|
"Showing preferences"
|
|
],
|
|
SOCIAL: [
|
|
"First laugh",
|
|
"Enjoying peek-a-boo",
|
|
"Responding to name",
|
|
"Stranger anxiety begins",
|
|
"Waving bye-bye",
|
|
"Playing pat-a-cake",
|
|
"Showing affection",
|
|
"Parallel play with others",
|
|
"Sharing toys",
|
|
"Showing empathy"
|
|
],
|
|
LANGUAGE: [
|
|
"First coo sounds",
|
|
"Babbling begins",
|
|
"Responding to voices",
|
|
"Making different sounds",
|
|
"First word attempt",
|
|
"Saying 'mama' or 'dada'",
|
|
"Understanding 'no'",
|
|
"Following simple commands",
|
|
"Saying first clear word",
|
|
"Two-word combinations"
|
|
]
|
|
};
|
|
|
|
// Adjectives and animals for slug generation (copied from family-migration.js)
|
|
const adjectives = [
|
|
'adorable', 'fluffy', 'cuddly', 'tiny', 'fuzzy', 'sweet', 'playful', 'gentle', 'happy',
|
|
'bouncy', 'sleepy', 'snuggly', 'cheerful', 'bubbly', 'cozy', 'merry', 'giggly', 'jolly',
|
|
'silly', 'wiggly', 'charming', 'dainty', 'darling', 'precious', 'lovable', 'huggable',
|
|
'perky', 'sprightly', 'twinkly', 'whimsical', 'delightful', 'friendly', 'joyful', 'peppy'
|
|
];
|
|
|
|
const animals = [
|
|
'kitten', 'puppy', 'bunny', 'duckling', 'chick', 'calf', 'lamb', 'piglet', 'fawn',
|
|
'cub', 'foal', 'joey', 'owlet', 'panda', 'koala', 'hamster', 'hedgehog', 'otter',
|
|
'chinchilla', 'squirrel', 'chipmunk', 'mouse', 'gerbil', 'ferret', 'meerkat', 'sloth',
|
|
'penguin', 'seal', 'walrus', 'alpaca', 'llama', 'capybara', 'quokka', 'wombat'
|
|
];
|
|
|
|
// Utility functions
|
|
function randomChoice(array) {
|
|
return array[Math.floor(Math.random() * array.length)];
|
|
}
|
|
|
|
function randomInt(min, max) {
|
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
}
|
|
|
|
function randomFloat(min, max) {
|
|
return Math.random() * (max - min) + min;
|
|
}
|
|
|
|
// Check if two dates are on the same day
|
|
function isSameDay(date1, date2) {
|
|
return date1.getFullYear() === date2.getFullYear() &&
|
|
date1.getMonth() === date2.getMonth() &&
|
|
date1.getDate() === date2.getDate();
|
|
}
|
|
|
|
// Generate the cutoff time (between 15 minutes and 3 hours ago)
|
|
function generateCutoffTime() {
|
|
const now = new Date();
|
|
const minMinutesAgo = 15;
|
|
const maxMinutesAgo = 3 * 60; // 3 hours
|
|
const minutesAgo = randomInt(minMinutesAgo, maxMinutesAgo);
|
|
return new Date(now.getTime() - (minutesAgo * 60 * 1000));
|
|
}
|
|
|
|
// Generate unique slug
|
|
async function generateUniqueSlug() {
|
|
let attempts = 0;
|
|
while (attempts < 20) {
|
|
const adjective = randomChoice(adjectives);
|
|
const animal = randomChoice(animals);
|
|
const slug = `${adjective}-${animal}`;
|
|
|
|
const existing = await prisma.family.findFirst({ where: { slug } });
|
|
if (!existing) {
|
|
return slug;
|
|
}
|
|
attempts++;
|
|
}
|
|
|
|
// Fallback with random number
|
|
const adjective = randomChoice(adjectives);
|
|
const animal = randomChoice(animals);
|
|
const randomNum = randomInt(1000, 9999);
|
|
return `${adjective}-${animal}-${randomNum}`;
|
|
}
|
|
|
|
// Generate realistic birth date (0-24 months ago)
|
|
function generateBabyBirthDate() {
|
|
const now = new Date();
|
|
const maxAgeMonths = 24;
|
|
const ageMonths = randomFloat(0, maxAgeMonths);
|
|
const birthDate = new Date(now.getTime() - (ageMonths * 30 * 24 * 60 * 60 * 1000));
|
|
return birthDate;
|
|
}
|
|
|
|
// Generate realistic timestamp within a day, ensuring it doesn't exceed maxTime
|
|
function generateTimeInDay(baseDate, hour, minuteVariation = 30, maxTime = null) {
|
|
const date = new Date(baseDate);
|
|
date.setHours(hour);
|
|
date.setMinutes(randomInt(-minuteVariation, minuteVariation));
|
|
date.setSeconds(randomInt(0, 59));
|
|
|
|
// If maxTime is provided and the generated time exceeds it, cap it at maxTime
|
|
if (maxTime && date > maxTime) {
|
|
return maxTime;
|
|
}
|
|
|
|
return date;
|
|
}
|
|
|
|
// Clear existing data
|
|
async function clearExistingData() {
|
|
console.log('Clearing existing data...');
|
|
|
|
// Delete in order to respect foreign key constraints
|
|
const models = [
|
|
'familyMember', 'sleepLog', 'feedLog', 'diaperLog', 'moodLog', 'note',
|
|
'milestone', 'pumpLog', 'playLog', 'bathLog', 'measurement', 'medicineLog',
|
|
'medicine', 'calendarEvent', 'contact', 'baby', 'caretaker', 'settings', 'family', 'appConfig'
|
|
];
|
|
|
|
for (const model of models) {
|
|
try {
|
|
await prisma[model].deleteMany({});
|
|
console.log(`Cleared ${model} records`);
|
|
} catch (error) {
|
|
console.log(`Note: Could not clear ${model} (may not exist): ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate app configuration
|
|
async function generateAppConfig() {
|
|
console.log('Generating app configuration...');
|
|
|
|
const appConfig = await prisma.appConfig.create({
|
|
data: {
|
|
id: randomUUID(),
|
|
adminPass: 'admin', // Plain text - will be encrypted by the API when used
|
|
rootDomain: 'demo.sprout-track.com',
|
|
enableHttps: true
|
|
}
|
|
});
|
|
|
|
console.log(`Created app config with domain: ${appConfig.rootDomain}`);
|
|
return appConfig;
|
|
}
|
|
|
|
// Generate essential units data (copied from seed.ts)
|
|
async function generateUnits() {
|
|
console.log('Ensuring essential units exist...');
|
|
|
|
const unitData = [
|
|
{ unitAbbr: 'OZ', unitName: 'Ounces', activityTypes: 'weight,feed,medicine' },
|
|
{ unitAbbr: 'ML', unitName: 'Milliliters', activityTypes: 'medicine,feed' },
|
|
{ unitAbbr: 'TBSP', unitName: 'Tablespoon', activityTypes: 'medicine,feed' },
|
|
{ unitAbbr: 'LB', unitName: 'Pounds', activityTypes: 'weight' },
|
|
{ unitAbbr: 'IN', unitName: 'Inches', activityTypes: 'height' },
|
|
{ unitAbbr: 'CM', unitName: 'Centimeters', activityTypes: 'height' },
|
|
{ unitAbbr: 'G', unitName: 'Grams', activityTypes: 'weight,feed,medicine' },
|
|
{ unitAbbr: 'KG', unitName: 'Kilograms', activityTypes: 'weight' },
|
|
{ unitAbbr: 'F', unitName: 'Fahrenheit', activityTypes: 'temp' },
|
|
{ unitAbbr: 'C', unitName: 'Celsius', activityTypes: 'temp' },
|
|
{ unitAbbr: 'MG', unitName: 'Milligrams', activityTypes: 'medicine' },
|
|
{ unitAbbr: 'MCG', unitName: 'Micrograms', activityTypes: 'medicine' },
|
|
{ unitAbbr: 'L', unitName: 'Liters', activityTypes: 'medicine' },
|
|
{ unitAbbr: 'CC', unitName: 'Cubic Centimeters', activityTypes: 'medicine' },
|
|
{ unitAbbr: 'MOL', unitName: 'Moles', activityTypes: 'medicine' },
|
|
{ unitAbbr: 'MMOL', unitName: 'Millimoles', activityTypes: 'medicine' }
|
|
];
|
|
|
|
// Get existing units from the database
|
|
const existingUnits = await prisma.unit.findMany({
|
|
select: { id: true, unitAbbr: true, activityTypes: true }
|
|
});
|
|
|
|
// Create a map of existing unit abbreviations for faster lookups
|
|
const existingUnitsMap = new Map(
|
|
existingUnits.map(unit => [unit.unitAbbr, { id: unit.id, activityTypes: unit.activityTypes }])
|
|
);
|
|
|
|
// Filter out units that already exist
|
|
const missingUnits = unitData.filter(unit => !existingUnitsMap.has(unit.unitAbbr));
|
|
|
|
// Create the missing units
|
|
if (missingUnits.length > 0) {
|
|
console.log(`Creating ${missingUnits.length} missing units: ${missingUnits.map(u => u.unitAbbr).join(', ')}`);
|
|
|
|
for (const unit of missingUnits) {
|
|
await prisma.unit.create({
|
|
data: {
|
|
id: randomUUID(),
|
|
...unit
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
console.log('All essential units already exist in the database.');
|
|
}
|
|
|
|
// Update activity types for existing units if needed
|
|
const unitsToUpdate = [];
|
|
for (const unit of unitData) {
|
|
const existingUnit = existingUnitsMap.get(unit.unitAbbr);
|
|
if (existingUnit && existingUnit.activityTypes !== unit.activityTypes) {
|
|
unitsToUpdate.push({
|
|
id: existingUnit.id,
|
|
unitAbbr: unit.unitAbbr,
|
|
activityTypes: unit.activityTypes
|
|
});
|
|
}
|
|
}
|
|
|
|
if (unitsToUpdate.length > 0) {
|
|
console.log(`Updating activity types for ${unitsToUpdate.length} units: ${unitsToUpdate.map(u => u.unitAbbr).join(', ')}`);
|
|
|
|
for (const unit of unitsToUpdate) {
|
|
await prisma.unit.update({
|
|
where: { id: unit.id },
|
|
data: { activityTypes: unit.activityTypes }
|
|
});
|
|
}
|
|
}
|
|
|
|
console.log('Units generation completed successfully.');
|
|
}
|
|
|
|
// Generate a family
|
|
async function generateFamily() {
|
|
const lastName = randomChoice(lastNames);
|
|
const slug = await generateUniqueSlug();
|
|
|
|
const family = await prisma.family.create({
|
|
data: {
|
|
id: randomUUID(),
|
|
slug: slug,
|
|
name: `${lastName} Family`,
|
|
isActive: true
|
|
}
|
|
});
|
|
|
|
// Create family settings
|
|
await prisma.settings.create({
|
|
data: {
|
|
id: randomUUID(),
|
|
familyId: family.id,
|
|
familyName: family.name,
|
|
securityPin: '111222',
|
|
defaultBottleUnit: 'OZ',
|
|
defaultSolidsUnit: 'TBSP',
|
|
defaultHeightUnit: 'IN',
|
|
defaultWeightUnit: 'LB',
|
|
defaultTempUnit: 'F'
|
|
}
|
|
});
|
|
|
|
return family;
|
|
}
|
|
|
|
// Generate caretakers for a family
|
|
async function generateCaretakers(family) {
|
|
const caretakers = [];
|
|
|
|
// ALWAYS create the system user "00" first - this is required for the application to work
|
|
console.log(` Creating system user "00"...`);
|
|
const systemCaretaker = await prisma.caretaker.create({
|
|
data: {
|
|
id: randomUUID(),
|
|
loginId: '00',
|
|
name: 'system',
|
|
type: 'System Administrator',
|
|
role: 'ADMIN',
|
|
inactive: false,
|
|
securityPin: '111222',
|
|
familyId: family.id
|
|
}
|
|
});
|
|
|
|
// Create family member relationship for system user
|
|
await prisma.familyMember.create({
|
|
data: {
|
|
familyId: family.id,
|
|
caretakerId: systemCaretaker.id,
|
|
role: 'admin'
|
|
}
|
|
});
|
|
|
|
caretakers.push(systemCaretaker);
|
|
|
|
// Now create regular caretakers (2-4 additional caretakers)
|
|
const regularCaretakerCount = randomInt(2, 4);
|
|
|
|
for (let i = 0; i < regularCaretakerCount; i++) {
|
|
const isFirstRegularCaretaker = i === 0;
|
|
const gender = Math.random() > 0.5 ? 'male' : 'female';
|
|
const firstName = gender === 'male' ? randomChoice(maleFirstNames) : randomChoice(femaleFirstNames);
|
|
|
|
const caretaker = await prisma.caretaker.create({
|
|
data: {
|
|
id: randomUUID(),
|
|
loginId: (i + 1).toString().padStart(2, '0'), // Start from "01" for regular users
|
|
name: firstName,
|
|
type: isFirstRegularCaretaker ? 'Parent' : randomChoice(caretakerTypes),
|
|
role: isFirstRegularCaretaker ? 'ADMIN' : 'USER',
|
|
inactive: false,
|
|
securityPin: '111222',
|
|
familyId: family.id
|
|
}
|
|
});
|
|
|
|
// Create family member relationship
|
|
await prisma.familyMember.create({
|
|
data: {
|
|
familyId: family.id,
|
|
caretakerId: caretaker.id,
|
|
role: isFirstRegularCaretaker ? 'admin' : 'member'
|
|
}
|
|
});
|
|
|
|
caretakers.push(caretaker);
|
|
}
|
|
|
|
return caretakers;
|
|
}
|
|
|
|
// Generate babies for a family
|
|
async function generateBabies(family, caretakers) {
|
|
const babyCount = randomInt(1, 2);
|
|
const babies = [];
|
|
|
|
for (let i = 0; i < babyCount; i++) {
|
|
const gender = Math.random() > 0.5 ? 'MALE' : 'FEMALE';
|
|
const firstName = gender === 'MALE' ? randomChoice(maleFirstNames) : randomChoice(femaleFirstNames);
|
|
const birthDate = generateBabyBirthDate();
|
|
|
|
const baby = await prisma.baby.create({
|
|
data: {
|
|
id: randomUUID(),
|
|
firstName: firstName,
|
|
lastName: family.name.replace(' Family', ''),
|
|
birthDate: birthDate,
|
|
gender: gender,
|
|
inactive: false,
|
|
familyId: family.id,
|
|
feedWarningTime: '03:00',
|
|
diaperWarningTime: '02:00'
|
|
}
|
|
});
|
|
|
|
babies.push(baby);
|
|
}
|
|
|
|
return babies;
|
|
}
|
|
|
|
// Generate sleep logs for a baby
|
|
async function generateSleepLogs(baby, caretakers, family, startDate, endDate, cutoffTime) {
|
|
const logs = [];
|
|
const currentDate = new Date(startDate);
|
|
|
|
while (currentDate <= endDate) {
|
|
const caretaker = randomChoice(caretakers);
|
|
const isToday = isSameDay(currentDate, new Date());
|
|
|
|
// Night sleep (9 PM - 7 AM)
|
|
const nightStart = generateTimeInDay(currentDate, 21, 30, isToday ? cutoffTime : null); // 9 PM
|
|
|
|
// Only create night sleep if start time is not in the future
|
|
if (nightStart <= cutoffTime) {
|
|
const nextDay = new Date(currentDate.getTime() + 24 * 60 * 60 * 1000);
|
|
const isTomorrowToday = isSameDay(nextDay, new Date());
|
|
let nightEnd = generateTimeInDay(nextDay, 7, 30, isTomorrowToday ? cutoffTime : null); // 7 AM next day
|
|
|
|
// Ensure night end is not in the future
|
|
if (nightEnd > cutoffTime) {
|
|
nightEnd = cutoffTime;
|
|
}
|
|
|
|
const nightDuration = Math.floor((nightEnd - nightStart) / (1000 * 60)); // minutes
|
|
|
|
// Only create the log if it has a positive duration
|
|
if (nightDuration > 0) {
|
|
logs.push({
|
|
id: randomUUID(),
|
|
startTime: nightStart,
|
|
endTime: nightEnd,
|
|
duration: nightDuration,
|
|
type: 'NIGHT_SLEEP',
|
|
location: 'Crib',
|
|
quality: randomChoice(['GOOD', 'EXCELLENT', 'FAIR']),
|
|
babyId: baby.id,
|
|
caretakerId: caretaker.id,
|
|
familyId: family.id
|
|
});
|
|
}
|
|
}
|
|
|
|
// Morning nap (10 AM - 12 PM)
|
|
if (Math.random() > 0.3) { // 70% chance
|
|
const napStart = generateTimeInDay(currentDate, 10, 30, isToday ? cutoffTime : null);
|
|
|
|
// Only create nap if start time is not in the future
|
|
if (napStart <= cutoffTime) {
|
|
const napDuration = randomInt(60, 120); // 1-2 hours
|
|
let napEnd = new Date(napStart.getTime() + napDuration * 60 * 1000);
|
|
|
|
// Ensure nap end is not in the future
|
|
if (napEnd > cutoffTime) {
|
|
napEnd = cutoffTime;
|
|
}
|
|
|
|
const actualDuration = Math.floor((napEnd - napStart) / (1000 * 60));
|
|
|
|
// Only create the log if it has a positive duration
|
|
if (actualDuration > 0) {
|
|
logs.push({
|
|
id: randomUUID(),
|
|
startTime: napStart,
|
|
endTime: napEnd,
|
|
duration: actualDuration,
|
|
type: 'NAP',
|
|
location: 'Crib',
|
|
quality: randomChoice(['GOOD', 'FAIR', 'EXCELLENT']),
|
|
babyId: baby.id,
|
|
caretakerId: caretaker.id,
|
|
familyId: family.id
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Afternoon nap (2 PM - 4 PM)
|
|
if (Math.random() > 0.2) { // 80% chance
|
|
const napStart = generateTimeInDay(currentDate, 14, 30, isToday ? cutoffTime : null);
|
|
|
|
// Only create nap if start time is not in the future
|
|
if (napStart <= cutoffTime) {
|
|
const napDuration = randomInt(90, 180); // 1.5-3 hours
|
|
let napEnd = new Date(napStart.getTime() + napDuration * 60 * 1000);
|
|
|
|
// Ensure nap end is not in the future
|
|
if (napEnd > cutoffTime) {
|
|
napEnd = cutoffTime;
|
|
}
|
|
|
|
const actualDuration = Math.floor((napEnd - napStart) / (1000 * 60));
|
|
|
|
// Only create the log if it has a positive duration
|
|
if (actualDuration > 0) {
|
|
logs.push({
|
|
id: randomUUID(),
|
|
startTime: napStart,
|
|
endTime: napEnd,
|
|
duration: actualDuration,
|
|
type: 'NAP',
|
|
location: 'Crib',
|
|
quality: randomChoice(['GOOD', 'FAIR', 'EXCELLENT']),
|
|
babyId: baby.id,
|
|
caretakerId: caretaker.id,
|
|
familyId: family.id
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
}
|
|
|
|
return logs;
|
|
}
|
|
|
|
// Generate feed logs for a baby
|
|
async function generateFeedLogs(baby, caretakers, family, startDate, endDate, cutoffTime) {
|
|
const logs = [];
|
|
const currentDate = new Date(startDate);
|
|
|
|
while (currentDate <= endDate) {
|
|
const caretaker = randomChoice(caretakers);
|
|
const isToday = isSameDay(currentDate, new Date());
|
|
|
|
// 6-8 bottle feeds per day, every 2-4 hours
|
|
const feedTimes = [2, 6, 10, 14, 18, 22]; // Base times: 2 AM, 6 AM, 10 AM, 2 PM, 6 PM, 10 PM
|
|
|
|
for (const baseHour of feedTimes) {
|
|
if (Math.random() > 0.15) { // 85% chance for each feed
|
|
const feedTime = generateTimeInDay(currentDate, baseHour, 45, isToday ? cutoffTime : null);
|
|
|
|
// Only create feed log if time is not in the future
|
|
if (feedTime <= cutoffTime) {
|
|
const amount = randomFloat(2, 8); // 2-8 ounces
|
|
|
|
logs.push({
|
|
id: randomUUID(),
|
|
time: feedTime,
|
|
type: 'BOTTLE',
|
|
amount: Math.round(amount * 10) / 10, // Round to 1 decimal
|
|
unitAbbr: 'OZ',
|
|
babyId: baby.id,
|
|
caretakerId: caretaker.id,
|
|
familyId: family.id
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
}
|
|
|
|
return logs;
|
|
}
|
|
|
|
// Generate diaper logs for a baby
|
|
async function generateDiaperLogs(baby, caretakers, family, startDate, endDate, cutoffTime) {
|
|
const logs = [];
|
|
const currentDate = new Date(startDate);
|
|
|
|
while (currentDate <= endDate) {
|
|
const caretaker = randomChoice(caretakers);
|
|
const isToday = isSameDay(currentDate, new Date());
|
|
|
|
// 6-10 diaper changes per day
|
|
const diaperCount = randomInt(6, 10);
|
|
|
|
for (let i = 0; i < diaperCount; i++) {
|
|
const hour = randomInt(0, 23);
|
|
const changeTime = generateTimeInDay(currentDate, hour, 30, isToday ? cutoffTime : null);
|
|
|
|
// Only create diaper log if time is not in the future
|
|
if (changeTime <= cutoffTime) {
|
|
// Determine diaper type (more wet than dirty)
|
|
let type;
|
|
const rand = Math.random();
|
|
if (rand < 0.6) {
|
|
type = 'WET';
|
|
} else if (rand < 0.85) {
|
|
type = 'DIRTY';
|
|
} else {
|
|
type = 'BOTH';
|
|
}
|
|
|
|
logs.push({
|
|
id: randomUUID(),
|
|
time: changeTime,
|
|
type: type,
|
|
condition: type === 'DIRTY' || type === 'BOTH' ? randomChoice(['Normal', 'Soft', 'Hard']) : null,
|
|
color: type === 'DIRTY' || type === 'BOTH' ? randomChoice(['Yellow', 'Brown', 'Green']) : null,
|
|
babyId: baby.id,
|
|
caretakerId: caretaker.id,
|
|
familyId: family.id
|
|
});
|
|
}
|
|
}
|
|
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
}
|
|
|
|
return logs;
|
|
}
|
|
|
|
// Generate bath logs for a baby (daily)
|
|
async function generateBathLogs(baby, caretakers, family, startDate, endDate, cutoffTime) {
|
|
const logs = [];
|
|
const currentDate = new Date(startDate);
|
|
|
|
while (currentDate <= endDate) {
|
|
const caretaker = randomChoice(caretakers);
|
|
const isToday = isSameDay(currentDate, new Date());
|
|
|
|
// 80% chance of bath per day, usually in the evening
|
|
if (Math.random() > 0.2) {
|
|
const bathTime = generateTimeInDay(currentDate, 19, 60, isToday ? cutoffTime : null); // 7 PM +/- 1 hour
|
|
|
|
// Only create bath log if time is not in the future
|
|
if (bathTime <= cutoffTime) {
|
|
const soapUsed = Math.random() > 0.1; // 90% chance
|
|
const shampooUsed = Math.random() > 0.3; // 70% chance
|
|
|
|
logs.push({
|
|
id: randomUUID(),
|
|
time: bathTime,
|
|
soapUsed: soapUsed,
|
|
shampooUsed: shampooUsed,
|
|
notes: Math.random() > 0.7 ? randomChoice([
|
|
'Baby loved splashing in the water',
|
|
'Calm and relaxed during bath',
|
|
'Fussy at first but settled down',
|
|
'Enjoyed playing with bath toys',
|
|
'Very sleepy after bath'
|
|
]) : null,
|
|
babyId: baby.id,
|
|
caretakerId: caretaker.id,
|
|
familyId: family.id
|
|
});
|
|
}
|
|
}
|
|
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
}
|
|
|
|
return logs;
|
|
}
|
|
|
|
// Generate notes for a baby (1 every day or two)
|
|
async function generateNotes(baby, caretakers, family, startDate, endDate, cutoffTime) {
|
|
const logs = [];
|
|
const currentDate = new Date(startDate);
|
|
|
|
while (currentDate <= endDate) {
|
|
const caretaker = randomChoice(caretakers);
|
|
const isToday = isSameDay(currentDate, new Date());
|
|
|
|
// 60% chance of note per day (roughly 1 every day or two)
|
|
if (Math.random() > 0.4) {
|
|
const noteTime = generateTimeInDay(currentDate, randomInt(8, 20), 30, isToday ? cutoffTime : null);
|
|
|
|
// Only create note if time is not in the future
|
|
if (noteTime <= cutoffTime) {
|
|
logs.push({
|
|
id: randomUUID(),
|
|
time: noteTime,
|
|
content: randomChoice(noteTemplates),
|
|
category: randomChoice(['General', 'Feeding', 'Sleep', 'Development', 'Health']),
|
|
babyId: baby.id,
|
|
caretakerId: caretaker.id,
|
|
familyId: family.id
|
|
});
|
|
}
|
|
}
|
|
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
}
|
|
|
|
return logs;
|
|
}
|
|
|
|
// Generate milestones for a baby
|
|
async function generateMilestones(baby, caretakers, family, startDate, endDate, cutoffTime) {
|
|
const logs = [];
|
|
const birthDate = new Date(baby.birthDate);
|
|
const currentDate = new Date(startDate);
|
|
|
|
// Generate milestones based on baby's age
|
|
const categories = Object.keys(milestoneTemplates);
|
|
|
|
while (currentDate <= endDate) {
|
|
const caretaker = randomChoice(caretakers);
|
|
const isToday = isSameDay(currentDate, new Date());
|
|
|
|
// Low chance of milestone per day (they're special!)
|
|
if (Math.random() > 0.95) { // 5% chance per day
|
|
const milestoneTime = generateTimeInDay(currentDate, randomInt(8, 20), 30, isToday ? cutoffTime : null);
|
|
|
|
// Only create milestone if time is not in the future
|
|
if (milestoneTime <= cutoffTime) {
|
|
const category = randomChoice(categories);
|
|
const title = randomChoice(milestoneTemplates[category]);
|
|
const ageInDays = Math.floor((milestoneTime - birthDate) / (1000 * 60 * 60 * 24));
|
|
|
|
logs.push({
|
|
id: randomUUID(),
|
|
date: milestoneTime,
|
|
title: title,
|
|
description: `${baby.firstName} ${title.toLowerCase()} at ${Math.floor(ageInDays / 30)} months and ${ageInDays % 30} days old!`,
|
|
category: category,
|
|
ageInDays: ageInDays,
|
|
babyId: baby.id,
|
|
caretakerId: caretaker.id,
|
|
familyId: family.id
|
|
});
|
|
}
|
|
}
|
|
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
}
|
|
|
|
return logs;
|
|
}
|
|
|
|
// Main data generation function
|
|
async function generateTestData() {
|
|
try {
|
|
console.log(`Starting test data generation...`);
|
|
console.log(`Families: ${familyCount}, Days: ${daysCount}, Clear data: ${clearData}`);
|
|
|
|
if (clearData) {
|
|
await clearExistingData();
|
|
}
|
|
|
|
// Generate app configuration
|
|
await generateAppConfig();
|
|
|
|
// Generate essential units
|
|
await generateUnits();
|
|
|
|
// Generate cutoff time (between 15 minutes and 3 hours ago)
|
|
const cutoffTime = generateCutoffTime();
|
|
const endDate = new Date(cutoffTime);
|
|
const startDate = new Date(endDate.getTime() - (daysCount * 24 * 60 * 60 * 1000));
|
|
|
|
console.log(`Data will be generated from ${startDate.toLocaleString()} to ${endDate.toLocaleString()}`);
|
|
console.log(`Last entries will be between 15 minutes and 3 hours ago`);
|
|
|
|
let totalBabies = 0;
|
|
let totalCaretakers = 0;
|
|
let totalSleepLogs = 0;
|
|
let totalFeedLogs = 0;
|
|
let totalDiaperLogs = 0;
|
|
let totalBathLogs = 0;
|
|
let totalNotes = 0;
|
|
let totalMilestones = 0;
|
|
|
|
for (let i = 0; i < familyCount; i++) {
|
|
console.log(`Generating family ${i + 1}/${familyCount}...`);
|
|
|
|
// Generate family
|
|
const family = await generateFamily();
|
|
console.log(` Created family: ${family.name} (${family.slug})`);
|
|
|
|
// Generate caretakers
|
|
const caretakers = await generateCaretakers(family);
|
|
totalCaretakers += caretakers.length;
|
|
console.log(` Created ${caretakers.length} caretakers`);
|
|
|
|
// Generate babies
|
|
const babies = await generateBabies(family, caretakers);
|
|
totalBabies += babies.length;
|
|
console.log(` Created ${babies.length} babies`);
|
|
|
|
// Generate logs for each baby
|
|
for (const baby of babies) {
|
|
console.log(` Generating logs for ${baby.firstName}...`);
|
|
|
|
// Generate sleep logs
|
|
const sleepLogs = await generateSleepLogs(baby, caretakers, family, startDate, endDate, cutoffTime);
|
|
if (sleepLogs.length > 0) {
|
|
await prisma.sleepLog.createMany({ data: sleepLogs });
|
|
totalSleepLogs += sleepLogs.length;
|
|
}
|
|
|
|
// Generate feed logs
|
|
const feedLogs = await generateFeedLogs(baby, caretakers, family, startDate, endDate, cutoffTime);
|
|
if (feedLogs.length > 0) {
|
|
await prisma.feedLog.createMany({ data: feedLogs });
|
|
totalFeedLogs += feedLogs.length;
|
|
}
|
|
|
|
// Generate diaper logs
|
|
const diaperLogs = await generateDiaperLogs(baby, caretakers, family, startDate, endDate, cutoffTime);
|
|
if (diaperLogs.length > 0) {
|
|
await prisma.diaperLog.createMany({ data: diaperLogs });
|
|
totalDiaperLogs += diaperLogs.length;
|
|
}
|
|
|
|
// Generate bath logs
|
|
const bathLogs = await generateBathLogs(baby, caretakers, family, startDate, endDate, cutoffTime);
|
|
if (bathLogs.length > 0) {
|
|
await prisma.bathLog.createMany({ data: bathLogs });
|
|
totalBathLogs += bathLogs.length;
|
|
}
|
|
|
|
// Generate notes
|
|
const notes = await generateNotes(baby, caretakers, family, startDate, endDate, cutoffTime);
|
|
if (notes.length > 0) {
|
|
await prisma.note.createMany({ data: notes });
|
|
totalNotes += notes.length;
|
|
}
|
|
|
|
// Generate milestones
|
|
const milestones = await generateMilestones(baby, caretakers, family, startDate, endDate, cutoffTime);
|
|
if (milestones.length > 0) {
|
|
await prisma.milestone.createMany({ data: milestones });
|
|
totalMilestones += milestones.length;
|
|
}
|
|
|
|
console.log(` Sleep: ${sleepLogs.length}, Feed: ${feedLogs.length}, Diaper: ${diaperLogs.length}, Bath: ${bathLogs.length}, Notes: ${notes.length}, Milestones: ${milestones.length}`);
|
|
}
|
|
}
|
|
|
|
console.log(`\nTest data generation completed successfully!`);
|
|
console.log(`Generated:`);
|
|
console.log(`- 1 app configuration (domain: demo.sprout-track.com, HTTPS: enabled)`);
|
|
console.log(`- Essential units (OZ, ML, TBSP, LB, IN, CM, G, KG, F, C, MG, MCG, L, CC, MOL, MMOL)`);
|
|
console.log(`- ${familyCount} families`);
|
|
console.log(`- ${totalCaretakers} caretakers (including system user "00" for each family)`);
|
|
console.log(`- ${totalBabies} babies`);
|
|
console.log(`- ${totalSleepLogs} sleep logs`);
|
|
console.log(`- ${totalFeedLogs} feed logs`);
|
|
console.log(`- ${totalDiaperLogs} diaper logs`);
|
|
console.log(`- ${totalBathLogs} bath logs`);
|
|
console.log(`- ${totalNotes} notes`);
|
|
console.log(`- ${totalMilestones} milestones`);
|
|
console.log(`Total log entries: ${totalSleepLogs + totalFeedLogs + totalDiaperLogs + totalBathLogs + totalNotes + totalMilestones}`);
|
|
|
|
} catch (error) {
|
|
console.error('Error generating test data:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Run the data generation
|
|
generateTestData()
|
|
.catch(e => {
|
|
console.error('Test data generation failed:', e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|