# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with the concentriq-ls-client project.

## Project Overview

Python client library for Proscia Concentriq Life Sciences API. Supports all three API versions (V1, V2, V3) with multiple authentication methods.

**Stack:**
- Python 3.10+
- `uv` package manager
- `httpx` for HTTP client
- `pydantic` v2 for models
- `pytest` for testing

## Development Philosophy: Extreme Programming (XP) & TDD

### Core Principles

**We are doing CANONICAL EXTREME PROGRAMMING here:**

1. **Test-Driven Development (TDD)**: Always RED → GREEN → REFACTOR
   - RED: Write failing test first
   - GREEN: Write minimal code to pass
   - REFACTOR: Clean up while keeping tests green

2. **Test Behavior, Not Implementation**
   - DO NOT test private methods (methods starting with `_`)
   - Test public API and observable behavior
   - Tests should describe WHAT the code does, not HOW it does it
   - Example: Test that `client.v1.image_sets.list()` returns ImageSet objects, don't test internal `_request()` calls

3. **Small, Focused Commits**
   - Each feature gets: test commit → implementation commit → refactor commit (if needed)
   - Commit message format: `[RED|GREEN|REFACTOR] Feature description`

4. **No Speculative Generality**
   - Don't build features we don't need yet
   - Solve the problem at hand
   - Refactor when we see duplication (rule of three)

### Testing Guidelines

**Test File Location:**
- Unit tests: `tests/unit/test_<feature>.py`
- Integration tests: `tests/integration/test_<feature>.py`

**Test Structure Pattern:**
```python
"""Tests for <Feature>."""

import pytest
from pytest_httpx import HTTPXMock

from concentriq import ConcentriqClient
from concentriq.auth import ApiKeyAuth


class Test<Feature>:
    """Tests for <feature> behavior."""

    def test_<specific_behavior>(self, httpx_mock: HTTPXMock):
        """Test should describe WHAT it verifies."""
        # Arrange: Setup mock responses
        httpx_mock.add_response(json={"data": "..."})

        # Act: Call the public API
        client = ConcentriqClient(
            base_url="https://test.concentriq.com",
            auth=ApiKeyAuth("test-key")
        )
        result = client.v1.resource.method()

        # Assert: Verify behavior
        assert result == expected_value
```

**What to Test (Public API):**
```python
# ✅ GOOD - Testing behavior
def test_list_image_sets_returns_list():
    """List should return a list of results."""
    httpx_mock.add_response(json={"data": [...]})
    client = ConcentriqClient(...)
    result = client.v1.image_sets.list()
    assert isinstance(result, list)

def test_get_image_set_raises_not_found_for_invalid_id():
    """Get should raise NotFoundError for invalid ID."""
    httpx_mock.add_response(status_code=404)
    client = ConcentriqClient(...)
    with pytest.raises(NotFoundError):
        client.v1.image_sets.get(id=999999)
```

**What NOT to Test (Implementation):**
```python
# ❌ BAD - Testing internal methods
def test_request_method_adds_headers():
    client = ConcentriqClient(...)
    response = client._request("GET", "/api/test")  # Don't test private methods!

# ❌ BAD - Testing implementation details
def test_constructs_correct_url():
    """Tests HOW it works, not WHAT it does"""
    # Don't test URL construction, test behavior
```

**Mocking Strategy:**
- Use `pytest-httpx` HTTPXMock fixture to mock HTTP responses
- Mock external HTTP calls, not the client code itself
- Don't mock the code you're testing
- Example:
```python
def test_example(httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url="https://test.concentriq.com/api/test",
        json={"data": "response"}
    )
    # Now test the real client code
```

### Development Workflow

**Use TODO.md as single source of truth:**
- Check off items as they're completed
- Add new items as they're discovered
- Keep it up to date

**For each feature:**
1. Check TODO.md for next uncompleted item
2. Write test(s) that describe the behavior (RED phase)
3. Run tests to confirm they fail
4. Implement minimal code to pass tests (GREEN phase)
5. Run tests to confirm they pass
6. Refactor if needed while keeping tests green
7. Update TODO.md
8. Commit

## Project Structure

```
concentriq-ls-client/
├── src/concentriq/
│   ├── client.py           # Main ConcentriqClient class
│   ├── exceptions.py       # Custom exceptions
│   ├── auth/               # Authentication handlers
│   ├── models/             # Pydantic models (shared across versions)
│   └── api/
│       ├── v1/             # V1 resource clients (/api/*)
│       ├── v2/             # V2 resource clients (/api/v2/*)
│       └── v3/             # V3 resource clients (/api/v3/*)
└── tests/
    ├── unit/               # Fast, isolated tests with mocked HTTP
    └── integration/        # Real API tests (require credentials)
```

