RFLNV436GWTYJVP3DSVBOOUHZQRUFBBKNDQDYZM5EVXJN66A64MAC C3YEXRHPVZVGUJDZEUPDYWC5JZYBCSSC2ZHORSYSER5TICPX76WAC DORZF5HSV672ZP5HUDYB3J6TBH5O2LMXJE4HPSE7H5SOGZQBDCXQC IFVRAERTCCDICNTYTG3TX2WASB6RXQQEJWWXQMQZJSQDQ3HLE5OQC OGLLBQQYE5KICDMI6EX7ZI4TZT5RB7UFHH7O2DUOZ44QQXVL5YAAC L4STQEXDGCPZXDHTEUBCOQKBMTFDRVXRLNFQHPDHOVXDCJO33LQQC OCRETPZZPDCUSOPYRH5MVRATJ37TRFGVSIMOI4IV755HFXXOVHEAC package utilsimport ("regexp""testing")func TestGenerateShortID(t *testing.T) {// Test that it generates a 12-character IDid, err := GenerateShortID()if err != nil {t.Fatalf("GenerateShortID() error = %v", err)}if len(id) != 12 {t.Errorf("GenerateShortID() length = %d, want 12", len(id))}// Verify it only contains valid alphabet characters// Default nanoid alphabet uses A-Za-z0-9_- symbols (64 characters)validPattern := regexp.MustCompile(`^[0-9A-Za-z_-]{12}$`)if !validPattern.MatchString(id) {t.Errorf("GenerateShortID() = %q, contains invalid characters", id)}// Test uniqueness - generate multiple IDs and check they're differentids := make(map[string]bool)for i := 0; i < 100; i++ {id, err := GenerateShortID()if err != nil {t.Fatalf("GenerateShortID() iteration %d error = %v", i, err)}if ids[id] {t.Errorf("GenerateShortID() produced duplicate: %q", id)}ids[id] = true}}func TestGenerateLongID(t *testing.T) {// Test that it generates a 21-character IDid, err := GenerateLongID()if err != nil {t.Fatalf("GenerateLongID() error = %v", err)}if len(id) != 21 {t.Errorf("GenerateLongID() length = %d, want 21", len(id))}// Verify it only contains valid alphabet characters// Default nanoid alphabet uses A-Za-z0-9_- symbols (64 characters)validPattern := regexp.MustCompile(`^[0-9A-Za-z_-]{21}$`)if !validPattern.MatchString(id) {t.Errorf("GenerateLongID() = %q, contains invalid characters", id)}// Test uniqueness - generate multiple IDs and check they're differentids := make(map[string]bool)for i := 0; i < 100; i++ {id, err := GenerateLongID()if err != nil {t.Fatalf("GenerateLongID() iteration %d error = %v", i, err)}if ids[id] {t.Errorf("GenerateLongID() produced duplicate: %q", id)}ids[id] = true}}func TestIDsAreDifferent(t *testing.T) {// Verify that short and long IDs are different typesshortID, err := GenerateShortID()if err != nil {t.Fatalf("GenerateShortID() error = %v", err)}longID, err := GenerateLongID()if err != nil {t.Fatalf("GenerateLongID() error = %v", err)}if len(shortID) == len(longID) {t.Error("Short and long IDs should have different lengths")}if len(shortID) != 12 {t.Errorf("Short ID length = %d, want 12", len(shortID))}if len(longID) != 21 {t.Errorf("Long ID length = %d, want 21", len(longID))}}
package utilsimport (gonanoid "github.com/matoous/go-nanoid/v2")// GenerateShortID generates a 12-character nanoid using the full alphabet// Used for: dataset_id, location_id, cluster_id, pattern_id// Entropy: ~71 bits (62^12 ≈ 3.2×10^21 combinations)func GenerateShortID() (string, error) {return gonanoid.New(12)}// GenerateLongID generates a 21-character nanoid using the full alphabet// Used for: file_id, selection_id, label_id// Entropy: ~125 bits (62^21 ≈ 2.7×10^37 combinations)func GenerateLongID() (string, error) {return gonanoid.New(21)}
// FileIDAlphabet is the standard alphabet used for generating file IDsconst FileIDAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"// FileIDLength is the standard length for generated file IDsconst FileIDLength = 21// GenerateFileID generates a 21-character nanoid file ID using the standard alphabetfunc GenerateFileID() (string, error) {return gonanoid.Generate(FileIDAlphabet, FileIDLength)}
selectionID := gonanoid.Must(12)
selectionID, err := utils.GenerateShortID()if err != nil {errors = append(errors, SelectionImportError{FileName: sel.BaseFilename,Species: sel.SpeciesLabel,CallType: sel.CallTypeLabel,Error: fmt.Sprintf("Failed to generate selection ID: %v", err),Stage: "insert",})continue}
labelID := gonanoid.Must(12)
labelID, err := utils.GenerateShortID()if err != nil {errors = append(errors, SelectionImportError{FileName: sel.BaseFilename,Species: sel.SpeciesLabel,CallType: sel.CallTypeLabel,Error: fmt.Sprintf("Failed to generate label ID: %v", err),Stage: "insert",})continue}
subtypeID := gonanoid.Must(12)
subtypeID, err := utils.GenerateShortID()if err != nil {errors = append(errors, SelectionImportError{FileName: sel.BaseFilename,Species: sel.SpeciesLabel,CallType: sel.CallTypeLabel,Error: fmt.Sprintf("Failed to generate label_subtype ID: %v", err),Stage: "insert",})continue}
Summary ImportSummary `json:"summary" jsonschema:"Import summary with counts and statistics"`FileIDs []string `json:"file_ids" jsonschema:"List of successfully imported file IDs"`Errors []FileImportError `json:"errors,omitempty" jsonschema:"Errors encountered during import (if any)"`
Summary ImportSummary `json:"summary" jsonschema:"Import summary with counts and statistics"`FileIDs []string `json:"file_ids" jsonschema:"List of successfully imported file IDs"`Errors []FileImportError `json:"errors,omitempty" jsonschema:"Errors encountered during import (if any)"`
TotalLocations int `json:"total_locations"`ClustersCreated int `json:"clusters_created"`ClustersExisting int `json:"clusters_existing"`TotalFilesScanned int `json:"total_files_scanned"`FilesImported int `json:"files_imported"`FilesDuplicate int `json:"files_duplicate"`FilesError int `json:"files_error"`ProcessingTime string `json:"processing_time"`Errors []string `json:"errors,omitempty"`
TotalLocations int `json:"total_locations"`ClustersCreated int `json:"clusters_created"`ClustersExisting int `json:"clusters_existing"`TotalFilesScanned int `json:"total_files_scanned"`FilesImported int `json:"files_imported"`FilesDuplicate int `json:"files_duplicate"`FilesError int `json:"files_error"`ProcessingTime string `json:"processing_time"`Errors []string `json:"errors,omitempty"`