fix(growth): harden deterministic report evidence

This commit is contained in:
2026-08-21 00:32:21 +03:30
parent 1d8d4edf92
commit 6c80f71847
11 changed files with 369 additions and 103 deletions

View File

@@ -12,26 +12,46 @@ func AnalyzeEventEffect(history []DailyPoint, event Event, catalog []Event) Even
byDate := pointsByDate(history)
eventDays := daysInclusive(event.StartDate, event.EndDate)
var baselineSessions, baselinePaid, baselineAmount int64
actualComplete := true
for _, eventDay := range eventDays {
if point, ok := byDate[dayKey(eventDay)]; ok {
effect.ActualSessionCount += point.SessionCount
effect.ActualPaidSessions += point.PaidSessionCount
effect.ActualAmountRial += point.PaidAmountRial
point, ok := byDate[dayKey(eventDay)]
if !ok {
actualComplete = false
break
}
for week := 1; week <= 8; week++ {
effect.ActualSessionCount += point.SessionCount
effect.ActualPaidSessions += point.PaidSessionCount
effect.ActualAmountRial += point.PaidAmountRial
}
if !actualComplete {
effect.SuppressionReason = SuppressionIncompleteEventWindow
return effect
}
for week := 1; week <= 8; week++ {
var windowSessions, windowPaid, windowAmount int64
complete := true
for _, eventDay := range eventDays {
candidate := eventDay.AddDate(0, 0, -7*week)
if eventDates[dayKey(candidate)] {
continue
complete = false
break
}
point, ok := byDate[dayKey(candidate)]
if !ok {
continue
complete = false
break
}
effect.ObservationCount++
baselineSessions += point.SessionCount
baselinePaid += point.PaidSessionCount
baselineAmount += point.PaidAmountRial
windowSessions += point.SessionCount
windowPaid += point.PaidSessionCount
windowAmount += point.PaidAmountRial
}
if !complete {
continue
}
effect.ObservationCount++
baselineSessions += windowSessions
baselinePaid += windowPaid
baselineAmount += windowAmount
}
if effect.ObservationCount < MinBaselineObservations {
effect.SuppressionReason = SuppressionInsufficientObservations
@@ -45,9 +65,9 @@ func AnalyzeEventEffect(history []DailyPoint, event Event, catalog []Event) Even
return effect
}
effect.Eligible = true
effect.SessionUpliftBPS = changeBPS(effect.ActualSessionCount/int64(len(eventDays)), effect.BaselineSessionCount)
effect.PaidSessionUpliftBPS = changeBPS(effect.ActualPaidSessions/int64(len(eventDays)), effect.BaselinePaidSessions)
effect.AmountUpliftBPS = changeBPS(effect.ActualAmountRial/int64(len(eventDays)), effect.BaselineAmountRial)
effect.SessionUpliftBPS = changeBPS(effect.ActualSessionCount, effect.BaselineSessionCount)
effect.PaidSessionUpliftBPS = changeBPS(effect.ActualPaidSessions, effect.BaselinePaidSessions)
effect.AmountUpliftBPS = changeBPS(effect.ActualAmountRial, effect.BaselineAmountRial)
effect.MaterialWin = effect.PaidSessionUpliftBPS >= MaterialChangeBPS || effect.AmountUpliftBPS >= MaterialChangeBPS
effect.MaterialDecline = effect.PaidSessionUpliftBPS <= -MaterialChangeBPS || effect.AmountUpliftBPS <= -MaterialChangeBPS
return effect
@@ -140,6 +160,9 @@ func normalizeDate(value time.Time) time.Time {
func dayKey(value time.Time) string { return normalizeDate(value).Format(time.DateOnly) }
func dateBefore(a, b time.Time) bool { return dayKey(a) < dayKey(b) }
func dateAfter(a, b time.Time) bool { return dayKey(a) > dayKey(b) }
func changeBPS(actual, baseline int64) int64 {
if baseline <= 0 {
return 0