# move_project

Moves a music project between vendor accounts in `art_relations`. Handles artist ID remapping across `project`, `releases`, and `product_video`, ensures any missing artists exist at the destination vendor, captures the project's `LabelParticipant`s from `graphql-participant` and upserts them on the destination vendor, then executes the MySQL transfer as a single atomic transaction.

## Setup

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

## Configuration

Set connection details as env vars or in a `.env` file in the same directory:

| Variable | Required | Default | Description |
|---|---|---|---|
| `MYSQL_HOST` | yes | — | Database host |
| `MYSQL_USER` | yes | — | Database user |
| `MYSQL_PASSWORD` | yes | — | Database password |
| `MYSQL_PORT` | no | `3306` | Database port |
| `MYSQL_DATABASE` | no | `art_relations` | Database name |
| `GRAPHQL_URL` | yes | — | Federated GraphQL gateway URL (e.g. `https://qa-graphql-router.theorchard.io/graphql`) |
| `GRAPHQL_ORCHARD_USER_ID` | yes | — | `Orchard-User-Id` (e.g. `oa:179`) |
| `GRAPHQL_ORCHARD_IDENTITY_ID` | yes | — | `Orchard-Identity-Id` for the calling user |
| `GRAPHQL_ORCHARD_PROFILE_ID` | yes | — | `Orchard-Profile-Id` for the calling user |
| `GRAPHQL_ORCHARD_PROFILE_TYPE` | yes | — | `Orchard-Profile-Type` for the calling user |
| `GRAPHQL_ORCHARD_ROLES` | yes | — | `Orchard-Roles` for the calling user (comma-separated) |

The `GRASS-ACCOUNT-TYPE: vendor` and `GRASS-ACCOUNT-ID: <vendor>` headers are set automatically per call (source vendor for the read query, destination vendor for the create mutation).

## Usage

```bash
# Always dry-run first
python move_project.py --project-id 12345 --destination-vendor-id 99 --dry-run

# Execute the transfer
python move_project.py --project-id 12345 --destination-vendor-id 99

# With a destination subaccount
python move_project.py --project-id 12345 --destination-vendor-id 99 --destination-subaccount-id 7
```

### Arguments

| Argument | Required | Description |
|---|---|---|
| `--project-id` | yes | ID of the project to move |
| `--destination-vendor-id` | yes | Vendor ID to move the project to |
| `--destination-subaccount-id` | no | Subaccount at the destination vendor (omit for none) |
| `--dry-run` | no | Print what would change without writing anything |

## What it does

**Phase 1 — Collect artists**: Gathers all unique artist IDs referenced by the project row, its releases, and any `product_video` records.

**Phase 2 — Ensure destination artists**: For each source artist, checks whether an `artist_info` row with the same name already exists at the destination vendor. Creates any that are missing. Builds a `source_artist_id → destination_artist_id` mapping.

**Phase 2.5 — Capture and upsert label participants**: For each release in the project, calls the federated `product(productId: ...)` GraphQL query to collect every `LabelParticipant` referenced by the product, its tracks' participations, and its tracks' `labelSoundRecording` participations. Dedupes by `uuid`, then calls `createLabelParticipant` against the destination vendor for each unique LP. The mutation is `MERGE`-based in Neo4j, so re-runs are idempotent and pre-existing LPs at the destination are left in place.

**Phase 3 — Atomic transfer**: Executes in a single transaction:
- `UPDATE project` — sets `vendor_id`, `artist_id`, and `subaccount_id`
- `UPDATE releases` — remaps `artist_id` and `subaccount_id` for all releases in the project
- `UPDATE product_video` — remaps `primary_artist_id` for all associated video records

The script is idempotent: if the project is already on the destination vendor it exits cleanly without making any changes. Phase 2.5 runs before the Phase 3 transaction; if any GraphQL call fails the script aborts before any MySQL writes are committed.

## Example output

```
Connecting to database...

Phase 1: Collecting artist IDs for project 12345...
  Source vendor: 10, Destination vendor: 99
  Found 3 unique artist ID(s) across project + releases + product_video

Phase 2: Ensuring all artists exist on destination vendor 99...
Phase 2: 3 unique source artist(s) processed. Created 2 new, 1 already existed at destination.

Phase 2.5: Capturing label participants from 12 release(s) and upserting on destination vendor...
Phase 2.5: queried 12 release(s); found 8 unique LabelParticipant(s).
  Upserted 8 LabelParticipant(s) on vendor 99 / subaccount 0.

Phase 3: Executing atomic transfer transaction...
  COMMITTED. Updated: 1 project row, 12 release(s), 3 product_video row(s).

Done.
```
