import { Injectable, Logger } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { Department, ArrivalMode, EventType, EventStatus, VehicleType, VehicleStatus } from '@prisma/client'; @Injectable() export class SeedService { private readonly logger = new Logger(SeedService.name); constructor(private prisma: PrismaService) {} /** * Clear all data using fast deleteMany operations */ async clearAllData() { const start = Date.now(); // Delete in order to respect foreign key constraints const results = await this.prisma.$transaction([ this.prisma.signalMessage.deleteMany(), this.prisma.scheduleEvent.deleteMany(), this.prisma.flight.deleteMany(), this.prisma.vehicle.deleteMany(), this.prisma.driver.deleteMany(), this.prisma.vIP.deleteMany(), ]); const elapsed = Date.now() - start; this.logger.log(`Cleared all data in ${elapsed}ms`); return { success: true, elapsed: `${elapsed}ms`, deleted: { messages: results[0].count, events: results[1].count, flights: results[2].count, vehicles: results[3].count, drivers: results[4].count, vips: results[5].count, }, }; } /** * Generate all test data in a single fast transaction */ async generateAllTestData(clearFirst: boolean = true) { const start = Date.now(); if (clearFirst) { await this.clearAllData(); } // Create all entities in a transaction const result = await this.prisma.$transaction(async (tx) => { // 1. Create VIPs with party sizes const vipData = this.getVIPData(); await tx.vIP.createMany({ data: vipData }); const vips = await tx.vIP.findMany({ orderBy: { createdAt: 'asc' } }); this.logger.log(`Created ${vips.length} VIPs`); // 2. Create Drivers with shifts const driverData = this.getDriverData(); await tx.driver.createMany({ data: driverData }); const drivers = await tx.driver.findMany({ orderBy: { createdAt: 'asc' } }); this.logger.log(`Created ${drivers.length} drivers`); // 3. Create Vehicles const vehicleData = this.getVehicleData(); await tx.vehicle.createMany({ data: vehicleData }); const vehicles = await tx.vehicle.findMany({ orderBy: { createdAt: 'asc' } }); this.logger.log(`Created ${vehicles.length} vehicles`); // 4. Create Flights for VIPs arriving by flight const flightVips = vips.filter(v => v.arrivalMode === 'FLIGHT'); const flightData = this.getFlightData(flightVips); await tx.flight.createMany({ data: flightData }); const flights = await tx.flight.findMany(); this.logger.log(`Created ${flights.length} flights`); // 5. Create Events (two-phase: master events first, then transport legs) const { masterEvents, transportLegs } = await this.createAllEvents(tx, vips, drivers, vehicles); this.logger.log(`Created ${masterEvents.length} master events and ${transportLegs.length} transport legs`); return { vips, drivers, vehicles, flights, events: [...masterEvents, ...transportLegs] }; }); const elapsed = Date.now() - start; this.logger.log(`Generated all test data in ${elapsed}ms`); return { success: true, elapsed: `${elapsed}ms`, created: { vips: result.vips.length, drivers: result.drivers.length, vehicles: result.vehicles.length, flights: result.flights.length, events: result.events.length, }, }; } /** * Generate only dynamic events (uses existing VIPs/drivers/vehicles) */ async generateDynamicEvents() { const start = Date.now(); // Clear existing events await this.prisma.scheduleEvent.deleteMany(); // Get existing entities const [vips, drivers, vehicles] = await Promise.all([ this.prisma.vIP.findMany({ where: { deletedAt: null } }), this.prisma.driver.findMany({ where: { deletedAt: null } }), this.prisma.vehicle.findMany({ where: { deletedAt: null } }), ]); if (vips.length === 0) { return { success: false, error: 'No VIPs found. Generate full test data first.' }; } // Create events const { masterEvents, transportLegs } = await this.prisma.$transaction(async (tx) => { return this.createAllEvents(tx, vips, drivers, vehicles); }); const elapsed = Date.now() - start; return { success: true, elapsed: `${elapsed}ms`, created: { events: masterEvents.length + transportLegs.length, masterEvents: masterEvents.length, transportLegs: transportLegs.length }, }; } // ============================================================ // DATA GENERATORS // ============================================================ private getVIPData() { return [ // OFFICE_OF_DEVELOPMENT (10 VIPs) - Corporate sponsors, foundations, major donors { name: 'Sarah Chen', organization: 'Microsoft Corporation', department: Department.OFFICE_OF_DEVELOPMENT, arrivalMode: ArrivalMode.FLIGHT, partySize: 1, airportPickup: true, venueTransport: true, notes: 'Executive VP - prefers quiet vehicles. Allergic to peanuts.' }, { name: 'Marcus Johnson', organization: 'The Coca-Cola Company', department: Department.OFFICE_OF_DEVELOPMENT, arrivalMode: ArrivalMode.FLIGHT, partySize: 2, airportPickup: true, venueTransport: true, notes: 'Bringing spouse. Needs wheelchair accessible transport.' }, { name: 'Jennifer Wu', organization: 'JPMorgan Chase Foundation', department: Department.OFFICE_OF_DEVELOPMENT, arrivalMode: ArrivalMode.FLIGHT, partySize: 1, airportPickup: true, venueTransport: true, notes: 'Major donor - $500K pledge. VIP treatment essential.' }, { name: 'Roberto Gonzalez', organization: 'AT&T Inc', department: Department.OFFICE_OF_DEVELOPMENT, arrivalMode: ArrivalMode.FLIGHT, partySize: 3, airportPickup: true, venueTransport: true, notes: 'First time visitor. Interested in STEM programs. Traveling with wife and daughter.' }, { name: 'Priya Sharma', organization: 'Google LLC', department: Department.OFFICE_OF_DEVELOPMENT, arrivalMode: ArrivalMode.FLIGHT, partySize: 1, airportPickup: true, venueTransport: true, notes: 'Vegetarian meals required. Interested in technology merit badges.' }, { name: 'David Okonkwo', organization: 'Bank of America', department: Department.OFFICE_OF_DEVELOPMENT, arrivalMode: ArrivalMode.FLIGHT, partySize: 2, airportPickup: false, venueTransport: true, notes: 'Has rental car for airport. Needs venue transport only. Traveling with assistant.' }, { name: 'Maria Rodriguez', organization: 'Walmart Foundation', department: Department.OFFICE_OF_DEVELOPMENT, arrivalMode: ArrivalMode.SELF_DRIVING, partySize: 1, airportPickup: false, venueTransport: true, expectedArrival: this.relativeTime(120), notes: 'Driving from nearby hotel. Call when 30 min out.' }, { name: 'Yuki Tanaka', organization: 'Honda Motor Company', department: Department.OFFICE_OF_DEVELOPMENT, arrivalMode: ArrivalMode.FLIGHT, partySize: 2, airportPickup: true, venueTransport: true, notes: 'Japanese executive with interpreter.' }, { name: 'Thomas Anderson', organization: 'Verizon Communications', department: Department.OFFICE_OF_DEVELOPMENT, arrivalMode: ArrivalMode.FLIGHT, partySize: 1, airportPickup: true, venueTransport: false, notes: 'Will use personal driver after airport pickup.' }, { name: 'Isabella Costa', organization: 'Target Corporation', department: Department.OFFICE_OF_DEVELOPMENT, arrivalMode: ArrivalMode.FLIGHT, partySize: 1, airportPickup: false, venueTransport: true, notes: 'Taking rideshare from airport. Venue transport needed.' }, // ADMIN (10 VIPs) - BSA Leadership and Staff { name: 'Roger A. Krone', organization: 'BSA National President', department: Department.ADMIN, arrivalMode: ArrivalMode.FLIGHT, partySize: 4, airportPickup: true, venueTransport: true, notes: 'HIGHEST PRIORITY VIP. Security detail traveling with him (total 4 people).' }, { name: 'Emily Richardson', organization: 'BSA Chief Scout Executive', department: Department.ADMIN, arrivalMode: ArrivalMode.SELF_DRIVING, partySize: 1, airportPickup: false, venueTransport: false, expectedArrival: this.relativeTime(60), notes: 'Has assigned BSA vehicle. No transport needed.' }, { name: 'Dr. Maya Krishnan', organization: 'BSA National Director of Program', department: Department.ADMIN, arrivalMode: ArrivalMode.SELF_DRIVING, partySize: 3, airportPickup: false, venueTransport: false, expectedArrival: this.relativeTime(180), notes: 'Carpooling with 2 regional directors.' }, { name: "James O'Brien", organization: 'BSA Northeast Regional Director', department: Department.ADMIN, arrivalMode: ArrivalMode.FLIGHT, partySize: 3, airportPickup: true, venueTransport: true, notes: 'Traveling with 2 staff members.' }, { name: 'Fatima Al-Rahman', organization: 'BSA Western Region Executive', department: Department.ADMIN, arrivalMode: ArrivalMode.FLIGHT, partySize: 1, airportPickup: true, venueTransport: true, notes: 'Halal meals required. Prayer room access needed.' }, { name: 'William Zhang', organization: 'BSA Southern Region Council', department: Department.ADMIN, arrivalMode: ArrivalMode.FLIGHT, partySize: 2, airportPickup: true, venueTransport: true, notes: 'Bringing presentation equipment - need vehicle with cargo space. Traveling with AV tech.' }, { name: 'Sophie Laurent', organization: 'BSA National Volunteer Training', department: Department.ADMIN, arrivalMode: ArrivalMode.SELF_DRIVING, partySize: 1, airportPickup: false, venueTransport: true, expectedArrival: this.relativeTime(240), notes: 'Training materials in personal vehicle.' }, { name: 'Alexander Volkov', organization: 'BSA High Adventure Director', department: Department.ADMIN, arrivalMode: ArrivalMode.FLIGHT, partySize: 1, airportPickup: true, venueTransport: false, notes: 'Outdoor enthusiast - prefers walking when possible.' }, { name: 'Dr. Aisha Patel', organization: 'BSA STEM & Innovation Programs', department: Department.ADMIN, arrivalMode: ArrivalMode.SELF_DRIVING, partySize: 2, airportPickup: false, venueTransport: true, expectedArrival: this.relativeTime(90), notes: 'Demo equipment for STEM showcase. Fragile items! Traveling with lab assistant.' }, { name: 'Henrik Larsson', organization: 'BSA International Commissioner', department: Department.ADMIN, arrivalMode: ArrivalMode.SELF_DRIVING, partySize: 1, airportPickup: false, venueTransport: true, expectedArrival: this.relativeTime(150), notes: 'Visiting from Sweden. International guest protocols apply.' }, ]; } private getDriverData() { const now = new Date(); const shiftStart = new Date(now); shiftStart.setHours(6, 0, 0, 0); const shiftEnd = new Date(now); shiftEnd.setHours(22, 0, 0, 0); const lateShiftStart = new Date(now); lateShiftStart.setHours(14, 0, 0, 0); const lateShiftEnd = new Date(now); lateShiftEnd.setHours(23, 59, 0, 0); return [ { name: 'Michael Thompson', phone: '555-0101', department: Department.OFFICE_OF_DEVELOPMENT, isAvailable: true, shiftStartTime: shiftStart, shiftEndTime: shiftEnd }, { name: 'Lisa Martinez', phone: '555-0102', department: Department.OFFICE_OF_DEVELOPMENT, isAvailable: true, shiftStartTime: shiftStart, shiftEndTime: shiftEnd }, { name: 'David Kim', phone: '555-0103', department: Department.OFFICE_OF_DEVELOPMENT, isAvailable: true, shiftStartTime: shiftStart, shiftEndTime: shiftEnd }, { name: 'Amanda Washington', phone: '555-0104', department: Department.ADMIN, isAvailable: true, shiftStartTime: shiftStart, shiftEndTime: shiftEnd }, { name: 'Carlos Hernandez', phone: '555-0105', department: Department.OFFICE_OF_DEVELOPMENT, isAvailable: false, shiftStartTime: lateShiftStart, shiftEndTime: lateShiftEnd }, { name: 'Jessica Lee', phone: '555-0106', department: Department.OFFICE_OF_DEVELOPMENT, isAvailable: true, shiftStartTime: shiftStart, shiftEndTime: shiftEnd }, { name: 'Brandon Jackson', phone: '555-0107', department: Department.OFFICE_OF_DEVELOPMENT, isAvailable: true, shiftStartTime: lateShiftStart, shiftEndTime: lateShiftEnd }, { name: 'Nicole Brown', phone: '555-0108', department: Department.ADMIN, isAvailable: true, shiftStartTime: shiftStart, shiftEndTime: shiftEnd }, ]; } private getVehicleData() { return [ { name: 'Black Suburban #1', type: VehicleType.SUV, licensePlate: 'SUV-101', seatCapacity: 6, status: VehicleStatus.AVAILABLE, notes: 'Premium VIP transport with leather interior' }, { name: 'Black Suburban #2', type: VehicleType.SUV, licensePlate: 'SUV-102', seatCapacity: 6, status: VehicleStatus.AVAILABLE, notes: 'Backup VIP transport with climate control' }, { name: 'White 15-Pax Van #1', type: VehicleType.VAN, licensePlate: 'VAN-151', seatCapacity: 14, status: VehicleStatus.AVAILABLE, notes: 'Large group transport with wheelchair accessibility' }, { name: 'White 15-Pax Van #2', type: VehicleType.VAN, licensePlate: 'VAN-152', seatCapacity: 14, status: VehicleStatus.AVAILABLE, notes: 'Large group transport with AV equipment' }, { name: 'Golf Cart A', type: VehicleType.GOLF_CART, licensePlate: 'GC-A', seatCapacity: 4, status: VehicleStatus.AVAILABLE, notes: 'Quick campus transport for short distances' }, { name: 'Golf Cart B', type: VehicleType.GOLF_CART, licensePlate: 'GC-B', seatCapacity: 4, status: VehicleStatus.AVAILABLE, notes: 'On-site shuttle for VIP areas' }, { name: 'Charter Bus', type: VehicleType.BUS, licensePlate: 'BUS-001', seatCapacity: 25, status: VehicleStatus.AVAILABLE, notes: 'Full-size coach for large group events' }, { name: 'Executive Sedan', type: VehicleType.SEDAN, licensePlate: 'SED-001', seatCapacity: 4, status: VehicleStatus.AVAILABLE, notes: 'Luxury sedan for single VIP executive transport' }, ]; } private getFlightData(vips: any[]) { const flights: any[] = []; const airlines = ['AA', 'UA', 'DL', 'SW', 'AS', 'JB']; const origins = ['JFK', 'LAX', 'ORD', 'DFW', 'ATL', 'SFO', 'SEA', 'BOS', 'DEN', 'MIA']; const destination = 'SLC'; vips.forEach((vip, index) => { const airline = airlines[index % airlines.length]; const flightNum = `${airline}${1000 + index * 123}`; const origin = origins[index % origins.length]; // Arrival flight - times relative to now const arrivalOffset = (index % 8) * 30 - 60; const scheduledArrival = this.relativeTime(arrivalOffset); const scheduledDeparture = new Date(scheduledArrival.getTime() - 3 * 60 * 60 * 1000); let status = 'scheduled'; let actualArrival = null; if (arrivalOffset < -30) { status = 'landed'; actualArrival = new Date(scheduledArrival.getTime() + (Math.random() * 20 - 10) * 60000); } else if (arrivalOffset < 0) { status = 'landing'; } else if (index % 5 === 0) { status = 'delayed'; } flights.push({ vipId: vip.id, flightNumber: flightNum, flightDate: new Date(), segment: 1, departureAirport: origin, arrivalAirport: destination, scheduledDeparture, scheduledArrival, actualArrival, status, }); // Some VIPs have connecting flights (segment 2) if (index % 4 === 0) { const connectOrigin = origins[(index + 3) % origins.length]; flights.push({ vipId: vip.id, flightNumber: `${airline}${500 + index}`, flightDate: new Date(), segment: 2, departureAirport: connectOrigin, arrivalAirport: origin, scheduledDeparture: new Date(scheduledDeparture.getTime() - 4 * 60 * 60 * 1000), scheduledArrival: new Date(scheduledDeparture.getTime() - 1 * 60 * 60 * 1000), status: 'landed', }); } }); return flights; } /** * Create all events (master events + transport legs) in correct order * Returns both arrays for counting */ private async createAllEvents(tx: any, vips: any[], drivers: any[], vehicles: any[]) { // Track schedules for conflict detection const vehicleSchedule: Map> = new Map(); const driverSchedule: Map> = new Map(); vehicles.forEach(v => vehicleSchedule.set(v.id, [])); drivers.forEach(d => driverSchedule.set(d.id, [])); // ============================================================ // DAY 1 MASTER EVENTS (relative to NOW) // ============================================================ // 1. Welcome Registration (-3hr to -2hr) - COMPLETED const welcomeReg = await tx.scheduleEvent.create({ data: { vipIds: vips.map(v => v.id), title: 'Welcome Registration & Check-In', type: EventType.EVENT, status: EventStatus.COMPLETED, location: 'Main Registration Pavilion', startTime: this.relativeTime(-180), endTime: this.relativeTime(-120), actualStartTime: this.relativeTime(-180), actualEndTime: this.relativeTime(-120), description: 'All VIPs check in and receive welcome packets, name badges, and event schedules', } }); // 2. Opening Ceremony (-1hr to +0.5hr) - IN_PROGRESS const openingCeremony = await tx.scheduleEvent.create({ data: { vipIds: vips.map(v => v.id), title: 'Grand Opening Ceremony', type: EventType.EVENT, status: EventStatus.IN_PROGRESS, location: 'Main Arena - VIP Section', startTime: this.relativeTime(-60), endTime: this.relativeTime(30), actualStartTime: this.relativeTime(-60), description: 'National anthem, welcome speeches by BSA leadership, flag ceremony. ALL VIPs in attendance.', } }); // 3. VIP Luncheon (+1hr to +2.5hr) - SCHEDULED const vipLuncheon = await tx.scheduleEvent.create({ data: { vipIds: vips.map(v => v.id), title: 'VIP Welcome Luncheon', type: EventType.MEAL, status: EventStatus.SCHEDULED, location: 'VIP Dining Pavilion', startTime: this.relativeTime(60), endTime: this.relativeTime(150), description: 'Catered lunch with networking. Menu includes vegetarian/halal options.', notes: 'Dietary restrictions: Priya Sharma (vegetarian), Fatima Al-Rahman (halal), Sarah Chen (no peanuts)', } }); // 4. Keynote Address (+3hr to +4.5hr) - SCHEDULED (16 VIPs, not self-driving Gov officials) const keynoteVips = vips.filter(v => v.arrivalMode === ArrivalMode.FLIGHT || !v.organization.includes('International Commissioner') ).slice(0, 16); const keynoteAddress = await tx.scheduleEvent.create({ data: { vipIds: keynoteVips.map(v => v.id), title: 'Keynote: Future of Youth Leadership', type: EventType.EVENT, status: EventStatus.SCHEDULED, location: 'Conference Center - Grand Hall', startTime: this.relativeTime(180), endTime: this.relativeTime(270), description: 'Keynote speaker addresses VIPs on modern youth development and leadership training', } }); // 5. Donor Strategy Meeting (+5hr to +6hr) - SCHEDULED (select 6 VIPs) const donorVips = vips.filter(v => v.department === Department.OFFICE_OF_DEVELOPMENT).slice(0, 6); const donorMeeting = await tx.scheduleEvent.create({ data: { vipIds: donorVips.map(v => v.id), title: 'Private Donor Strategy Session', type: EventType.MEETING, status: EventStatus.SCHEDULED, location: 'Executive Conference Room', startTime: this.relativeTime(300), endTime: this.relativeTime(360), description: 'Closed-door meeting with major donors to discuss funding initiatives', notes: 'CONFIDENTIAL - No photography', } }); // 6. Campfire Night (+9hr to +11hr) - SCHEDULED (all VIPs) const campfireNight = await tx.scheduleEvent.create({ data: { vipIds: vips.map(v => v.id), title: 'Traditional Scout Campfire Gathering', type: EventType.EVENT, status: EventStatus.SCHEDULED, location: 'Outdoor Amphitheater', startTime: this.relativeTime(540), endTime: this.relativeTime(660), description: 'Evening campfire with songs, skits, and scout traditions. Casual attire.', } }); // ============================================================ // DAY 2 MASTER EVENTS (+24hr base) // ============================================================ // 1. Morning Fitness Hike (+18hr = 6am next day) - 10 VIPs const hikeVips = vips.slice(0, 10); const morningHike = await tx.scheduleEvent.create({ data: { vipIds: hikeVips.map(v => v.id), title: 'Sunrise Fitness Hike', type: EventType.EVENT, status: EventStatus.SCHEDULED, location: 'Nature Trail - Starting at Lodge', startTime: this.relativeTime(1080), // +18hr endTime: this.relativeTime(1200), // +20hr description: 'Optional guided morning hike through scenic trails. Moderate difficulty.', notes: 'Athletic wear recommended. Water provided.', } }); // 2. STEM Showcase (+20hr) - 15 VIPs const stemVips = [...vips.filter(v => v.department === Department.OFFICE_OF_DEVELOPMENT), ...vips.filter(v => v.organization.includes('STEM'))]; const stemShowcase = await tx.scheduleEvent.create({ data: { vipIds: stemVips.slice(0, 15).map(v => v.id), title: 'STEM & Innovation Showcase', type: EventType.EVENT, status: EventStatus.SCHEDULED, location: 'Technology Center', startTime: this.relativeTime(1200), // +20hr endTime: this.relativeTime(1350), // +22.5hr description: 'Interactive demonstrations of robotics, coding, and engineering merit badge projects', notes: 'Dr. Aisha Patel will lead special demos', } }); // 3. Eagle Scout Ceremony (+22.5hr) - all 20 VIPs const eagleCeremony = await tx.scheduleEvent.create({ data: { vipIds: vips.map(v => v.id), title: 'Eagle Scout Recognition Ceremony', type: EventType.EVENT, status: EventStatus.SCHEDULED, location: 'Main Arena', startTime: this.relativeTime(1350), // +22.5hr endTime: this.relativeTime(1500), // +25hr description: 'Prestigious ceremony honoring new Eagle Scouts. Formal attire required.', } }); // 4. Gala Dinner (+27hr) - all 20 VIPs const galaDinner = await tx.scheduleEvent.create({ data: { vipIds: vips.map(v => v.id), title: 'Black-Tie Gala Dinner', type: EventType.MEAL, status: EventStatus.SCHEDULED, location: 'Grand Ballroom', startTime: this.relativeTime(1620), // +27hr endTime: this.relativeTime(1800), // +30hr description: 'Formal dinner with awards presentation and entertainment. Black-tie required.', notes: 'Seating chart will be distributed at check-in', } }); // ============================================================ // DAY 3 MASTER EVENTS (+48hr base) // ============================================================ // 1. Service Project Tour (+42hr) - 12 VIPs const serviceVips = vips.slice(0, 12); const serviceTour = await tx.scheduleEvent.create({ data: { vipIds: serviceVips.map(v => v.id), title: 'Community Service Project Tour', type: EventType.EVENT, status: EventStatus.SCHEDULED, location: 'Community Center - Multiple Sites', startTime: this.relativeTime(2520), // +42hr endTime: this.relativeTime(2640), // +44hr description: 'Tour of ongoing service projects including park restoration and food drive', } }); // 2. Farewell Brunch (+45hr) - all 20 VIPs const farewellBrunch = await tx.scheduleEvent.create({ data: { vipIds: vips.map(v => v.id), title: 'Farewell Brunch & Thank You', type: EventType.MEAL, status: EventStatus.SCHEDULED, location: 'VIP Dining Pavilion', startTime: this.relativeTime(2700), // +45hr endTime: this.relativeTime(2820), // +47hr description: 'Casual farewell brunch with closing remarks. Business casual attire.', } }); // 3. Closing Ceremony (+47hr) - all 20 VIPs const closingCeremony = await tx.scheduleEvent.create({ data: { vipIds: vips.map(v => v.id), title: 'Official Closing Ceremony', type: EventType.EVENT, status: EventStatus.SCHEDULED, location: 'Main Arena', startTime: this.relativeTime(2820), // +47hr endTime: this.relativeTime(2940), // +49hr description: 'Final ceremony with flag retirement and closing remarks from BSA National President', } }); const masterEvents = [ welcomeReg, openingCeremony, vipLuncheon, keynoteAddress, donorMeeting, campfireNight, morningHike, stemShowcase, eagleCeremony, galaDinner, serviceTour, farewellBrunch, closingCeremony ]; // ============================================================ // TRANSPORT LEGS LINKED TO MASTER EVENTS // ============================================================ const transportLegs: any[] = []; // Helper: find available driver/vehicle considering conflicts const hasConflict = (schedule: Array<{ start: Date; end: Date }>, start: Date, end: Date): boolean => { return schedule.some(slot => (start < slot.end && end > slot.start)); }; const findAvailableDriver = (start: Date, end: Date): any | null => { for (const driver of drivers) { const schedule = driverSchedule.get(driver.id) || []; if (!hasConflict(schedule, start, end)) { schedule.push({ start, end }); return driver; } } return null; }; const findAvailableVehicle = (start: Date, end: Date, minCapacity: number): any | null => { for (const vehicle of vehicles) { if (vehicle.seatCapacity < minCapacity) continue; const schedule = vehicleSchedule.get(vehicle.id) || []; if (!hasConflict(schedule, start, end)) { schedule.push({ start, end }); return vehicle; } } return null; }; // ============================================================ // DAY 1 TRANSPORT LEGS // ============================================================ // Airport pickups for flight-arriving VIPs (staggered arrivals, SOME COMPLETED/IN_PROGRESS) const flightVips = vips.filter(v => v.arrivalMode === ArrivalMode.FLIGHT && v.airportPickup); // Group 1: Roger Krone (party of 4) - HIGH PRIORITY - COMPLETED { const vip = flightVips.find(v => v.name === 'Roger A. Krone'); if (vip) { const start = this.relativeTime(-150); const end = this.relativeTime(-90); const driver = findAvailableDriver(start, end); const vehicle = findAvailableVehicle(start, end, vip.partySize); transportLegs.push({ vipIds: [vip.id], driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: welcomeReg.id, title: `Airport Pickup - ${vip.name} (PRIORITY)`, type: EventType.TRANSPORT, status: EventStatus.COMPLETED, pickupLocation: 'SLC Airport - Private Terminal', dropoffLocation: 'Main Registration Pavilion', startTime: start, endTime: end, actualStartTime: this.relativeTime(-150), actualEndTime: this.relativeTime(-90), description: `Completed VIP airport pickup for National President + security detail (${vip.partySize} people)`, }); } } // Group 2: Marcus + spouse + James O'Brien + staff - Suburban (party sizes 2+3=5) - IN_PROGRESS { const marcus = flightVips.find(v => v.name === 'Marcus Johnson'); const james = flightVips.find(v => v.name === "James O'Brien"); if (marcus && james) { const totalParty = marcus.partySize + james.partySize; const start = this.relativeTime(-45); const end = this.relativeTime(15); const driver = findAvailableDriver(start, end); const vehicle = findAvailableVehicle(start, end, totalParty); transportLegs.push({ vipIds: [marcus.id, james.id], driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: openingCeremony.id, title: `Multi-VIP Airport Pickup (${totalParty} passengers)`, type: EventType.TRANSPORT, status: EventStatus.IN_PROGRESS, pickupLocation: 'SLC Airport - Terminal 1', dropoffLocation: 'Main Arena - VIP Entrance', startTime: start, endTime: end, actualStartTime: this.relativeTime(-43), description: `ACTIVE: Picking up ${totalParty} people (Marcus Johnson party of ${marcus.partySize} + James O'Brien party of ${james.partySize})`, notes: 'Wheelchair accessible vehicle required for Marcus', }); } } // Group 3: Roberto Gonzalez (party of 3) + William Zhang (party of 2) = 5 total - SCHEDULED { const roberto = flightVips.find(v => v.name === 'Roberto Gonzalez'); const william = flightVips.find(v => v.name === 'William Zhang'); if (roberto && william) { const totalParty = roberto.partySize + william.partySize; const start = this.relativeTime(-15); const end = this.relativeTime(45); const driver = findAvailableDriver(start, end); const vehicle = findAvailableVehicle(start, end, totalParty); transportLegs.push({ vipIds: [roberto.id, william.id], driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: openingCeremony.id, title: `Airport Pickup - Group Transport (${totalParty} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'SLC Airport - Terminal 2', dropoffLocation: 'Main Arena - VIP Entrance', startTime: start, endTime: end, description: `Group pickup: Roberto Gonzalez family (${roberto.partySize}) + William Zhang + AV tech (${william.partySize})`, notes: 'Need cargo space for William Zhang\'s presentation equipment', }); } } // Remaining individual/pair airport pickups (spread across remaining SUVs/sedan) const remainingFlightVips = flightVips.filter(v => !['Roger A. Krone', 'Marcus Johnson', "James O'Brien", 'Roberto Gonzalez', 'William Zhang'].includes(v.name) ); remainingFlightVips.forEach((vip, idx) => { const offset = idx * 25 - 30; const start = this.relativeTime(offset); const end = this.relativeTime(offset + 60); const driver = findAvailableDriver(start, end); const vehicle = findAvailableVehicle(start, end, vip.partySize); let status: EventStatus = EventStatus.SCHEDULED; let actualStart: Date | null = null; if (offset < -20) { status = EventStatus.COMPLETED; actualStart = this.relativeTime(offset + 2); } else if (offset < 0) { status = EventStatus.IN_PROGRESS; actualStart = this.relativeTime(offset); } transportLegs.push({ vipIds: [vip.id], driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: offset < 30 ? openingCeremony.id : vipLuncheon.id, title: `Airport Pickup - ${vip.name}${vip.partySize > 1 ? ` (party of ${vip.partySize})` : ''}`, type: EventType.TRANSPORT, status, pickupLocation: 'SLC Airport - Terminal 1', dropoffLocation: offset < 30 ? 'Main Arena - VIP Entrance' : 'VIP Dining Pavilion', startTime: start, endTime: end, actualStartTime: actualStart, description: `Airport pickup for ${vip.name}${vip.partySize > 1 ? ` + ${vip.partySize - 1} companions` : ''}`, }); }); // Transport to VIP Luncheon - Use charter bus for most VIPs (party sizes sum check) { const lunchTransportVips = vips.slice(0, 12); // First 12 VIPs via bus const totalPartySize = lunchTransportVips.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(45); const end = this.relativeTime(60); const driver = findAvailableDriver(start, end); const vehicle = vehicles.find(v => v.name === 'Charter Bus'); if (vehicle) { const schedule = vehicleSchedule.get(vehicle.id) || []; if (!hasConflict(schedule, start, end)) { schedule.push({ start, end }); } } transportLegs.push({ vipIds: lunchTransportVips.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: vipLuncheon.id, title: `Group Transport to VIP Luncheon (${totalPartySize} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'Main Arena - VIP Exit', dropoffLocation: 'VIP Dining Pavilion', startTime: start, endTime: end, description: `Charter bus for ${lunchTransportVips.length} VIPs + companions (total ${totalPartySize} people)`, notes: `Capacity check: ${totalPartySize}/${vehicle?.seatCapacity} seats used`, }); } // Transport to Keynote - Two vans for 16 VIPs { const keynoteTransportVips = keynoteVips.slice(0, 8); const totalParty1 = keynoteTransportVips.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(165); const end = this.relativeTime(180); const driver = findAvailableDriver(start, end); const vehicle = findAvailableVehicle(start, end, totalParty1); transportLegs.push({ vipIds: keynoteTransportVips.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: keynoteAddress.id, title: `Transport to Keynote Address - Van 1 (${totalParty1} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Dining Pavilion', dropoffLocation: 'Conference Center - Grand Hall', startTime: start, endTime: end, description: `First van for keynote event (${totalParty1} people)`, }); // Second van const keynoteTransportVips2 = keynoteVips.slice(8, 16); const totalParty2 = keynoteTransportVips2.reduce((sum, v) => sum + v.partySize, 0); const driver2 = findAvailableDriver(start, end); const vehicle2 = findAvailableVehicle(start, end, totalParty2); transportLegs.push({ vipIds: keynoteTransportVips2.map(v => v.id), driverId: driver2?.id, vehicleId: vehicle2?.id, masterEventId: keynoteAddress.id, title: `Transport to Keynote Address - Van 2 (${totalParty2} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Dining Pavilion', dropoffLocation: 'Conference Center - Grand Hall', startTime: start, endTime: end, description: `Second van for keynote event (${totalParty2} people)`, }); } // Transport to Donor Meeting - Suburban for 6 VIPs { const donorTransportVips = donorVips; const totalParty = donorTransportVips.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(285); const end = this.relativeTime(300); const driver = findAvailableDriver(start, end); const vehicle = findAvailableVehicle(start, end, totalParty); transportLegs.push({ vipIds: donorTransportVips.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: donorMeeting.id, title: `Executive Transport to Donor Meeting (${totalParty} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'Conference Center', dropoffLocation: 'Executive Conference Room', startTime: start, endTime: end, description: `VIP transport for private donor strategy session (${totalParty} people)`, notes: 'CONFIDENTIAL - Use discrete route', }); } // Transport to Campfire Night - Charter bus for all 20 VIPs { const totalParty = vips.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(525); const end = this.relativeTime(540); const driver = findAvailableDriver(start, end); const vehicle = vehicles.find(v => v.name === 'Charter Bus'); if (vehicle) { const schedule = vehicleSchedule.get(vehicle.id) || []; if (!hasConflict(schedule, start, end)) { schedule.push({ start, end }); } } transportLegs.push({ vipIds: vips.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: campfireNight.id, title: `Group Transport to Campfire Night (${totalParty} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Lodge - Main Entrance', dropoffLocation: 'Outdoor Amphitheater', startTime: start, endTime: end, description: `Charter bus for all VIPs to evening campfire (${totalParty} total people including companions)`, notes: `Capacity: ${totalParty}/${vehicle?.seatCapacity} seats`, }); } // ============================================================ // DAY 2 TRANSPORT LEGS // ============================================================ // Morning Hike Transport - Van for 10 VIPs (early morning) { const totalParty = hikeVips.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(1065); const end = this.relativeTime(1080); const driver = findAvailableDriver(start, end); const vehicle = findAvailableVehicle(start, end, totalParty); transportLegs.push({ vipIds: hikeVips.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: morningHike.id, title: `Early Morning Transport to Fitness Hike (${totalParty} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Lodge', dropoffLocation: 'Nature Trail - Trailhead', startTime: start, endTime: end, description: `Van transport for sunrise hike participants (${totalParty} people)`, }); } // STEM Showcase Transport - Use two vans for 15 VIPs { const stemGroup1 = stemVips.slice(0, 8); const totalParty1 = stemGroup1.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(1185); const end = this.relativeTime(1200); const driver = findAvailableDriver(start, end); const vehicle = findAvailableVehicle(start, end, totalParty1); transportLegs.push({ vipIds: stemGroup1.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: stemShowcase.id, title: `STEM Showcase Transport - Van 1 (${totalParty1} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Lodge', dropoffLocation: 'Technology Center', startTime: start, endTime: end, description: `Transport to STEM showcase (${totalParty1} people)`, }); const stemGroup2 = stemVips.slice(8, 15); const totalParty2 = stemGroup2.reduce((sum, v) => sum + v.partySize, 0); const driver2 = findAvailableDriver(start, end); const vehicle2 = findAvailableVehicle(start, end, totalParty2); transportLegs.push({ vipIds: stemGroup2.map(v => v.id), driverId: driver2?.id, vehicleId: vehicle2?.id, masterEventId: stemShowcase.id, title: `STEM Showcase Transport - Van 2 (${totalParty2} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Lodge', dropoffLocation: 'Technology Center', startTime: start, endTime: end, description: `Transport to STEM showcase (${totalParty2} people)`, }); } // Eagle Ceremony Transport - Charter bus for all { const totalParty = vips.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(1335); const end = this.relativeTime(1350); const driver = findAvailableDriver(start, end); const vehicle = vehicles.find(v => v.name === 'Charter Bus'); if (vehicle) { const schedule = vehicleSchedule.get(vehicle.id) || []; if (!hasConflict(schedule, start, end)) { schedule.push({ start, end }); } } transportLegs.push({ vipIds: vips.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: eagleCeremony.id, title: `Formal Transport to Eagle Scout Ceremony (${totalParty} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Lodge', dropoffLocation: 'Main Arena - VIP Entrance', startTime: start, endTime: end, description: `Charter bus for prestigious Eagle ceremony (${totalParty} total people)`, notes: 'Formal attire - allow extra loading time', }); } // Gala Dinner Transport - Charter bus { const totalParty = vips.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(1605); const end = this.relativeTime(1620); const driver = findAvailableDriver(start, end); const vehicle = vehicles.find(v => v.name === 'Charter Bus'); if (vehicle) { const schedule = vehicleSchedule.get(vehicle.id) || []; if (!hasConflict(schedule, start, end)) { schedule.push({ start, end }); } } transportLegs.push({ vipIds: vips.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: galaDinner.id, title: `Black-Tie Gala Transport (${totalParty} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Lodge', dropoffLocation: 'Grand Ballroom - Red Carpet Entrance', startTime: start, endTime: end, description: `Charter bus for black-tie gala (${totalParty} people)`, notes: 'BLACK-TIE EVENT - Coordinate arrival timing for red carpet photos', }); } // ============================================================ // DAY 3 TRANSPORT LEGS // ============================================================ // Service Project Tour - Van for 12 VIPs { const totalParty = serviceVips.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(2505); const end = this.relativeTime(2520); const driver = findAvailableDriver(start, end); const vehicle = findAvailableVehicle(start, end, totalParty); transportLegs.push({ vipIds: serviceVips.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: serviceTour.id, title: `Service Project Tour Transport (${totalParty} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Lodge', dropoffLocation: 'Community Center - Tour Starting Point', startTime: start, endTime: end, description: `Transport to community service sites (${totalParty} people)`, notes: 'Casual attire - may get dirty during service activities', }); } // Farewell Brunch Transport - Charter bus { const totalParty = vips.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(2685); const end = this.relativeTime(2700); const driver = findAvailableDriver(start, end); const vehicle = vehicles.find(v => v.name === 'Charter Bus'); if (vehicle) { const schedule = vehicleSchedule.get(vehicle.id) || []; if (!hasConflict(schedule, start, end)) { schedule.push({ start, end }); } } transportLegs.push({ vipIds: vips.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: farewellBrunch.id, title: `Transport to Farewell Brunch (${totalParty} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Lodge', dropoffLocation: 'VIP Dining Pavilion', startTime: start, endTime: end, description: `Final group transport to farewell event (${totalParty} people)`, }); } // Closing Ceremony Transport - Charter bus { const totalParty = vips.reduce((sum, v) => sum + v.partySize, 0); const start = this.relativeTime(2805); const end = this.relativeTime(2820); const driver = findAvailableDriver(start, end); const vehicle = vehicles.find(v => v.name === 'Charter Bus'); if (vehicle) { const schedule = vehicleSchedule.get(vehicle.id) || []; if (!hasConflict(schedule, start, end)) { schedule.push({ start, end }); } } transportLegs.push({ vipIds: vips.map(v => v.id), driverId: driver?.id, vehicleId: vehicle?.id, masterEventId: closingCeremony.id, title: `Final Transport to Closing Ceremony (${totalParty} passengers)`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Dining Pavilion', dropoffLocation: 'Main Arena - VIP Entrance', startTime: start, endTime: end, description: `Charter bus for closing ceremony (${totalParty} people)`, }); } // Airport Departures (Day 3 afternoon) - Staggered individual/group departures const departureVips = vips.filter(v => v.arrivalMode === ArrivalMode.FLIGHT); departureVips.forEach((vip, idx) => { const offset = 2940 + (idx * 30); // Starting after closing ceremony const start = this.relativeTime(offset); const end = this.relativeTime(offset + 45); const driver = findAvailableDriver(start, end); const vehicle = findAvailableVehicle(start, end, vip.partySize); transportLegs.push({ vipIds: [vip.id], driverId: driver?.id, vehicleId: vehicle?.id, title: `Airport Departure - ${vip.name}${vip.partySize > 1 ? ` (party of ${vip.partySize})` : ''}`, type: EventType.TRANSPORT, status: EventStatus.SCHEDULED, pickupLocation: 'VIP Lodge', dropoffLocation: 'SLC Airport - Departures', startTime: start, endTime: end, description: `Airport departure transport for ${vip.name}${vip.partySize > 1 ? ` + ${vip.partySize - 1} companions` : ''}`, notes: 'Confirm flight departure time 2 hours prior', }); }); // CANCELLED event for realism { const vip = vips[5]; transportLegs.push({ vipIds: [vip.id], title: 'Private Museum Tour - CANCELLED', type: EventType.TRANSPORT, status: EventStatus.CANCELLED, pickupLocation: 'VIP Lodge', dropoffLocation: 'Local History Museum', startTime: this.relativeTime(400), endTime: this.relativeTime(480), description: 'Museum tour cancelled due to facility closure', notes: 'Facility unexpectedly closed for maintenance', }); } // Create all transport legs using createMany await tx.scheduleEvent.createMany({ data: transportLegs }); return { masterEvents, transportLegs: transportLegs }; } // ============================================================ // HELPER METHODS // ============================================================ /** * Get a date relative to now * @param minutesOffset - Minutes from now (negative = past, positive = future) */ private relativeTime(minutesOffset: number): Date { return new Date(Date.now() + minutesOffset * 60 * 1000); } }