# MCA SDK for Go

OpenTelemetry instrumentation library for ML model monitoring in Go.

## Installation

```bash
go get github.com/baptisthealth/mca-sdk-go
```

## Quick Start

```go
package main

import (
    "context"
    "log"

    mca "github.com/baptisthealth/mca-sdk-go"
    "github.com/baptisthealth/mca-sdk-go/config"
)

func main() {
    // Create client configuration
    cfg := &config.Config{
        ServiceName: "readmission-model",
        ModelID:     "mdl-001",
        TeamName:    "clinical-ai",
        TLSInsecure: true, // Only for local development
    }

    // Initialize client with context
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    client, err := mca.NewClient(ctx, cfg)
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
    }
    defer client.Shutdown()

    // Record predictions
    latency := 0.15
    pred := &mca.Prediction{
        Latency:      &latency, // Use pointer to distinguish 0.0 from nil
        PredictionID: "pred-123",
        Attributes: map[string]string{
            "model_version": "2.0",
            "input_size":    "1024",
        },
    }

    if err := client.RecordPrediction(context.Background(), pred); err != nil {
        log.Printf("Failed to record prediction: %v", err)
    }
}
```

## Configuration

### Required Fields

- `ServiceName` - Service name for resource attributes
- `ModelID` - Model identifier
- `TeamName` - Team name for resource attributes

### Optional Fields (with defaults)

- `ModelVersion` - Model version (default: "1.0.0")
- `ModelType` - Model type: internal, vendor, genai, agentic (default: "internal")
- `ModelCategory` - Model category: internal, external (default: "internal")
- `CollectorEndpoint` - OTel Collector endpoint (default: "http://localhost:4318")
- `Protocol` - OTLP protocol: http, grpc (default: "http")
- `TLSInsecure` - Allow insecure TLS (default: false, set true only for local dev)

### Configuration from Environment

```go
cfg := config.LoadFromEnv()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
client, err := mca.NewClient(ctx, cfg)
```

Environment variables:
- `MCA_SERVICE_NAME` - Service name (required)
- `MCA_MODEL_ID` - Model ID (required)
- `MCA_TEAM_NAME` - Team name (required)
- `MCA_MODEL_VERSION` - Model version
- `MCA_COLLECTOR_ENDPOINT` - Collector endpoint
- `MCA_OTEL_PROTOCOL` - Protocol (http or grpc)
- `MCA_TLS_INSECURE` - Allow insecure TLS (true/false)

## API Reference

### `NewClient(ctx context.Context, cfg *config.Config) (*Client, error)`

Creates and initializes a new MCA Client. The context is used for provider initialization with timeout to prevent hangs.

### `RecordPrediction(ctx context.Context, pred *Prediction) error`

Records a model prediction with metrics, logs, and traces. Creates a trace span for correlation, increments the prediction counter, and records latency if provided (use `*float64` pointer - `nil` means no latency, `&0.0` records zero latency).

### `Shutdown() error`

Gracefully shuts down all OpenTelemetry providers. Use with `defer` for automatic cleanup.

## Important Notes

### Latency as Pointer

`Prediction.Latency` uses `*float64` to distinguish between:
- `nil` - No latency provided
- `&0.0` - Valid zero latency (e.g., cache hit)

```go
// No latency
pred := &mca.Prediction{PredictionID: "pred-1"}

// Zero latency
latency := 0.0
pred := &mca.Prediction{Latency: &latency, PredictionID: "pred-2"}

// Non-zero latency
latency := 0.15
pred := &mca.Prediction{Latency: &latency, PredictionID: "pred-3"}
```

### Attribute Limits

- Maximum 50 custom attributes per prediction
- String values truncated to 256 characters
- Excess attributes are ignored with warning logged

## Idiomatic Go Patterns

### Error Handling

```go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
client, err := mca.NewClient(ctx, cfg)
if err != nil {
    return fmt.Errorf("failed to create client: %w", err)
}
```

### Context Propagation

```go
ctx := context.Background()
err := client.RecordPrediction(ctx, pred)
```

### Resource Cleanup with Defer

```go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
client, err := mca.NewClient(ctx, cfg)
if err != nil {
    return err
}
defer client.Shutdown()
```

### Goroutine Safety

The client is goroutine-safe and can be used concurrently:

```go
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        latency := 0.15
        pred := &mca.Prediction{Latency: &latency}
        client.RecordPrediction(ctx, pred)
    }(i)
}
wg.Wait()
```

## Testing

```bash
# Run unit tests
go test -v ./...

# Run tests with race detection
go test -race ./...

# Run tests with coverage
go test -cover ./...
```

## Architecture

The SDK connects to OpenTelemetry Collector via OTLP/HTTP:

```
Go Application
    ↓
MCA SDK Client
    ↓
OTLP/HTTP (port 4318)
    ↓
OpenTelemetry Collector
    ↓
Backends (Prometheus, Cloud Trace, Cloud Logging)
```

## Requirements

- Go 1.21+
- OpenTelemetry Collector running on localhost:4318 (or configured endpoint)

## License

Apache 2.0
