package growth import ( "slices" "strings" "time" ) func historicalWins(history []DailyPoint, catalog []Event, before time.Time) []EventEffect { wins := make([]EventEffect, 0) for _, event := range catalog { if !dateBefore(event.EndDate, before) { continue } effect := AnalyzeEventEffect(history, event, catalog) if effect.Eligible && effect.MaterialWin { wins = append(wins, effect) } } slices.SortFunc(wins, func(a, b EventEffect) int { aScore := maxInt64(a.PaidSessionUpliftBPS, a.AmountUpliftBPS) bScore := maxInt64(b.PaidSessionUpliftBPS, b.AmountUpliftBPS) if aScore != bScore { if aScore > bScore { return -1 } return 1 } return strings.Compare(a.EventID, b.EventID) }) return wins } func buildOpportunities(wins []EventEffect, catalog []Event, forecast Forecast, start time.Time, limit int) []Opportunity { if forecast.Confidence == ConfidenceInsufficientData || limit <= 0 { return nil } eventByID := make(map[string]Event, len(catalog)) for _, event := range catalog { eventByID[event.ID] = event } forecastByDate := make(map[string]ForecastPoint, len(forecast.Points)) for _, point := range forecast.Points { forecastByDate[dayKey(point.Date)] = point } best := make(map[string]Opportunity) for _, win := range wins { historical := eventByID[win.EventID] upcoming := make([]Event, 0) for _, event := range catalog { if !dateBefore(event.EndDate, start) { upcoming = append(upcoming, event) } } for _, match := range MatchUpcomingEvents(historical, upcoming) { point, ok := forecastByDate[dayKey(match.Event.StartDate)] if !ok { continue } candidate := Opportunity{ EvidenceID: EvidenceID("opportunity", match.Event.ID+":"+win.EventID), EventID: match.Event.ID, EventTitleFA: match.Event.TitleFA, Date: normalizeDate(match.Event.StartDate), SimilarityBPS: match.ScoreBPS, ExpectedSessions: point.PaidSessions, ExpectedAmountRial: point.PaidAmountRial, HistoricalEvidenceID: win.EvidenceID, } if current, exists := best[match.Event.ID]; !exists || candidate.SimilarityBPS > current.SimilarityBPS { best[match.Event.ID] = candidate } } } result := make([]Opportunity, 0, len(best)) for _, opportunity := range best { result = append(result, opportunity) } slices.SortFunc(result, func(a, b Opportunity) int { if a.SimilarityBPS != b.SimilarityBPS { if a.SimilarityBPS > b.SimilarityBPS { return -1 } return 1 } if comparison := a.Date.Compare(b.Date); comparison != 0 { return comparison } return strings.Compare(a.EventID, b.EventID) }) if len(result) > limit { result = result[:limit] } return result } func filterCatalog(catalog []Event, category string) []Event { filtered := make([]Event, 0, len(catalog)) for _, event := range catalog { if category == "" || event.Universal || slices.Contains(event.Categories, category) { filtered = append(filtered, event) } } return filtered }