fix(experiments): include full 24h of end_date in DiD overlap bounds

This commit is contained in:
Christian Vidal Wolf
2026-02-25 13:10:00 +01:00
parent be829c938e
commit 6e94a32a47
+23 -39
View File
@@ -264,21 +264,21 @@ export function computeDiD(
salesData: CombinedKPIs[]
): DifferenceInDifferencesResult {
const startDate = parseLocalDate(experiment.start_date);
const endDate = experiment.end_date ? parseLocalDate(experiment.end_date) : new Date();
const endDate = experiment.end_date ? parseLocalDate(experiment.end_date) : new Date(new Date().setHours(0, 0, 0, 0));
// Seasonal Baseline override logic
let beforeStart: Date;
let beforeEnd = startDate; // By default, before period goes up to the exact start date
const startTs = startDate.getTime();
const endTs = endDate.getTime() + 86400000; // Add 24h so end bound is midnight of next day
let beforeStartTs: number;
let beforeEndTs: number;
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);
beforeStartTs = parseLocalDate(experiment.baseline_start_date).getTime();
beforeEndTs = parseLocalDate(experiment.baseline_end_date).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 durationMs = endTs - startTs;
beforeEndTs = startTs; // Adjacent: baseline ends right at the moment experiment starts
beforeStartTs = beforeEndTs - durationMs;
}
const treatmentAsinSet = getExperimentAsins(experiment.asins || [], salesData);
@@ -291,11 +291,6 @@ export function computeDiD(
controlWeekly = aggregateWeeklyMetrics(controlAsinSet, salesData, experiment.marketplace);
}
const startTs = startDate.getTime();
const endTs = endDate.getTime();
const beforeStartTs = beforeStart.getTime();
const beforeEndTs = beforeEnd.getTime();
const tSplit = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs, beforeEndTs);
const cSplit = hasControlGroup
? splitPeriods(controlWeekly, startTs, endTs, beforeStartTs, beforeEndTs)
@@ -347,19 +342,21 @@ export function buildCounterfactualSeries(
metric: string
): TrendDataPoint[] {
const startDate = parseLocalDate(experiment.start_date);
const endDate = experiment.end_date ? parseLocalDate(experiment.end_date) : new Date();
const endDate = experiment.end_date ? parseLocalDate(experiment.end_date) : new Date(new Date().setHours(0, 0, 0, 0));
// Seasonal Baseline override logic
let beforeStart: Date;
let beforeEnd = startDate;
const startTs = startDate.getTime();
const endTs = endDate.getTime() + 86400000; // Add 24h
let beforeStartTs: number;
let beforeEndTs: number;
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);
beforeStartTs = parseLocalDate(experiment.baseline_start_date).getTime();
beforeEndTs = parseLocalDate(experiment.baseline_end_date).getTime() + 86400000;
} else {
const durationMs = endDate.getTime() - startDate.getTime();
beforeStart = new Date(startDate.getTime() - durationMs);
const durationMs = endTs - startTs;
beforeEndTs = startTs;
beforeStartTs = beforeEndTs - durationMs;
}
const treatmentAsinSet = getExperimentAsins(experiment.asins || [], salesData);
@@ -368,15 +365,7 @@ export function buildCounterfactualSeries(
const hasControlGroup = (experiment.control_asins || []).length > 0;
if (!hasControlGroup) {
// Without control group, counterfactual = flat line at pre-treatment average
const startTs = startDate.getTime();
const endTs = endDate.getTime();
const beforeStartTs = beforeStart.getTime();
const beforeEndTs = beforeEnd.getTime();
// To calculate counterfactual, we need the "before" average.
// Treatment before period:
const { before: beforeData } = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs, beforeEndTs); // Use splitPeriods to match exactly
const { before: beforeData } = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs, beforeEndTs);
const preAvg = avgMetric(beforeData, metric);
return treatmentWeekly.map(w => ({
@@ -390,11 +379,6 @@ export function buildCounterfactualSeries(
const controlAsinSet = getExperimentAsins(experiment.control_asins, salesData);
const controlWeekly = aggregateWeeklyMetrics(controlAsinSet, salesData, experiment.marketplace);
const startTs = startDate.getTime();
const endTs = endDate.getTime();
const beforeStartTs = beforeStart.getTime();
const beforeEndTs = beforeEnd.getTime();
const { before: tBeforeData } = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs, beforeEndTs);
const { before: cBeforeData } = splitPeriods(controlWeekly, startTs, endTs, beforeStartTs, beforeEndTs);