cluster.go
package cmd
import (
"context"
"flag"
"fmt"
"strconv"
"skraak/tools"
)
// RunClusterCreate creates a new cluster for grouping recordings.
//
// JSON output schema:
//
// {
// "cluster": {
// "id": string, // Cluster ID (12 characters)
// "dataset_id": string, // Parent dataset ID
// "location_id": string, // Parent location ID
// "name": string, // Cluster name
// "description": string, // Optional description (nullable)
// "created_at": string, // Creation timestamp (RFC3339)
// "last_modified": string, // Last modification timestamp (RFC3339)
// "active": bool, // Whether the cluster is active
// "cyclic_recording_pattern_id": string, // Optional pattern ID (nullable)
// "sample_rate": int // Sample rate in Hz
// },
// "message": string // Success message
// }
func RunClusterCreate(args []string) error {
fs := flag.NewFlagSet("cluster create", flag.ExitOnError)
dbPath := fs.String("db", "", "Path to DuckDB database (required)")
datasetID := fs.String("dataset", "", "Dataset ID (required)")
locationID := fs.String("location", "", "Location ID (required)")
name := fs.String("name", "", "Cluster name (required)")
sampleRate := fs.String("sample-rate", "", "Sample rate in Hz (required)")
description := fs.String("description", "", "Cluster description (optional)")
cyclicPattern := fs.String("cyclic-recording-pattern", "", "Cyclic recording pattern ID (optional)")
fs.Usage = usagePrinter(fs,
"skraak cluster create [options]",
"Create a new cluster for grouping recordings.",
"skraak cluster create --db ./db/skraak.duckdb --dataset abc123 --location loc456 --name \"2024-01\" --sample-rate 250000",
)
if err := fs.Parse(args); err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
if err := requireFlags(fs, map[string]any{
"--db": *dbPath,
"--dataset": *datasetID,
"--location": *locationID,
"--name": *name,
"--sample-rate": *sampleRate,
}); err != nil {
return err
}
sr, err := strconv.Atoi(*sampleRate)
if err != nil {
return fmt.Errorf("invalid sample rate: %w", err)
}
defer initEventLog(*dbPath)()
input := tools.ClusterInput{
DBPath: *dbPath,
DatasetID: datasetID,
LocationID: locationID,
Name: name,
SampleRate: &sr,
Description: description,
}
if *cyclicPattern != "" {
input.CyclicRecordingPatternID = cyclicPattern
}
output, err := tools.CreateOrUpdateCluster(context.Background(), input)
if err != nil {
return fmt.Errorf("creating cluster: %w", err)
}
return printJSON(output)
}
// RunClusterUpdate updates an existing cluster.
//
// JSON output schema: same as RunClusterCreate
func RunClusterUpdate(args []string) error {
fs := flag.NewFlagSet("cluster update", flag.ExitOnError)
dbPath := fs.String("db", "", "Path to DuckDB database (required)")
id := fs.String("id", "", "Cluster ID (required)")
name := fs.String("name", "", "New cluster name (optional)")
sampleRate := fs.String("sample-rate", "", "New sample rate in Hz (optional)")
description := fs.String("description", "", "New cluster description (optional)")
cyclicPattern := fs.String("cyclic-recording-pattern", "", "New cyclic recording pattern ID (optional, empty string clears it)")
clearCyclicPattern := fs.Bool("clear-cyclic-recording-pattern", false, "Clear the cyclic recording pattern (set to NULL)")
fs.Usage = usagePrinter(fs,
"skraak cluster update [options]",
"Update an existing cluster. Only provided fields are updated.",
"skraak cluster update --db ./db/skraak.duckdb --id clust123 --name \"New Name\"",
)
if err := fs.Parse(args); err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
if err := requireFlags(fs, map[string]any{"--db": *dbPath, "--id": *id}); err != nil {
return err
}
var sr *int
if *sampleRate != "" {
srVal, err := strconv.Atoi(*sampleRate)
if err != nil {
return fmt.Errorf("invalid sample rate: %w", err)
}
sr = &srVal
}
defer initEventLog(*dbPath)()
input := tools.ClusterInput{
DBPath: *dbPath,
ID: id,
}
if *name != "" {
input.Name = name
}
if sr != nil {
input.SampleRate = sr
}
if *description != "" {
input.Description = description
}
if *cyclicPattern != "" {
input.CyclicRecordingPatternID = cyclicPattern
} else if *clearCyclicPattern {
empty := ""
input.CyclicRecordingPatternID = &empty
}
output, err := tools.CreateOrUpdateCluster(context.Background(), input)
if err != nil {
return fmt.Errorf("updating cluster: %w", err)
}
return printJSON(output)
}