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[] salesData: CombinedKPIs[]
): DifferenceInDifferencesResult { ): DifferenceInDifferencesResult {
const startDate = parseLocalDate(experiment.start_date); 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 const startTs = startDate.getTime();
let beforeStart: Date; const endTs = endDate.getTime() + 86400000; // Add 24h so end bound is midnight of next day
let beforeEnd = startDate; // By default, before period goes up to the exact start date
let beforeStartTs: number;
let beforeEndTs: number;
if (experiment.baseline_start_date && experiment.baseline_end_date) { if (experiment.baseline_start_date && experiment.baseline_end_date) {
beforeStart = parseLocalDate(experiment.baseline_start_date); beforeStartTs = parseLocalDate(experiment.baseline_start_date).getTime();
beforeEnd = parseLocalDate(experiment.baseline_end_date); beforeEndTs = parseLocalDate(experiment.baseline_end_date).getTime() + 86400000;
// Explicit end + 1 day so `< beforeEnd` logic includes the last day
beforeEnd = new Date(beforeEnd.getTime() + 86400000);
} else { } else {
// Legacy auto-deduction (shifts the exact timeframe to the left) const durationMs = endTs - startTs;
const durationMs = endDate.getTime() - startDate.getTime(); beforeEndTs = startTs; // Adjacent: baseline ends right at the moment experiment starts
beforeStart = new Date(startDate.getTime() - durationMs); beforeStartTs = beforeEndTs - durationMs;
} }
const treatmentAsinSet = getExperimentAsins(experiment.asins || [], salesData); const treatmentAsinSet = getExperimentAsins(experiment.asins || [], salesData);
@@ -291,11 +291,6 @@ export function computeDiD(
controlWeekly = aggregateWeeklyMetrics(controlAsinSet, salesData, experiment.marketplace); 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 tSplit = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs, beforeEndTs);
const cSplit = hasControlGroup const cSplit = hasControlGroup
? splitPeriods(controlWeekly, startTs, endTs, beforeStartTs, beforeEndTs) ? splitPeriods(controlWeekly, startTs, endTs, beforeStartTs, beforeEndTs)
@@ -347,19 +342,21 @@ export function buildCounterfactualSeries(
metric: string metric: string
): TrendDataPoint[] { ): TrendDataPoint[] {
const startDate = parseLocalDate(experiment.start_date); 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 const startTs = startDate.getTime();
let beforeStart: Date; const endTs = endDate.getTime() + 86400000; // Add 24h
let beforeEnd = startDate;
let beforeStartTs: number;
let beforeEndTs: number;
if (experiment.baseline_start_date && experiment.baseline_end_date) { if (experiment.baseline_start_date && experiment.baseline_end_date) {
beforeStart = parseLocalDate(experiment.baseline_start_date); beforeStartTs = parseLocalDate(experiment.baseline_start_date).getTime();
beforeEnd = parseLocalDate(experiment.baseline_end_date); beforeEndTs = parseLocalDate(experiment.baseline_end_date).getTime() + 86400000;
beforeEnd = new Date(beforeEnd.getTime() + 86400000);
} else { } else {
const durationMs = endDate.getTime() - startDate.getTime(); const durationMs = endTs - startTs;
beforeStart = new Date(startDate.getTime() - durationMs); beforeEndTs = startTs;
beforeStartTs = beforeEndTs - durationMs;
} }
const treatmentAsinSet = getExperimentAsins(experiment.asins || [], salesData); const treatmentAsinSet = getExperimentAsins(experiment.asins || [], salesData);
@@ -368,15 +365,7 @@ export function buildCounterfactualSeries(
const hasControlGroup = (experiment.control_asins || []).length > 0; const hasControlGroup = (experiment.control_asins || []).length > 0;
if (!hasControlGroup) { if (!hasControlGroup) {
// Without control group, counterfactual = flat line at pre-treatment average const { before: beforeData } = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs, beforeEndTs);
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 preAvg = avgMetric(beforeData, metric); const preAvg = avgMetric(beforeData, metric);
return treatmentWeekly.map(w => ({ return treatmentWeekly.map(w => ({
@@ -390,11 +379,6 @@ export function buildCounterfactualSeries(
const controlAsinSet = getExperimentAsins(experiment.control_asins, salesData); const controlAsinSet = getExperimentAsins(experiment.control_asins, salesData);
const controlWeekly = aggregateWeeklyMetrics(controlAsinSet, salesData, experiment.marketplace); 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: tBeforeData } = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs, beforeEndTs);
const { before: cBeforeData } = splitPeriods(controlWeekly, startTs, endTs, beforeStartTs, beforeEndTs); const { before: cBeforeData } = splitPeriods(controlWeekly, startTs, endTs, beforeStartTs, beforeEndTs);