npm package: @solidstudio.io/observability-ai-client
Description: TypeScript SDK for Observability AI API - EV charging session anomaly detection
Author: SolidStudio
Latest version: 0.0.1

# @solidstudio.io/observability-ai-client

TypeScript SDK for the [Observability AI API](https://solidstudio.ai/developers) — EV charging session anomaly detection and predictive maintenance.

## Installation

```bash
npm install @solidstudio.io/observability-ai-client
```

## Quick Start

```typescript
import { ObservabilityAIClient } from '@solidstudio.io/observability-ai-client';

const client = new ObservabilityAIClient({
  token: 'your-api-key',
  baseUrl: 'https://api.solidstudio.ai', // production (default)
});

// Create a charging session with OCPP meter values
const session = await client.sessions.create({
  sessionId: 'sess-001',
  chargingStationId: 'station-1',
  connectorId: 'conn-1',
  meterValues: [
    { timestamp: '2025-01-15T10:00:00Z', value: 100, measurand: 'Energy.Active.Import.Register', unit: 'Wh' },
    { timestamp: '2025-01-15T10:05:00Z', value: 200, measurand: 'Energy.Active.Import.Register', unit: 'Wh' },
    { timestamp: '2025-01-15T10:10:00Z', value: 300, measurand: 'Energy.Active.Import.Register', unit: 'Wh' },
  ],
});

console.log(session.status, session.anomalies);
```

## Configuration

```typescript
const client = new ObservabilityAIClient({
  token: 'your-api-key',                    // Required
  baseUrl: 'https://api.solidstudio.ai',     // Optional - production default
  // baseUrl: 'https://api.dev.solidstudio.ai/api/saas', // dev environment
  timeout: 30000,                            // Optional - request timeout in ms
  timezone: 'Europe/Warsaw',                 // Optional - IANA timezone for responses
  headers: { 'X-Custom': 'value' },          // Optional - additional headers
});
```

## API Resources

### Sessions

```typescript
// Create a session (auto-runs analysis pipeline if >= 3 meter values)
const session = await client.sessions.create({
  sessionId: 'sess-001',
  chargingStationId: 'station-1',
  connectorId: 'conn-1',
  evseId: 'evse-1',
  metadata: { source: 'ocpp-gateway' },
  meterValues: [
    { timestamp: '2025-01-15T10:00:00Z', value: 100, measurand: 'Energy.Active.Import.Register', unit: 'Wh' },
    { timestamp: '2025-01-15T10:05:00Z', value: 200, measurand: 'Energy.Active.Import.Register', unit: 'Wh' },
    { timestamp: '2025-01-15T10:10:00Z', value: 300, measurand: 'Energy.Active.Import.Register', unit: 'Wh' },
  ],
});

// Replace all meter values for a session
await client.sessions.replace('sess-001', {
  meterValues: [/* new complete set of meter values */],
  status: 'completed', // optional: mark session status
});

// Append/update meter values incrementally
await client.sessions.update('sess-001', {
  meterValues: [
    { timestamp: '2025-01-15T10:15:00Z', value: 400, measurand: 'Energy.Active.Import.Register', unit: 'Wh' },
  ],
});

// List sessions with filters
const sessions = await client.sessions.list({
  chargingStationId: 'station-1',
  isAnomaly: true,
  minScore: 0.5,
  dateFrom: '2025-01-01',
  dateTo: '2025-01-31',
  page: 1,
  pageSize: 20,
});

// Get full session details
const detail = await client.sessions.get('sess-001');
```

### LLM (Explain & Ask)

```typescript
// Generate AI explanation of session anomalies
const explanation = await client.sessions.explain('sess-001', { lang: 'en' });
console.log(explanation.explanation);

// Get cached explanation (no tokens consumed)
const cached = await client.sessions.getExplanation('sess-001', { lang: 'en' });

// Ask a natural language question about a session
const answer = await client.sessions.ask('sess-001', {
  question: 'What caused the energy pit anomaly?',
  lang: 'en',
});
console.log(answer.answer);
```

### Batch Processing

```typescript
// Submit multiple sessions for async analysis
const batch = await client.batches.submit({
  sessions: [
    { sessionId: 'sess-001', chargingStationId: 'st-1', connectorId: 'c-1', meterValues: [/*...*/] },
    { sessionId: 'sess-002', chargingStationId: 'st-1', connectorId: 'c-2', meterValues: [/*...*/] },
  ],
});
console.log(batch.batchId, batch.status); // 'pending'

// Poll batch status (paginated results)
const status = await client.batches.getStatus(batch.batchId, { page: 1, pageSize: 50 });
console.log(status.status, status.processedSessions, status.totalSessions);
console.log(status.results); // SessionAnalysisResponse[] when completed
```

### Stations

```typescript
// List stations with aggregated anomaly statistics
const stations = await client.stations.list({ page: 1, pageSize: 20 });

// Lookup all station IDs
const ids = await client.stations.lookup();

// Get station details (connectors, sessions, anomaly counts)
const station = await client.stations.get('charging-station-id');

// Per-connector breakdown
const connectors = await client.stations.getConnectors('charging-station-id');

// Latest SHADE inference result
const inference = await client.stations.getInference('charging-station-id');

// Station anomaly history
const anomalies = await client.stations.listAnomalies('charging-station-id', { page: 1, pageSize: 20 });

// SHADE overview (energy timeline + anomaly markers)
const overview = await client.stations.getShadeOverview('charging-station-id');
console.log(overview.dayScore, overview.isAnomaly, overview.energyTimeline);
```

### Anomalies

```typescript
// List anomalies with filters
const anomalies = await client.anomalies.list({
  anomalyType: 'ENERGY_PIT',
  chargingPointId: 'cp-uuid',
  sortByScore: 'desc',
  page: 1,
  pageSize: 50,
});

// List anomaly sessions
const sessions = await client.anomalies.listSessions({
  anomalyType: 'KWH_DECREASE',
  occurrenceFrom: '2025-01-01T00:00:00Z',
});

// List anomaly connectors
const connectors = await client.anomalies.listConnectors({
  sortBy: 'anomalyCount',
});

// Get anomaly detail for a specific session + category
const detail = await client.anomalies.getDetail('session-id', 'category-id');

// Update anomaly issue status
await client.anomalies.updateIssue('issue-id', {
  status: 'RESOLVED',
  assignedTo: 'user-uuid',
});
```

### Statistics

```typescript
const stats = await client.statistics.get();
console.log(stats.totalSessions, stats.totalAnomaliesCount, stats.revenueAtRisk);
```

### Feedback

```typescript
await client.feedback.submit({
  category: 'GENERAL', // 'GENERAL' | 'ANOMALY_DETAIL' | 'SESSION_DETAIL' | 'LLM_EXPLANATION'
  rating: 5,
  comment: 'Great anomaly detection!',
  referenceId: 'session-id', // optional: link feedback to a specific resource
});
```

## Error Handling

```typescript
import {
  ObservabilityAIError,
  ObservabilityAIAuthError,
  ObservabilityAINotFoundError,
  ObservabilityAIPaymentRequiredError,
  ObservabilityAIRateLimitError,
  ObservabilityAIValidationError,
} from '@solidstudio.io/observability-ai-client';

try {
  await client.sessions.explain('sess-001');
} catch (error) {
  if (error instanceof ObservabilityAIAuthError) {
    // 401/403 - invalid or missing token
  } else if (error instanceof ObservabilityAIPaymentRequiredError) {
    // 402 - insufficient tokens
  } else if (error instanceof ObservabilityAINotFoundError) {
    // 404 - resource not found
  } else if (error instanceof ObservabilityAIRateLimitError) {
    // 429 - rate limit exceeded
  } else if (error instanceof ObservabilityAIValidationError) {
    // 422 - validation error
    console.log(error.details); // [{loc: [...], msg: '...', type: '...'}]
  } else if (error instanceof ObservabilityAIError) {
    // Other API errors (500, 503, etc.)
    console.log(error.statusCode, error.message);
  }
}
```

## Timezone Support

```typescript
const client = new ObservabilityAIClient({
  token: 'your-token',
  timezone: 'Europe/Warsaw', // IANA timezone name
});
// All datetime fields in responses will use the specified timezone
```

## Anomaly Categories

| Category | Description |
|---|---|
| `CHARGING_EXPECTED` | Charging expected but not occurring |
| `KWH_DECREASE` | Energy counter decreased |
| `UNEXPECTED_KWH_ACCUMULATE` | Unexpected energy accumulation |
| `ENERGY_PIT` | Sudden drop in energy delivery |
| `INCORRECT_ACTIVE_POWER_REGISTER` | Power register inconsistency |
| `UNEXPECTED_CHARGE_LEVEL_CHANGES` | Unexpected SoC changes |
| `ZERO_SESSION` | No energy delivered |

## Requirements

- Node.js >= 18.0.0 (uses native `fetch`)
- Zero runtime dependencies

## Development

```bash
npm install
cp .env.example .env       # Configure API key and environment
npm run build              # Build (ESM + CJS)
npm run test               # Run unit tests
npm run verify             # Run verification against live API (uses .env)
npm run typecheck          # Type check
npm run lint               # Lint
npm run format             # Format
```

## License

MIT