## Common Commands

### Development Setup
```bash
# Install dependencies
uv sync --all-extras

# Run tests
uv run pytest tests/unit/ -v

# Run specific test file
uv run pytest tests/unit/test_client.py -v

# Run with coverage
uv run pytest tests/unit/ --cov=concentriq --cov-report=term-missing

# Type checking
uv run mypy src/concentriq

# Linting
uv run ruff check src/ tests/
```

### TDD Cycle Commands
```bash
# RED: Run tests (should fail for new feature)
uv run pytest tests/unit/test_new_feature.py -v

# GREEN: Run tests (should pass after implementation)
uv run pytest tests/unit/test_new_feature.py -v

# Run all tests to ensure nothing broke
uv run pytest tests/unit/ -v
```

## API Structure Reference

### Authentication
- `BasicAuth(username, password)` - HTTP Basic
- `ApiKeyAuth(api_key)` - concentriq-api-key header
- `JWTAuth(token)` - Bearer token
- `SessionAuth(session_cookie)` - connect.sid cookie

### Client Usage
```python
from concentriq import ConcentriqClient
from concentriq.auth import ApiKeyAuth

with ConcentriqClient(
    base_url="https://app.concentriq.com",
    auth=ApiKeyAuth(api_key="...")
) as client:
    # V1 API
    image_sets = client.v1.image_sets.list()

    # V2 API
    groups = client.v2.user_groups.list()

    # V3 API
    token = client.v3.auth.create_token()
```

## Reference Documentation

### Concentriq RX Repository
**Location:** `/Users/kyriakostoulgaridis/techops/concentriq-rx` (sibling directory)

### API Specifications

**V1 API (Legacy - Most Complete):**
- Spec: `/Users/kyriakostoulgaridis/techops/concentriq-rx/api-docs-base.json`
- OpenAPI 3.0 specification with 54+ documented endpoints
- Covers: ImageSets, Images, Folders, Annotations, Users, Organizations, Metadata, Templates, Workflows, Orders
- Note: ~30-40% of endpoints undocumented, use controllers as reference

**V2 API (Limited Scope):**
- No committed spec file
- Only 3 resources: user_groups, modules, saved_display_settings
- Defined in: `backend/controllers/api/lib/controllerBuilder.js`

**V3 API (Modern):**
- Generated at runtime from `/api/v3/docs` endpoint
- 15+ controllers in: `/Users/kyriakostoulgaridis/techops/concentriq-rx/services/api/src/Controllers/`
- Includes: Auth, Studies, AuditLogs, ChannelGroups, AnnotationClasses, AppConfig

### TypeScript SDK Reference
**Location:** `/Users/kyriakostoulgaridis/techops/concentriq-rx/sdk/typescript/`

**Key Files:**
- Client wrapper: `sdk/typescript/src/CrClient.ts`
- Generator config: `sdk/typescript/generator/config.json`
- Generation script: `sdk/typescript/generator/generate.sh`

**Pattern to Follow:**
```typescript
// CrClient.ts wraps generated API with convenience methods
export class CrClient {
  constructor(private config: ClientConfig) {
    this.api = new GeneratedAPI(config)
  }

  // Convenience wrappers
  async getImageSet(id: number) {
    return this.api.imageSets.getImageSet(id)
  }
}
```

### Backend Controllers (V1 Reference)
**Location:** `/Users/kyriakostoulgaridis/techops/concentriq-rx/backend/controllers/api/`

When V1 OpenAPI spec is incomplete, reference actual controller implementations:
- `imageSets.js` - ImageSet CRUD and navigation
- `images.js` - Image operations
- `folders.js` - Folder management
- `annotations.js` - Annotation CRUD
- `users.js` - User management
- `metadata.js` - Metadata fields and values
- `savedSearches.js` - Saved search queries
- `records.js` - Record operations

### Authentication Implementation Reference
- V1 Auth: `/Users/kyriakostoulgaridis/techops/concentriq-rx/backend/controllers/api/auth.js`
- V3 Auth: `/Users/kyriakostoulgaridis/techops/concentriq-rx/services/api/src/Controllers/Auth.ts`
- API Key: `/Users/kyriakostoulgaridis/techops/concentriq-rx/services/api/src/ApiKeyAuth.ts`
- JWT: `/Users/kyriakostoulgaridis/techops/concentriq-rx/services/api/src/JWTAuth/JWTAuth.ts`

