fix(growth): validate reviewed event catalog

This commit is contained in:
2026-08-21 00:18:10 +03:30
parent df140b4c25
commit 8a0b30e936
2 changed files with 106 additions and 4 deletions

View File

@@ -1,10 +1,23 @@
package growth
import "time"
import (
"fmt"
"slices"
"strings"
"time"
)
const officialCalendarSource = "https://calendar.ut.ac.ir/"
const curatedCommercialSource = "CURATED_COMMERCIAL_1405_V1"
var importedMerchantCategories = []string{
"ارائه دهنده خدمات اینترنت",
"مراکز آموزشی مجازی",
"کیف و کفش فروشی",
"خدمات شبکه‌های کامپیوتری و اینترنت",
"فروشگاه لوازم آرایشی و بهداشتی",
}
// Catalog1405 returns a defensive copy of the reviewed initial catalog. Dates are
// Gregorian Tehran-local calendar dates and cover the imported 1405 history plus
// the first 90 forecast days after 2026-06-30.
@@ -14,10 +27,16 @@ func Catalog1405() []Event {
catalogEvent("nature-day-1405", "روز طبیعت", "2026-04-02", "2026-04-02", "leisure", EventOfficial, []string{"تفریح", "خرید"}),
catalogEvent("teachers-day-1405", "روز معلم", "2026-05-02", "2026-05-02", "appreciation", EventOfficial, []string{"هدیه", "آموزش", "قدردانی"}),
catalogEvent("khordad-15-1405", "قیام پانزده خرداد", "2026-06-05", "2026-06-05", "official-holiday", EventOfficial, []string{"تعطیل"}),
catalogEvent("fathers-day-commercial-1405", "روز پدر", "2026-08-25", "2026-08-25", "appreciation", EventCommercial, []string{"هدیه", "خرید", "قدردانی"}),
catalogEvent("doctors-day-1405", "روز پزشک", "2026-08-23", "2026-08-23", "appreciation", EventOfficial, []string{"هدیه", "قدردانی", "حرفه‌ای"}),
catalogEvent("back-to-school-1405", "آمادگی بازگشایی مدارس", "2026-09-10", "2026-09-22", "education-season", EventCommercial, []string{"آموزش", "خرید", "مدرسه"}),
}
return append([]Event(nil), events...)
result := make([]Event, len(events))
for index, event := range events {
result[index] = event
result[index].Tags = slices.Clone(event.Tags)
result[index].Categories = slices.Clone(event.Categories)
}
return result
}
func catalogEvent(id, title, start, end, family string, kind EventKind, tags []string) Event {
@@ -25,7 +44,30 @@ func catalogEvent(id, title, start, end, family string, kind EventKind, tags []s
if kind == EventCommercial {
source = curatedCommercialSource
}
return Event{ID: id, TitleFA: title, StartDate: mustCatalogDate(start), EndDate: mustCatalogDate(end), Family: family, Tags: append([]string(nil), tags...), Source: source, Kind: kind}
return Event{ID: id, TitleFA: title, StartDate: mustCatalogDate(start), EndDate: mustCatalogDate(end), Family: family, Tags: slices.Clone(tags), Source: source, Categories: slices.Clone(importedMerchantCategories), Kind: kind}
}
func ValidateCatalog(events []Event) error {
seen := make(map[string]struct{}, len(events))
for index, event := range events {
if strings.TrimSpace(event.ID) == "" || strings.TrimSpace(event.TitleFA) == "" || strings.TrimSpace(event.Family) == "" || strings.TrimSpace(event.Source) == "" {
return fmt.Errorf("growth catalog event %d is incomplete", index)
}
if _, exists := seen[event.ID]; exists {
return fmt.Errorf("growth catalog contains duplicate id %q", event.ID)
}
seen[event.ID] = struct{}{}
if event.StartDate.IsZero() || event.EndDate.Before(event.StartDate) {
return fmt.Errorf("growth catalog event %q has invalid date range", event.ID)
}
if event.Kind != EventOfficial && event.Kind != EventCommercial {
return fmt.Errorf("growth catalog event %q has invalid kind", event.ID)
}
if len(normalizedSet(event.Tags)) == 0 || len(event.Categories) == 0 {
return fmt.Errorf("growth catalog event %q lacks tags or supported categories", event.ID)
}
}
return nil
}
func mustCatalogDate(value string) time.Time {