# Track Artist Corrections

## Overview

Track corrections are applied when a downstream system requires track metadata to differ from what is stored in the canonical track record. Corrections are formatted in `validate_tracks_for_product` by wrapping each track dict in a `collections.ChainMap`:

```python
tracks[idx] = ChainMap(track_corrections.get(track_id, {}), track)
```

The ChainMap places the corrections dict in front of the original track dict. Any key present in the corrections dict shadows the same key on the original track, leaving all other keys unaffected.

---

## Correction Shape

Corrections for artist fields are stored as **top-level keys on the track**, keyed by artist type (e.g. `remixer`, `primary_artist`). Each value is a list of artist dicts.

```
{
    "<artist_type_key>": [
        {"type": "<actual_artist_type>", "name": "<corrected_name>"},
        ...
    ]
}
```

### The `primary_artist` / `performer` nuance

In the source data, performer-type artists are stored under the `artists` list with `type: 'performer'`. However, in the corrections system, the correction key for those artists is `primary_artist` — while the artist dicts inside still carry `type: 'performer'`.

This means the correction key and the artist dict's `type` field are **not always the same string**.

```python
# Correction for a performer artist:
correction = {
    'primary_artist': [
        {'type': 'performer', 'name': 'Corrected Artist Name'}
    ]
}
```

`get_artists_with_corrections` handles this by tracking **both** the correction key (`primary_artist`) and the type found inside the first artist dict (`performer`) in its `all_corrected_types` set. This prevents the original performer from appearing in the merged result alongside the corrected one.

---

## `get_artists_with_corrections`

Located in `backend/utils/track_utils.py`.

**Algorithm:**
1. Iterate `tf.ARTIST_TYPES` (the list of all artist type keys).
2. For each key present on the track (i.e. a correction exists), collect those artists and record both the key and the artist `type` from the first dict as corrected.
3. From `track['artists']`, append any artist whose `type` was **not** covered by a correction.
4. Return the merged list.

---

## Examples

### 1. No corrections — plain track dict

```python
track = {
    'tuid': 1,
    'artists': [
        {'type': 'performer', 'name': 'Original Artist'},
        {'type': 'remixer',   'name': 'Original Remixer'},
    ]
}

get_artists_with_corrections(track)
```

Result:

```python
[
    {'type': 'performer', 'name': 'Original Artist'},
    {'type': 'remixer',   'name': 'Original Remixer'},
]
```

---

### 2. Remixer corrected

The `remixer` key replaces the original remixer; the performer is untouched.

```python
track = ChainMap(
    {'remixer': [{'type': 'remixer', 'name': 'Corrected Remixer'}]},
    {
        'tuid': 1,
        'artists': [
            {'type': 'performer', 'name': 'Original Artist'},
            {'type': 'remixer',   'name': 'Original Remixer'},
        ]
    }
)

get_artists_with_corrections(track)
```

Result:

```python
[
    {'type': 'remixer',   'name': 'Corrected Remixer'},  # from correction
    {'type': 'performer', 'name': 'Original Artist'},     # original, untouched
]
```

---

### 3. Performer corrected via `primary_artist` key

The correction key is `primary_artist` but the artist dict's `type` is `performer`. Both strings are recorded as corrected, so the original performer is excluded from the result.

```python
track = ChainMap(
    {'primary_artist': [{'type': 'performer', 'name': 'Corrected Performer'}]},
    {
        'tuid': 1,
        'artists': [
            {'type': 'performer', 'name': 'Original Performer'},
            {'type': 'remixer',   'name': 'Original Remixer'},
        ]
    }
)

get_artists_with_corrections(track)
```

Result:

```python
[
    {'type': 'performer', 'name': 'Corrected Performer'},  # from correction (key: primary_artist)
    {'type': 'remixer',   'name': 'Original Remixer'},      # original, untouched
]
```

> **Key nuance:** `'primary_artist'` and `'performer'` are both added to `all_corrected_types`. Without tracking the inner `type`, the original `performer` artist would leak into the merged result.

---

### 4. Empty correction list — artist type removed entirely

A correction key mapped to `[]` signals that the artist type should be removed.

```python
track = ChainMap(
    {'remixer': []},
    {
        'tuid': 1,
        'artists': [
            {'type': 'performer', 'name': 'Original Artist'},
            {'type': 'remixer',   'name': 'Original Remixer'},
        ]
    }
)

get_artists_with_corrections(track)
```

Result:

```python
[
    {'type': 'performer', 'name': 'Original Artist'},
]
```

---

### 5. All types corrected

When every artist type has a correction, no original artists appear.

```python
track = ChainMap(
    {
        'primary_artist': [{'type': 'performer', 'name': 'Corrected Performer'}],
        'remixer':        [{'type': 'remixer',   'name': 'Corrected Remixer'}],
        'featuring':      [{'type': 'featuring', 'name': 'Corrected Featuring'}],
    },
    {
        'tuid': 1,
        'artists': [
            {'type': 'performer', 'name': 'Original Performer'},
            {'type': 'remixer',   'name': 'Original Remixer'},
            {'type': 'featuring', 'name': 'Original Featuring'},
        ]
    }
)

get_artists_with_corrections(track)
```

Result:

```python
[
    {'type': 'featuring', 'name': 'Corrected Featuring'},
    {'type': 'performer', 'name': 'Corrected Performer'},
    {'type': 'remixer',   'name': 'Corrected Remixer'},
]
```

---

### 6. Correction found on new artist type

A correction key is found and no artist of that type is part of the current `track['artists']`. The correction effectively **creates** a new artist. 

```python
track = ChainMap(
    {'featuring': [{'type': 'featuring', 'name': 'New Featuring'}]},
    {
        'tuid': 1,
        'artists': [
            {'type': 'performer', 'name': 'Original Artist'},
            # no featuring artist here
        ]
    }
)

get_artists_with_corrections(track)
```

Result:

```python
[
    {'type': 'featuring',   'name': 'New Featuring'},      # injected by correction
    {'type': 'performer', 'name': 'Original Artist'},  # original, untouched
]
```

---

## `tf.ARTIST_TYPES`

The recognized artist types. Note `primary_artist` is here but `artists` (the original artist list) is not.

```python
['featuring', 'performer', 'producer', 'primary_artist', 'remixer', 'composer', 'orchestra', 'conductor', 'ensemble']
```