### API Response Format (V1)
```json
{
  "meta": {
    "duration": 123,
    "userId": 456,
    "pagination": {
      "rowsPerPage": 20,
      "page": 1,
      "sortBy": "created",
      "descending": false
    },
    "status": 200
  },
  "data": { ... }
}
```

### Key Findings from Research

**API Versions Priority:**
- V1 (`/api/*`) - Most complete, 24+ controllers, should be primary focus
- V2 (`/api/v2/*`) - Limited to 3 resources, lower priority
- V3 (`/api/v3/*`) - Modern but smaller scope, auth and newer features

**OpenAPI Quality:**
- V1 spec is incomplete (missing required fields, error responses)
- Pure code generation NOT viable
- Hand-craft client with spec as reference

**Authentication Methods (All Versions):**
1. Basic Auth - HTTP Basic (username/password)
2. API Key - `concentriq-api-key` header
3. JWT - Bearer token in Authorization header
4. Session - `connect.sid` cookie

**Common Patterns:**
- Pagination: `page`, `rowsPerPage`, `sortBy`, `descending`
- Filtering: Complex filter objects passed as query params
- IDs: Integer IDs for all resources

## Important Reminders

1. **ALWAYS write tests before implementation** (RED phase first)
2. **Test public behavior**, not private methods or internal implementation
3. **Use pytest-httpx** to mock HTTP responses in unit tests
4. **Keep tests fast** - unit tests should run in milliseconds
5. **Update TODO.md** as you progress
6. **No premature optimization** - make it work, then make it fast
7. **Each feature must have tests** - no untested code
8. **Run full test suite** before considering a feature complete

## Implementation Patterns

### Resource API Client Pattern

```python
"""<Resource> API client for V1."""

from typing import Any, Optional

from concentriq.client import ConcentriqClient


class <Resource>API:
    """Client for <Resource> resources (/api/<resources>)."""

    def __init__(self, client: ConcentriqClient):
        """Initialize with client instance."""
        self._client = client
        self._base_path = "/api/<resources>"

    def list(
        self,
        page: int = 1,
        limit: int = 20,
        filters: Optional[dict[str, Any]] = None
    ):
        """List <resources> with pagination and filters.

        Args:
            page: Page number (1-indexed)
            limit: Items per page
            filters: Filter criteria

        Returns:
            List of <resource> data

        Raises:
            AuthenticationError: If not authenticated
            ValidationError: If parameters are invalid
        """
        params = {"page": page, "rowsPerPage": limit}
        if filters:
            params["filters"] = filters

        response = self._client._request("GET", self._base_path, params=params)
        return response.json()

    def get(self, id: int):
        """Get a single <resource> by ID."""
        response = self._client._request("GET", f"{self._base_path}/{id}")
        return response.json()
```

### Pydantic Model Pattern

```python
"""Pydantic models for <Resource>."""

from typing import Optional
from pydantic import BaseModel, Field


class <Resource>(BaseModel):
    """<Resource> model."""

    id: int
    name: str
    created_at: Optional[str] = Field(None, alias="createdAt")

    class Config:
        populate_by_name = True  # Allow both snake_case and camelCase
```

## When Adding New Features

**Use slash commands for discipline:**

1. `/next` - Pick next item from TODO.md
2. `/red` - Write failing tests first (RED phase)
3. `/green` - Implement to pass tests (GREEN phase)
4. `/test` - Verify all tests pass
5. `/refactor` - Clean up code (optional)
6. Repeat

**Manual process (if not using commands):**

1. Pick next item from TODO.md
2. Create test file: `tests/unit/test_<feature>.py`
3. Write test class with descriptive test methods
4. Run tests to see them fail (RED)
5. Implement feature in `src/concentriq/`
6. Run tests to see them pass (GREEN)
7. Refactor if needed (keep tests green)
8. Update TODO.md to track progress (DO NOT COMMIT TODO.md)
9. Commit with appropriate prefix: `[RED]`, `[GREEN]`, or `[REFACTOR]`

**IMPORTANT: DO NOT commit TODO.md to git. It's a local working file for tracking progress only.**

## Slash Commands Available

**Generic TDD workflow commands** (reusable across projects):

- `/next` - Pick next feature from TODO.md and start RED phase
- `/red` - Start RED phase: write failing tests for feature
- `/green` - Start GREEN phase: implement to make tests pass
- `/test` - Run all tests and show results
- `/refactor` - Improve code quality while keeping tests green (optional)

These commands read CLAUDE.md and TODO.md for project-specific context.
