From 484d64a852a7f25be5841ead34d6056c3d0fabe6 Mon Sep 17 00:00:00 2001 From: Santiordon Date: Tue, 14 Jul 2026 22:15:12 -0400 Subject: [PATCH 01/10] #4244 Add events, schedule slots, documents and meeting attendance --- src/backend/src/prisma/dates.ts | 16 +- src/backend/src/prisma/dev-seed.ts | 4 +- .../src/prisma/factories/event.factory.ts | 204 ++++++++++++++++++ src/backend/src/prisma/seed/event.process.ts | 160 ++++++++++++++ src/backend/src/prisma/utils/arrays.ts | 6 + src/backend/src/prisma/utils/strings.ts | 4 + 6 files changed, 386 insertions(+), 8 deletions(-) create mode 100644 src/backend/src/prisma/factories/event.factory.ts create mode 100644 src/backend/src/prisma/seed/event.process.ts create mode 100644 src/backend/src/prisma/utils/arrays.ts create mode 100644 src/backend/src/prisma/utils/strings.ts diff --git a/src/backend/src/prisma/dates.ts b/src/backend/src/prisma/dates.ts index 8af22dc738..e674440783 100644 --- a/src/backend/src/prisma/dates.ts +++ b/src/backend/src/prisma/dates.ts @@ -5,10 +5,6 @@ import dayjs from 'dayjs'; https://fakerjs.dev/api/date.html */ -interface WithFaker { - faker: Faker; -} - export const SECOND_MS = 1000; export const MINUTE_MS = SECOND_MS * 60; export const HOUR_MS = MINUTE_MS * 60; @@ -17,15 +13,21 @@ export const WEEK_MS = DAY_MS * 7; export const DAYS_PER_WEEK = 7; -export function generateRandomDate({ faker }: WithFaker, from?: Date, to?: Date) { +export function generateRandomDate(faker: Faker, from?: Date, to?: Date) { return faker.date.between({ from: from ?? '2000-01-01', to: to ?? Date.now() }); } -export function generateRandomDateAround({ faker }: WithFaker, date: Date) { - return faker.date.recent({ days: 5, refDate: date }); +export function generateRandomDateAround(faker: Faker, date: Date, days: number) { + return faker.date.recent({ days, refDate: date }); } export const daysBetween = ({ start, end }: DateRange): number => Math.max(0, dayjs(end).diff(dayjs(start), 'day')); export const clampDate = (date: Date, { start, end }: DateRange): Date => new Date(Math.min(Math.max(date.getTime(), start.getTime()), end.getTime())); + +export const subtractDaysFromDate = (start: Date, days: number) => { + const end = new Date(start); + end.setDate(start.getDate() - days); + return end; +}; diff --git a/src/backend/src/prisma/dev-seed.ts b/src/backend/src/prisma/dev-seed.ts index eaff3d271d..01a7e50fb4 100644 --- a/src/backend/src/prisma/dev-seed.ts +++ b/src/backend/src/prisma/dev-seed.ts @@ -16,6 +16,7 @@ import { TeamProcess } from './seed/team.process.js'; import { ProjectProcess } from './seed/project.process.js'; import { SchedulingProcess } from './seed/scheduling.process.js'; import { WorkPackageProcess } from './seed/work-package.process.js'; +import { EventProcess } from './seed/event.process.js'; const prisma = new PrismaClient(); @@ -31,7 +32,8 @@ await new SeedRunner() new TeamProcess(), new ShopProcess(), new ProjectProcess(), - new WorkPackageProcess() + new WorkPackageProcess(), + new EventProcess() ) .run(); diff --git a/src/backend/src/prisma/factories/event.factory.ts b/src/backend/src/prisma/factories/event.factory.ts new file mode 100644 index 0000000000..b88c093272 --- /dev/null +++ b/src/backend/src/prisma/factories/event.factory.ts @@ -0,0 +1,204 @@ +import { faker, Faker } from '@faker-js/faker'; +import { Conflict_Status, Event_Status, Prisma } from '@prisma/client'; +import { arrayOrNull } from '../utils/arrays.js'; +import { UsersOutput } from '../seed/user.process.js'; + +const EVENT_TITLE_PREFIXES = [ + 'Design Review -', + 'Design Review -', + 'Design Review -', + 'Design Review -', + 'Weekly Meeting -', + 'Team Meeting -', + 'Knowledge Transfer -', + 'Office Hours -', + 'Bay Time -', + 'Planning Meeting -' +]; + +export const EVENT_LOCATIONS = [ + 'The Bay', + 'Richards 054', + 'Richards 300', + 'Hastings 211', + 'Egan 206', + 'Curry 435', + 'Snell 031', + 'Robinson 107', + 'Zoom', + 'Bay/Zoom', + null, + null, + null +]; + +export const EVENT_DESCRIPTIONS = [ + 'Weekly team meeting to go over project updates.', + 'Design review for current project phase.', + 'Office hours for team members to ask questions.', + 'Knowledge transfer session for new members.', + 'Bay time for hands-on work.', + 'Planning session for upcoming milestones.' +]; + +const APPROVAL_REQUIRED_PROBABILITY = 0.15; +export const EVENTS_PER_PROJECT = 3; +export const DOCUMENT_PROBABILITY = 0.6; +export const MEETING_ATTENDANCE_PROBABILITY = 0.4; +const MAX_ATTENDEES = 10; + +export const generateEventCount = (faker: Faker): number => faker.number.int({ min: 1, max: EVENTS_PER_PROJECT }); + +export const generateScheduleSlotCount = (faker: Faker, title: string): number => { + const isRecurring = + title.includes('Weekly') || title.includes('Meeting') || title.includes('Office Hours') || title.includes('Bay Time'); + + if (isRecurring) { + return faker.number.int({ min: 4, max: 12 }); + } + + return faker.number.int({ min: 1, max: 2 }); +}; + +export const generateScheduleSlotOffset = (faker: Faker): number => faker.number.int({ min: 7, max: 21 }); + +export const generateInitialDateOffset = (faker: Faker, availableDays: number, isCurrentYear: boolean): number => { + if (isCurrentYear) { + const recentWindow = Math.min(availableDays, 150); + const recentStart = Math.max(0, availableDays - recentWindow); + return faker.number.int({ min: recentStart, max: availableDays }); + } + return faker.number.int({ min: 0, max: Math.max(0, availableDays) }); +}; + +export const shouldCreateDocument = (faker: Faker): boolean => faker.datatype.boolean({ probability: DOCUMENT_PROBABILITY }); + +export const shouldCreateMeetingAttendance = (faker: Faker): boolean => + faker.datatype.boolean({ probability: MEETING_ATTENDANCE_PROBABILITY }); + +export const generateAttendeeCount = (faker: Faker, maxUsers: number): number => + faker.number.int({ min: 2, max: Math.min(MAX_ATTENDEES, maxUsers) }); + +export const generateLocation = (faker: Faker): string | null => arrayOrNull(faker, EVENT_LOCATIONS, 0.5); + +export const generateEventDescription = (faker: Faker): string | null => arrayOrNull(faker, EVENT_DESCRIPTIONS, 0.5); + +export const generateZoomLink = (faker: Faker): string | null => { + if (!faker.datatype.boolean({ probability: 0.5 })) return null; + return `https://northeastern.zoom.us/j/${faker.string.numeric(11)}`; +}; + +export const generateQuestionDocumentLink = (faker: Faker): string | null => { + if (!faker.datatype.boolean({ probability: 0.6 })) return null; + return `https://docs.google.com/document/d/${faker.string.alphanumeric(44)}/edit`; +}; + +export const generateEventTitle = (faker: Faker, projectName: string): string => { + const prefix = faker.helpers.arrayElement(EVENT_TITLE_PREFIXES); + return `${prefix} ${projectName}`; +}; + +export const generateApprovalRequiredFromUserId = ( + faker: Faker, + creators: UsersOutput['leadership'] +): string | undefined => { + return faker.helpers.maybe(() => faker.helpers.arrayElement(creators).userId, { + probability: APPROVAL_REQUIRED_PROBABILITY + }); +}; + +export const generateEventStatus = (faker: Faker): Event_Status => + faker.helpers.weightedArrayElement([ + { weight: 60, value: Event_Status.SCHEDULED }, + { weight: 20, value: Event_Status.UNCONFIRMED }, + { weight: 12, value: Event_Status.CONFIRMED }, + { weight: 8, value: Event_Status.DONE } + ]); + +export const generateConflictStatus = (faker: Faker): Conflict_Status => + faker.helpers.weightedArrayElement([ + { weight: 85, value: Conflict_Status.NO_CONFLICT }, + { weight: 10, value: Conflict_Status.PENDING }, + { weight: 5, value: Conflict_Status.APPROVED } + ]); + +export const generateScheduleSlotTimes = (faker: Faker, baseDate: Date): { startTime: Date; endTime: Date } => { + const startTime = new Date(baseDate); + startTime.setHours(faker.number.int({ min: 9, max: 20 }), faker.helpers.arrayElement([0, 30]), 0, 0); + + const durationHours = faker.helpers.weightedArrayElement([ + { weight: 50, value: 1 }, + { weight: 30, value: 2 }, + { weight: 15, value: 1.5 }, + { weight: 5, value: 3 } + ]); + + const endTime = new Date(startTime); + endTime.setTime(endTime.getTime() + durationHours * 60 * 60 * 1000); + + return { startTime, endTime }; +}; + +export const eventCreateInput = ( + title: string, + userCreatedId: string, + eventTypeId: string, + status: Event_Status, + approved: Conflict_Status, + initialDateScheduled: Date, + location: string | null, + zoomLink: string | null, + description: string | null, + questionDocumentLink: string | null, + approvalRequiredFromUserId?: string +): Prisma.EventCreateInput => ({ + title, + status, + approved, + initialDateScheduled, + calendarEventIds: [], + userCreated: { connect: { userId: userCreatedId } }, + eventType: { connect: { eventTypeId } }, + ...(location ? { location } : {}), + ...(zoomLink ? { zoomLink } : {}), + ...(description ? { description } : {}), + ...(questionDocumentLink ? { questionDocumentLink } : {}), + ...(approvalRequiredFromUserId ? { approvalRequiredBy: { connect: { userId: approvalRequiredFromUserId } } } : {}) +}); + +export const scheduleSlotCreateInput = ( + eventId: string, + startTime: Date, + endTime: Date, + allDay: boolean = false +): Prisma.Schedule_SlotCreateInput => ({ + startTime, + endTime, + allDay, + event: { connect: { eventId } } +}); + +export const documentCreateInput = ( + eventId: string, + createdByUserId: string, + identifier: number +): Prisma.DocumentCreateInput => ({ + googleFileId: `seed-doc-${identifier}-${crypto.randomUUID()}`, + name: `document-${identifier}.pdf`, + createdBy: { connect: { userId: createdByUserId } }, + documentEvent: { connect: { eventId } } +}); + +export const meetingAttendanceCreateInput = ( + organizationId: string, + teamId: string, + userCreatedId: string, + attendeeIds: string[] +): Prisma.Meeting_AttendanceCreateInput => ({ + slackChannelId: `C${Math.random().toString(36).substring(2, 12).toUpperCase()}`, + slackMessageTimestamp: `${Math.floor(faker.date.past({ years: 1 }).getTime() / 1000)}.${faker.string.numeric(6)}`, + organization: { connect: { organizationId } }, + team: { connect: { teamId } }, + userCreated: { connect: { userId: userCreatedId } }, + attendees: { connect: attendeeIds.map((userId) => ({ userId })) } +}); diff --git a/src/backend/src/prisma/seed/event.process.ts b/src/backend/src/prisma/seed/event.process.ts new file mode 100644 index 0000000000..40947fc479 --- /dev/null +++ b/src/backend/src/prisma/seed/event.process.ts @@ -0,0 +1,160 @@ +import { SeedProcess } from '../processes/seed-process.js'; +import { OrganizationOutput, OrganizationProcess } from './organization.process.js'; +import { UsersOutput, UsersProcess } from './user.process.js'; +import { ConfigDataOutput, ConfigDataProcess } from './config-data.process.js'; +import { TeamOutput, TeamProcess } from './team.process.js'; +import { ProjectOutput, ProjectProcess } from './project.process.js'; +import { CarOutput } from '../context.js'; +import { CarProcess } from './car.process.js'; +import { + documentCreateInput, + eventCreateInput, + generateApprovalRequiredFromUserId, + generateAttendeeCount, + generateConflictStatus, + generateEventCount, + generateEventDescription, + generateEventStatus, + generateEventTitle, + generateInitialDateOffset, + generateLocation, + generateQuestionDocumentLink, + generateScheduleSlotCount, + generateScheduleSlotTimes, + generateZoomLink, + meetingAttendanceCreateInput, + scheduleSlotCreateInput, + shouldCreateDocument, + shouldCreateMeetingAttendance +} from '../factories/event.factory.js'; +import { addDaysToDate } from 'shared'; +import { daysBetween, subtractDaysFromDate } from '../dates.js'; + +type EventInput = OrganizationOutput & UsersOutput & ConfigDataOutput & TeamOutput & CarOutput & ProjectOutput; + +let documentIdentifier = 0; + +export class EventProcess extends SeedProcess> { + dependencies() { + return [OrganizationProcess, UsersProcess, ConfigDataProcess, TeamProcess, CarProcess, ProjectProcess]; + } + + async run({ + organization, + projects, + leadership, + heads, + admins, + appAdmins, + members, + teams, + eventTypes + }: EventInput): Promise> { + const { organizationId } = organization; + const creators = [...leadership, ...heads, ...admins, ...appAdmins]; + const allUsers = [...members, ...leadership, ...heads, ...admins, ...appAdmins]; + + const now = new Date(); + + const BATCH_SIZE = 20; + for (let i = 0; i < projects.length; i += BATCH_SIZE) { + const batch = projects.slice(i, i + BATCH_SIZE); + await Promise.all( + batch.map(({ project, timeline }) => + this.generateEventsForProject( + organizationId, + project.wbsElement.name, + timeline, + creators, + allUsers, + teams, + eventTypes, + now + ) + ) + ); + } + + return {}; + } + + private async generateEventsForProject( + organizationId: string, + projectName: string, + timeline: { start: Date; end: Date }, + creators: UsersOutput['leadership'], + allUsers: UsersOutput['members'], + teams: TeamOutput['teams'], + eventTypes: ConfigDataOutput['eventTypes'], + now: Date + ) { + const count = generateEventCount(this.faker); + // Increase the window with about 50 days of padding to account for car switching + const window = { start: subtractDaysFromDate(timeline.start, 50), end: addDaysToDate(timeline.end, 50) }; + + for (let i = 0; i < count; i++) { + const creator = this.faker.helpers.arrayElement(creators); + const eventType = this.faker.helpers.arrayElement(eventTypes); + const status = generateEventStatus(this.faker); + const approved = generateConflictStatus(this.faker); + const title = generateEventTitle(this.faker, projectName); + + const availableDays = daysBetween(window); + const offsetDays = generateInitialDateOffset(this.faker, availableDays, window.end > now); + const initialDateScheduled = addDaysToDate(window.start, offsetDays); + + const location = generateLocation(this.faker); + const zoomLink = generateZoomLink(this.faker); + const description = generateEventDescription(this.faker); + const questionDocumentLink = generateQuestionDocumentLink(this.faker); + const approvalRequiredFromUserId = generateApprovalRequiredFromUserId(this.faker, creators); + + const event = await this.prisma.event.create({ + data: eventCreateInput( + title, + creator.userId, + eventType.eventTypeId, + status, + approved, + initialDateScheduled, + location, + zoomLink, + description, + questionDocumentLink, + approvalRequiredFromUserId + ) + }); + + const slotCount = generateScheduleSlotCount(this.faker, title); + for (let s = 0; s < slotCount; s++) { + const slotDate = addDaysToDate(initialDateScheduled, s * 7); + const { startTime, endTime } = generateScheduleSlotTimes(this.faker, slotDate); + await this.prisma.schedule_Slot.create({ + data: scheduleSlotCreateInput(event.eventId, startTime, endTime) + }); + } + + if (shouldCreateDocument(this.faker)) { + documentIdentifier++; + await this.prisma.document.create({ + data: documentCreateInput(event.eventId, creator.userId, documentIdentifier) + }); + } + + if (shouldCreateMeetingAttendance(this.faker)) { + const team = this.faker.helpers.arrayElement(teams); + const attendeeCount = generateAttendeeCount(this.faker, allUsers.length); + const attendees = this.faker.helpers.arrayElements(allUsers, attendeeCount); + + await this.prisma.meeting_Attendance.create({ + data: meetingAttendanceCreateInput( + organizationId, + team.teamId, + creator.userId, + attendees.map((u) => u.userId) + ) + }); + } + } + } +} diff --git a/src/backend/src/prisma/utils/arrays.ts b/src/backend/src/prisma/utils/arrays.ts new file mode 100644 index 0000000000..b29e80a91e --- /dev/null +++ b/src/backend/src/prisma/utils/arrays.ts @@ -0,0 +1,6 @@ +import { Faker } from '@faker-js/faker'; + +export const arrayOrNull = (faker: Faker, array: Array, nullChance: number): T | null => { + if (!faker.datatype.boolean({ probability: nullChance })) return null; + return faker.helpers.arrayElement(array); +}; diff --git a/src/backend/src/prisma/utils/strings.ts b/src/backend/src/prisma/utils/strings.ts new file mode 100644 index 0000000000..8dcdd9e0a3 --- /dev/null +++ b/src/backend/src/prisma/utils/strings.ts @@ -0,0 +1,4 @@ +import { Faker } from '@faker-js/faker'; + +export const generateLoremIpsum = (faker: Faker, maxSentences: number): string => + faker.lorem.sentences(faker.number.int({ min: 1, max: maxSentences })); From 5ea558d69064f9c16aaeec2b4804c9e2abc30c36 Mon Sep 17 00:00:00 2001 From: Santiordon Date: Tue, 14 Jul 2026 22:17:18 -0400 Subject: [PATCH 02/10] #4244 fix conflict --- src/backend/src/prisma/dev-seed.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/backend/src/prisma/dev-seed.ts b/src/backend/src/prisma/dev-seed.ts index be11534140..a3c529b600 100644 --- a/src/backend/src/prisma/dev-seed.ts +++ b/src/backend/src/prisma/dev-seed.ts @@ -33,9 +33,7 @@ await new SeedRunner() new TeamProcess(), new ShopProcess(), new ProjectProcess(), - new WorkPackageProcess(), - new EventProcess() - new WorkPackageProcess(), + new EventProcess(), new TaskProcess() ) .run(); From 636a9b399d8ccaaf4b40f5c782d9b9c16b70652f Mon Sep 17 00:00:00 2001 From: Santiordon Date: Wed, 15 Jul 2026 22:37:09 -0400 Subject: [PATCH 03/10] #4244 Address changes and fix dummy random vars --- src/backend/src/prisma/factories/event.factory.ts | 12 ++++-------- src/backend/src/prisma/seed/event.process.ts | 9 +++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/backend/src/prisma/factories/event.factory.ts b/src/backend/src/prisma/factories/event.factory.ts index b88c093272..c47c075302 100644 --- a/src/backend/src/prisma/factories/event.factory.ts +++ b/src/backend/src/prisma/factories/event.factory.ts @@ -178,13 +178,9 @@ export const scheduleSlotCreateInput = ( event: { connect: { eventId } } }); -export const documentCreateInput = ( - eventId: string, - createdByUserId: string, - identifier: number -): Prisma.DocumentCreateInput => ({ - googleFileId: `seed-doc-${identifier}-${crypto.randomUUID()}`, - name: `document-${identifier}.pdf`, +export const documentCreateInput = (eventId: string, createdByUserId: string): Prisma.DocumentCreateInput => ({ + googleFileId: `${faker.string.alphanumeric(33)}`, + name: `document-${faker.string.uuid()}.pdf`, createdBy: { connect: { userId: createdByUserId } }, documentEvent: { connect: { eventId } } }); @@ -195,7 +191,7 @@ export const meetingAttendanceCreateInput = ( userCreatedId: string, attendeeIds: string[] ): Prisma.Meeting_AttendanceCreateInput => ({ - slackChannelId: `C${Math.random().toString(36).substring(2, 12).toUpperCase()}`, + slackChannelId: `C${faker.string.alphanumeric(10).toUpperCase()}`, slackMessageTimestamp: `${Math.floor(faker.date.past({ years: 1 }).getTime() / 1000)}.${faker.string.numeric(6)}`, organization: { connect: { organizationId } }, team: { connect: { teamId } }, diff --git a/src/backend/src/prisma/seed/event.process.ts b/src/backend/src/prisma/seed/event.process.ts index 40947fc479..5d063cce49 100644 --- a/src/backend/src/prisma/seed/event.process.ts +++ b/src/backend/src/prisma/seed/event.process.ts @@ -28,12 +28,10 @@ import { shouldCreateMeetingAttendance } from '../factories/event.factory.js'; import { addDaysToDate } from 'shared'; -import { daysBetween, subtractDaysFromDate } from '../dates.js'; +import { clampDate, daysBetween, subtractDaysFromDate } from '../dates.js'; type EventInput = OrganizationOutput & UsersOutput & ConfigDataOutput & TeamOutput & CarOutput & ProjectOutput; -let documentIdentifier = 0; - export class EventProcess extends SeedProcess> { dependencies() { return [OrganizationProcess, UsersProcess, ConfigDataProcess, TeamProcess, CarProcess, ProjectProcess]; @@ -128,16 +126,15 @@ export class EventProcess extends SeedProcess> const slotCount = generateScheduleSlotCount(this.faker, title); for (let s = 0; s < slotCount; s++) { const slotDate = addDaysToDate(initialDateScheduled, s * 7); - const { startTime, endTime } = generateScheduleSlotTimes(this.faker, slotDate); + const { startTime, endTime } = generateScheduleSlotTimes(this.faker, clampDate(slotDate, window)); await this.prisma.schedule_Slot.create({ data: scheduleSlotCreateInput(event.eventId, startTime, endTime) }); } if (shouldCreateDocument(this.faker)) { - documentIdentifier++; await this.prisma.document.create({ - data: documentCreateInput(event.eventId, creator.userId, documentIdentifier) + data: documentCreateInput(event.eventId, creator.userId) }); } From de5dc0b0e3ef2be32a7429b409c8b3068df98d2f Mon Sep 17 00:00:00 2001 From: Santiordon Date: Thu, 16 Jul 2026 22:24:59 -0400 Subject: [PATCH 04/10] #4244 Address comments --- .../src/prisma/factories/event.factory.ts | 67 +++++++++++++------ src/backend/src/prisma/seed/event.process.ts | 10 +-- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/backend/src/prisma/factories/event.factory.ts b/src/backend/src/prisma/factories/event.factory.ts index c47c075302..e258281515 100644 --- a/src/backend/src/prisma/factories/event.factory.ts +++ b/src/backend/src/prisma/factories/event.factory.ts @@ -1,7 +1,8 @@ -import { faker, Faker } from '@faker-js/faker'; +import { Faker } from '@faker-js/faker'; import { Conflict_Status, Event_Status, Prisma } from '@prisma/client'; import { arrayOrNull } from '../utils/arrays.js'; import { UsersOutput } from '../seed/user.process.js'; +import { DAY_MS, MINUTE_MS } from '../dates.js'; const EVENT_TITLE_PREFIXES = [ 'Design Review -', @@ -45,6 +46,7 @@ const APPROVAL_REQUIRED_PROBABILITY = 0.15; export const EVENTS_PER_PROJECT = 3; export const DOCUMENT_PROBABILITY = 0.6; export const MEETING_ATTENDANCE_PROBABILITY = 0.4; +const EVENT_DATE_BUFFER_DAYS = 7; const MAX_ATTENDEES = 10; export const generateEventCount = (faker: Faker): number => faker.number.int({ min: 1, max: EVENTS_PER_PROJECT }); @@ -60,8 +62,6 @@ export const generateScheduleSlotCount = (faker: Faker, title: string): number = return faker.number.int({ min: 1, max: 2 }); }; -export const generateScheduleSlotOffset = (faker: Faker): number => faker.number.int({ min: 7, max: 21 }); - export const generateInitialDateOffset = (faker: Faker, availableDays: number, isCurrentYear: boolean): number => { if (isCurrentYear) { const recentWindow = Math.min(availableDays, 150); @@ -107,13 +107,32 @@ export const generateApprovalRequiredFromUserId = ( }); }; -export const generateEventStatus = (faker: Faker): Event_Status => - faker.helpers.weightedArrayElement([ - { weight: 60, value: Event_Status.SCHEDULED }, - { weight: 20, value: Event_Status.UNCONFIRMED }, - { weight: 12, value: Event_Status.CONFIRMED }, - { weight: 8, value: Event_Status.DONE } +export const generateEventStatus = (faker: Faker, initialDateScheduled: Date, now: Date = new Date()): Event_Status => { + const daysUntilDue = Math.floor((initialDateScheduled.getTime() - now.getTime()) / DAY_MS); + + if (daysUntilDue < -EVENT_DATE_BUFFER_DAYS) { + return faker.helpers.weightedArrayElement([ + { weight: 50, value: Event_Status.DONE }, + { weight: 30, value: Event_Status.CONFIRMED }, + { weight: 20, value: Event_Status.SCHEDULED } + ]); + } + + if (daysUntilDue > EVENT_DATE_BUFFER_DAYS) { + return faker.helpers.weightedArrayElement([ + { weight: 60, value: Event_Status.SCHEDULED }, + { weight: 25, value: Event_Status.UNCONFIRMED }, + { weight: 15, value: Event_Status.DONE } + ]); + } + + return faker.helpers.weightedArrayElement([ + { weight: 50, value: Event_Status.SCHEDULED }, + { weight: 25, value: Event_Status.CONFIRMED }, + { weight: 15, value: Event_Status.UNCONFIRMED }, + { weight: 10, value: Event_Status.DONE } ]); +}; export const generateConflictStatus = (faker: Faker): Conflict_Status => faker.helpers.weightedArrayElement([ @@ -178,7 +197,7 @@ export const scheduleSlotCreateInput = ( event: { connect: { eventId } } }); -export const documentCreateInput = (eventId: string, createdByUserId: string): Prisma.DocumentCreateInput => ({ +export const documentCreateInput = (faker: Faker, eventId: string, createdByUserId: string): Prisma.DocumentCreateInput => ({ googleFileId: `${faker.string.alphanumeric(33)}`, name: `document-${faker.string.uuid()}.pdf`, createdBy: { connect: { userId: createdByUserId } }, @@ -186,15 +205,25 @@ export const documentCreateInput = (eventId: string, createdByUserId: string): P }); export const meetingAttendanceCreateInput = ( + faker: Faker, organizationId: string, teamId: string, userCreatedId: string, - attendeeIds: string[] -): Prisma.Meeting_AttendanceCreateInput => ({ - slackChannelId: `C${faker.string.alphanumeric(10).toUpperCase()}`, - slackMessageTimestamp: `${Math.floor(faker.date.past({ years: 1 }).getTime() / 1000)}.${faker.string.numeric(6)}`, - organization: { connect: { organizationId } }, - team: { connect: { teamId } }, - userCreated: { connect: { userId: userCreatedId } }, - attendees: { connect: attendeeIds.map((userId) => ({ userId })) } -}); + attendeeIds: string[], + initialDateScheduled: Date +): Prisma.Meeting_AttendanceCreateInput => { + const openedAt = initialDateScheduled; + const closedAt = new Date(openedAt); + closedAt.setTime(openedAt.getTime() + faker.number.int({ min: 60, max: 120 }) * MINUTE_MS); + + return { + slackChannelId: `C${faker.string.alphanumeric(10).toUpperCase()}`, + slackMessageTimestamp: `${Math.floor(openedAt.getTime() / 1000)}.${faker.string.numeric(6)}`, + openedAt, + closedAt, + organization: { connect: { organizationId } }, + team: { connect: { teamId } }, + userCreated: { connect: { userId: userCreatedId } }, + attendees: { connect: attendeeIds.map((userId) => ({ userId })) } + }; +}; diff --git a/src/backend/src/prisma/seed/event.process.ts b/src/backend/src/prisma/seed/event.process.ts index 5d063cce49..760625c76d 100644 --- a/src/backend/src/prisma/seed/event.process.ts +++ b/src/backend/src/prisma/seed/event.process.ts @@ -93,13 +93,13 @@ export class EventProcess extends SeedProcess> for (let i = 0; i < count; i++) { const creator = this.faker.helpers.arrayElement(creators); const eventType = this.faker.helpers.arrayElement(eventTypes); - const status = generateEventStatus(this.faker); const approved = generateConflictStatus(this.faker); const title = generateEventTitle(this.faker, projectName); - + const availableDays = daysBetween(window); const offsetDays = generateInitialDateOffset(this.faker, availableDays, window.end > now); const initialDateScheduled = addDaysToDate(window.start, offsetDays); + const status = generateEventStatus(this.faker, initialDateScheduled); const location = generateLocation(this.faker); const zoomLink = generateZoomLink(this.faker); @@ -134,7 +134,7 @@ export class EventProcess extends SeedProcess> if (shouldCreateDocument(this.faker)) { await this.prisma.document.create({ - data: documentCreateInput(event.eventId, creator.userId) + data: documentCreateInput(this.faker, event.eventId, creator.userId) }); } @@ -145,10 +145,12 @@ export class EventProcess extends SeedProcess> await this.prisma.meeting_Attendance.create({ data: meetingAttendanceCreateInput( + this.faker, organizationId, team.teamId, creator.userId, - attendees.map((u) => u.userId) + attendees.map((u) => u.userId), + initialDateScheduled ) }); } From 74e51420da25377de8a10bb42d7a1adc05226717 Mon Sep 17 00:00:00 2001 From: Santiordon Date: Fri, 17 Jul 2026 17:14:27 -0400 Subject: [PATCH 05/10] #4244 Prettier --- src/backend/src/prisma/seed/event.process.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/src/prisma/seed/event.process.ts b/src/backend/src/prisma/seed/event.process.ts index 760625c76d..484416f4fe 100644 --- a/src/backend/src/prisma/seed/event.process.ts +++ b/src/backend/src/prisma/seed/event.process.ts @@ -95,7 +95,7 @@ export class EventProcess extends SeedProcess> const eventType = this.faker.helpers.arrayElement(eventTypes); const approved = generateConflictStatus(this.faker); const title = generateEventTitle(this.faker, projectName); - + const availableDays = daysBetween(window); const offsetDays = generateInitialDateOffset(this.faker, availableDays, window.end > now); const initialDateScheduled = addDaysToDate(window.start, offsetDays); From d2fa3bc231822030108a711c53969cad1a0ebc85 Mon Sep 17 00:00:00 2001 From: Santiordon Date: Sat, 18 Jul 2026 23:19:25 -0400 Subject: [PATCH 06/10] #4244 Bug fixes --- .../src/prisma/factories/car.factory.ts | 6 +- .../src/prisma/factories/event.factory.ts | 113 +++++++++++++----- src/backend/src/prisma/seed/event.process.ts | 67 ++++++++--- 3 files changed, 135 insertions(+), 51 deletions(-) diff --git a/src/backend/src/prisma/factories/car.factory.ts b/src/backend/src/prisma/factories/car.factory.ts index 8c72cbe507..92b7e66ccb 100644 --- a/src/backend/src/prisma/factories/car.factory.ts +++ b/src/backend/src/prisma/factories/car.factory.ts @@ -5,10 +5,10 @@ import { DateRange } from '../context.js'; const CURRENT_YEAR = new Date().getFullYear(); const CAR_COUNT = 5; -const FROM_MONTH = 1; // January +const FROM_MONTH = 5; // May const FROM_DAY = 1; -const TO_MONTH = 12; // December -const TO_DAY = 30; +const TO_MONTH = 7; // July +const TO_DAY = 31; export const getCarConfigs = (faker: Faker) => { return Array.from({ length: CAR_COUNT }, (_, i) => { diff --git a/src/backend/src/prisma/factories/event.factory.ts b/src/backend/src/prisma/factories/event.factory.ts index e258281515..7c94143346 100644 --- a/src/backend/src/prisma/factories/event.factory.ts +++ b/src/backend/src/prisma/factories/event.factory.ts @@ -1,8 +1,13 @@ import { Faker } from '@faker-js/faker'; import { Conflict_Status, Event_Status, Prisma } from '@prisma/client'; import { arrayOrNull } from '../utils/arrays.js'; -import { UsersOutput } from '../seed/user.process.js'; import { DAY_MS, MINUTE_MS } from '../dates.js'; +import { addDaysToDate } from 'shared'; + +type ConflictResult = { + approved: Conflict_Status; + approvalRequiredFromUserId: string | undefined; +}; const EVENT_TITLE_PREFIXES = [ 'Design Review -', @@ -42,12 +47,12 @@ export const EVENT_DESCRIPTIONS = [ 'Planning session for upcoming milestones.' ]; -const APPROVAL_REQUIRED_PROBABILITY = 0.15; export const EVENTS_PER_PROJECT = 3; export const DOCUMENT_PROBABILITY = 0.6; export const MEETING_ATTENDANCE_PROBABILITY = 0.4; const EVENT_DATE_BUFFER_DAYS = 7; const MAX_ATTENDEES = 10; +export const DAYS_AFTER_NO_EVENT = 20; export const generateEventCount = (faker: Faker): number => faker.number.int({ min: 1, max: EVENTS_PER_PROJECT }); @@ -64,9 +69,7 @@ export const generateScheduleSlotCount = (faker: Faker, title: string): number = export const generateInitialDateOffset = (faker: Faker, availableDays: number, isCurrentYear: boolean): number => { if (isCurrentYear) { - const recentWindow = Math.min(availableDays, 150); - const recentStart = Math.max(0, availableDays - recentWindow); - return faker.number.int({ min: recentStart, max: availableDays }); + return faker.number.int({ min: 0, max: Math.min(DAYS_AFTER_NO_EVENT, availableDays) }); } return faker.number.int({ min: 0, max: Math.max(0, availableDays) }); }; @@ -98,49 +101,62 @@ export const generateEventTitle = (faker: Faker, projectName: string): string => return `${prefix} ${projectName}`; }; -export const generateApprovalRequiredFromUserId = ( - faker: Faker, - creators: UsersOutput['leadership'] -): string | undefined => { - return faker.helpers.maybe(() => faker.helpers.arrayElement(creators).userId, { - probability: APPROVAL_REQUIRED_PROBABILITY - }); -}; +export const generateEventDateCreated = (faker: Faker, initialDateScheduled: Date): Date => + addDaysToDate(initialDateScheduled, -faker.number.int({ min: 1, max: 14 })); -export const generateEventStatus = (faker: Faker, initialDateScheduled: Date, now: Date = new Date()): Event_Status => { +export const generateEventStatus = ( + faker: Faker, + requiresConfirmation: boolean, + initialDateScheduled: Date, + now: Date = new Date() +): Event_Status => { const daysUntilDue = Math.floor((initialDateScheduled.getTime() - now.getTime()) / DAY_MS); + if (requiresConfirmation && daysUntilDue > 0) return Event_Status.UNCONFIRMED; + if (daysUntilDue < -EVENT_DATE_BUFFER_DAYS) { return faker.helpers.weightedArrayElement([ { weight: 50, value: Event_Status.DONE }, - { weight: 30, value: Event_Status.CONFIRMED }, - { weight: 20, value: Event_Status.SCHEDULED } + { weight: 50, value: Event_Status.SCHEDULED } ]); } - if (daysUntilDue > EVENT_DATE_BUFFER_DAYS) { - return faker.helpers.weightedArrayElement([ - { weight: 60, value: Event_Status.SCHEDULED }, - { weight: 25, value: Event_Status.UNCONFIRMED }, - { weight: 15, value: Event_Status.DONE } - ]); + if (daysUntilDue > 0) { + return requiresConfirmation + ? faker.helpers.weightedArrayElement([ + { weight: 60, value: Event_Status.UNCONFIRMED }, + { weight: 40, value: Event_Status.SCHEDULED } + ]) + : faker.helpers.weightedArrayElement([ + { weight: 60, value: Event_Status.SCHEDULED }, + { weight: 40, value: Event_Status.UNCONFIRMED } + ]); } return faker.helpers.weightedArrayElement([ { weight: 50, value: Event_Status.SCHEDULED }, - { weight: 25, value: Event_Status.CONFIRMED }, - { weight: 15, value: Event_Status.UNCONFIRMED }, + { weight: 40, value: Event_Status.UNCONFIRMED }, { weight: 10, value: Event_Status.DONE } ]); }; -export const generateConflictStatus = (faker: Faker): Conflict_Status => - faker.helpers.weightedArrayElement([ +export const generateConflictStatus = (faker: Faker, creators: { userId: string }[]): ConflictResult => { + const approved = faker.helpers.weightedArrayElement([ { weight: 85, value: Conflict_Status.NO_CONFLICT }, { weight: 10, value: Conflict_Status.PENDING }, { weight: 5, value: Conflict_Status.APPROVED } ]); + if (approved === Conflict_Status.NO_CONFLICT) { + return { approved, approvalRequiredFromUserId: undefined }; + } + + return { + approved, + approvalRequiredFromUserId: faker.helpers.arrayElement(creators).userId + }; +}; + export const generateScheduleSlotTimes = (faker: Faker, baseDate: Date): { startTime: Date; endTime: Date } => { const startTime = new Date(baseDate); startTime.setHours(faker.number.int({ min: 9, max: 20 }), faker.helpers.arrayElement([0, 30]), 0, 0); @@ -159,6 +175,16 @@ export const generateScheduleSlotTimes = (faker: Faker, baseDate: Date): { start }; export const eventCreateInput = ( + eventType: { + requiresConfirmation: boolean; + location: boolean; + zoomLink: boolean; + questionDocument: boolean; + description: boolean; + documents: boolean; + requiredMembers: boolean; + optionalMembers: boolean; + }, title: string, userCreatedId: string, eventTypeId: string, @@ -169,6 +195,10 @@ export const eventCreateInput = ( zoomLink: string | null, description: string | null, questionDocumentLink: string | null, + requiredMemberIds: string[], + optionalMemberIds: string[], + confirmedMemberIds: string[], + deniedMemberIds: string[], approvalRequiredFromUserId?: string ): Prisma.EventCreateInput => ({ title, @@ -178,11 +208,24 @@ export const eventCreateInput = ( calendarEventIds: [], userCreated: { connect: { userId: userCreatedId } }, eventType: { connect: { eventTypeId } }, - ...(location ? { location } : {}), - ...(zoomLink ? { zoomLink } : {}), - ...(description ? { description } : {}), - ...(questionDocumentLink ? { questionDocumentLink } : {}), - ...(approvalRequiredFromUserId ? { approvalRequiredBy: { connect: { userId: approvalRequiredFromUserId } } } : {}) + requiredMembers: { + connect: [ + { userId: userCreatedId }, + ...requiredMemberIds.filter((id) => id !== userCreatedId).map((id) => ({ userId: id })) + ] + }, + ...(eventType.optionalMembers && optionalMemberIds.length > 0 + ? { optionalMembers: { connect: optionalMemberIds.map((userId) => ({ userId })) } } + : {}), + confirmedMembers: { connect: confirmedMemberIds.map((userId) => ({ userId })) }, + deniedMembers: { connect: deniedMemberIds.map((userId) => ({ userId })) }, + ...(eventType.location && location ? { location } : {}), + ...(eventType.zoomLink && zoomLink ? { zoomLink } : {}), + ...(eventType.description && description ? { description } : {}), + ...(eventType.questionDocument && questionDocumentLink ? { questionDocumentLink } : {}), + ...(approved !== Conflict_Status.NO_CONFLICT && approvalRequiredFromUserId + ? { approvalRequiredBy: { connect: { userId: approvalRequiredFromUserId } } } + : {}) }); export const scheduleSlotCreateInput = ( @@ -197,9 +240,15 @@ export const scheduleSlotCreateInput = ( event: { connect: { eventId } } }); -export const documentCreateInput = (faker: Faker, eventId: string, createdByUserId: string): Prisma.DocumentCreateInput => ({ +export const documentCreateInput = ( + faker: Faker, + eventId: string, + createdByUserId: string, + dateCreated: Date +): Prisma.DocumentCreateInput => ({ googleFileId: `${faker.string.alphanumeric(33)}`, name: `document-${faker.string.uuid()}.pdf`, + dateCreated, createdBy: { connect: { userId: createdByUserId } }, documentEvent: { connect: { eventId } } }); diff --git a/src/backend/src/prisma/seed/event.process.ts b/src/backend/src/prisma/seed/event.process.ts index 484416f4fe..7e609fa40b 100644 --- a/src/backend/src/prisma/seed/event.process.ts +++ b/src/backend/src/prisma/seed/event.process.ts @@ -7,12 +7,13 @@ import { ProjectOutput, ProjectProcess } from './project.process.js'; import { CarOutput } from '../context.js'; import { CarProcess } from './car.process.js'; import { + DAYS_AFTER_NO_EVENT, documentCreateInput, eventCreateInput, - generateApprovalRequiredFromUserId, generateAttendeeCount, generateConflictStatus, generateEventCount, + generateEventDateCreated, generateEventDescription, generateEventStatus, generateEventTitle, @@ -28,7 +29,8 @@ import { shouldCreateMeetingAttendance } from '../factories/event.factory.js'; import { addDaysToDate } from 'shared'; -import { clampDate, daysBetween, subtractDaysFromDate } from '../dates.js'; +import { clampDate, DAY_MS, daysBetween } from '../dates.js'; +import { Event_Status } from '@prisma/client'; type EventInput = OrganizationOutput & UsersOutput & ConfigDataOutput & TeamOutput & CarOutput & ProjectOutput; @@ -58,8 +60,8 @@ export class EventProcess extends SeedProcess> for (let i = 0; i < projects.length; i += BATCH_SIZE) { const batch = projects.slice(i, i + BATCH_SIZE); await Promise.all( - batch.map(({ project, timeline }) => - this.generateEventsForProject( + batch.map(({ project, timeline }) => { + return this.generateEventsForProject( organizationId, project.wbsElement.name, timeline, @@ -68,8 +70,8 @@ export class EventProcess extends SeedProcess> teams, eventTypes, now - ) - ) + ); + }) ); } @@ -86,29 +88,54 @@ export class EventProcess extends SeedProcess> eventTypes: ConfigDataOutput['eventTypes'], now: Date ) { + if ((timeline.start.getTime() - now.getTime()) / DAY_MS > DAYS_AFTER_NO_EVENT) return; + const count = generateEventCount(this.faker); // Increase the window with about 50 days of padding to account for car switching - const window = { start: subtractDaysFromDate(timeline.start, 50), end: addDaysToDate(timeline.end, 50) }; for (let i = 0; i < count; i++) { const creator = this.faker.helpers.arrayElement(creators); const eventType = this.faker.helpers.arrayElement(eventTypes); - const approved = generateConflictStatus(this.faker); + const { approved, approvalRequiredFromUserId } = generateConflictStatus(this.faker, creators); const title = generateEventTitle(this.faker, projectName); - const availableDays = daysBetween(window); - const offsetDays = generateInitialDateOffset(this.faker, availableDays, window.end > now); - const initialDateScheduled = addDaysToDate(window.start, offsetDays); - const status = generateEventStatus(this.faker, initialDateScheduled); + const availableDays = daysBetween(timeline); + const offsetDays = generateInitialDateOffset(this.faker, availableDays, timeline.end > now); + const initialDateScheduled = addDaysToDate(timeline.start, offsetDays); + const status = generateEventStatus(this.faker, eventType.requiresConfirmation, initialDateScheduled); const location = generateLocation(this.faker); const zoomLink = generateZoomLink(this.faker); const description = generateEventDescription(this.faker); const questionDocumentLink = generateQuestionDocumentLink(this.faker); - const approvalRequiredFromUserId = generateApprovalRequiredFromUserId(this.faker, creators); + const dateCreated = generateEventDateCreated(this.faker, initialDateScheduled); + + const requiredMemberIds = eventType.requiredMembers + ? this.faker.helpers.arrayElements(allUsers, this.faker.number.int({ min: 1, max: 5 })).map((user) => user.userId) + : []; + const optionalMemberIds = eventType.optionalMembers + ? this.faker.helpers + .arrayElements( + allUsers.filter((user) => !requiredMemberIds.includes(user.userId)), + this.faker.number.int({ min: 1, max: 5 }) + ) + .map((user) => user.userId) + : []; + + const allMemberIds = [...requiredMemberIds, ...optionalMemberIds]; + + const confirmedMemberIds = eventType.requiresConfirmation + ? status === Event_Status.CONFIRMED + ? [...requiredMemberIds, ...this.faker.helpers.arrayElements(optionalMemberIds)] + : [...this.faker.helpers.arrayElements(requiredMemberIds), ...this.faker.helpers.arrayElements(optionalMemberIds)] + : []; + const deniedMemberIds = eventType.requiresConfirmation + ? [...this.faker.helpers.arrayElements(allMemberIds.filter((id) => !confirmedMemberIds.includes(id)))] + : []; const event = await this.prisma.event.create({ data: eventCreateInput( + eventType, title, creator.userId, eventType.eventTypeId, @@ -119,14 +146,22 @@ export class EventProcess extends SeedProcess> zoomLink, description, questionDocumentLink, + requiredMemberIds, + optionalMemberIds, + confirmedMemberIds, + deniedMemberIds, approvalRequiredFromUserId ) }); const slotCount = generateScheduleSlotCount(this.faker, title); - for (let s = 0; s < slotCount; s++) { + const daysRemaining = daysBetween({ start: initialDateScheduled, end: timeline.end }); + const maxSlots = Math.max(1, Math.floor(daysRemaining / 7)); + const actualSlotCount = Math.min(slotCount, maxSlots); + + for (let s = 0; s < actualSlotCount; s++) { const slotDate = addDaysToDate(initialDateScheduled, s * 7); - const { startTime, endTime } = generateScheduleSlotTimes(this.faker, clampDate(slotDate, window)); + const { startTime, endTime } = generateScheduleSlotTimes(this.faker, clampDate(slotDate, timeline)); await this.prisma.schedule_Slot.create({ data: scheduleSlotCreateInput(event.eventId, startTime, endTime) }); @@ -134,7 +169,7 @@ export class EventProcess extends SeedProcess> if (shouldCreateDocument(this.faker)) { await this.prisma.document.create({ - data: documentCreateInput(this.faker, event.eventId, creator.userId) + data: documentCreateInput(this.faker, event.eventId, creator.userId, dateCreated) }); } From 41870f9c0f7d790b94a8815514ea13cc9d3fd3fa Mon Sep 17 00:00:00 2001 From: Santiordon Date: Mon, 20 Jul 2026 10:46:38 -0400 Subject: [PATCH 07/10] #4244 Address comments --- src/backend/src/prisma/factories/event.factory.ts | 10 ++++++++++ src/backend/src/prisma/seed/event.process.ts | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/backend/src/prisma/factories/event.factory.ts b/src/backend/src/prisma/factories/event.factory.ts index 7c94143346..152b632f72 100644 --- a/src/backend/src/prisma/factories/event.factory.ts +++ b/src/backend/src/prisma/factories/event.factory.ts @@ -53,6 +53,7 @@ export const MEETING_ATTENDANCE_PROBABILITY = 0.4; const EVENT_DATE_BUFFER_DAYS = 7; const MAX_ATTENDEES = 10; export const DAYS_AFTER_NO_EVENT = 20; +const CONFIRMED_WINDOW = 14 export const generateEventCount = (faker: Faker): number => faker.number.int({ min: 1, max: EVENTS_PER_PROJECT }); @@ -114,6 +115,15 @@ export const generateEventStatus = ( if (requiresConfirmation && daysUntilDue > 0) return Event_Status.UNCONFIRMED; + // Confirmed status only gets a change in a window of time since they are very rare and not common amongst an aggegate dataset + if (requiresConfirmation && daysUntilDue > 0 && daysUntilDue < CONFIRMED_WINDOW) { + return faker.helpers.weightedArrayElement([ + { weight: 70, value: Event_Status.UNCONFIRMED }, + { weight: 20, value: Event_Status.SCHEDULED }, + { weight: 10, value: Event_Status.CONFIRMED } + ]); + } + if (daysUntilDue < -EVENT_DATE_BUFFER_DAYS) { return faker.helpers.weightedArrayElement([ { weight: 50, value: Event_Status.DONE }, diff --git a/src/backend/src/prisma/seed/event.process.ts b/src/backend/src/prisma/seed/event.process.ts index 7e609fa40b..e7f658112a 100644 --- a/src/backend/src/prisma/seed/event.process.ts +++ b/src/backend/src/prisma/seed/event.process.ts @@ -129,9 +129,9 @@ export class EventProcess extends SeedProcess> ? [...requiredMemberIds, ...this.faker.helpers.arrayElements(optionalMemberIds)] : [...this.faker.helpers.arrayElements(requiredMemberIds), ...this.faker.helpers.arrayElements(optionalMemberIds)] : []; - const deniedMemberIds = eventType.requiresConfirmation - ? [...this.faker.helpers.arrayElements(allMemberIds.filter((id) => !confirmedMemberIds.includes(id)))] - : []; + + // Unused. For reference if later reused. + const deniedMemberIds: string[] = []; const event = await this.prisma.event.create({ data: eventCreateInput( From 531210d0f570f1f726ff14547563910560a4fbab Mon Sep 17 00:00:00 2001 From: Santiordon Date: Mon, 20 Jul 2026 10:50:14 -0400 Subject: [PATCH 08/10] #4244 Prettier --- src/backend/src/prisma/seed/event.process.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/src/prisma/seed/event.process.ts b/src/backend/src/prisma/seed/event.process.ts index e7f658112a..2be14f64d6 100644 --- a/src/backend/src/prisma/seed/event.process.ts +++ b/src/backend/src/prisma/seed/event.process.ts @@ -122,8 +122,6 @@ export class EventProcess extends SeedProcess> .map((user) => user.userId) : []; - const allMemberIds = [...requiredMemberIds, ...optionalMemberIds]; - const confirmedMemberIds = eventType.requiresConfirmation ? status === Event_Status.CONFIRMED ? [...requiredMemberIds, ...this.faker.helpers.arrayElements(optionalMemberIds)] From 6f58cbef8aea81a018097057f1f6167d97ac45f0 Mon Sep 17 00:00:00 2001 From: Santiordon Date: Mon, 20 Jul 2026 11:11:24 -0400 Subject: [PATCH 09/10] #4244 Prettier #2 --- src/backend/src/prisma/factories/event.factory.ts | 2 +- src/backend/src/prisma/seed/event.process.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/src/prisma/factories/event.factory.ts b/src/backend/src/prisma/factories/event.factory.ts index 152b632f72..ea980b5061 100644 --- a/src/backend/src/prisma/factories/event.factory.ts +++ b/src/backend/src/prisma/factories/event.factory.ts @@ -53,7 +53,7 @@ export const MEETING_ATTENDANCE_PROBABILITY = 0.4; const EVENT_DATE_BUFFER_DAYS = 7; const MAX_ATTENDEES = 10; export const DAYS_AFTER_NO_EVENT = 20; -const CONFIRMED_WINDOW = 14 +const CONFIRMED_WINDOW = 14; export const generateEventCount = (faker: Faker): number => faker.number.int({ min: 1, max: EVENTS_PER_PROJECT }); diff --git a/src/backend/src/prisma/seed/event.process.ts b/src/backend/src/prisma/seed/event.process.ts index 2be14f64d6..56366c1260 100644 --- a/src/backend/src/prisma/seed/event.process.ts +++ b/src/backend/src/prisma/seed/event.process.ts @@ -127,7 +127,7 @@ export class EventProcess extends SeedProcess> ? [...requiredMemberIds, ...this.faker.helpers.arrayElements(optionalMemberIds)] : [...this.faker.helpers.arrayElements(requiredMemberIds), ...this.faker.helpers.arrayElements(optionalMemberIds)] : []; - + // Unused. For reference if later reused. const deniedMemberIds: string[] = []; From fbd627ec3741bdb94da408abcd33f9344b68c020 Mon Sep 17 00:00:00 2001 From: Santiordon Date: Mon, 20 Jul 2026 22:17:16 -0400 Subject: [PATCH 10/10] #4244 Address Comments --- src/backend/src/prisma/factories/event.factory.ts | 2 ++ src/backend/src/prisma/seed/event.process.ts | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/backend/src/prisma/factories/event.factory.ts b/src/backend/src/prisma/factories/event.factory.ts index ea980b5061..fe3c8b8655 100644 --- a/src/backend/src/prisma/factories/event.factory.ts +++ b/src/backend/src/prisma/factories/event.factory.ts @@ -209,6 +209,7 @@ export const eventCreateInput = ( optionalMemberIds: string[], confirmedMemberIds: string[], deniedMemberIds: string[], + dateCreated: Date, approvalRequiredFromUserId?: string ): Prisma.EventCreateInput => ({ title, @@ -218,6 +219,7 @@ export const eventCreateInput = ( calendarEventIds: [], userCreated: { connect: { userId: userCreatedId } }, eventType: { connect: { eventTypeId } }, + dateCreated, requiredMembers: { connect: [ { userId: userCreatedId }, diff --git a/src/backend/src/prisma/seed/event.process.ts b/src/backend/src/prisma/seed/event.process.ts index 56366c1260..fab59f12e3 100644 --- a/src/backend/src/prisma/seed/event.process.ts +++ b/src/backend/src/prisma/seed/event.process.ts @@ -111,13 +111,18 @@ export class EventProcess extends SeedProcess> const dateCreated = generateEventDateCreated(this.faker, initialDateScheduled); const requiredMemberIds = eventType.requiredMembers - ? this.faker.helpers.arrayElements(allUsers, this.faker.number.int({ min: 1, max: 5 })).map((user) => user.userId) - : []; + ? [ + creator.userId, + ...this.faker.helpers + .arrayElements(allUsers, this.faker.number.int({ min: 0, max: 4 })) + .map((user) => user.userId) + ] + : [creator.userId]; const optionalMemberIds = eventType.optionalMembers ? this.faker.helpers .arrayElements( allUsers.filter((user) => !requiredMemberIds.includes(user.userId)), - this.faker.number.int({ min: 1, max: 5 }) + this.faker.number.int({ min: 0, max: 5 }) ) .map((user) => user.userId) : []; @@ -148,6 +153,7 @@ export class EventProcess extends SeedProcess> optionalMemberIds, confirmedMemberIds, deniedMemberIds, + dateCreated, approvalRequiredFromUserId ) });