import type { Customer, SegmentSummary, SegmentName } from "./types.ts"; import { SEGMENT_COLORS, SEGMENTS } from "./types.ts"; // Company name generation const PREFIXES = [ "Apex", "Nova", "Prime", "Vertex", "Atlas", "Quantum", "Summit", "Nexus", "Titan", "Pinnacle", "Zenith", "Vanguard", "Horizon", "Stellar", "Onyx", "Cobalt", "Vector", "Pulse", "Forge", "Spark", ]; const CORES = [ "Tech", "Data", "Cloud", "Logic", "Sync", "Flow", "Core", "Net", "Soft", "Wave", "Link", "Mind", "Byte", "Grid", "Hub", ]; const SUFFIXES = [ "Corp", "Inc", "Solutions", "Systems", "Labs", "Group", "Industries", "Dynamics", "Partners", "Ventures", "Global", "Digital", ]; // Cluster centers for each segment interface ClusterCenter { annualRevenue: { min: number; max: number }; employeeCount: { min: number; max: number }; accountAge: { min: number; max: number }; engagementScore: { min: number; max: number }; supportTickets: { min: number; max: number }; nps: { min: number; max: number }; } const CLUSTER_CENTERS: Record = { Enterprise: { annualRevenue: { min: 2_000_000, max: 10_000_000 }, employeeCount: { min: 500, max: 5000 }, accountAge: { min: 60, max: 120 }, engagementScore: { min: 70, max: 95 }, supportTickets: { min: 5, max: 20 }, nps: { min: 40, max: 80 }, }, "Mid-Market": { annualRevenue: { min: 500_000, max: 2_000_000 }, employeeCount: { min: 100, max: 500 }, accountAge: { min: 36, max: 84 }, engagementScore: { min: 60, max: 85 }, supportTickets: { min: 10, max: 30 }, nps: { min: 20, max: 60 }, }, SMB: { annualRevenue: { min: 50_000, max: 500_000 }, employeeCount: { min: 10, max: 100 }, accountAge: { min: 12, max: 48 }, engagementScore: { min: 40, max: 70 }, supportTickets: { min: 15, max: 40 }, nps: { min: 0, max: 40 }, }, Startup: { annualRevenue: { min: 10_000, max: 200_000 }, employeeCount: { min: 1, max: 50 }, accountAge: { min: 1, max: 24 }, engagementScore: { min: 50, max: 90 }, supportTickets: { min: 5, max: 25 }, nps: { min: 10, max: 70 }, }, }; // Segment distribution weights const SEGMENT_WEIGHTS: Record = { Enterprise: 0.15, "Mid-Market": 0.25, SMB: 0.35, Startup: 0.25, }; // Box-Muller transform for Gaussian random numbers function gaussianRandom(mean: number, stdDev: number): number { const u1 = Math.random(); const u2 = Math.random(); const z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2); return z0 * stdDev + mean; } // Generate a value within range with Gaussian distribution centered in range function generateClusteredValue(min: number, max: number): number { const mean = (min + max) / 2; const stdDev = (max - min) / 4; // 95% of values within range const value = gaussianRandom(mean, stdDev); return Math.max(min * 0.8, Math.min(max * 1.2, value)); // Allow slight overflow } // Generate unique company name function generateCompanyName(usedNames: Set): string { let attempts = 0; while (attempts < 100) { const prefix = PREFIXES[Math.floor(Math.random() * PREFIXES.length)]; const core = CORES[Math.floor(Math.random() * CORES.length)]; const suffix = SUFFIXES[Math.floor(Math.random() * SUFFIXES.length)]; const name = `${prefix} ${core} ${suffix}`; if (!usedNames.has(name)) { usedNames.add(name); return name; } attempts++; } // Fallback: add a number const prefix = PREFIXES[Math.floor(Math.random() * PREFIXES.length)]; const core = CORES[Math.floor(Math.random() * CORES.length)]; const num = Math.floor(Math.random() * 1000); return `${prefix} ${core} ${num}`; } // Select segment based on weights function selectSegment(): SegmentName { const rand = Math.random(); let cumulative = 0; for (const segment of SEGMENTS) { cumulative += SEGMENT_WEIGHTS[segment]; if (rand < cumulative) { return segment; } } return "SMB"; } // Generate a single customer function generateCustomer(id: number, usedNames: Set): Customer { const segment = selectSegment(); const center = CLUSTER_CENTERS[segment]; return { id: `cust-${id.toString().padStart(4, "0")}`, name: generateCompanyName(usedNames), segment, annualRevenue: Math.round( generateClusteredValue( center.annualRevenue.min, center.annualRevenue.max, ), ), employeeCount: Math.round( generateClusteredValue( center.employeeCount.min, center.employeeCount.max, ), ), accountAge: Math.round( generateClusteredValue(center.accountAge.min, center.accountAge.max), ), engagementScore: Math.round( generateClusteredValue( center.engagementScore.min, center.engagementScore.max, ), ), supportTickets: Math.round( generateClusteredValue( center.supportTickets.min, center.supportTickets.max, ), ), nps: Math.round(generateClusteredValue(center.nps.min, center.nps.max)), }; } // Generate all customers export function generateCustomers(count: number = 250): Customer[] { const usedNames = new Set(); const customers: Customer[] = []; for (let i = 0; i < count; i++) { customers.push(generateCustomer(i + 1, usedNames)); } return customers; } // Generate segment summaries from customers export function generateSegmentSummaries( customers: Customer[], ): SegmentSummary[] { const counts = new Map(); for (const customer of customers) { counts.set(customer.segment, (counts.get(customer.segment) || 0) + 1); } return SEGMENTS.map((segment) => ({ name: segment, count: counts.get(segment) || 0, color: SEGMENT_COLORS[segment], })); }