mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:35:24 +02:00
feat: complete experiments tab analytics optimization with modular kpis and sparklines
This commit is contained in:
+49
-19
@@ -6,7 +6,7 @@ import {
|
||||
ExperimentType,
|
||||
ExperimentStatus,
|
||||
} from '../types';
|
||||
import { SalesRecord } from '../types';
|
||||
import { CombinedKPIs } from '../types';
|
||||
|
||||
const API_BASE = '/api/experiments';
|
||||
|
||||
@@ -109,7 +109,7 @@ export const listExperiments = async (
|
||||
const today = new Date();
|
||||
const startDate = new Date(exp.start_date);
|
||||
const endDate = exp.end_date ? new Date(exp.end_date) : null;
|
||||
|
||||
|
||||
let progressPercent = 0;
|
||||
if (endDate) {
|
||||
const totalDays = endDate.getTime() - startDate.getTime();
|
||||
@@ -123,10 +123,12 @@ export const listExperiments = async (
|
||||
type: exp.type,
|
||||
status: exp.status,
|
||||
asin_count: exp.asins?.length || 0,
|
||||
asins: exp.asins || [],
|
||||
marketplace: exp.marketplace,
|
||||
start_date: exp.start_date,
|
||||
end_date: exp.end_date,
|
||||
progress_percent: Math.round(progressPercent),
|
||||
primary_metric: exp.primary_metric,
|
||||
actual_lift_percent: exp.actual_lift_percent,
|
||||
owner: exp.owner,
|
||||
};
|
||||
@@ -138,7 +140,7 @@ export const getActiveExperimentsForASINs = async (
|
||||
): Promise<Map<string, ActiveExperiment[]>> => {
|
||||
const params = new URLSearchParams();
|
||||
asins.forEach(asin => params.append('asin', asin));
|
||||
|
||||
|
||||
const response = await fetch(`${API_BASE}?${params.toString()}&status=active`);
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -148,10 +150,10 @@ export const getActiveExperimentsForASINs = async (
|
||||
|
||||
const data = await response.json();
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
|
||||
// Filter experiments that are currently active
|
||||
const activeExperiments = data.filter((exp: any) =>
|
||||
exp.status === 'active' &&
|
||||
const activeExperiments = data.filter((exp: any) =>
|
||||
exp.status === 'active' &&
|
||||
exp.start_date <= today &&
|
||||
(!exp.end_date || exp.end_date >= today)
|
||||
);
|
||||
@@ -191,17 +193,23 @@ export const getActiveExperimentsForASINs = async (
|
||||
|
||||
export const calculateExperimentPerformance = async (
|
||||
experiment: Experiment,
|
||||
salesData: SalesRecord[]
|
||||
salesData: CombinedKPIs[]
|
||||
): Promise<{
|
||||
baseline_units: number;
|
||||
baseline_revenue: number;
|
||||
baseline_gv: number;
|
||||
baseline_cvr: number;
|
||||
baseline_acos: number;
|
||||
experiment_units: number;
|
||||
experiment_revenue: number;
|
||||
experiment_gv: number;
|
||||
experiment_cvr: number;
|
||||
experiment_acos: number;
|
||||
actual_lift_percent: number;
|
||||
}> => {
|
||||
const startDate = new Date(experiment.start_date);
|
||||
const endDate = experiment.end_date ? new Date(experiment.end_date) : new Date();
|
||||
|
||||
|
||||
// Calculate baseline period (same duration before experiment)
|
||||
const durationMs = endDate.getTime() - startDate.getTime();
|
||||
const baselineStart = new Date(startDate.getTime() - durationMs);
|
||||
@@ -209,40 +217,62 @@ export const calculateExperimentPerformance = async (
|
||||
|
||||
// Filter sales data for experiment ASINs
|
||||
const asinSet = new Set(experiment.asins.map(a => a.toUpperCase()));
|
||||
|
||||
|
||||
const baselineData = salesData.filter(r => {
|
||||
const recordDate = new Date(`${r.year}-${getMonthNumber(r.month)}-01`);
|
||||
const recordDate = new Date(r.year, 0, 1 + (r.week - 1) * 7);
|
||||
return asinSet.has(r.asin.toUpperCase()) &&
|
||||
recordDate >= baselineStart && recordDate < baselineEnd;
|
||||
});
|
||||
|
||||
const experimentData = salesData.filter(r => {
|
||||
const recordDate = new Date(`${r.year}-${getMonthNumber(r.month)}-01`);
|
||||
const recordDate = new Date(r.year, 0, 1 + (r.week - 1) * 7);
|
||||
return asinSet.has(r.asin.toUpperCase()) &&
|
||||
recordDate >= startDate && recordDate <= endDate;
|
||||
});
|
||||
|
||||
const baseline_units = baselineData.reduce((sum, r) => sum + r.units, 0);
|
||||
const baseline_revenue = baselineData.reduce((sum, r) => sum + r.sellOut, 0);
|
||||
const experiment_units = experimentData.reduce((sum, r) => sum + r.units, 0);
|
||||
const experiment_revenue = experimentData.reduce((sum, r) => sum + r.sellOut, 0);
|
||||
const baseline_units = baselineData.reduce((sum, r) => sum + r.unitsTotal, 0);
|
||||
const baseline_revenue = baselineData.reduce((sum, r) => sum + r.salesTotal, 0);
|
||||
const baseline_gv = baselineData.reduce((sum, r) => sum + (r.glanceViews || 0), 0);
|
||||
const baseline_cvr = baseline_gv > 0 ? (baseline_units / baseline_gv) * 100 : 0;
|
||||
const baseline_spend = baselineData.reduce((sum, r) => sum + (r.cost || 0), 0);
|
||||
const baseline_acos = baseline_revenue > 0 ? (baseline_spend / baseline_revenue) * 100 : 0;
|
||||
|
||||
const actual_lift_percent = baseline_units > 0
|
||||
? ((experiment_units - baseline_units) / baseline_units) * 100
|
||||
: 0;
|
||||
const experiment_units = experimentData.reduce((sum, r) => sum + r.unitsTotal, 0);
|
||||
const experiment_revenue = experimentData.reduce((sum, r) => sum + r.salesTotal, 0);
|
||||
const experiment_gv = experimentData.reduce((sum, r) => sum + (r.glanceViews || 0), 0);
|
||||
const experiment_cvr = experiment_gv > 0 ? (experiment_units / experiment_gv) * 100 : 0;
|
||||
const experiment_spend = experimentData.reduce((sum, r) => sum + (r.cost || 0), 0);
|
||||
const experiment_acos = experiment_revenue > 0 ? (experiment_spend / experiment_revenue) * 100 : 0;
|
||||
|
||||
let actual_lift_percent = 0;
|
||||
if (experiment.primary_metric === 'cvr' && baseline_cvr > 0) {
|
||||
actual_lift_percent = ((experiment_cvr - baseline_cvr) / baseline_cvr) * 100;
|
||||
} else if (experiment.primary_metric === 'revenue' && baseline_revenue > 0) {
|
||||
actual_lift_percent = ((experiment_revenue - baseline_revenue) / baseline_revenue) * 100;
|
||||
} else if (experiment.primary_metric === 'acos' && baseline_acos > 0) {
|
||||
actual_lift_percent = ((baseline_acos - experiment_acos) / Math.max(0.1, baseline_acos)) * 100; // inverted, lower is better
|
||||
} else if (baseline_units > 0) {
|
||||
actual_lift_percent = ((experiment_units - baseline_units) / baseline_units) * 100;
|
||||
}
|
||||
|
||||
return {
|
||||
baseline_units,
|
||||
baseline_revenue,
|
||||
baseline_gv,
|
||||
baseline_cvr: Math.round(baseline_cvr * 10) / 10,
|
||||
baseline_acos: Math.round(baseline_acos * 10) / 10,
|
||||
experiment_units,
|
||||
experiment_revenue,
|
||||
experiment_gv,
|
||||
experiment_cvr: Math.round(experiment_cvr * 10) / 10,
|
||||
experiment_acos: Math.round(experiment_acos * 10) / 10,
|
||||
actual_lift_percent: Math.round(actual_lift_percent * 10) / 10,
|
||||
};
|
||||
};
|
||||
|
||||
export const updateExperimentResults = async (
|
||||
experimentId: string,
|
||||
salesData: SalesRecord[]
|
||||
salesData: CombinedKPIs[]
|
||||
): Promise<Experiment> => {
|
||||
const experiment = await getExperiment(experimentId);
|
||||
if (!experiment) throw new Error('Experiment not found');
|
||||
|
||||
Reference in New Issue
Block a user