mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:35:24 +02:00
feat: enable custom baseline periods for Experiments to solve seasonality
This commit is contained in:
@@ -118,13 +118,14 @@ function splitPeriods(
|
||||
data: ComputedWeeklyMetrics[],
|
||||
startTs: number,
|
||||
endTs: number,
|
||||
beforeStartTs: number
|
||||
beforeStartTs: number,
|
||||
beforeEndTs: number // Added to support separated custom periods
|
||||
): { before: ComputedWeeklyMetrics[]; after: ComputedWeeklyMetrics[] } {
|
||||
const before: ComputedWeeklyMetrics[] = [];
|
||||
const after: ComputedWeeklyMetrics[] = [];
|
||||
|
||||
for (const w of data) {
|
||||
if (w.timestamp >= beforeStartTs && w.timestamp < startTs) {
|
||||
if (w.timestamp >= beforeStartTs && w.timestamp < beforeEndTs) {
|
||||
before.push(w);
|
||||
} else if (w.timestamp >= startTs && w.timestamp <= endTs) {
|
||||
after.push(w);
|
||||
@@ -216,8 +217,21 @@ export function computeDiD(
|
||||
): DifferenceInDifferencesResult {
|
||||
const startDate = parseLocalDate(experiment.start_date);
|
||||
const endDate = experiment.end_date ? parseLocalDate(experiment.end_date) : new Date();
|
||||
const durationMs = endDate.getTime() - startDate.getTime();
|
||||
const beforeStart = new Date(startDate.getTime() - durationMs);
|
||||
|
||||
// Seasonal Baseline override logic
|
||||
let beforeStart: Date;
|
||||
let beforeEnd = startDate; // By default, before period goes up to the exact start date
|
||||
|
||||
if (experiment.baseline_start_date && experiment.baseline_end_date) {
|
||||
beforeStart = parseLocalDate(experiment.baseline_start_date);
|
||||
beforeEnd = parseLocalDate(experiment.baseline_end_date);
|
||||
// Explicit end + 1 day so `< beforeEnd` logic includes the last day
|
||||
beforeEnd = new Date(beforeEnd.getTime() + 86400000);
|
||||
} else {
|
||||
// Legacy auto-deduction (shifts the exact timeframe to the left)
|
||||
const durationMs = endDate.getTime() - startDate.getTime();
|
||||
beforeStart = new Date(startDate.getTime() - durationMs);
|
||||
}
|
||||
|
||||
const treatmentAsinSet = getExperimentAsins(experiment.asins || [], salesData);
|
||||
const treatmentWeekly = aggregateWeeklyMetrics(treatmentAsinSet, salesData, experiment.marketplace);
|
||||
@@ -232,10 +246,11 @@ export function computeDiD(
|
||||
const startTs = startDate.getTime();
|
||||
const endTs = endDate.getTime();
|
||||
const beforeStartTs = beforeStart.getTime();
|
||||
const beforeEndTs = beforeEnd.getTime();
|
||||
|
||||
const tSplit = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs);
|
||||
const tSplit = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs, beforeEndTs);
|
||||
const cSplit = hasControlGroup
|
||||
? splitPeriods(controlWeekly, startTs, endTs, beforeStartTs)
|
||||
? splitPeriods(controlWeekly, startTs, endTs, beforeStartTs, beforeEndTs)
|
||||
: { before: [] as ComputedWeeklyMetrics[], after: [] as ComputedWeeklyMetrics[] };
|
||||
|
||||
const metrics: Record<string, DiDMetricResult> = {};
|
||||
@@ -285,8 +300,19 @@ export function buildCounterfactualSeries(
|
||||
): TrendDataPoint[] {
|
||||
const startDate = parseLocalDate(experiment.start_date);
|
||||
const endDate = experiment.end_date ? parseLocalDate(experiment.end_date) : new Date();
|
||||
const durationMs = endDate.getTime() - startDate.getTime();
|
||||
const beforeStart = new Date(startDate.getTime() - durationMs);
|
||||
|
||||
// Seasonal Baseline override logic
|
||||
let beforeStart: Date;
|
||||
let beforeEnd = startDate;
|
||||
|
||||
if (experiment.baseline_start_date && experiment.baseline_end_date) {
|
||||
beforeStart = parseLocalDate(experiment.baseline_start_date);
|
||||
beforeEnd = parseLocalDate(experiment.baseline_end_date);
|
||||
beforeEnd = new Date(beforeEnd.getTime() + 86400000);
|
||||
} else {
|
||||
const durationMs = endDate.getTime() - startDate.getTime();
|
||||
beforeStart = new Date(startDate.getTime() - durationMs);
|
||||
}
|
||||
|
||||
const treatmentAsinSet = getExperimentAsins(experiment.asins || [], salesData);
|
||||
const treatmentWeekly = aggregateWeeklyMetrics(treatmentAsinSet, salesData, experiment.marketplace);
|
||||
@@ -297,7 +323,11 @@ export function buildCounterfactualSeries(
|
||||
// Without control group, counterfactual = flat line at pre-treatment average
|
||||
const startTs = startDate.getTime();
|
||||
const beforeStartTs = beforeStart.getTime();
|
||||
const beforeData = treatmentWeekly.filter(w => w.timestamp >= beforeStartTs && w.timestamp < startTs);
|
||||
const beforeEndTs = beforeEnd.getTime();
|
||||
|
||||
// To calculate counterfactual, we need the "before" average.
|
||||
// Treatment before period:
|
||||
const beforeData = treatmentWeekly.filter(w => w.timestamp >= beforeStartTs && w.timestamp < beforeEndTs);
|
||||
const preAvg = avgMetric(beforeData, metric);
|
||||
|
||||
return treatmentWeekly.map(w => ({
|
||||
@@ -313,9 +343,10 @@ export function buildCounterfactualSeries(
|
||||
|
||||
const startTs = startDate.getTime();
|
||||
const beforeStartTs = beforeStart.getTime();
|
||||
const beforeEndTs = beforeEnd.getTime();
|
||||
|
||||
const tBeforeData = treatmentWeekly.filter(w => w.timestamp >= beforeStartTs && w.timestamp < startTs);
|
||||
const cBeforeData = controlWeekly.filter(w => w.timestamp >= beforeStartTs && w.timestamp < startTs);
|
||||
const tBeforeData = treatmentWeekly.filter(w => w.timestamp >= beforeStartTs && w.timestamp < beforeEndTs);
|
||||
const cBeforeData = controlWeekly.filter(w => w.timestamp >= beforeStartTs && w.timestamp < beforeEndTs);
|
||||
|
||||
const tPreAvg = avgMetric(tBeforeData, metric);
|
||||
const cPreAvg = avgMetric(cBeforeData, metric);
|
||||
|
||||
Reference in New Issue
Block a user