# Usage Guide

This guide provides practical examples for working with the Concentriq API using the Python client.

## Table of Contents

- [V1 API - Core Resources](#v1-api---core-resources)
- [V2 API - Extended Features](#v2-api---extended-features)
- [V3 API - Modern Features](#v3-api---modern-features)
- [Error Handling](#error-handling)
- [Pagination](#pagination)
- [Filtering](#filtering)

## V1 API - Core Resources

The V1 API (`/api/*`) provides access to core Concentriq resources.

### ImageSets

ImageSets are the primary container for images (like "cases" or "studies").

```python
from concentriq import ConcentriqClient
from concentriq.auth import ApiKeyAuth

with ConcentriqClient(
    base_url="https://app.concentriq.com",
    auth=ApiKeyAuth(api_key="your-key")
) as client:
    # List all image sets
    image_sets = client.v1.image_sets.list()

    # List with pagination and sorting
    image_sets = client.v1.image_sets.list(
        page=1, rows_per_page=50, sort_by="created", descending=True
    )

    # List with filters
    from concentriq.models.filters import ImageSetFilters

    filters = ImageSetFilters(
        ownerId=123,
        isFavorite=True,
        archived=False
    )
    image_sets = client.v1.image_sets.list(filters=filters)

    # Get single image set
    image_set = client.v1.image_sets.get(id=123)

    # Create image set
    new_image_set = client.v1.image_sets.create({
        "name": "Research Study 2025",
        "description": "Q1 research samples"
    })

    # Update image set
    updated = client.v1.image_sets.update(
        id=123,
        data={"name": "Updated Name"}
    )

    # Delete image set
    client.v1.image_sets.delete(id=123)
```

### Images

Access and manage individual slide images.

```python
with ConcentriqClient(...) as client:
    # List images in an image set
    from concentriq.models.filters import ImageFilters

    filters = ImageFilters(imageSetId=123)
    images = client.v1.images.list(filters=filters)

    # Get single image
    image = client.v1.images.get(id=456)

    # Get download URL for image file
    download_url = client.v1.images.download(id=456)
    print(f"Download from: {download_url['url']}")
```

### Folders

Organize images within image sets using folders.

```python
with ConcentriqClient(...) as client:
    # List folders in an image set
    from concentriq.models.filters import FolderFilters

    filters = FolderFilters(imageSetId=123)
    folders = client.v1.folders.list(filters=filters)

    # Create folder
    folder = client.v1.folders.create({
        "imageSetId": 123,
        "name": "Section A",
        "description": "First batch of samples"
    })

    # Update folder
    client.v1.folders.update(
        id=folder["id"],
        data={"name": "Section A - Updated"}
    )

    # Delete folder
    client.v1.folders.delete(id=folder["id"])
```

### Annotations

Create and manage annotations on images.

```python
with ConcentriqClient(...) as client:
    # List annotations for an image
    from concentriq.models.filters import AnnotationFilters

    filters = AnnotationFilters(imageId=456)
    annotations = client.v1.annotations.list(filters=filters)

    # Create annotation
    annotation = client.v1.annotations.create({
        "imageId": 456,
        "text": "Region of interest",
        "annotationClassId": 789,
        "geometry": {
            "type": "rectangle",
            "coordinates": [100, 100, 200, 200]
        }
    })

    # Update annotation
    client.v1.annotations.update(
        id=annotation["id"],
        data={"text": "Updated annotation"}
    )

    # Delete annotation
    client.v1.annotations.delete(id=annotation["id"])
```

### Metadata

Manage custom metadata fields and values.

```python
from concentriq.models.v1.filters import MetadataValueFilters

with ConcentriqClient(...) as client:
    # List all metadata field definitions
    fields = client.v1.metadata_fields.list()

    # Look up a field by name (cached — only fetches once)
    field = client.v1.metadata_fields.get_by_name("Patient ID")
    field = client.v1.metadata_fields.get_by_name("Status", resource_type="image")

    # Force cache refresh (e.g. after creating a new field)
    field = client.v1.metadata_fields.get_by_name("New Field", refresh=True)

    # Get metadata values for an image
    filters = MetadataValueFilters(imageId=[456])
    values = client.v1.metadata_values.list(filters=filters)
    for v in values:
        print(f"Field {v['fieldId']}: {v['content']}")

    # Update metadata values (batch)
    client.v1.metadata_values.update([
        {"fieldId": 1, "resourceId": 456, "content": "PAT-2025-001"},
        {"fieldId": 2, "resourceId": 456, "content": 42},
        {"fieldId": 3, "resourceId": 456, "content": True},
        {"fieldId": 4, "resourceId": 456, "content": "03/16/2026"},  # MM/DD/YYYY
    ])
```

#### Working with Dropdown Fields

Dropdown metadata fields store numeric option IDs, not text. Use `DropdownResolver` to translate between the two.

```python
from concentriq.metadata import DropdownResolver

with ConcentriqClient(...) as client:
    # Look up the dropdown field definition
    field = client.v1.metadata_fields.get_by_name("Status", resource_type="image")

    # Create a resolver from the field definition
    resolver = DropdownResolver(field)

    # See available options
    print(resolver.values)       # ["Active", "Inactive", "Pending"]
    print(resolver.options)      # [{"id": 10, "value": "Active"}, ...]

    # Translate text → ID (for writing)
    option_id = resolver.to_id("Active")  # → 10

    # Translate ID → text (for reading)
    text = resolver.to_value(10)  # → "Active"

    # Use in metadata updates
    client.v1.metadata_values.update([
        {"fieldId": field["id"], "resourceId": 456, "content": resolver.to_id("Active")}
    ])
```

### Image Upload

Upload image files with the high-level `upload_image` helper. It handles image record creation, presigned URL generation, and single-part or multipart upload automatically.

```python
from concentriq.upload import upload_image

with ConcentriqClient(...) as client:
    # Upload from file path (auto-detects name and size)
    image = upload_image(client, "/path/to/slide.svs", image_set_id=100)
    print(f"Created image: {image['id']}")

    # Upload from file object
    with open("slide.svs", "rb") as f:
        image = upload_image(
            client, f, image_set_id=100,
            file_name="slide.svs", file_size=os.path.getsize("slide.svs")
        )

    # Custom part size for multipart uploads (default: 15 MB)
    image = upload_image(
        client, "/path/to/large_slide.svs",
        image_set_id=100, part_size=50 * 1024 * 1024
    )
```

Files smaller than `part_size` use a single presigned PUT. Larger files use S3 multipart upload, which is automatically aborted on failure.

### Users and Organizations

```python
with ConcentriqClient(...) as client:
    # List users
    users = client.v1.users.list()

    # Get organization info
    org = client.v1.organizations.get(id=1)
```

### Workflows and Templates

```python
with ConcentriqClient(...) as client:
    # List workflows
    workflows = client.v1.workflows.list()

    # List templates
    templates = client.v1.templates.list()

    # Create template
    template = client.v1.templates.create({
        "name": "Research Template",
        "metadataFields": [1, 2, 3]
    })
```

### Orders

Submit stain and recut orders.

```python
with ConcentriqClient(...) as client:
    # List available stains
    stains = client.v1.orders.list_stains()

    # List existing orders
    orders = client.v1.orders.list(filters={"imageSetId": 123})

    # Create stain order
    order = client.v1.orders.create({
        "imageId": 456,
        "stainId": 789,
        "quantity": 1
    })
```

### Attachments

Upload and manage file attachments.

```python
with ConcentriqClient(...) as client:
    # List attachments for a folder
    attachments = client.v1.attachments.list(filters={"folderId": 123})

    # Create attachment (returns storage key for S3 upload)
    attachment = client.v1.attachments.create({
        "folderId": 123,
        "fileName": "report.pdf",
        "fileSize": 1024000
    })

    # Use storageKey to upload file to S3
    storage_key = attachment["storageKey"]

    # Delete attachment
    client.v1.attachments.delete(id=attachment["id"])
```

## V2 API - Extended Features

The V2 API (`/api/v2/*`) provides user group management and module configuration.

### User Groups

```python
with ConcentriqClient(...) as client:
    # List user groups
    groups = client.v2.user_groups.list()

    # Create group (requires canManageGroups permission)
    group = client.v2.user_groups.create({
        "name": "Research Team",
        "description": "Q1 2025 Research Study",
        "userIds": [1, 2, 3]
    })

    # Update group
    client.v2.user_groups.update(
        id=group["id"],
        data={"name": "Research Team - Updated"}
    )

    # Delete group
    client.v2.user_groups.delete(id=group["id"])
```

### Modules

Configure external module integrations.

```python
with ConcentriqClient(...) as client:
    # List modules
    modules = client.v2.modules.list()

    # Create module
    module = client.v2.modules.create({
        "name": "External Analysis Tool",
        "endpoint": "https://api.example.com/analyze",
        "apiKey": "module-api-key"
    })
```

### Saved Display Settings

Manage fluorescence image display settings.

```python
with ConcentriqClient(...) as client:
    # List saved settings
    settings = client.v2.saved_display_settings.list()

    # Create display setting
    setting = client.v2.saved_display_settings.create({
        "name": "Default Fluorescence",
        "channels": [
            {"index": 0, "color": "red", "min": 0, "max": 255},
            {"index": 1, "color": "green", "min": 0, "max": 255}
        ]
    })
```

## V3 API - Modern Features

The V3 API (`/api/v3/*`) provides modern features including auth, studies, and research tools.

### Authentication

```python
with ConcentriqClient(...) as client:
    # Get current user info
    user_info = client.v3.auth.whoami()
    print(f"User ID: {user_info['userId']}")
    print(f"Permissions: {user_info['roleRules']}")

    # Create JWT token
    token_response = client.v3.auth.create_token()
    jwt_token = token_response["token"]

    # Create API key
    api_key_response = client.v3.auth.create_api_key()
    api_key = api_key_response["apiKey"]

    # Check API key status
    status = client.v3.auth.check_api_key_status()
    print(f"API key active: {status['active']}")

    # Delete API key
    client.v3.auth.delete_api_key()
```

### Studies

Manage research studies with fields and user assignments.

```python
with ConcentriqClient(...) as client:
    # List studies
    studies = client.v3.studies.list()

    # Create study
    study = client.v3.studies.create({
        "name": "Q1 2025 Research Study",
        "description": "Investigating disease markers"
    })

    # Add field to study
    client.v3.studies.add_field(
        study_id=study["id"],
        field_id=123
    )

    # Get study fields
    fields = client.v3.studies.get_fields(study_id=study["id"])

    # Add user to study
    client.v3.studies.upsert_user(
        study_id=study["id"],
        user_id=456,
        data={"role": "participant"}
    )

    # Get study users
    users = client.v3.studies.get_users(study_id=study["id"])

    # Get study statistics
    stats = client.v3.studies.get_statistics(study_id=study["id"])
    print(f"Total responses: {stats[0]['responseCount']}")

    # Remove user from study
    client.v3.studies.remove_user(
        study_id=study["id"],
        user_id=456
    )

    # Remove field from study
    client.v3.studies.remove_field(
        study_id=study["id"],
        field_id=123
    )

    # Delete study
    client.v3.studies.delete(id=study["id"])
```

### ImageSets - Research Features

Seed generation and blinding for research studies.

```python
with ConcentriqClient(...) as client:
    # Generate random seed for image randomization
    response = client.v3.image_sets.generate_seed(image_set_id=123)
    seed = response["seed"]
    print(f"Generated seed: {seed}")

    # Get current seed
    response = client.v3.image_sets.get_seed(image_set_id=123)
    current_seed = response["seed"]  # None if not set

    # Delete seed
    client.v3.image_sets.delete_seed(image_set_id=123)

    # Get blind configuration
    blinds = client.v3.image_sets.get_blinds(image_set_id=123)
    print(f"Resource blinds: {blinds['resourceBlinds']}")

    # Update blinds (blind specific fields)
    client.v3.image_sets.update_blinds(
        image_set_id=123,
        data={
            "resourceBlinds": {
                "Image": {"name": True, "createdAt": False},
                "Folder": {"name": True}
            }
        }
    )

    # Delete blinds
    client.v3.image_sets.delete_blinds(image_set_id=123)
```

### Configuration Resources

```python
with ConcentriqClient(...) as client:
    # Annotation Classes
    classes = client.v3.annotation_classes.list()
    new_class = client.v3.annotation_classes.create({
        "name": "Tumor Region",
        "color": "#FF0000"
    })

    # Channel Groups (Fluorescence)
    groups = client.v3.channel_groups.list()
    group = client.v3.channel_groups.create({
        "name": "DAPI + GFP",
        "channels": [0, 1]
    })

    # Add channel group to repository
    client.v3.channel_groups.add_to_repo(
        channel_group_id=group["id"],
        repo_id=456
    )

    # App Config
    configs = client.v3.app_config.list()
    config = client.v3.app_config.get(id="feature.darkMode")
    client.v3.app_config.update(
        id="feature.darkMode",
        data={"value": True}
    )

    # User Settings
    settings = client.v3.users.get_settings(user_id=123)
    client.v3.users.update_settings(
        user_id=123,
        data={"theme": "dark", "language": "en"}
    )
```

### Audit Logs

```python
with ConcentriqClient(...) as client:
    # Get audit log processing status
    status = client.v3.audit_logs.get_status()
    print(f"Pending events: {status['countPending']}")
    print(f"Oldest unprocessed: {status['processedPriorTo']}")
```

## Error Handling

Handle specific error types for better error recovery:

```python
from concentriq import ConcentriqClient
from concentriq.auth import ApiKeyAuth
from concentriq.exceptions import (
    AuthenticationError,
    AuthorizationError,
    NotFoundError,
    ValidationError,
    ServerError,
    APIError
)

with ConcentriqClient(...) as client:
    try:
        image_set = client.v1.image_sets.get(id=999999)
    except NotFoundError:
        print("Image set not found")
    except AuthorizationError:
        print("You don't have permission to access this resource")
    except AuthenticationError:
        print("Authentication failed - check your credentials")
    except ValidationError as e:
        print(f"Invalid request: {e}")
    except ServerError as e:
        print(f"Server error: {e}")
    except APIError as e:
        print(f"API error: {e}")
```

## Pagination and Sorting

V1 list endpoints support server-side pagination and sorting. Use `sort_by` and `descending` to let the server do the work — avoid fetching everything and sorting client-side.

```python
with ConcentriqClient(...) as client:
    # Top 10 repositories by image count (server-side sort)
    repos = client.v1.image_sets.list(
        page=1, rows_per_page=10, sort_by="imageCount", descending=True
    )

    # Most recently created images in a repository
    from concentriq.models.v1.filters import ImageFilters

    images = client.v1.images.list(
        page=1, rows_per_page=20, sort_by="created", descending=True,
        filters=ImageFilters(imageSetId=[123])
    )

    # Basic pagination
    image_sets = client.v1.image_sets.list(page=1, rows_per_page=50)

    # Iterate through all pages (only when you truly need everything)
    page = 1
    all_results = []

    while True:
        batch = client.v1.image_sets.list(page=page, rows_per_page=100)
        all_results.extend(batch)

        if len(batch) < 100:
            break  # Last page

        page += 1

    # Access pagination metadata from any list response
    repos = client.v1.image_sets.list(page=1, rows_per_page=10)
    print(repos.meta)  # {"page": 1, "rowsPerPage": 10, ...}
```

## Filtering

Use filter models for type-safe filtering:

```python
from concentriq.models.filters import (
    ImageSetFilters,
    ImageFilters,
    FolderFilters,
    AnnotationFilters,
    MetadataValueFilters
)

with ConcentriqClient(...) as client:
    # ImageSet filters
    filters = ImageSetFilters(
        ownerId=123,
        isFavorite=True,
        archived=False,
        workflowId=456,
        generalSearch="cancer"
    )
    image_sets = client.v1.image_sets.list(filters=filters)

    # Image filters
    filters = ImageFilters(
        imageSetId=123,
        hasAnnotations=True,
        isFluorescence=False
    )
    images = client.v1.images.list(filters=filters)

    # Folder filters
    filters = FolderFilters(
        imageSetId=123,
        hasMetadata=True,
        hasAttachments=False
    )
    folders = client.v1.folders.list(filters=filters)
```

## Next Steps

- **[API Reference](api-reference.md)** - Complete method documentation
- **[Installation Guide](installation.md)** - Setup and configuration
