package growth import ( "fmt" "strings" ) // ValidateCatalog validates the database-backed catalog after it has been // mapped into the deterministic growth model. Production catalog contents are // intentionally not embedded here; calendar_events is the sole source. 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) == "" || strings.TrimSpace(event.SourceVersion) == "" { 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 { return fmt.Errorf("growth catalog event %q lacks tags", event.ID) } if event.Universal && len(event.Categories) != 0 { return fmt.Errorf("growth catalog universal event %q must not contain categories", event.ID) } if !event.Universal && len(event.Categories) == 0 { return fmt.Errorf("growth catalog scoped event %q lacks categories", event.ID) } } return nil }