fix: deliver validated AI insight responses
This commit is contained in:
@@ -2,20 +2,19 @@ package growth
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCatalog1405IsValidAndDefensivelyCopied(t *testing.T) {
|
||||
first := Catalog1405()
|
||||
if err := ValidateCatalog(first); err != nil {
|
||||
t.Fatalf("catalog should be valid: %v", err)
|
||||
}
|
||||
first[0].Tags[0] = "mutated"
|
||||
second := Catalog1405()
|
||||
if second[0].Tags[0] == "mutated" {
|
||||
t.Fatal("catalog tags escaped through a shared backing array")
|
||||
func testCatalog() []Event {
|
||||
category := []string{"مراکز آموزشی مجازی", "کیف و کفش فروشی"}
|
||||
return []Event{
|
||||
{ID: "history", TitleFA: "رویداد تاریخی", StartDate: date("2026-01-03"), EndDate: date("2026-01-03"), Family: "history", Tags: []string{"history"}, Source: "test", SourceVersion: "1", Kind: EventOfficial, Universal: true},
|
||||
{ID: "back-to-school-1405", TitleFA: "بازگشایی", StartDate: date("2026-09-10"), EndDate: date("2026-09-22"), Family: "education", Tags: []string{"education"}, Source: "test", SourceVersion: "1", Categories: category, Kind: EventCommercial},
|
||||
{ID: "autumn-opening-1405", TitleFA: "پاییز", StartDate: date("2026-09-23"), EndDate: date("2026-09-28"), Family: "seasonal", Tags: []string{"seasonal"}, Source: "test", SourceVersion: "1", Categories: category, Kind: EventCommercial},
|
||||
{ID: "halloween-2026", TitleFA: "هالووین", StartDate: date("2026-10-31"), EndDate: date("2026-10-31"), Family: "commercial", Tags: []string{"campaign"}, Source: "test", SourceVersion: "1", Categories: category, Kind: EventCommercial},
|
||||
{ID: "black-friday-2026", TitleFA: "بلکفرایدی", StartDate: date("2026-11-27"), EndDate: date("2026-11-27"), Family: "commercial", Tags: []string{"campaign"}, Source: "test", SourceVersion: "1", Categories: category, Kind: EventCommercial},
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCatalogRejectsDuplicateAndIncompleteEvents(t *testing.T) {
|
||||
event := officialEvent("duplicate", "رویداد", "2026-01-01", "2026-01-01", "family", false, []string{"tag"})
|
||||
event := testCatalog()[0]
|
||||
if err := ValidateCatalog([]Event{event, event}); err == nil {
|
||||
t.Fatal("expected duplicate id error")
|
||||
}
|
||||
@@ -25,87 +24,8 @@ func TestValidateCatalogRejectsDuplicateAndIncompleteEvents(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalogCoversImportedHistoryAndForecastHorizon(t *testing.T) {
|
||||
catalog := Catalog1405()
|
||||
if catalog[0].StartDate.After(date("2026-01-03")) || catalog[len(catalog)-1].EndDate.Before(date("2026-09-28")) {
|
||||
t.Fatalf("catalog coverage is incomplete: %s..%s", catalog[0].StartDate, catalog[len(catalog)-1].EndDate)
|
||||
}
|
||||
var scoped bool
|
||||
for _, event := range catalog {
|
||||
if event.ID == "back-to-school-1405" {
|
||||
scoped = !event.Universal && len(event.Categories) == 2
|
||||
}
|
||||
}
|
||||
if !scoped {
|
||||
t.Fatal("back-to-school event must preserve explicit category scope")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeEventEffectExcludesCatalogEventsFromBaseline(t *testing.T) {
|
||||
target := Event{ID: "target", StartDate: date("2026-05-02"), EndDate: date("2026-05-02")}
|
||||
excluded := Event{ID: "other", StartDate: target.StartDate.AddDate(0, 0, -7), EndDate: target.StartDate.AddDate(0, 0, -7)}
|
||||
history := make([]DailyPoint, 0, 9)
|
||||
for week := 1; week <= 8; week++ {
|
||||
count := int64(30)
|
||||
if week == 1 {
|
||||
count = 1_000
|
||||
}
|
||||
history = append(history, DailyPoint{Date: target.StartDate.AddDate(0, 0, -7*week), SessionCount: count, PaidSessionCount: count, PaidAmountRial: count * 100})
|
||||
}
|
||||
history = append(history, DailyPoint{Date: target.StartDate, SessionCount: 50, PaidSessionCount: 50, PaidAmountRial: 5_000})
|
||||
effect := AnalyzeEventEffect(history, target, []Event{excluded})
|
||||
if effect.ObservationCount != 7 || effect.BaselineSessionCount != 30 || !effect.MaterialWin {
|
||||
t.Fatalf("catalog event polluted baseline: %+v", effect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeEventEffectUsesCompleteComparableWindows(t *testing.T) {
|
||||
target := Event{ID: "two-day", StartDate: date("2026-05-02"), EndDate: date("2026-05-03")}
|
||||
history := []DailyPoint{
|
||||
{Date: date("2026-04-25"), SessionCount: 60, PaidSessionCount: 50, PaidAmountRial: 5_000},
|
||||
{Date: date("2026-04-26"), SessionCount: 60, PaidSessionCount: 50, PaidAmountRial: 5_000},
|
||||
{Date: date("2026-04-18"), SessionCount: 1_000, PaidSessionCount: 1_000, PaidAmountRial: 100_000}, // fragmented week, next day absent
|
||||
{Date: date("2026-04-11"), SessionCount: 60, PaidSessionCount: 50, PaidAmountRial: 5_000},
|
||||
{Date: date("2026-04-12"), SessionCount: 60, PaidSessionCount: 50, PaidAmountRial: 5_000},
|
||||
{Date: date("2026-04-04"), SessionCount: 60, PaidSessionCount: 50, PaidAmountRial: 5_000},
|
||||
{Date: date("2026-04-05"), SessionCount: 60, PaidSessionCount: 50, PaidAmountRial: 5_000},
|
||||
{Date: date("2026-03-28"), SessionCount: 60, PaidSessionCount: 50, PaidAmountRial: 5_000},
|
||||
{Date: date("2026-03-29"), SessionCount: 60, PaidSessionCount: 50, PaidAmountRial: 5_000},
|
||||
{Date: target.StartDate, SessionCount: 80, PaidSessionCount: 75, PaidAmountRial: 7_500},
|
||||
{Date: target.EndDate, SessionCount: 80, PaidSessionCount: 75, PaidAmountRial: 7_500},
|
||||
}
|
||||
effect := AnalyzeEventEffect(history, target, nil)
|
||||
if effect.ObservationCount != 4 || effect.BaselineSessionCount != 120 || effect.ActualSessionCount != 160 || effect.PaidSessionUpliftBPS != 5_000 {
|
||||
t.Fatalf("unexpected complete-window effect: %+v", effect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForecastKeepsPoorBacktestVisibleWithLowConfidence(t *testing.T) {
|
||||
start := date("2026-07-01")
|
||||
history := make([]DailyPoint, 0, 140)
|
||||
for i := 140; i > 0; i-- {
|
||||
value := int64(100)
|
||||
if i%2 == 0 {
|
||||
value = 1_000
|
||||
}
|
||||
history = append(history, DailyPoint{Date: start.AddDate(0, 0, -i), SessionCount: value, PaidSessionCount: value, PaidAmountRial: value * 1_000})
|
||||
}
|
||||
forecast := BuildForecast(history, nil, start, 90)
|
||||
if forecast.Backtest.Origins < 4 || forecast.Confidence != ConfidenceLow || len(forecast.Points) != 90 {
|
||||
t.Fatalf("poor model should remain visible with low confidence: %+v", forecast)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBacktestUsesProductionTrendPredictorAcrossFourteenDayHorizons(t *testing.T) {
|
||||
start := date("2026-07-01")
|
||||
history := make([]DailyPoint, 0, 140)
|
||||
for index := 0; index < 140; index++ {
|
||||
block := index / 28
|
||||
value := int64(100 + block*20)
|
||||
history = append(history, DailyPoint{Date: start.AddDate(0, 0, index-140), SessionCount: value, PaidSessionCount: value, PaidAmountRial: value * 1_000})
|
||||
}
|
||||
forecast := BuildForecast(history, nil, start, 90)
|
||||
if forecast.Backtest.Origins < 4 || forecast.Confidence == ConfidenceInsufficientData {
|
||||
t.Fatalf("trend-aware rolling backtest should be usable: %+v", forecast.Backtest)
|
||||
func TestDatabaseMappedCatalogCanBeValidated(t *testing.T) {
|
||||
if err := ValidateCatalog(testCatalog()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user