# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Purpose

`sr-add-version` aggregates sound recording metadata from multiple external services (OWS API, GraphQL gateway, S3) into a versioned JSON snapshot. When a new version is produced, it publishes a Kafka event so downstream consumers can react to changes.

See the root `CLAUDE.md` for shared development commands (Docker Compose, linting, testing).

## Data Pipeline

`src/runner.py` orchestrates the full flow in `handle_event(event)`:

1. **Parse input** — accepts four input shapes (see below)
2. **Fetch SRs** from OWS API (`src/connectors/ows_sound_recordings.py`) — returns assets and raw track metadata
3. **Fetch products** via GraphQL (chunked, 100 UPCs per request) — release date, label, subgenre
4. **Fetch tracks** via GraphQL (chunked, 100 track IDs per request) — ISRC, participations, attributes, territories
5. **Transform tracks** — filter null-release-date tracks, clean explicit field, reformat label ID structure, mark primary track
6. **Select primary assets** — one asset per file extension, lowest TUID first, `source='ows-assets-db'` only
7. **Validate** against `OrchardSoundRecording` Pydantic model (`soundrecording-utils` package)
8. **Write to S3** via `src.common.connectors.s3_sound_recordings.write_sound_recording_version`
9. **Publish Kafka event** only if S3 write produced a new version (i.e., content changed)

## Input Shapes

The event `data` field accepts one of:

```python
{'sound_recording_ids': [...]}                          # direct SR IDs
{'label': 'Track', 'id': '<tuid>'}                     # TUID
{'label': 'Product', 'id': '<upc>'}                    # UPC
{'label': 'OrchardAsset', 'id': '<uuid>'}              # asset UUID
{'label': 'OrchardSoundRecording', 'id': '<uuid>'}     # SR UUID
```

## Error Handling Strategy

- **Retryable** (`RetryableException`): HTTP 401, 408, 502, 503, 504 from OWS or GraphQL; Kafka delivery failures; GraphQL query-level errors
- **Fatal** (re-raised): all other HTTP errors, model validation failures, missing required env vars

## Kafka Output

Topic is `KAFKA_TOPIC` env var (dev default: `event.sr.newVersion`). Message key is `sound_recording_id`. Payload:

```json
{
  "sound_recording_id": "...",
  "previous_version_id": "...",
  "version_id": "...",
  "updated": true,
  "last_modified": "...",
  "tracks_isrcs": ["..."]
}
```

A Kafka event is only sent when the S3 write produces a new `version_id` (content actually changed).

## GraphQL Headers

All GraphQL requests identify as the admin service identity:

```
apollographql-client-name: lambda-sr-add-version
Orchard-Identity-Id: 8955b4aa-2ad0-4b0b-a2ea-6f070554d6d0
Orchard-Profile-Id: 179
Orchard-Profile-Type: OrchAdminProfile
```

These are hardcoded in `config.py` — do not change them without coordinating with the GraphQL gateway team.

## Key Environment Variables

| Variable | Purpose | Dev default |
|---|---|---|
| `KAFKA_BROKERS` | Kafka bootstrap servers | dev cluster endpoints |
| `KAFKA_TOPIC` | Output topic | `event.sr.newVersion` |
| `KAFKA_TIMEOUT` | Producer flush timeout (ms) | `3000` |
| `ENVIRONMENT` | Tier (`dev`/`qa`/`prod`) | `dev` |

Secrets (`SENTRY_DSN`, OWS credentials) are fetched at cold start via `LambdaSecretsManager`.

## Test Patterns

Unit tests live in `tests/unit/` and mock all external calls (`OWS`, `GraphQL`, `S3`, `Kafka`). Key fixtures are in `conftest.py`: `products`, `track_result`, `ows_result`.

Run a single test:
```bash
TEST_ARGS="-k test_handler" docker compose up --build lint-and-test
```

`tests/integration/test_lambda.py` is a placeholder — integration tests invoke the deployed Lambda via boto3 and require QA AWS credentials.
