UNPKG

@akson/cortex-landing-analytics

Version:

Enhanced analytics for landing pages with lead scoring, multi-channel conversion tracking, and A/B testing support

165 lines (163 loc) 5.03 kB
// src/ab-testing/index.ts var ABTesting = class { constructor() { this.experiments = /* @__PURE__ */ new Map(); this.assignments = /* @__PURE__ */ new Map(); } /** * Create a new A/B test experiment */ createExperiment(experiment) { const newExperiment = { ...experiment, status: "draft" }; const totalWeight = experiment.variants.reduce((sum, v) => sum + v.weight, 0); if (Math.abs(totalWeight - 100) > 0.01) { throw new Error("Variant weights must sum to 100"); } this.experiments.set(experiment.id, newExperiment); this.assignments.set(experiment.id, []); return newExperiment; } /** * Get variant assignment for a user/session */ getVariant(experimentId, sessionId, userId) { const experiment = this.experiments.get(experimentId); if (!experiment || experiment.status !== "running") { return null; } const assignments = this.assignments.get(experimentId) || []; const existingAssignment = assignments.find( (a) => a.sessionId === sessionId || userId && a.userId === userId ); if (existingAssignment) { const variant2 = experiment.variants.find((v) => v.id === existingAssignment.variantId); return variant2 ? { variant: variant2, isNewAssignment: false } : null; } if (!this.isEligible(experiment, sessionId, userId)) { return null; } const variant = this.assignVariant(experiment, sessionId); if (!variant) return null; const assignment = { sessionId, userId, experimentId, variantId: variant.id, timestamp: /* @__PURE__ */ new Date() }; assignments.push(assignment); this.assignments.set(experimentId, assignments); return { variant, isNewAssignment: true }; } /** * Track experiment event/conversion */ trackEvent(experimentId, sessionId, event, value, metadata) { const assignments = this.assignments.get(experimentId) || []; const assignment = assignments.find((a) => a.sessionId === sessionId); if (!assignment) { return false; } console.log("AB Test Event:", { experiment_id: experimentId, variant_id: assignment.variantId, session_id: sessionId, user_id: assignment.userId, event, value, metadata, timestamp: (/* @__PURE__ */ new Date()).toISOString() }); return true; } /** * Get experiment results and statistics */ getResults(experimentId) { const experiment = this.experiments.get(experimentId); const assignments = this.assignments.get(experimentId) || []; if (!experiment) return null; const variantStats = experiment.variants.map((variant) => { const variantAssignments = assignments.filter((a) => a.variantId === variant.id); return { variant_id: variant.id, variant_name: variant.name, is_control: variant.isControl || false, assignments: variantAssignments.length // In real implementation, would calculate conversion rates, statistical significance, etc. }; }); return { experiment_id: experimentId, experiment_name: experiment.name, status: experiment.status, total_assignments: assignments.length, variant_stats: variantStats, start_date: experiment.startDate?.toISOString(), end_date: experiment.endDate?.toISOString() }; } /** * Start an experiment */ startExperiment(experimentId) { const experiment = this.experiments.get(experimentId); if (!experiment || experiment.status !== "draft") { return false; } experiment.status = "running"; experiment.startDate = /* @__PURE__ */ new Date(); return true; } /** * Stop an experiment */ stopExperiment(experimentId) { const experiment = this.experiments.get(experimentId); if (!experiment || experiment.status !== "running") { return false; } experiment.status = "completed"; experiment.endDate = /* @__PURE__ */ new Date(); return true; } isEligible(experiment, sessionId, userId) { if (experiment.targetAudience?.percentage) { const hash = this.hashString(userId || sessionId); const percentage = hash % 100 + 1; if (percentage > experiment.targetAudience.percentage) { return false; } } if (experiment.targetAudience?.conditions) { } return true; } assignVariant(experiment, sessionId) { const hash = this.hashString(sessionId + experiment.id); const randomValue = hash % 100 + 1; let cumulativeWeight = 0; for (const variant of experiment.variants) { cumulativeWeight += variant.weight; if (randomValue <= cumulativeWeight) { return variant; } } return null; } hashString(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return Math.abs(hash); } }; export { ABTesting };