complexity over 12 now gone, but have some lint fails

quietlight
May 13, 2026, 9:38 PM
ZDZDASRTTRPJRIBAMNO3TB533XFELVYJQQGAMA3WOQYY35SVAQUQC

Dependencies

  • [2] DNLR36BZ minor comments
  • [3] ZKLAOPUR fix event logging
  • [4] ZCCQ4P5T reduce complexity to under 14, gocyclo but cilint test still has 3 functions over
  • [5] RUVJ3V4N cyclo to 14 now
  • [6] DD3LCTLZ tidy up lat lng timezone api for calls classify and push certainty
  • [7] SMWSHUOW cyclo over 15
  • [8] JAT3DXOL cyclo over 15
  • [9] BZ6KQRYD added complexity lint test
  • [10] 3DVPQOKB big tidy up of tools/
  • [11] NS4TDPLN cyclomatic complexity
  • [12] KZKLAINJ run out of space on nest, cleaned out
  • [13] GPQSOVBP cyclo complexity over 25
  • [14] HYCZTLSZ fixed tests with cyclo over 15
  • [15] QFPEKXL5 ck 6

Change contents

  • replacement in utils/filename_parser_test.go at line 57
    [5.2675][5.99024:99270](),[5.99024][5.99024:99270]()
    func TestParseFilenameTimestamps(t *testing.T) {
    t.Run("should parse YYMMDD format (test case a)", func(t *testing.T) {
    filenames := []string{
    "201012_123456.wav",
    "201014_123456.WAV",
    "201217_123456.wav",
    "211122_123456.WAV",
    }
    [5.2675]
    [5.99270]
    // parseTestCase defines a table-driven test case for ParseFilenameTimestamps.
    type parseTestCase struct {
    name string
    files []string
    expected map[int]expectedTS // index → expected timestamp
    }
  • replacement in utils/filename_parser_test.go at line 64
    [5.99271][5.99271:99396]()
    results, err := ParseFilenameTimestamps(filenames)
    if err != nil {
    t.Fatalf("Failed to parse filenames: %v", err)
    }
    [5.99271]
    [5.99396]
    func runParseTestCase(t *testing.T, tc parseTestCase) {
    t.Helper()
    results, err := ParseFilenameTimestamps(tc.files)
    if err != nil {
    t.Fatalf("Failed to parse filenames: %v", err)
    }
    if len(results) != len(tc.files) {
    t.Fatalf("Expected %d results, got %d", len(tc.files), len(results))
    }
    for idx, want := range tc.expected {
    assertTimestamp(t, results[idx].Timestamp, want)
    }
    }
  • replacement in utils/filename_parser_test.go at line 78
    [5.99397][5.99397:99552](),[5.99552][5.2676:2838](),[5.2838][5.100784:100986](),[5.100784][5.100784:100986]()
    if len(results) != 4 {
    t.Fatalf("Expected 4 results, got %d", len(results))
    }
    // Year 20 should be interpreted as 2020 (less variance than days)
    assertTimestamp(t, results[0].Timestamp, expectedTS{2020, 10, 12, 12, 34, 56})
    assertTimestamp(t, results[3].Timestamp, expectedTS{2021, 11, 22, 12, 34, 56})
    })
    t.Run("should parse DDMMYY format (test case b)", func(t *testing.T) {
    filenames := []string{
    "121020_123456.WAV",
    "141020_123456.wav",
    "171220_123456.WAV",
    "221121_123456.wav",
    }
    [5.99397]
    [5.100986]
    func TestParseFilenameTimestamps(t *testing.T) {
    cases := []parseTestCase{
    {
    name: "YYMMDD format (test case a)",
    files: []string{"201012_123456.wav", "201014_123456.WAV", "201217_123456.wav", "211122_123456.WAV"},
    expected: map[int]expectedTS{
    0: {2020, 10, 12, 12, 34, 56}, // Year 20 → 2020
    3: {2021, 11, 22, 12, 34, 56},
    },
    },
    {
    name: "DDMMYY format (test case b)",
    files: []string{"121020_123456.WAV", "141020_123456.wav", "171220_123456.WAV", "221121_123456.wav"},
    expected: map[int]expectedTS{
    0: {2020, 10, 12, 12, 34, 56},
    2: {2020, 12, 17, 12, 34, 56},
    },
    },
    {
    name: "YYYYMMDD format (test case c)",
    files: []string{"20230609_103000.WAV", "20241109_201504.wav"},
    expected: map[int]expectedTS{
    0: {2023, 6, 9, 10, 30, 0},
    1: {2024, 11, 9, 20, 15, 4},
    },
    },
    {
    name: "6-digit with variance detection (test case d)",
    files: []string{"120119_003002.wav", "180120_231502.wav", "170122_010005.wav", "010419_234502.WAV", "310320_231502.wav", "220824_231502.WAV", "240123_231502.wav"},
    expected: map[int]expectedTS{
    0: {2019, 1, 12, 0, 30, 2}, // DDMMYY
    4: {2020, 3, 31, 23, 15, 2},
    },
    },
    {
    name: "prefixes (test case e)",
    files: []string{"XYZ123_7689_20230609_103000.WAV", "string 20241109_201504.wav"},
    expected: map[int]expectedTS{
    0: {2023, 6, 9, 10, 30, 0},
    1: {2024, 11, 9, 20, 15, 4},
    },
    },
    {
    name: "complex prefixes (test case f)",
    files: []string{"abcdefg__1234_180120_231502.wav", "string 120119_003002.wav", "ABCD EFG___170122_010005.wav", "BHD_1234 010419_234502.WAV", "cill xyz 310320_231502.wav", "220824_231502.WAV", "240123_231502.wav"},
    expected: map[int]expectedTS{
    0: {2020, 1, 18, 23, 15, 2},
    1: {2019, 1, 12, 0, 30, 2},
    4: {2020, 3, 31, 23, 15, 2},
    },
    },
    }
  • replacement in utils/filename_parser_test.go at line 131
    [5.100987][5.100987:101343](),[5.101343][5.2839:3001](),[5.3001][5.102167:102540](),[5.102167][5.102167:102540](),[5.102540][5.3002:3159](),[5.3159][5.103410:104080](),[5.103410][5.103410:104080](),[5.104080][5.3160:3317](),[5.3317][5.105032:105037](),[5.105032][5.105032:105037](),[5.105285][5.105285:105680](),[5.105680][5.3318:3475](),[5.3475][5.107175:107782](),[5.107175][5.107175:107782](),[5.107782][5.3476:3716]()
    results, err := ParseFilenameTimestamps(filenames)
    if err != nil {
    t.Fatalf("Failed to parse filenames: %v", err)
    }
    if len(results) != 4 {
    t.Fatalf("Expected 4 results, got %d", len(results))
    }
    // More variance in first two digits (12,14,17,22) than last two (20,20,20,21)
    // So DDMMYY format: day=first, month=middle, year=last+2000
    assertTimestamp(t, results[0].Timestamp, expectedTS{2020, 10, 12, 12, 34, 56})
    assertTimestamp(t, results[2].Timestamp, expectedTS{2020, 12, 17, 12, 34, 56})
    })
    t.Run("should parse YYYYMMDD format (test case c)", func(t *testing.T) {
    filenames := []string{
    "20230609_103000.WAV",
    "20241109_201504.wav",
    }
    results, err := ParseFilenameTimestamps(filenames)
    if err != nil {
    t.Fatalf("Failed to parse filenames: %v", err)
    }
    if len(results) != 2 {
    t.Fatalf("Expected 2 results, got %d", len(results))
    }
    assertTimestamp(t, results[0].Timestamp, expectedTS{2023, 6, 9, 10, 30, 0})
    assertTimestamp(t, results[1].Timestamp, expectedTS{2024, 11, 9, 20, 15, 4})
    })
    t.Run("should parse mixed 6-digit dates with variance detection (test case d)", func(t *testing.T) {
    filenames := []string{
    "120119_003002.wav",
    "180120_231502.wav",
    "170122_010005.wav",
    "010419_234502.WAV",
    "310320_231502.wav",
    "220824_231502.WAV",
    "240123_231502.wav",
    }
    results, err := ParseFilenameTimestamps(filenames)
    if err != nil {
    t.Fatalf("Failed to parse filenames: %v", err)
    }
    if len(results) != 7 {
    t.Fatalf("Expected 7 results, got %d", len(results))
    }
    // First two digits: 12,18,17,01,31,22,24 (variance = high)
    // Last two digits: 19,20,22,19,20,24,23 (variance = lower)
    // Should be DDMMYY format
    assertTimestamp(t, results[0].Timestamp, expectedTS{2019, 1, 12, 0, 30, 2})
    assertTimestamp(t, results[4].Timestamp, expectedTS{2020, 3, 31, 23, 15, 2})
    })
    t.Run("should parse filenames with prefixes (test case e)", func(t *testing.T) {
    filenames := []string{
    "XYZ123_7689_20230609_103000.WAV",
    "string 20241109_201504.wav",
    }
    results, err := ParseFilenameTimestamps(filenames)
    if err != nil {
    t.Fatalf("Failed to parse filenames: %v", err)
    }
    if len(results) != 2 {
    t.Fatalf("Expected 2 results, got %d", len(results))
    }
    assertTimestamp(t, results[0].Timestamp, expectedTS{2023, 6, 9, 10, 30, 0})
    assertTimestamp(t, results[1].Timestamp, expectedTS{2024, 11, 9, 20, 15, 4})
    })
    t.Run("should parse filenames with complex prefixes (test case f)", func(t *testing.T) {
    filenames := []string{
    "abcdefg__1234_180120_231502.wav",
    "string 120119_003002.wav",
    "ABCD EFG___170122_010005.wav",
    "BHD_1234 010419_234502.WAV",
    "cill xyz 310320_231502.wav",
    "220824_231502.WAV",
    "240123_231502.wav",
    }
    results, err := ParseFilenameTimestamps(filenames)
    if err != nil {
    t.Fatalf("Failed to parse filenames: %v", err)
    }
    if len(results) != 7 {
    t.Fatalf("Expected 7 results, got %d", len(results))
    }
    // Same pattern as test case d - should be DDMMYY
    assertTimestamp(t, results[0].Timestamp, expectedTS{2020, 1, 18, 23, 15, 2})
    assertTimestamp(t, results[1].Timestamp, expectedTS{2019, 1, 12, 0, 30, 2})
    assertTimestamp(t, results[4].Timestamp, expectedTS{2020, 3, 31, 23, 15, 2})
    })
    [5.100987]
    [5.3716]
    for _, tc := range cases {
    t.Run(tc.name, func(t *testing.T) {
    runParseTestCase(t, tc)
    })
    }
  • edit in tools/prepend_test.go at line 187
    [5.270782]
    [5.270782]
    }
    }
    func TestProcessPrependFile(t *testing.T) {
    tmpDir, err := os.MkdirTemp("", "prepend_test")
    if err != nil {
    t.Fatalf("Failed to create temp dir: %v", err)
    }
    defer os.RemoveAll(tmpDir)
    tests := []struct {
    name string
    filename string
    dryRun bool
    wantRename int
    wantSkip int
    wantErr int
    }{
    {"datestring wav - dry run", "20250920_011509.wav", true, 1, 0, 0},
    {"datestring wav - real", "20250920_011509.wav", false, 1, 0, 0},
    {"no datestring wav", "recording.wav", false, 0, 1, 0},
    {"log.txt", "log.txt", false, 1, 0, 0},
    {"non-target file", "readme.txt", false, 0, 0, 0},
    }
    for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
    oldPath := filepath.Join(tmpDir, tt.filename)
    if err := os.WriteFile(oldPath, []byte{}, 0644); err != nil {
    t.Fatalf("create file: %v", err)
    }
    defer os.Remove(oldPath)
    output := &PrependOutput{
    Renamed: []PrependResult{},
    Skipped: []PrependSkipped{},
    Errors: []PrependError{},
    }
    processPrependFile(tmpDir, tt.filename, "LOC", tt.dryRun, output)
    if len(output.Renamed) != tt.wantRename {
    t.Errorf("renamed = %d, want %d", len(output.Renamed), tt.wantRename)
    }
    if len(output.Skipped) != tt.wantSkip {
    t.Errorf("skipped = %d, want %d", len(output.Skipped), tt.wantSkip)
    }
    if len(output.Errors) != tt.wantErr {
    t.Errorf("errors = %d, want %d", len(output.Errors), tt.wantErr)
    }
    })
  • edit in tools/prepend.go at line 50
    [5.273143]
    [5.273143]
    // prependFolders collects the folders to process (root + immediate subdirs if recursive).
    func prependFolders(input PrependInput) ([]string, error) {
    folders := []string{input.Folder}
    if !input.Recursive {
    return folders, nil
    }
    entries, err := os.ReadDir(input.Folder)
    if err != nil {
    return nil, fmt.Errorf("failed to read folder: %w", err)
    }
    for _, entry := range entries {
    if entry.IsDir() {
    folders = append(folders, filepath.Join(input.Folder, entry.Name()))
    }
    }
    return folders, nil
    }
    // processPrependFile handles a single file within a folder rename pass.
    func processPrependFile(folder, filename, prefix string, dryRun bool, output *PrependOutput) {
    oldPath := filepath.Join(folder, filename)
    shouldRename, skipReason := shouldPrependFile(filename, prefix)
    if !shouldRename {
    if skipReason != "" {
    output.Skipped = append(output.Skipped, PrependSkipped{File: oldPath, Reason: skipReason})
    }
    return
    }
  • edit in tools/prepend.go at line 81
    [5.273144]
    [5.273144]
    newPath := filepath.Join(folder, prefix+"_"+filename)
    if dryRun {
    output.Renamed = append(output.Renamed, PrependResult{Old: oldPath, New: newPath})
    return
    }
    if err := os.Rename(oldPath, newPath); err != nil {
    output.Errors = append(output.Errors, PrependError{File: oldPath, Error: err.Error()})
    return
    }
    output.Renamed = append(output.Renamed, PrependResult{Old: oldPath, New: newPath})
    }
  • replacement in tools/prepend.go at line 108
    [5.273641][5.273641:273992]()
    // Collect folders to process
    folders := []string{input.Folder}
    if input.Recursive {
    entries, err := os.ReadDir(input.Folder)
    if err != nil {
    return nil, fmt.Errorf("failed to read folder: %w", err)
    }
    for _, entry := range entries {
    if entry.IsDir() {
    folders = append(folders, filepath.Join(input.Folder, entry.Name()))
    }
    }
    [5.273641]
    [5.273992]
    folders, err := prependFolders(input)
    if err != nil {
    return nil, err
  • edit in tools/prepend.go at line 113
    [5.273996][5.273996:274020]()
    // Process each folder
  • edit in tools/prepend.go at line 118
    [5.274184][5.274184:274185]()
  • edit in tools/prepend.go at line 120
    [5.274241][5.274241:274822]()
    continue
    }
    filename := entry.Name()
    oldPath := filepath.Join(folder, filename)
    shouldRename, skipReason := shouldPrependFile(filename, input.Prefix)
    if !shouldRename {
    if skipReason != "" {
    output.Skipped = append(output.Skipped, PrependSkipped{
    File: oldPath,
    Reason: skipReason,
    })
    }
    continue
    }
    newFilename := input.Prefix + "_" + filename
    newPath := filepath.Join(folder, newFilename)
    if input.DryRun {
    output.Renamed = append(output.Renamed, PrependResult{
    Old: oldPath,
    New: newPath,
    })
  • replacement in tools/prepend.go at line 122
    [5.274840][5.274840:275149]()
    // Perform the rename
    if err := os.Rename(oldPath, newPath); err != nil {
    output.Errors = append(output.Errors, PrependError{
    File: oldPath,
    Error: err.Error(),
    })
    continue
    }
    output.Renamed = append(output.Renamed, PrependResult{
    Old: oldPath,
    New: newPath,
    })
    [5.274840]
    [5.275149]
    processPrependFile(folder, entry.Name(), input.Prefix, input.DryRun, output)
  • edit in tools/pattern_test.go at line 9
    [5.276523]
    [5.276523]
    func testExistingPattern(t *testing.T, ctx context.Context, testDB string) {
    t.Helper()
    record := 60
    sleep := 1740
    input := PatternInput{
    DBPath: testDB,
    RecordSeconds: &record,
    SleepSeconds: &sleep,
    }
    output, err := CreateOrUpdatePattern(ctx, input)
    if err != nil {
    t.Fatalf("Expected no error, got: %v", err)
    }
    if output.Pattern.ID != "IBv_KxDGsNQs" {
    t.Errorf("Expected existing pattern ID 'IBv_KxDGsNQs', got '%s'", output.Pattern.ID)
    }
    if output.Pattern.RecordS != 60 {
    t.Errorf("Expected record_s 60, got %d", output.Pattern.RecordS)
    }
    if output.Pattern.SleepS != 1740 {
    t.Errorf("Expected sleep_s 1740, got %d", output.Pattern.SleepS)
    }
    if output.Message == "" {
    t.Error("Expected non-empty message")
    }
    t.Logf("Message: %s", output.Message)
    }
    func testUniquePattern(t *testing.T, ctx context.Context, testDB string) {
    t.Helper()
    record := 999
    sleep := 888
    input := PatternInput{
    DBPath: testDB,
    RecordSeconds: &record,
    SleepSeconds: &sleep,
    }
  • edit in tools/pattern_test.go at line 50
    [5.276524]
    [5.276524]
    output, err := CreateOrUpdatePattern(ctx, input)
    if err != nil {
    t.Fatalf("Expected no error, got: %v", err)
    }
    firstID := output.Pattern.ID
    if firstID == "" {
    t.Fatal("Expected non-empty ID")
    }
    if output.Pattern.RecordS != 999 {
    t.Errorf("Expected record_s 999, got %d", output.Pattern.RecordS)
    }
    if output.Pattern.SleepS != 888 {
    t.Errorf("Expected sleep_s 888, got %d", output.Pattern.SleepS)
    }
    t.Logf("Created pattern ID: %s", firstID)
    testIdempotentCreate(t, ctx, testDB, record, sleep, firstID)
    }
    func testIdempotentCreate(t *testing.T, ctx context.Context, testDB string, record, sleep int, expectedID string) {
    t.Helper()
    input2 := PatternInput{
    DBPath: testDB,
    RecordSeconds: &record,
    SleepSeconds: &sleep,
    }
    output2, err2 := CreateOrUpdatePattern(ctx, input2)
    if err2 != nil {
    t.Fatalf("Expected no error on duplicate, got: %v", err2)
    }
    if output2.Pattern.ID != expectedID {
    t.Errorf("Expected same pattern ID '%s', got '%s'", expectedID, output2.Pattern.ID)
    }
    t.Logf("Idempotent test passed - returned same ID: %s", output2.Pattern.ID)
    }
  • edit in tools/pattern_test.go at line 88
    [5.276587][5.276587:276616]()
    // Setup: Use test database
  • edit in tools/pattern_test.go at line 94
    [5.276824][5.276824:276940]()
    // Test 1: Try to create duplicate of existing pattern (60s/1740s)
    // Should return existing pattern IBv_KxDGsNQs
  • replacement in tools/pattern_test.go at line 95
    [5.276994][5.276994:277050](),[5.277050][5.313114:313140](),[5.313140][5.277050:277778](),[5.277050][5.277050:277778]()
    record := 60
    sleep := 1740
    input := PatternInput{
    DBPath: testDB,
    RecordSeconds: &record,
    SleepSeconds: &sleep,
    }
    output, err := CreateOrUpdatePattern(ctx, input)
    if err != nil {
    t.Fatalf("Expected no error, got: %v", err)
    }
    // Should return existing pattern
    if output.Pattern.ID != "IBv_KxDGsNQs" {
    t.Errorf("Expected existing pattern ID 'IBv_KxDGsNQs', got '%s'", output.Pattern.ID)
    }
    if output.Pattern.RecordS != 60 {
    t.Errorf("Expected record_s 60, got %d", output.Pattern.RecordS)
    }
    if output.Pattern.SleepS != 1740 {
    t.Errorf("Expected sleep_s 1740, got %d", output.Pattern.SleepS)
    }
    // Check message indicates existing pattern
    if output.Message == "" {
    t.Error("Expected non-empty message")
    }
    t.Logf("Message: %s", output.Message)
    [5.276994]
    [5.277778]
    testExistingPattern(t, ctx, testDB)
  • edit in tools/pattern_test.go at line 97
    [5.277782][5.277782:277821]()
    // Test 2: Create new unique pattern
  • replacement in tools/pattern_test.go at line 98
    [5.277872][5.277872:277928](),[5.277928][5.313141:313167](),[5.313167][5.277928:278573](),[5.277928][5.277928:278573](),[5.278573][5.313168:313332](),[5.313332][5.278626:278946](),[5.278626][5.278626:278946]()
    record := 999
    sleep := 888
    input := PatternInput{
    DBPath: testDB,
    RecordSeconds: &record,
    SleepSeconds: &sleep,
    }
    output, err := CreateOrUpdatePattern(ctx, input)
    if err != nil {
    t.Fatalf("Expected no error, got: %v", err)
    }
    // Should create new pattern
    firstID := output.Pattern.ID
    if firstID == "" {
    t.Fatal("Expected non-empty ID")
    }
    if output.Pattern.RecordS != 999 {
    t.Errorf("Expected record_s 999, got %d", output.Pattern.RecordS)
    }
    if output.Pattern.SleepS != 888 {
    t.Errorf("Expected sleep_s 888, got %d", output.Pattern.SleepS)
    }
    t.Logf("Created pattern ID: %s", firstID)
    // Test 3: Try to create duplicate of the pattern we just created (idempotent)
    input2 := PatternInput{
    DBPath: testDB,
    RecordSeconds: &record,
    SleepSeconds: &sleep,
    }
    output2, err2 := CreateOrUpdatePattern(ctx, input2)
    if err2 != nil {
    t.Fatalf("Expected no error on duplicate, got: %v", err2)
    }
    // Should return same pattern
    if output2.Pattern.ID != firstID {
    t.Errorf("Expected same pattern ID '%s', got '%s'", firstID, output2.Pattern.ID)
    }
    t.Logf("Idempotent test passed - returned same ID: %s", output2.Pattern.ID)
    [5.277872]
    [5.278946]
    testUniquePattern(t, ctx, testDB)
  • replacement in tools/integration_test.go at line 48
    [5.306843][5.313742:313828]()
    func testCreateClusterWithPattern(t *testing.T, ctx context.Context, testDB string) {
    [5.306843]
    [5.4706]
    // lookupActiveDatasetAndLocation finds an active dataset and location for integration tests.
    func lookupActiveDatasetAndLocation(t *testing.T, ctx context.Context, testDB string) (datasetID, locationID string) {
  • edit in tools/integration_test.go at line 51
    [5.4718][5.4718:4743]()
    // Find a valid dataset
  • replacement in tools/integration_test.go at line 58
    [5.4972][5.4972:5023]()
    datasetID := datasetOutput.Rows[0]["id"].(string)
    [5.4972]
    [5.306938]
    datasetID = datasetOutput.Rows[0]["id"].(string)
  • edit in tools/integration_test.go at line 60
    [5.306939][5.5024:5050]()
    // Find a valid location
  • replacement in tools/integration_test.go at line 68
    [5.5339][5.5339:5392]()
    locationID := locationOutput.Rows[0]["id"].(string)
    [5.5339]
    [5.306966]
    locationID = locationOutput.Rows[0]["id"].(string)
  • edit in tools/integration_test.go at line 71
    [5.5459]
    [5.306998]
    return datasetID, locationID
    }
  • replacement in tools/integration_test.go at line 74
    [5.306999][5.5460:5538](),[5.5538][5.313935:313971](),[5.313971][5.5538:5939](),[5.5538][5.5538:5939](),[5.5939][5.307194:307195](),[5.307194][5.307194:307195](),[5.307195][5.5940:5989]()
    sampleRate := 16000
    output, err := CreateOrUpdateCluster(ctx, ClusterInput{
    DBPath: testDB,
    DatasetID: &datasetID,
    LocationID: &locationID,
    Name: new("Integration Test Cluster"),
    SampleRate: &sampleRate,
    CyclicRecordingPatternID: new("IBv_KxDGsNQs"),
    })
    if err != nil {
    t.Fatalf("Failed to create cluster: %v", err)
    }
    clusterID := output.Cluster.ID
    t.Logf("Created cluster: %s with pattern reference", clusterID)
    // Verify the cluster has the pattern reference
    [5.306999]
    [5.5989]
    // verifyClusterPattern verifies a cluster has the expected pattern reference.
    func verifyClusterPattern(t *testing.T, ctx context.Context, testDB, clusterID, expectedPatternID string) {
    t.Helper()
  • replacement in tools/integration_test.go at line 92
    [5.307422][5.6487:6769]()
    if row["cyclic_recording_pattern_id"] != "IBv_KxDGsNQs" {
    t.Errorf("Expected pattern ID 'IBv_KxDGsNQs', got '%v'", row["cyclic_recording_pattern_id"])
    }
    if row["cyclic_recording_pattern_id"] == nil || row["cyclic_recording_pattern_id"] == "" {
    t.Error("Pattern ID is empty")
    [5.307422]
    [5.6769]
    if row["cyclic_recording_pattern_id"] != expectedPatternID {
    t.Errorf("Expected pattern ID '%s', got '%v'", expectedPatternID, row["cyclic_recording_pattern_id"])
  • edit in tools/integration_test.go at line 102
    [5.307631]
    func testCreateClusterWithPattern(t *testing.T, ctx context.Context, testDB string) {
    t.Helper()
    datasetID, locationID := lookupActiveDatasetAndLocation(t, ctx, testDB)
    sampleRate := 16000
    output, err := CreateOrUpdateCluster(ctx, ClusterInput{
    DBPath: testDB,
    DatasetID: &datasetID,
    LocationID: &locationID,
    Name: new("Integration Test Cluster"),
    SampleRate: &sampleRate,
    CyclicRecordingPatternID: new("IBv_KxDGsNQs"),
    })
    if err != nil {
    t.Fatalf("Failed to create cluster: %v", err)
    }
    t.Logf("Created cluster: %s with pattern reference", output.Cluster.ID)
    verifyClusterPattern(t, ctx, testDB, output.Cluster.ID, "IBv_KxDGsNQs")
    }
  • edit in tools/calls/calls_from_common.go at line 79
    [5.187571]
    [5.187571]
    }
    // sequentialFileResult holds the outcome of processing a single file sequentially.
    type sequentialFileResult struct {
    calls []ClusteredCall
    dataFilesWritten int
    dataFilesSkipped int
    filesDeleted int
    }
    // processSequentialFile handles one source file in the sequential path.
    func processSequentialFile(src CallSource, file string, dirCaches map[string]*DirCache, shouldDelete bool) (sequentialFileResult, error) {
    var res sequentialFileResult
    dir := filepath.Dir(file)
    cache := dirCaches[dir]
    if cache == nil {
    cache = NewDirCache(dir)
    dirCaches[dir] = cache
    }
    calls, written, skipped, err := src.ProcessFile(file, cache)
    if err != nil {
    return res, fmt.Errorf("Error processing %s: %v", file, err)
    }
    if written {
    res.dataFilesWritten = 1
    }
    if skipped {
    res.dataFilesSkipped = 1
    }
    res.calls = calls
    if shouldDelete && written {
    if err := os.Remove(file); err != nil {
    return res, fmt.Errorf("Failed to delete %s: %v", file, err)
    }
    res.filesDeleted = 1
    }
    return res, nil
  • edit in tools/calls/calls_from_common.go at line 134
    [5.188010][5.188010:188048]()
    speciesCount := make(map[string]int)
  • edit in tools/calls/calls_from_common.go at line 135
    [5.188078]
    [5.188078]
    speciesCount := make(map[string]int)
  • replacement in tools/calls/calls_from_common.go at line 142
    [5.188195][5.188195:188391]()
    dir := filepath.Dir(file)
    cache := dirCaches[dir]
    if cache == nil {
    cache = NewDirCache(dir)
    dirCaches[dir] = cache
    }
    calls, written, skipped, err := src.ProcessFile(file, cache)
    [5.188195]
    [5.188391]
    res, err := processSequentialFile(src, file, dirCaches, input.Delete)
  • replacement in tools/calls/calls_from_common.go at line 144
    [5.188409][5.188409:188472]()
    errMsg := fmt.Sprintf("Error processing %s: %v", file, err)
    [5.188409]
    [5.188472]
    errMsg := err.Error()
  • replacement in tools/calls/calls_from_common.go at line 146
    [5.188498][5.188498:188624]()
    return output, fmt.Errorf("%s", errMsg)
    }
    if written {
    dataFilesWritten++
    }
    if skipped {
    dataFilesSkipped++
    [5.188498]
    [5.188624]
    return output, err
  • replacement in tools/calls/calls_from_common.go at line 149
    [5.188629][5.188629:188736]()
    for _, call := range calls {
    allCalls = append(allCalls, call)
    speciesCount[call.EbirdCode]++
    }
    [5.188629]
    [5.188736]
    allCalls = append(allCalls, res.calls...)
    dataFilesWritten += res.dataFilesWritten
    dataFilesSkipped += res.dataFilesSkipped
    filesDeleted += res.filesDeleted
  • replacement in tools/calls/calls_from_common.go at line 155
    [5.188756][5.188756:189040]()
    // Delete if requested and successfully processed
    if input.Delete && written {
    if err := os.Remove(file); err != nil {
    errMsg := fmt.Sprintf("Failed to delete %s: %v", file, err)
    output.Error = &errMsg
    return output, fmt.Errorf("%s", errMsg)
    }
    filesDeleted++
    [5.188756]
    [5.189040]
    for _, call := range res.calls {
    speciesCount[call.EbirdCode]++
  • edit in tools/calls/calls_from_common.go at line 164
    [5.189163][5.189163:189207]()
    // Sort all calls by file, then start time
  • edit in tools/calls/calls_clip_labels.go at line 113
    [5.236960]
    [5.236960]
    // collectSpeciesFromDataFile parses a .data file, validates it, and returns
    // its DataFile and the set of species names seen (filtered by filter).
    func collectSpeciesFromDataFile(path, filter string) (*utils.DataFile, map[string]bool, error) {
    df, err := utils.ParseDataFile(path)
    if err != nil {
    return nil, nil, fmt.Errorf("parse %s: %w", path, err)
    }
    if df.Meta == nil || df.Meta.Duration <= 0 {
    return nil, nil, fmt.Errorf("missing or non-positive Duration in %s (cannot generate clips)", path)
    }
    speciesSeen := map[string]bool{}
    for _, seg := range df.Segments {
    for _, lbl := range seg.Labels {
    if filter != "" && lbl.Filter != filter {
    continue
    }
    speciesSeen[lbl.Species] = true
    }
    }
    return df, speciesSeen, nil
    }
  • replacement in tools/calls/calls_clip_labels.go at line 147
    [5.237406][5.237406:237442]()
    df, err := utils.ParseDataFile(p)
    [5.237406]
    [5.237442]
    df, species, err := collectSpeciesFromDataFile(p, filter)
  • replacement in tools/calls/calls_clip_labels.go at line 149
    [5.237460][5.237460:237656]()
    return nil, fmt.Errorf("parse %s: %w", p, err)
    }
    if df.Meta == nil || df.Meta.Duration <= 0 {
    return nil, fmt.Errorf("missing or non-positive Duration in %s (cannot generate clips)", p)
    [5.237460]
    [5.237656]
    return nil, err
  • replacement in tools/calls/calls_clip_labels.go at line 151
    [5.237660][5.237660:237839]()
    for _, seg := range df.Segments {
    for _, lbl := range seg.Labels {
    if filter != "" && lbl.Filter != filter {
    continue
    }
    speciesSeen[lbl.Species] = true
    }
    [5.237660]
    [5.237839]
    for s := range species {
    speciesSeen[s] = true
  • replacement in tools/calls/calls_clip_labels.go at line 179
    [5.238691][5.238691:238920]()
    func CallsClipLabels(input CallsClipLabelsInput) (CallsClipLabelsOutput, error) {
    out := CallsClipLabelsOutput{
    Folder: input.Folder,
    OutputPath: input.OutputPath,
    PerClassTrueCount: map[string]int{},
    }
    [5.238691]
    [5.238920]
    // clipLabelsContext holds the initialized state needed to process clip labels.
    type clipLabelsContext struct {
    mapping utils.MappingFile
    classes []string
    classIdx map[string]int
    parsed []parsedClipFile
    existing map[rowKey]bool
    appendMode bool
    expectedHeader []string
    cwd string
    folderAbs string
    finalClipMode utils.FinalClipMode
    }
  • edit in tools/calls/calls_clip_labels.go at line 193
    [5.238921]
    [5.238921]
    // initClipLabelsContext performs all initialization steps for CallsClipLabels.
    func initClipLabelsContext(input CallsClipLabelsInput, out *CallsClipLabelsOutput) (*clipLabelsContext, error) {
  • replacement in tools/calls/calls_clip_labels.go at line 197
    [5.238992][5.238992:239010]()
    return out, err
    [5.238992]
    [5.239010]
    return nil, err
  • replacement in tools/calls/calls_clip_labels.go at line 202
    [5.239089][5.239089:239161]()
    return out, fmt.Errorf("load mapping %s: %w", input.MappingPath, err)
    [5.239089]
    [5.239161]
    return nil, fmt.Errorf("load mapping %s: %w", input.MappingPath, err)
  • replacement in tools/calls/calls_clip_labels.go at line 207
    [5.239219][5.239219:239295]()
    return out, fmt.Errorf("mapping.json has no real (non-sentinel) classes")
    [5.239219]
    [5.239295]
    return nil, fmt.Errorf("mapping.json has no real (non-sentinel) classes")
  • replacement in tools/calls/calls_clip_labels.go at line 218
    [5.239524][5.239524:239542]()
    return out, err
    [5.239524]
    [5.239542]
    return nil, err
  • replacement in tools/calls/calls_clip_labels.go at line 225
    [5.239761][5.239761:239779]()
    return out, err
    [5.239761]
    [5.239779]
    return nil, err
  • replacement in tools/calls/calls_clip_labels.go at line 232
    [5.239896][5.239896:239939]()
    return out, fmt.Errorf("getwd: %w", err)
    [5.239896]
    [5.239939]
    return nil, fmt.Errorf("getwd: %w", err)
  • replacement in tools/calls/calls_clip_labels.go at line 236
    [5.240005][5.240005:240063]()
    return out, fmt.Errorf("abs %s: %w", input.Folder, err)
    [5.240005]
    [5.240063]
    return nil, fmt.Errorf("abs %s: %w", input.Folder, err)
    }
    return &clipLabelsContext{
    mapping: mapping,
    classes: classes,
    classIdx: classIdx,
    parsed: parsed,
    existing: existing,
    appendMode: appendMode,
    expectedHeader: expectedHeader,
    cwd: cwd,
    folderAbs: folderAbs,
    finalClipMode: finalClipMode,
    }, nil
    }
    func CallsClipLabels(input CallsClipLabelsInput) (CallsClipLabelsOutput, error) {
    out := CallsClipLabelsOutput{
    Folder: input.Folder,
    OutputPath: input.OutputPath,
    PerClassTrueCount: map[string]int{},
    }
    ctx, err := initClipLabelsContext(input, &out)
    if err != nil {
    return out, err
  • replacement in tools/calls/calls_clip_labels.go at line 266
    [5.240107][5.240107:240265]()
    for _, pf := range parsed {
    fileRows, err := processClipLabelsFile(pf.path, pf.df, mapping, classIdx, classes, input, finalClipMode, cwd, folderAbs, &out)
    [5.240107]
    [5.240265]
    for _, pf := range ctx.parsed {
    fileRows, err := processClipLabelsFile(pf.path, pf.df, ctx.mapping, ctx.classIdx, ctx.classes, input, ctx.finalClipMode, ctx.cwd, ctx.folderAbs, &out)
  • replacement in tools/calls/calls_clip_labels.go at line 274
    [5.240345][5.240345:240406]()
    if err := dedupClipLabelsRows(rows, existing); err != nil {
    [5.240345]
    [5.240406]
    if err := dedupClipLabelsRows(rows, ctx.existing); err != nil {
  • replacement in tools/calls/calls_clip_labels.go at line 278
    [5.240428][5.240428:240515]()
    if err := writeRows(input.OutputPath, expectedHeader, rows, appendMode); err != nil {
    [5.240428]
    [5.240515]
    if err := writeRows(input.OutputPath, ctx.expectedHeader, rows, ctx.appendMode); err != nil {
  • edit in tools/calls/calls_clip_labels.go at line 321
    [5.241521]
    [5.241521]
    }
    // resolveLabel attempts to classify a single label, returning a resolvedSeg if valid.
    // Returns (seg, isIgnored, ok). When ok is false the label should be skipped.
    func resolveLabel(lbl *utils.Label, seg *utils.Segment, filter string, mapping utils.MappingFile, classIdx map[string]int) (resolvedSeg, bool, bool) {
    if filter != "" && lbl.Filter != filter {
    return resolvedSeg{}, false, false
    }
    canon, kind, ok := mapping.Classify(lbl.Species)
    if !ok {
    return resolvedSeg{}, false, false
    }
    switch kind {
    case utils.MappingIgn:
    return resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind}, true, true
    case utils.MappingNeg:
    return resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind}, false, true
    case utils.MappingReal:
    idx, present := classIdx[canon]
    if !present {
    return resolvedSeg{}, false, false
    }
    return resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind, classIdx: idx}, false, true
    }
    return resolvedSeg{}, false, false
  • replacement in tools/calls/calls_clip_labels.go at line 363
    [5.241983][5.241983:242098]()
    if filter != "" && lbl.Filter != filter {
    continue
    }
    canon, kind, ok := mapping.Classify(lbl.Species)
    [5.241983]
    [5.242098]
    rs, isIgnored, ok := resolveLabel(lbl, seg, filter, mapping, classIdx)
  • replacement in tools/calls/calls_clip_labels.go at line 367
    [5.242128][5.242128:242171]()
    switch kind {
    case utils.MappingIgn:
    [5.242128]
    [5.242171]
    if isIgnored {
  • edit in tools/calls/calls_clip_labels.go at line 369
    [5.242197][5.242197:242606]()
    segs = append(segs, resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind})
    case utils.MappingNeg:
    segs = append(segs, resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind})
    case utils.MappingReal:
    idx, present := classIdx[canon]
    if !present {
    continue
    }
    segs = append(segs, resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind, classIdx: idx})
  • edit in tools/calls/calls_clip_labels.go at line 370
    [5.242611]
    [5.242611]
    segs = append(segs, rs)
  • replacement in tools/calls/calls_clip_labels.go at line 462
    [5.244972][5.244972:245583]()
    // loadExistingRows reads an existing output CSV and returns its row keys
    // (for deduplication) and whether we're in append mode.
    func loadExistingRows(outputPath string, expectedHeader []string) (map[rowKey]bool, bool, error) {
    fi, err := os.Stat(outputPath)
    if err != nil {
    if os.IsNotExist(err) {
    return nil, false, nil
    }
    return nil, false, fmt.Errorf("stat %s: %w", outputPath, err)
    }
    if fi.Size() == 0 {
    return nil, false, nil
    }
    f, err := os.Open(outputPath)
    if err != nil {
    return nil, false, fmt.Errorf("open existing %s: %w", outputPath, err)
    }
    defer func() { _ = f.Close() }()
    [5.244972]
    [5.245583]
    // readExistingCSV reads an existing CSV file, validates its header, and returns row keys.
    func readExistingCSV(f *os.File, outputPath string, expectedHeader []string) (map[rowKey]bool, error) {
  • replacement in tools/calls/calls_clip_labels.go at line 469
    [5.245673][5.245673:245756]()
    return nil, false, fmt.Errorf("read header of existing %s: %w", outputPath, err)
    [5.245673]
    [5.245756]
    return nil, fmt.Errorf("read header of existing %s: %w", outputPath, err)
  • replacement in tools/calls/calls_clip_labels.go at line 472
    [5.245803][5.245803:245905]()
    return nil, false, fmt.Errorf("column-set mismatch in existing %s\n existing: %s\n new: %s",
    [5.245803]
    [5.245905]
    return nil, fmt.Errorf("column-set mismatch in existing %s\n existing: %s\n new: %s",
  • replacement in tools/calls/calls_clip_labels.go at line 483
    [5.246099][5.246099:246180]()
    return nil, false, fmt.Errorf("read row of existing %s: %w", outputPath, err)
    [5.246099]
    [5.246180]
    return nil, fmt.Errorf("read row of existing %s: %w", outputPath, err)
  • replacement in tools/calls/calls_clip_labels.go at line 486
    [5.246204][5.246204:246290]()
    return nil, false, fmt.Errorf("malformed row in existing %s: %v", outputPath, rec)
    [5.246204]
    [5.246290]
    return nil, fmt.Errorf("malformed row in existing %s: %v", outputPath, rec)
  • edit in tools/calls/calls_clip_labels.go at line 490
    [5.246365]
    [5.246365]
    return existing, nil
    }
  • edit in tools/calls/calls_clip_labels.go at line 493
    [5.246366]
    [5.246366]
    // loadExistingRows reads an existing output CSV and returns its row keys
    // (for deduplication) and whether we're in append mode.
    func loadExistingRows(outputPath string, expectedHeader []string) (map[rowKey]bool, bool, error) {
    fi, err := os.Stat(outputPath)
    if err != nil {
    if os.IsNotExist(err) {
    return nil, false, nil
    }
    return nil, false, fmt.Errorf("stat %s: %w", outputPath, err)
    }
    if fi.Size() == 0 {
    return nil, false, nil
    }
    f, err := os.Open(outputPath)
    if err != nil {
    return nil, false, fmt.Errorf("open existing %s: %w", outputPath, err)
    }
    defer func() { _ = f.Close() }()
    existing, err := readExistingCSV(f, outputPath, expectedHeader)
    if err != nil {
    return nil, false, err
    }
  • replacement in lint_test.go at line 48
    [5.158][4.14313:14365]()
    cmd := exec.Command("gocyclo", "-over", "13", ".")
    [5.158]
    [5.210]
    cmd := exec.Command("gocyclo", "-over", "12", ".")
  • replacement in lint_test.go at line 52
    [5.276][2.122:181]()
    t.Errorf("cyclometric complexity is above 14:\n%s", out)
    [5.276]
    [5.335]
    t.Errorf("cyclometric complexity is above 12:\n%s", out)
  • edit in cmd/calls_modify.go at line 60
    [5.25244]
    [5.25545]
    }
    // modifyFlagHandler returns a function that sets a flag value on modifyArgs,
    // or nil if the flag is unknown. Returns (handler, isBool, handled).
    // isBool indicates the flag takes no value.
    func modifyFlagHandler(arg string, ma *modifyArgs) (func(string), bool, bool) {
    switch arg {
    case "--file":
    return func(v string) { ma.file = v }, false, true
    case "--reviewer":
    return func(v string) { ma.reviewer = v }, false, true
    case "--filter":
    return func(v string) { ma.filter = v }, false, true
    case "--segment":
    return func(v string) { ma.segment = v }, false, true
    case "--species":
    return func(v string) { ma.species = v }, false, true
    case "--certainty":
    return func(v string) {
    n, err := strconv.Atoi(v)
    if err != nil {
    fmt.Fprintf(os.Stderr, "Error: --certainty must be an integer\n")
    os.Exit(1)
    }
    ma.certainty = n
    ma.certaintySet = true
    }, false, true
    case "--bookmark":
    return func(_ string) { ma.bookmark = true }, true, true
    case "--comment":
    return func(v string) { ma.comment = v }, false, true
    default:
    return nil, false, false
    }
  • edit in cmd/calls_modify.go at line 97
    [5.25628][5.5020:5090]()
    // Uses mustValue for flag-value extraction (exits on missing value).
  • replacement in cmd/calls_modify.go at line 102
    [5.1121025][5.1121025:1121057](),[5.1121057][5.5091:5134](),[5.5134][5.1121196:1121217](),[5.1121196][5.1121196:1121217](),[5.1121217][5.5135:5186](),[5.5186][5.1121364:1121383](),[5.1121364][5.1121364:1121383](),[5.1121383][5.5187:5234](),[5.5234][5.1121526:1121546](),[5.1121526][5.1121526:1121546](),[5.1121546][5.5235:5284](),[5.5284][5.1121691:1121711](),[5.1121691][5.1121691:1121711](),[5.1121711][5.5285:5334](),[5.5334][5.1121856:1121878](),[5.1121856][5.1121856:1121878](),[5.1121878][5.5335:5380](),[5.5380][5.26022:26053](),[5.26022][5.26022:26053](),[5.26053][5.1122028:1122137](),[5.1122028][5.1122028:1122137](),[5.1122137][5.26054:26100](),[5.1122188][5.1122188:1122209](),[5.1122209][5.26101:26123](),[5.26123][5.1122228:1122235](),[5.1122228][5.1122228:1122235](),[5.1122236][5.1122236:1122256](),[5.1122256][5.5381:5430](),[5.5430][5.1122401:1122424](),[5.1122401][5.1122401:1122424]()
    switch arg {
    case "--file":
    ma.file = mustValue(args, &i, "--file")
    case "--reviewer":
    ma.reviewer = mustValue(args, &i, "--reviewer")
    case "--filter":
    ma.filter = mustValue(args, &i, "--filter")
    case "--segment":
    ma.segment = mustValue(args, &i, "--segment")
    case "--species":
    ma.species = mustValue(args, &i, "--species")
    case "--certainty":
    val := mustValue(args, &i, "--certainty")
    v, err := strconv.Atoi(val)
    if err != nil {
    fmt.Fprintf(os.Stderr, "Error: --certainty must be an integer\n")
    os.Exit(1)
    }
    ma.certainty = v
    ma.certaintySet = true
    case "--bookmark":
    ma.bookmark = true
    i++
    case "--comment":
    ma.comment = mustValue(args, &i, "--comment")
    case "-h", "--help":
    [5.1121024]
    [5.1122424]
    if arg == "-h" || arg == "--help" {
  • replacement in cmd/calls_modify.go at line 105
    [5.1122461][5.1122461:1122472](),[5.1122502][5.1122502:1122539](),[5.1122602][5.1122602:1122625](),[5.1122625][5.5431:5492](),[5.5492][5.1122625:1122645](),[5.1122625][5.1122625:1122645]()
    default:
    if strings.HasPrefix(arg, "--") {
    printModifyUsage()
    fmt.Fprintf(os.Stderr, "Error: unknown flag: %s\n", arg)
    os.Exit(1)
    }
    [5.1122460]
    [5.1122645]
    }
    handler, isBool, handled := modifyFlagHandler(arg, &ma)
    if !handled {
    handleUnknownFlag(arg)
  • edit in cmd/calls_modify.go at line 110
    [5.1122652]
    [5.1122652]
    continue
  • edit in cmd/calls_modify.go at line 112
    [5.1122656]
    [5.1122656]
    if isBool {
    i++
    } else {
    handler(mustValue(args, &i, arg))
    }
  • edit in cmd/calls_modify.go at line 119
    [5.26191]
    [5.26191]
    }
    // handleUnknownFlag prints usage and exits for an unknown flag.
    func handleUnknownFlag(arg string) {
    if strings.HasPrefix(arg, "--") {
    printModifyUsage()
    fmt.Fprintf(os.Stderr, "Error: unknown flag: %s\n", arg)
    os.Exit(1)
    }
  • replacement in cmd/calls_detect_anomalies.go at line 60
    [5.1126708][5.5848:5900](),[5.5900][5.1126754:1126816](),[5.1126754][5.1126754:1126816]()
    func runCallsDetectAnomalies(args []string) error {
    var folder string
    var models []string
    var species []string
    [5.1126708]
    [5.1126816]
    //
    // detectAnomaliesArgs holds the parsed arguments.
    type detectAnomaliesArgs struct {
    folder string
    models []string
    species []string
    }
  • edit in cmd/calls_detect_anomalies.go at line 68
    [5.1126817]
    [5.1126817]
    // parseDetectAnomaliesArgs parses CLI arguments for the detect-anomalies command.
    func parseDetectAnomaliesArgs(args []string) (detectAnomaliesArgs, error) {
    var da detectAnomaliesArgs
  • replacement in cmd/calls_detect_anomalies.go at line 77
    [5.1126922][5.5901:5952]()
    return fmt.Errorf("--folder requires a value")
    [5.1126922]
    [5.1127002]
    return da, fmt.Errorf("--folder requires a value")
  • replacement in cmd/calls_detect_anomalies.go at line 79
    [5.1127007][5.1127007:1127029]()
    folder = args[i+1]
    [5.1127007]
    [5.1127029]
    da.folder = args[i+1]
  • edit in cmd/calls_detect_anomalies.go at line 81
    [5.1127039][5.1127039:1127040]()
  • replacement in cmd/calls_detect_anomalies.go at line 83
    [5.1127083][5.5953:6003]()
    return fmt.Errorf("--model requires a value")
    [5.1127083]
    [5.1127162]
    return da, fmt.Errorf("--model requires a value")
  • replacement in cmd/calls_detect_anomalies.go at line 85
    [5.1127167][5.1127167:1127205]()
    models = append(models, args[i+1])
    [5.1127167]
    [5.1127205]
    da.models = append(da.models, args[i+1])
  • edit in cmd/calls_detect_anomalies.go at line 87
    [5.1127215][5.1127215:1127216]()
  • replacement in cmd/calls_detect_anomalies.go at line 89
    [5.1127261][5.6004:6056]()
    return fmt.Errorf("--species requires a value")
    [5.1127261]
    [5.1127342]
    return da, fmt.Errorf("--species requires a value")
  • replacement in cmd/calls_detect_anomalies.go at line 91
    [5.1127347][5.1127347:1127387]()
    species = append(species, args[i+1])
    [5.1127347]
    [5.1127387]
    da.species = append(da.species, args[i+1])
  • edit in cmd/calls_detect_anomalies.go at line 93
    [5.1127397][5.1127397:1127398]()
  • replacement in cmd/calls_detect_anomalies.go at line 95
    [5.1127452][5.6057:6071](),[5.6071][5.1127466:1127467](),[5.1127466][5.1127466:1127467]()
    return nil
    [5.1127452]
    [5.1127467]
    return da, nil
  • replacement in cmd/calls_detect_anomalies.go at line 99
    [5.1127571][5.6072:6118]()
    return fmt.Errorf("unknown flag: %s", arg)
    [5.1127571]
    [5.1127585]
    return da, fmt.Errorf("unknown flag: %s", arg)
  • edit in cmd/calls_detect_anomalies.go at line 102
    [5.1127592]
    [5.1127592]
    return da, nil
    }
  • replacement in cmd/calls_detect_anomalies.go at line 105
    [5.1127593][5.1127593:1127612]()
    if folder == "" {
    [5.1127593]
    [5.1127672]
    // validateDetectAnomaliesArgs checks required flags.
    func validateDetectAnomaliesArgs(da detectAnomaliesArgs) error {
    if da.folder == "" {
  • replacement in cmd/calls_detect_anomalies.go at line 111
    [5.1127718][5.1127718:1127740]()
    if len(models) < 2 {
    [5.1127718]
    [5.1127814]
    if len(da.models) < 2 {
  • edit in cmd/calls_detect_anomalies.go at line 115
    [5.1127860]
    [5.1127860]
    return nil
    }
  • edit in cmd/calls_detect_anomalies.go at line 118
    [5.1127861]
    [5.315072]
    func runCallsDetectAnomalies(args []string) error {
    da, err := parseDetectAnomaliesArgs(args)
    if err != nil {
    return err
    }
    if err := validateDetectAnomaliesArgs(da); err != nil {
    return err
    }
  • replacement in cmd/calls_detect_anomalies.go at line 128
    [5.315138][5.1127927:1127985](),[5.1127927][5.1127927:1127985]()
    Folder: folder,
    Models: models,
    Species: species,
    [5.315138]
    [5.1127985]
    Folder: da.folder,
    Models: da.models,
    Species: da.species,
  • edit in cmd/calls_classify.go at line 6
    [5.1141729]
    [5.1141740]
    "strconv"
  • edit in cmd/calls_classify.go at line 78
    [5.2327]
    [5.7209]
    }
    // classifyFlagSet maps a flag name to a setter function.
    // The setter receives the value string and the current args state, returning the updated state.
    // isBool flags take no value argument.
    type classifyFlag struct {
    set func(val string, a *classifyArgs)
    isBool bool
  • replacement in cmd/calls_classify.go at line 88
    [5.7212][5.7212:7363]()
    // mustUniqueValue is like mustValue but exits if the flag was already set.
    func mustUniqueValue(args []string, i *int, flag, current string) string {
    [5.7212]
    [5.7363]
    // classifyFlags defines the flag dispatch table for parseClassifyArgs.
    var classifyFlags = map[string]classifyFlag{
    "--folder": {func(v string, a *classifyArgs) { a.folder = v }, false},
    "--file": {func(v string, a *classifyArgs) { a.file = v }, false},
    "--filter": {func(v string, a *classifyArgs) { a.filter = classifyUniqueSet(v, "--filter", a.filter) }, false},
    "--species": {func(v string, a *classifyArgs) { a.species = classifyUniqueSet(v, "--species", a.species) }, false},
    "--certainty": {func(v string, a *classifyArgs) { a.certainty = classifyIntValue(v, "--certainty", 0, 100) }, false},
    "--sample": {func(v string, a *classifyArgs) { a.sample = classifyIntValue(v, "--sample", 1, 100) }, false},
    "--goto": {func(v string, a *classifyArgs) { a.gotoFile = v }, false},
    "--location": {func(v string, a *classifyArgs) { a.location = classifyUniqueSet(v, "--location", a.location) }, false},
    "--night": {func(_ string, a *classifyArgs) { a.night = true }, true},
    "--day": {func(_ string, a *classifyArgs) { a.day = true }, true},
    }
    // classifyUniqueSet sets a string field, exiting if already set.
    func classifyUniqueSet(val, flag, current string) string {
  • replacement in cmd/calls_classify.go at line 108
    [5.7472][5.7472:7505]()
    return mustValue(args, i, flag)
    [5.7472]
    [5.8224]
    return val
    }
    // classifyIntValue parses and validates an integer flag value.
    func classifyIntValue(val, flag string, lo, hi int) int {
    v, err := strconv.Atoi(val)
    if err != nil {
    fmt.Fprintf(os.Stderr, "Error: %s must be an integer\n", flag)
    os.Exit(1)
    }
    if v < lo || v > hi {
    fmt.Fprintf(os.Stderr, "Error: %s must be between %d and %d\n", flag, lo, hi)
    os.Exit(1)
    }
    return v
  • edit in cmd/calls_classify.go at line 126
    [5.8303][5.7506:7576]()
    // Uses mustValue for flag-value extraction (exits on missing value).
  • replacement in cmd/calls_classify.go at line 132
    [5.1145577][5.1145577:1145612](),[5.1145612][5.7577:7623](),[5.7623][5.1145755:1145772](),[5.1145755][5.1145755:1145772](),[5.1145772][5.7624:7666](),[5.7666][5.1145911:1145930](),[5.1145911][5.1145911:1145930](),[5.1145930][5.7667:7729](),[5.7729][5.1146189:1146209](),[5.1146189][5.1146189:1146209](),[5.1146209][5.7730:7795](),[5.7795][5.1146472:1146494](),[5.1146472][5.1146472:1146494](),[5.1146494][5.7796:7859](),[5.7859][5.8732:8751](),[5.1146902][5.8732:8751](),[5.8751][5.7860:7917](),[5.7917][5.8813:8830](),[5.1147292][5.8813:8830](),[5.8830][5.7918:7964](),[5.7964][5.2328:2349](),[5.9034][5.2328:2349](),[5.2349][5.7965:8033](),[5.8033][5.9114:9196](),[5.1147770][5.9114:9196](),[5.9196][5.1147771:1147794](),[5.1147771][5.1147771:1147794]()
    switch arg {
    case "--folder":
    a.folder = mustValue(args, &i, "--folder")
    case "--file":
    a.file = mustValue(args, &i, "--file")
    case "--filter":
    a.filter = mustUniqueValue(args, &i, "--filter", a.filter)
    case "--species":
    a.species = mustUniqueValue(args, &i, "--species", a.species)
    case "--certainty":
    a.certainty = mustIntValue(args, &i, "--certainty", 0, 100)
    case "--sample":
    a.sample = mustIntValue(args, &i, "--sample", 1, 100)
    case "--goto":
    a.gotoFile = mustValue(args, &i, "--goto")
    case "--location":
    a.location = mustUniqueValue(args, &i, "--location", a.location)
    case "--night":
    a.night = true
    i++
    case "--day":
    a.day = true
    i++
    case "--help", "-h":
    [5.1145577]
    [5.1147794]
    if arg == "--help" || arg == "-h" {
  • replacement in cmd/calls_classify.go at line 135
    [5.1148410][5.1148410:1148421]()
    default:
    [5.1147832]
    [5.1148421]
    }
    fl, ok := classifyFlags[arg]
    if !ok {
  • edit in cmd/calls_classify.go at line 142
    [5.1148525]
    [5.1148525]
    if fl.isBool {
    fl.set("", &a)
    i++
    } else {
    fl.set(mustValue(args, &i, arg), &a)
    }
  • edit in cmd/calls_classify.go at line 155
    [5.8075]
    [5.10577]
    if err := a.validateSampleCertainty(); err != nil {
    return err
    }
    if err := a.validateSource(); err != nil {
    return err
    }
    return a.validateDayNight()
    }
    func (a classifyArgs) validateSampleCertainty() error {
  • edit in cmd/calls_classify.go at line 168
    [5.10726]
    [5.10726]
    return nil
    }
    func (a classifyArgs) validateSource() error {
  • edit in cmd/calls_classify.go at line 176
    [5.1149710]
    [5.10868]
    return nil
    }
    func (a classifyArgs) validateDayNight() error {
  • edit in cmd/calls_classify.go at line 232
    [5.11423][5.11423:11484](),[5.11484][5.9126:9171](),[5.9171][5.11523:11553](),[5.11523][5.11523:11553](),[5.11553][5.9172:9226]()
    // RunCallsClassify handles the "calls classify" subcommand
    func RunCallsClassify(args []string) error {
    a := parseClassifyArgs(args)
    if err := a.validate(); err != nil {
    return err
    }
  • replacement in cmd/calls_classify.go at line 233
    [5.11568][5.11568:11643]()
    // Load reviewer, bindings, and display flags from ~/.skraak/config.json.
    [5.11568]
    [5.11643]
    // loadClassifyConfig loads and validates the classify config from disk.
    func loadClassifyConfig() (utils.Config, string, []calls.KeyBinding, error) {
  • replacement in cmd/calls_classify.go at line 238
    [5.11875][5.9227:9274]()
    return fmt.Errorf("loading config: %w", err)
    [5.11875]
    [5.1151534]
    return cfg, cfgPath, nil, fmt.Errorf("loading config: %w", err)
  • edit in cmd/calls_classify.go at line 241
    [5.1151538][5.11889:11918]()
    // Validate config contents
  • replacement in cmd/calls_classify.go at line 242
    [5.11952][5.9275:9343]()
    return fmt.Errorf("%s is missing \"classify.reviewer\"", cfgPath)
    [5.11952]
    [5.12047]
    return cfg, cfgPath, nil, fmt.Errorf("%s is missing \"classify.reviewer\"", cfgPath)
  • replacement in cmd/calls_classify.go at line 245
    [5.12088][5.9344:9436]()
    return fmt.Errorf("%s is missing \"classify.bindings\" (need at least one key)", cfgPath)
    [5.12088]
    [5.12207]
    return cfg, cfgPath, nil, fmt.Errorf("%s is missing \"classify.bindings\" (need at least one key)", cfgPath)
  • replacement in cmd/calls_classify.go at line 250
    [5.9504][5.9504:9517]()
    return err
    [5.9504]
    [5.9517]
    return cfg, cfgPath, nil, err
  • edit in cmd/calls_classify.go at line 252
    [5.9520]
    [5.12256]
    return cfg, cfgPath, bindings, nil
    }
  • replacement in cmd/calls_classify.go at line 255
    [5.12257][5.1151538:1151565](),[5.1151538][5.1151538:1151565]()
    // Parse species+calltype
    [5.12257]
    [5.12258]
    // buildClassifyConfig constructs the ClassifyConfig from parsed args and loaded config.
    func buildClassifyConfig(a classifyArgs, cfg utils.Config, bindings []calls.KeyBinding) (calls.ClassifyConfig, error) {
  • edit in cmd/calls_classify.go at line 259
    [5.1151628][5.2541:2582]()
    // Parse location into lat/lng/timezone
  • edit in cmd/calls_classify.go at line 262
    [5.2648]
    [5.2664]
    var err error
  • replacement in cmd/calls_classify.go at line 265
    [5.2742][5.9521:9571]()
    return fmt.Errorf("parsing location: %w", err)
    [5.2742]
    [5.2802]
    return calls.ClassifyConfig{}, fmt.Errorf("parsing location: %w", err)
  • replacement in cmd/calls_classify.go at line 269
    [5.2810][5.1151628:1151645](),[5.1151628][5.1151628:1151645](),[5.1151645][5.315524:315557]()
    // Build config
    config := calls.ClassifyConfig{
    [5.2810]
    [5.12323]
    return calls.ClassifyConfig{
  • edit in cmd/calls_classify.go at line 290
    [5.2894]
    [5.1152351]
    }, nil
    }
    // RunCallsClassify handles the "calls classify" subcommand
    func RunCallsClassify(args []string) error {
    a := parseClassifyArgs(args)
    if err := a.validate(); err != nil {
    return err
    }
    cfg, _, bindings, err := loadClassifyConfig()
    if err != nil {
    return err
  • edit in cmd/calls_classify.go at line 305
    [5.1152355]
    [5.1152355]
    config, err := buildClassifyConfig(a, cfg, bindings)
    if err != nil {
    return err
    }
  • edit in CHANGELOG.md at line 4
    [5.1198010]
    [3.22905]
    ## [2026-05-14] Reduce cyclomatic complexity of 10 functions below threshold of 10
    Reduced complexity of all 10 functions previously scoring 13 on gocyclo.
    Refactoring focused on extracting helpers, introducing table-driven tests,
    and grouping initialization logic into context structs.
    ### Changed
    - `utils/filename_parser_test.go`: `TestParseFilenameTimestamps` → table-driven with `runParseTestCase` helper (13→4)
    - `tools/prepend.go`: `Prepend` → extracted `prependFolders` and `processPrependFile` (13→6)
    - `tools/pattern_test.go`: `TestCreateOrUpdatePattern_CreateDuplicate` → extracted `testExistingPattern`, `testUniquePattern`, `testIdempotentCreate` (13→2)
    - `tools/integration_test.go`: `testCreateClusterWithPattern` → extracted `lookupActiveDatasetAndLocation` and `verifyClusterPattern` (13→2)
    - `tools/calls/calls_from_common.go`: `callsFromSourceSequential` → extracted `processSequentialFile` (13→7)
    - `tools/calls/calls_clip_labels.go`: `CallsClipLabels` → extracted `initClipLabelsContext` and `clipLabelsContext` struct (13→6); also reduced `parseClipLabelsDataFiles` (12→7), `loadExistingRows` (11→6), `resolveSegments` (11→6) via helper extraction
    - `cmd/calls_modify.go`: `parseModifyArgs` → extracted `modifyFlagHandler` and `handleUnknownFlag` (13→6)
    - `cmd/calls_detect_anomalies.go`: `runCallsDetectAnomalies` → extracted `parseDetectAnomaliesArgs` and `validateDetectAnomaliesArgs` (13→5)
    - `cmd/calls_classify.go`: `RunCallsClassify` → extracted `loadClassifyConfig` and `buildClassifyConfig` (13→9); `parseClassifyArgs` → map-based dispatch with `classifyFlags` table (13→6); `validate` → split into `validateSampleCertainty`, `validateSource`, `validateDayNight` (11→3)
  • edit in CHANGELOG.md at line 22
    [3.22906]
    [3.22906]
    ### Added
    - `tools/prepend_test.go`: `TestProcessPrependFile` - table-driven unit test for extracted `processPrependFile`