# CLAUDE.md

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

## Purpose

Consumes Kafka CDC events from `cdc.musicGraphV5.orchardSoundRecording` (Neo4j V5), filters for meaningful changes, and triggers the fingerprinter Step Function for each qualifying record. Fires SFNs in parallel (up to 20 concurrent threads) with backpressure throttling when too many executions are already running.

## Development Commands

All commands run from this directory (`lambda/osr-fingerprint-filter/`):

```bash
# Run linting (flake8 + mypy + yamllint) and unit tests
docker compose up --build lint-and-test

# Run tests only, skip linting
SKIP_LINT=true docker compose up --build lint-and-test

# Run a specific test
TEST_ARGS="-k test_handler" docker compose up --build lint-and-test

# Run with HTML coverage report (served on port 8000)
COV_REPORT=html docker compose up --build lint-and-test

# Start the Lambda locally (HTTP on port 9000)
docker compose up --build -d function

# Invoke locally with a sample event
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d @tests/sample_events/neo4j_v5/orchard_sound_recording/created.json

# Quick local invocation without Docker
python simulate_event.py
```

Linting: `flake8` (max line 120, single quotes, Google import order) + `mypy` (entry: `src/app.py`) + `yamllint`.

## Architecture

**Handler flow** (`src/app.py`):
1. `decode_records` — base64-decodes Kafka MSK records, builds `(source_key, payload)` tuples
2. `filter_events` — drops UPDATE events where only `lastModifiedBy`/`lastModifiedAt` changed (noise reduction)
3. `generate_outputs` — routes each event to its typed `InputEvent` subclass, calls `.output()` to produce `OutputEvent` instances
4. `throttle` — blocks until the fingerprinter SFN has fewer than `SFN_FINGERPRINTER_MAX_RUNNING` (default 500) running executions
5. `execute_sfn` — starts one SFN execution per `OutputEvent`; silently skips `ExecutionAlreadyExists`

**Event class hierarchy** (`src/events/`):
- `OutputEvent` — canonical SFN input payload; `exc_id()` builds the dedup key, `to_json()` serializes it
- `InputEvent` — abstract base; subclasses override `output()` to filter and produce `OutputEvent` list or `None`
- `BaseV5Event` (`src/events/neo4j_v5/base.py`) — parses Neo4j CDC V5 envelope (txId, operation, before/after state, timestamp)
- `OrchardSoundRecording` (`src/events/neo4j_v5/orchard_soundrecording.py`) — only triggers SFN on UPDATE when `primaryTrackId`, `lastModifiedAt`, `touchedAt`, or `touchedByBackfillAt` changes; ignores all other updates

**Filtering logic** (`src/utils/events.py`):
- `filter_events` pre-screens at the raw payload level — drops UPDATEs with no significant changes (ignores `lastModifiedBy`/`lastModifiedAt` fields recursively)
- `OrchardSoundRecording.output()` applies a second, field-specific filter on the parsed event — only the four listed properties trigger an SFN

**Key config** (`config.py`):
- `SFN_FINGERPRINTER_ARN` — required; the target state machine
- `SFN_FINGERPRINTER_MAX_RUNNING` — ceiling for concurrent executions (default 500)
- `SFN_THROTTLE_SLEEP_SECONDS` — polling interval when throttled (default 30s)

## Testing

Unit tests (`tests/unit/`) use `conftest.py` helpers to build realistic MSK envelopes:
- `msk_event(topic, value)` — wraps a decoded payload in the MSK trigger format
- `neo4j_v5_cdc_node_event(operation, labels, before, after)` — builds a Neo4j V5 CDC node event

Integration tests (`tests/integration/`) are skipped in CI and require real AWS credentials. The placeholder test intentionally fails — replace it before using.

## Local Dev Setup

Copy `.env.shadow` to `.env` and optionally fill in `SENTRY_DSN`. The `docker-compose.yaml` mounts `~/.aws/credentials`; use `awsume` to populate the `default` profile before starting containers. `SFN_FINGERPRINTER_ARN` must be set in the environment or `.env` for any invocation that reaches the SFN throttle/execute path.
