diff options
Diffstat (limited to 'scripts/seed/generators')
| -rw-r--r-- | scripts/seed/generators/events.ts | 191 | ||||
| -rw-r--r-- | scripts/seed/generators/revenue.ts | 65 | ||||
| -rw-r--r-- | scripts/seed/generators/sessions.ts | 52 |
3 files changed, 308 insertions, 0 deletions
diff --git a/scripts/seed/generators/events.ts b/scripts/seed/generators/events.ts new file mode 100644 index 0000000..7242906 --- /dev/null +++ b/scripts/seed/generators/events.ts @@ -0,0 +1,191 @@ +import { uuid, addSeconds, randomInt } from '../utils.js'; +import { getRandomReferrer } from '../distributions/referrers.js'; +import type { SessionData } from './sessions.js'; + +export const EVENT_TYPE = { + pageView: 1, + customEvent: 2, +} as const; + +export interface PageConfig { + path: string; + title: string; + weight: number; + avgTimeOnPage: number; +} + +export interface CustomEventConfig { + name: string; + weight: number; + pages?: string[]; + data?: Record<string, string[] | number[]>; +} + +export interface JourneyConfig { + pages: string[]; + weight: number; +} + +export interface EventData { + id: string; + websiteId: string; + sessionId: string; + visitId: string; + eventType: number; + urlPath: string; + urlQuery: string | null; + pageTitle: string | null; + hostname: string; + referrerDomain: string | null; + referrerPath: string | null; + utmSource: string | null; + utmMedium: string | null; + utmCampaign: string | null; + utmContent: string | null; + utmTerm: string | null; + gclid: string | null; + fbclid: string | null; + eventName: string | null; + tag: string | null; + createdAt: Date; +} + +export interface EventDataEntry { + id: string; + websiteId: string; + websiteEventId: string; + dataKey: string; + stringValue: string | null; + numberValue: number | null; + dateValue: Date | null; + dataType: number; + createdAt: Date; +} + +export interface SiteConfig { + hostname: string; + pages: PageConfig[]; + journeys: JourneyConfig[]; + customEvents: CustomEventConfig[]; +} + +function getPageTitle(pages: PageConfig[], path: string): string | null { + const page = pages.find(p => p.path === path); + return page?.title ?? null; +} + +function getPageTimeOnPage(pages: PageConfig[], path: string): number { + const page = pages.find(p => p.path === path); + return page?.avgTimeOnPage ?? 30; +} + +export function generateEventsForSession( + session: SessionData, + siteConfig: SiteConfig, + journey: string[], +): { events: EventData[]; eventDataEntries: EventDataEntry[] } { + const events: EventData[] = []; + const eventDataEntries: EventDataEntry[] = []; + const visitId = uuid(); + + let currentTime = session.createdAt; + const referrer = getRandomReferrer(); + + for (let i = 0; i < journey.length; i++) { + const pagePath = journey[i]; + const isFirstPage = i === 0; + + const eventId = uuid(); + const pageTitle = getPageTitle(siteConfig.pages, pagePath); + + events.push({ + id: eventId, + websiteId: session.websiteId, + sessionId: session.id, + visitId, + eventType: EVENT_TYPE.pageView, + urlPath: pagePath, + urlQuery: null, + pageTitle, + hostname: siteConfig.hostname, + referrerDomain: isFirstPage ? referrer.domain : null, + referrerPath: isFirstPage ? referrer.path : null, + utmSource: isFirstPage ? referrer.utmSource : null, + utmMedium: isFirstPage ? referrer.utmMedium : null, + utmCampaign: isFirstPage ? referrer.utmCampaign : null, + utmContent: isFirstPage ? referrer.utmContent : null, + utmTerm: isFirstPage ? referrer.utmTerm : null, + gclid: isFirstPage ? referrer.gclid : null, + fbclid: isFirstPage ? referrer.fbclid : null, + eventName: null, + tag: null, + createdAt: currentTime, + }); + + // Check for custom events on this page + for (const customEvent of siteConfig.customEvents) { + // Check if this event can occur on this page + if (customEvent.pages && !customEvent.pages.includes(pagePath)) { + continue; + } + + // Random chance based on weight + if (Math.random() < customEvent.weight) { + currentTime = addSeconds(currentTime, randomInt(2, 15)); + + const customEventId = uuid(); + events.push({ + id: customEventId, + websiteId: session.websiteId, + sessionId: session.id, + visitId, + eventType: EVENT_TYPE.customEvent, + urlPath: pagePath, + urlQuery: null, + pageTitle, + hostname: siteConfig.hostname, + referrerDomain: null, + referrerPath: null, + utmSource: null, + utmMedium: null, + utmCampaign: null, + utmContent: null, + utmTerm: null, + gclid: null, + fbclid: null, + eventName: customEvent.name, + tag: null, + createdAt: currentTime, + }); + + // Generate event data if configured + if (customEvent.data) { + for (const [key, values] of Object.entries(customEvent.data)) { + const value = values[Math.floor(Math.random() * values.length)]; + const isNumber = typeof value === 'number'; + + eventDataEntries.push({ + id: uuid(), + websiteId: session.websiteId, + websiteEventId: customEventId, + dataKey: key, + stringValue: isNumber ? null : String(value), + numberValue: isNumber ? value : null, + dateValue: null, + dataType: isNumber ? 2 : 1, // 1 = string, 2 = number + createdAt: currentTime, + }); + } + } + } + } + + // Time spent on page before navigating + const timeOnPage = getPageTimeOnPage(siteConfig.pages, pagePath); + const variance = Math.floor(timeOnPage * 0.5); + const actualTime = timeOnPage + randomInt(-variance, variance); + currentTime = addSeconds(currentTime, Math.max(5, actualTime)); + } + + return { events, eventDataEntries }; +} diff --git a/scripts/seed/generators/revenue.ts b/scripts/seed/generators/revenue.ts new file mode 100644 index 0000000..deea9e6 --- /dev/null +++ b/scripts/seed/generators/revenue.ts @@ -0,0 +1,65 @@ +import { uuid, randomFloat } from '../utils.js'; +import type { EventData } from './events.js'; + +export interface RevenueConfig { + eventName: string; + minAmount: number; + maxAmount: number; + currency: string; + weight: number; +} + +export interface RevenueData { + id: string; + websiteId: string; + sessionId: string; + eventId: string; + eventName: string; + currency: string; + revenue: number; + createdAt: Date; +} + +export function generateRevenue(event: EventData, config: RevenueConfig): RevenueData | null { + if (event.eventName !== config.eventName) { + return null; + } + + if (Math.random() > config.weight) { + return null; + } + + const revenue = randomFloat(config.minAmount, config.maxAmount); + + return { + id: uuid(), + websiteId: event.websiteId, + sessionId: event.sessionId, + eventId: event.id, + eventName: event.eventName!, + currency: config.currency, + revenue: Math.round(revenue * 100) / 100, // Round to 2 decimal places + createdAt: event.createdAt, + }; +} + +export function generateRevenueForEvents( + events: EventData[], + configs: RevenueConfig[], +): RevenueData[] { + const revenueEntries: RevenueData[] = []; + + for (const event of events) { + if (!event.eventName) continue; + + for (const config of configs) { + const revenue = generateRevenue(event, config); + if (revenue) { + revenueEntries.push(revenue); + break; // Only one revenue per event + } + } + } + + return revenueEntries; +} diff --git a/scripts/seed/generators/sessions.ts b/scripts/seed/generators/sessions.ts new file mode 100644 index 0000000..1370511 --- /dev/null +++ b/scripts/seed/generators/sessions.ts @@ -0,0 +1,52 @@ +import { uuid } from '../utils.js'; +import { getRandomDevice } from '../distributions/devices.js'; +import { getRandomGeo, getRandomLanguage } from '../distributions/geographic.js'; +import { generateTimestampForDay } from '../distributions/temporal.js'; + +export interface SessionData { + id: string; + websiteId: string; + browser: string; + os: string; + device: string; + screen: string; + language: string; + country: string; + region: string; + city: string; + createdAt: Date; +} + +export function createSession(websiteId: string, day: Date): SessionData { + const deviceInfo = getRandomDevice(); + const geo = getRandomGeo(); + const language = getRandomLanguage(); + const createdAt = generateTimestampForDay(day); + + return { + id: uuid(), + websiteId, + browser: deviceInfo.browser, + os: deviceInfo.os, + device: deviceInfo.device, + screen: deviceInfo.screen, + language, + country: geo.country, + region: geo.region, + city: geo.city, + createdAt, + }; +} + +export function createSessions(websiteId: string, day: Date, count: number): SessionData[] { + const sessions: SessionData[] = []; + + for (let i = 0; i < count; i++) { + sessions.push(createSession(websiteId, day)); + } + + // Sort by createdAt to maintain chronological order + sessions.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); + + return sessions; +} |