@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
JavaScript
// 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
};