feat: rank growth events with grounded evidence
This commit is contained in:
@@ -73,6 +73,57 @@ func AnalyzeEventEffect(history []DailyPoint, event Event, catalog []Event) Even
|
||||
return effect
|
||||
}
|
||||
|
||||
// MostRecentExactOccurrence returns only a completed occurrence with the same
|
||||
// canonical occasion identity and variant. Similar titles, tags, and families
|
||||
// are deliberately ignored so they cannot be represented as merchant history
|
||||
// for the upcoming event.
|
||||
func MostRecentExactOccurrence(upcoming Event, catalog []Event) (Event, bool) {
|
||||
if strings.TrimSpace(upcoming.OccasionKey) == "" || strings.TrimSpace(upcoming.EventVariant) == "" {
|
||||
return Event{}, false
|
||||
}
|
||||
var result Event
|
||||
found := false
|
||||
for _, candidate := range catalog {
|
||||
if candidate.OccasionKey != upcoming.OccasionKey || candidate.EventVariant != upcoming.EventVariant || !dateBefore(candidate.EndDate, upcoming.StartDate) {
|
||||
continue
|
||||
}
|
||||
if !found || dateAfter(candidate.EndDate, result.EndDate) || dayKey(candidate.EndDate) == dayKey(result.EndDate) && candidate.ID < result.ID {
|
||||
result = candidate
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return result, found
|
||||
}
|
||||
|
||||
func RankEvent(event Event, input RankInput) EventRank {
|
||||
rank := EventRank{Promotable: event.CommercialPosture != "NO_PROMOTION" && event.CommercialPosture != "CAUTION" && event.Tone != "MOURNING" && event.Tone != "MEMORIAL"}
|
||||
rank.ScoreBPS = event.ImportanceBPS + event.ConfidenceBPS + event.RelevanceBPS
|
||||
merchantTerms := normalizedSet(input.MerchantTerms)
|
||||
for value := range normalizedSet(append(slices.Clone(event.Categories), event.Tags...)) {
|
||||
if _, ok := merchantTerms[value]; ok {
|
||||
rank.ScoreBPS += 2_000
|
||||
rank.Factors = append(rank.Factors, value)
|
||||
}
|
||||
}
|
||||
slices.Sort(rank.Factors)
|
||||
if input.WithinPreparationWindow {
|
||||
rank.ScoreBPS += 1_000
|
||||
rank.Factors = append(rank.Factors, "PREPARATION_WINDOW")
|
||||
}
|
||||
if input.HistoricalEligible {
|
||||
rank.ScoreBPS += 20_000 + abs64(input.HistoricalUpliftBPS)/10
|
||||
rank.Factors = append(rank.Factors, "HISTORICAL_EVIDENCE")
|
||||
}
|
||||
return rank
|
||||
}
|
||||
|
||||
func abs64(value int64) int64 {
|
||||
if value < 0 {
|
||||
return -value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func MatchScoreBPS(historical, upcoming Event) (int64, string) {
|
||||
if historical.Family != "" && strings.EqualFold(strings.TrimSpace(historical.Family), strings.TrimSpace(upcoming.Family)) {
|
||||
return 10_000, "FAMILY"
|
||||
|
||||
Reference in New Issue
Block a user