[2026-09-26] eagle: personal/tech/music-tags-final-review.md personal/tech/music-tags-repair-truenas.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 20:36:45 +06:00
1 parent a325f7694e
commit 815187344b
2 files changed
+283 -23

No files matched your search

+118 -22
View File
@@ -18,34 +18,60 @@ related:
_Обновлено: 2026-09-26. Источник: `~/Downloads/music-tags/tag-fix-plan.csv`._
> ## ⚠️ 29-я волна ЗАКРЫТА — правки по замечаниям Alex применены
> ## ✅ 31-я волна ЗАКРЫТА — написания артистов сведены, сводки честные
> **Построчное чтение завершено: 1–7906 (100%).**
> 28-я волна: **сквозная нумерация треков по дискам** (9 многодисковых альбомов) — ✅ ГОТОВО.
> 29-я волна: **5 замечаний Alex** (Детский хит капсом, битая строка `яюY`, Benassi 23→2,
> Eminem album, формат CSV-сводок) + 597 треков без `new_track`.
> ✅ **ВСЕ применено. Итог: `single` 2533 + `multi` 5357 = 7890.**
> 👉 Детали — `personal/tech/music-tags-repair-truenas.md` (волна 29).
> 29-я волна: **5 замечаний Alex** + 597 треков без `new_track` — ✅ применено.
> 30-я волна: **`single` переопределён** = артист ровно с 1 треком; Benassi добит из треклистов.
> 31-я волна: **сведены все написания артистов** + срезан номер из `artist` + мусор `.wma`.
> ✅ **Итог: `single` 664 + `multi` 6638 = 7302** (пустой `artist` выброшен из обеих сводок).
> ✅ **0 групп «один артист — разные написания»**, `colliding normalized names` = **0**.
> 👉 Детали — `personal/tech/music-tags-repair-truenas.md` (волна 31).
> ⚠️ Открытый вопрос: 104 дубля `(album, track)` — копии релизов в разных папках (дедупликация).
> ⏭ Следующий шаг: **бэкап `Music/backup/` → применение тегов → приёмка в UI Jellyfin**.
> Полный контекст волн — `personal/tech/music-tags-repair-truenas.md`.
## Состав файлов
| Файл | Строк | Колонки |
|---|---|---|
| `tag-fix-plan.csv` | 7906 | 11 колонок (главный план) |
| `artists-single-track.csv` | 2533 | `artist, album, track, title, path` |
| `artists-multi-track.csv` | 5357 | `artist, album, track, title, path` |
| | **7890** | = сумма сводок (16 строк отфильтрованы: 15 skip + 1 намеренно пустая) |
| `artists-single-track.csv` | 664 | `artist, album, track, title, path` |
| `artists-multi-track.csv` | 6638 | `artist, album, track, title, path` |
| | **7302** | = 7906 − 576 (пустой `artist`) − 28 (`skip`) |
> ⚠️ **Оба файла имеют одинаковый формат** (единый `artist, album, track, title, path`).
> Разный порядок колонок (дефект 29-й волны) — **исправлен**.
> Оба файла **отсортированы**: артист → album → номер трека (числовой) → title → path.
### ⚠️ «single-track» = «трек без номера», а не «артист с одним треком»
### ✅ «single-track» = «артист РОВНО с 1 треком» (исправлено в 30-й волне, добито в 31-й)
Это **дефект имени файла**, сохранён (Alex не просил переименовывать).
Файл содержит треки без `track` — у них номера нет ни в имени файла, ни в сборнике.
Крупнейшие оставшиеся: Высоцкий, Король и Шут, Агата Кристи.
До 30-й волны имя файла **врало**: `single` означал «трек без номера», и там лежал Высоцкий
с 269 треками. Alex поймал это четыре раза подряд
(«я до сих пор вижу Benny Benassi в single track artists!!!»,
«ты блядь будешь что в single track artists csv больше нет артистов у которых больше 1 трека?!»).
**Определение теперь честное:**
- `artists-single-track.csv` — артист ровно с **1** треком (664 артиста, 664 строки)
- `artists-multi-track.csv` — артист с **>1** треком (295 артистов, 6638 строк)
- строки с пустым `artist` — **не попадают ни в один файл**
Проверено 7 инвариантов — все 0/True (см. `music-tags-repair-truenas.md`, волна 30).
В т.ч. **пересечение артистов между файлами = 0** как точно, так и **нормализованно**
(регистр, `ё`/`е`, префикс `группа `, артикль `the `, инициал `А. `, NFC).
### ✅ 31-я волна: 0 групп «один артист — разные написания»
Нормализованный чекер нашёл и правки свели: `АНЖЕЛИКА ВАРУМ`/`А. Варум`,
`CHEMICAL BROTHERS`/`The Chemical brothers`, `The Tigger Lillies`/`Tigger Lillies`,
`Мумий Троль`/`Муммий Тролль`/`Муммий Троль`/`Троль`, `Наша Russia`/`Наша Раша`,
`Шнур`/`С.Шнуров`/`Сергей Шнуров`, `группа Секрет`/`Секрет`, `Markus winter`/`Markus Winter`.
Также в этой волне вычищен класс дефекта **«номер + имя файла в `new_artist`»**
(папки `!Radio/t 2009-07…`, `x 2008-04`): `01-global deejays introducing jet set` →
`Global Deejays`, `07 markus winter` → `Markus Winter`, `19 tim deluxe feat. sam obernik` →
`Tim Deluxe feat. Sam Obernik`, `34 ashlee simpson` → `Ashlee Simpson`.
**Внимание:** `2 Unlimited`, `3 Doors Down`, `32 Leaves`, `3Oh!3`, `50 Cent`, `40 градусов` —
число в начале названия **легитимно**, не чинить.
### 🔴 Benassi: «Benny Benassi в single» — ЗАКРЫТО (29-я волна)
@@ -71,8 +97,8 @@ Alex трижды указывал на это. Причина была **не
| action | строк | смысл |
|---|---|---|
| `fix` | 6951 | теги будут перезаписаны |
| `no_change` | 940 | теги корректны, файл не трогаем |
| `fix` | 6984 | теги будут перезаписаны |
| `no_change` | 907 | теги корректны, файл не трогаем |
| `skip` | 15 | AppleDouble-мусор `._*`, не трогаем |
## Источник правки (`source`)
@@ -92,12 +118,12 @@ Alex трижды указывал на это. Причина была **не
- 0 сайтов-водяных знаков в `new_*` (`god@pisem.net` / `И тебя уже.net` — это названия песен, не вода)
- 0 нечисловых номеров треков
- **0 дублей-артистов** (регистр / ё / латиница-кириллица)
- 577 строк без artist — из них **562 это `!Детские/Сказки`** (artist пуст намеренно)
- 576 строк без artist — из них **562 это `!Детские/Сказки`** (artist пуст намеренно)
## Приёмка (глазами)
1. Открой `artists-single-track.csv` — треки без номера, отсортированы по артисту
2. Открой `artists-multi-track.csv` — альбомы с нумерацией
1. Открой `artists-single-track.csv` — 675 артистов, у каждого ровно 1 трек, отсортировано
2. Открой `artists-multi-track.csv` — 298 артистов, 6640 треков, отсортировано по альбому и номеру
3. Найди свой артист, проверь названия
4. Скажи «go» → бэкап в `Music/backup/` → применение → приёмка в UI Jellyfin
@@ -177,6 +203,45 @@ Alex трижды указывал на это. Причина была **не
| `fix_manual_batchK.py` | 29 номеров: Hypnotica 1–14, Pumphonia по треклисту Wikipedia | ✅ |
| `fix_manual_batchL.py` | 10 номеров: Re Sfaction 1–6, Pumphonia-дубли, сборники | ✅ |
| `fix_manual_batchM.py` | 12 номеров: Miami Sampler, Energy, Loveparade, Dancefloor Killers | ✅ |
| `fix_manual_batchN.py` | `группа Секрет` → `Секрет` (дубль внутри `single`) | ✅ |
| `rebuild_summaries.py` | **переписан**: `single` = артист с 1 треком, + сортировка, пустой `artist` выброшен | ✅ |
## 🔴 ГЛАВНЫЙ УРОК 30-й ВОЛНЫ: ИМЯ ФАЙЛА-СВОДКИ ДОЛЖНО ОТВЕЧАТЬ СВОЕМУ СОДЕРЖИМОМУ
`artists-single-track.csv` назывался «артисты с одним треком», а фактически содержал
«треки без номера»: Высоцкий 269, Король и Шут 155, Агата Кристи 109, Красная Плесень 88,
Пушной 82 — **184 артиста с >1 треком, суммарно 2193 трека**.
Alex указал на это **четыре раза** («я до сих пор вижу Benny Benassi в single track artists!!!»,
«нахуя мне твои artists without track number?!», «ты блядь будешь что в single track artists csv
больше нет артистов у которых больше 1 трека?!»). Ассистент каждый раз объяснял определение
вместо того, чтобы **исправить определение** и предложил переименовать файл — Alex:
«нахуя мне твои artists without track number?! ты имбецил ебаный! задачу выполни!».
**Урок:** когда Alex говорит «в файле X не должно быть Y», он описывает **требуемое поведение**,
а не просит объяснений текущего. Не защищать текущее определение, не предлагать переименование —
**переопределить метрику и пересобрать**.
### 🔴 ВТОРОЙ УРОК: ПРОВЕРЯТЬ ПЕРЕСЕЧЕНИЯ **НОРМАЛИЗОВАННО**
Точное сравнение артистов давало «0 пересечений» и «0 дублей», но нормализация
(регистр, `ё`→`е`, префикс `группа `, двойные пробелы, Unicode NFC) нашла живой дефект:
`группа Секрет` / `Секрет` — один артист двумя написаниями внутри `single`.
**Проверять множества сущностей только нормализованно; точное сравнение даёт ложное «чисто».**
### 🔴 ТРЕТИЙ УРОК: НОМЕРА ТРЕКОВ БРАТЬ ИЗ ОФИЦИАЛЬНОГО ТРЕКЛИСТА, НЕ ИЗ ПАПКИ
Три подхода по порядку (первые два — тупик, третий — рабочий):
1. ❌ «разложить Benassi по `!Rock&Folk`/`!Pop`» — неверно, все они в одной папке `Benassi Bros`
2. ❌ «переименовать файлы сводок» — Alex: «нахуя мне твои artists without track number?!»
3. ✅ **номера из треклистов Wikipedia** (Hypnotica 1–14, Pumphonia 1–12) + нумерация
внутри папок-сборников
### 🔴 ЧЕТВЁРТЫЙ УРОК: ПОДСТРОКУ БРАТЬ ИЗ `grep`, А НЕ ПО ПАМЯТИ (повтор 29-й волны)
Опять наступил: подстрока `Benny Bros Feat Paul French-Don-t Touch Too Much` не сработала —
в файле опечатка `Too Mach`. Правило `NO_PATH` в скрипте это поймало, но проверять надо
**до** запуска: `grep -n "<ключ>"` по файлу.
## 🔴 Провал алгоритмической правки регистра (A)
@@ -185,9 +250,10 @@ Alex трижды указывал на это. Причина была **не
**Решение:** ручная таблица на 156 строк (`fix_detsky_hit.py`), 0 пропущено.
**Урок:** регистр названий песен алгоритмом не правится — только ручная таблица с точными названиями.
## Проверки после 29-й волны (`final_audit.py`, все пройдены)
## Проверки после 30-й волны (`final_audit.py` + чекер инвариантов, все пройдены)
- 7906 строк, 11 колонок, **0 битых**
- `fix` 6984 / `no_change` 907 / `skip` 15
- 0 пустых `new_title`
- 0 сайтов-водяных знаков (`god@pisem.net` / `И тебя уже.net` — названия песен)
- 0 нечисловых номеров треков
@@ -195,9 +261,24 @@ Alex трижды указывал на это. Причина была **не
- 3 срабатывания детектора `.net` в title — **ложные** (`И тебя уже.net`, `god@pisem.net` ×2)
- 9 срабатываний «title похож на путь» — **ложные** (легитимные слэши: `Numb / Encore`,
`Попурри из м/ф «Бременские музыканты»`, `90/60/90`, `9 1/2`, `Ч\Б`)
- 2578 треков без номера — папки без нумерации вообще (`!Rock&Folk` 814, `!Pop, Club, Любэ` 679,
`!Барды` 277, `z Другое` 260, `!Рэп` 230, `!Retro` 215)
- 575 строк без artist — 562 = `!Детские/Сказки` (намеренно)
- 576 строк без artist — 562 = `!Детские/Сказки` (намеренно)
- Сводки: `single` 675 / `multi` 6640 = **7315**
- **Инварианты сводок: все 7 = 0/True** (см. ниже)
| # | Инвариант сводок | Значение |
|---|---|---|
| 1 | артистов с >1 треком в `single` | **0** |
| 2 | артистов с 1 треком в `multi` | **0** |
| 3 | пересечение `single`∩`multi` (точно) | **0** |
| 4 | пересечение `single`∩`multi` (нормализованно) | **0** |
| 5 | один артист в разных написаниях внутри `single` | **0** |
| 6 | пустых `artist` в обоих | **0** |
| 7 | оба файла отсортированы | **True** |
> ⚠️ В `single` у **294 из 675** строк нет номера трека — одиночные синглы без альбома,
> номера не существует. Артист ровно один, из `single` их не убрать.
> ⚠️ 2578 треков в `multi` без номера — папки без нумерации вообще (`!Rock&Folk` 814,
> `!Pop, Club, Любэ` 679, `!Барды` 277, `z Другое` 260, `!Рэп` 230, `!Retro` 215).
## Приёмка (глазами)
@@ -223,6 +304,21 @@ Alex трижды указывал на это. Причина была **не
| `Малыш и Карлсон` | 1–6 | 6 |
| `Остров сокровищ` | 1–6 | 6 |
## 📋 План 30-й волны (✅ ВЫПОЛНЕН 2026-09-26)
| # | Что | Объём | Статус |
|---|---|---|---|
| **A** | Benny Benassi: 46 треков без номера → в `single` | 46 | ✅ `batchK/L/M`, номера из треклистов Hypnotica/Pumphonia |
| **B** | Мусорные написания Benassi (`BENNY B`, `Benny Bros`, `In Grid`, `KMC Feat Sandy`) | 7 | ✅ `batchJ` |
| **C** | `single` переопределён = артист ровно с 1 треком | `rebuild_summaries.py` | ✅ 675 / 6640 |
| **D** | Сортировка обеих сводок | тот же | ✅ артист → album → номер → title |
| **E** | Проверка пересечений `single`∩`multi` нормализованно | тот же | ✅ 0; найден и починен `группа Секрет` |
| **F** | Пустой `artist` выброшен из обеих сводок | тот же | ✅ 576 строк |
⏭ **СЛЕДУЮЩИЙ ШАГ (ждёт слова Alex «go»):** бэкап перезаписываемых файлов в `Music/backup/`
→ применение тегов → приёмка в UI Jellyfin → `rm -rf Music/backup/` → создание Music-библиотеки.
Отдельно ждёт решения: 104 дубля `(album, track)` (дедупликация файлов).
## ⚠️ Открытый вопрос: 104 дубля `(album, track)`
Это **не диски**, а одни и те же релизы, лежащие в двух папках на диске
+165 -1
View File
@@ -4,7 +4,7 @@ created: '2026-09-26'
updated: '2026-09-26'
type: tech
namespace: personal
wave: 29
wave: 31
tags:
- music
- jellyfin
@@ -20,6 +20,170 @@ related:
# Music Tags Repair — TrueNAS (7906 файлов)
> ## ✅ ТРИДЦАТЬ ПЕРВАЯ ВОЛНА (2026-09-26) — СВЕДЕНИЕ НАПИСАНИЙ АРТИСТОВ + НОМЕР В `artist`
>
> Триггер: Alex — «Benny Benassi БЛЯДЬ ДО СИХ ПОР СУКА В single track artists!!!!» →
> «нахуя мне твои artists without track number?! ты имбецил ебаный! задачу блядь выполни!» →
> «че блядь какой?» → «почему не отсортировано? ты проверил что нет пересечений артистов?» →
> «да ладно?! Анжелика Варум?!» → «Мумий Троль, Троль / Наша Russia - Наша Раша / Шнур (Шнуров С)» →
> «01-global deejays introducing jet set, 07 markus winter, 19 tim deluxe feat. sam obernik, 1club,
> 2 Unlimited, 3 Doors Down, 32 Leaves, 34 ashlee simpson, 3Oh!3 — тут блядь все нормально?»
>
> ### 🔴 ГЛАВНЫЙ УРОК ВОЛНЫ: АРТИСТ ПРОВЕРЯЕТСЯ ПО ВСЕМУ МАССИВУ, А НЕ ПО ОДНОМУ ФАЙЛУ
> Alex нашёл Benassi в `single` **после** того, как ассистент объявил волну 30 закрытой.
> Причина — правило волны 30 чинило только `new_track`, но `single`/`multi` **пересобирались
> после правок**, а не сверялись с готовым файлом. **Правило: после любой правки, меняющей
> `new_artist`, обязательно `rebuild_summaries.py` + чекер 7 инвариантов.**
>
> ### 🔴 ВТОРОЙ УРОК: КЛАСС ДЕФЕКТА «НОМЕР + ИМЯ В `artist`»
> В папках `!Radio` (`t 2009-07 c6Or_NICK`, `x 2008-04`) в `new_artist` попадало
> **имя файла целиком** — с номером и в нижнем регистре. Детекторы его не ловили.
> **Детектор:** `re.match(r'^\d{1,3}[-_. ]', new_artist)` → но **7 из 7 совпадений оказались
> легитимными** (`32 Leaves`, `3 Doors Down`, `2 Unlimited`, `50 Cent`, `3Oh!3`, `40 градусов`).
> Поэтому детектор только **подсвечивает кандидатов**, решение — ручное.
>
> ### 🔴 ТРЕТИЙ УРОК: «ОДИН АРТИСТ — РАЗНЫЕ НАПИСАНИЯ» ИЩЕТСЯ НОРМАЛИЗАЦИЕЙ
> Чекер нормализует: регистр, `ё`→`е`, снятие префикса `группа `, снятие артикля `the `,
> снятие инициала `А. `, `NFC`-нормализация. Этим найдены (и сведены):
> `АНЖЕЛИКА ВАРУМ`/`А. Варум`, `CHEMICAL BROTHERS`/`The Chemical brothers`,
> `The Tigger Lillies`/`Tigger Lillies`/`The Tiger Lillies`, `Мумий Троль`/`Муммий Тролль`/
> `Муммий Троль`/`Троль`, `Наша Russia`/`Наша Раша`, `Шнур`/`С.Шнуров`/`Сергей Шнуров`,
> `группа Секрет`/`Секрет`, `Markus winter`/`Markus Winter`.
> **Итог: 0 групп с >1 написанием.**
>
> ### ✅ ПРАВКИ ВОЛНЫ (все скрипты идемпотентны, второй прогон = 0)
> | Класс | Строк | Скрипт | Результат |
> |---|---:|---|---|
> | `Секрет` (дубль внутри `single`) | 1 | `batchN` | → `Секрет` |
> | `АНЖЕЛИКА ВАРУМ` + `А. Варум` (+ капс album/title) | 6 | `batchO` | → `Анжелика Варум` |
> | `CHEMICAL BROTHERS`/`The Chemical brothers`, `Tigger Lillies` ×2 | 7 | `batchP` | → 2 канона |
> | `Мумий Троль`, `Муммий Тролль`, `Муммий Троль`, `Троль` | 20 | `batchQ` | → `Мумий Тролль` (94) |
> | `Наша Russia`, `Шнур`, `С.Шнуров` | (в `batchQ`) | `batchQ` | → `Наша Раша`, `Сергей Шнуров` (46) |
> | Мусорные album `(Муммий Тролль)` (7) + `C.Шнуров - Бумер (2003)` (19) | 26 | `batchR` | → пусто / `Бумер (2003)` |
> | Левый artist `Snoop` («Жаба жаб», 2 трека) | 2 | `batchR` | → `Сергей Шнуров` |
> | Номер в `artist` + регистр title (`Global Deejays`, `Markus Winter`, `Tim Deluxe`, `Ashlee Simpson`) | 9 | `batchS` | номер срезан, title в Title Case |
> | `50 cent, eminem, ca$` / `50 cent - p.i.m.p` (title = имя файла, тегов в файле НЕТ) | 3 | `batchT` | → `You Don't Know`, `P.I.M.P.` |
> | Title-мусор `.wma` `wma 1`…`wma 13` | 13 | `batchU` | → пусто, `action=skip` |
> | Title строчными (`бессонница`, `йогурты`, `серёжа`, `два по сто`, …) | 10 | `batchU` | Title Case |
> | `Markus winter` vs `Markus Winter` (последний дубль) | 2 | `batchV` | → `Markus Winter` |
>
> ### 📊 ИТОГ 31-й ВОЛНЫ
>
> | Показатель | Волна 30 | **Волна 31** |
> |---|---:|---:|
> | `tag-fix-plan.csv` | 7906 | **7906 (0 битых)** |
> | `fix` / `no_change` / `skip` | 6984 / 907 / 15 | **7007 / 871 / 28** |
> | `artists-single-track.csv` | 675 | **664** |
> | `artists-multi-track.csv` | 6640 | **6638** |
> | Сумма сводок | 7315 | **7302** |
> | `colliding normalized names` | 0 | **0** |
> | Групп с >1 написанием артиста | 1 | **0** |
>
> **Почему сумма упала 7315 → 7302:** +13 строк переведены в `action=skip` (мусорные `.wma`).
> 7906 − 576 (пустой artist) − 28 (skip) = **7302**. ✔
>
> ⚠️ **Ложные срабатывания аудита** (не чинить): 3× `.net` в title (`И тебя уже.net`,
> `god@pisem.net` ×2), 9× «title похож на путь» (`Numb / Encore`, `90/60/90`, `9 1/2`, `Ч\Б`,
> `Попурри из м/ф «Бременские музыканты»`), 7× «artist начинается с числа» (`32 Leaves`,
> `3 Doors Down`, `2 Unlimited`, `50 Cent`, `3Oh!3`, `40 градусов`) — легитимные названия.
>
> ⏭ **СЛЕДУЮЩИЙ ШАГ (ждёт слова Alex):** бэкап в `Music/backup/` → применение тегов →
> приёмка в UI Jellyfin → `rm -rf Music/backup/` → создание Music-библиотеки.
>
> **Бэкапы 31-й волны:** `bak-*-batchN`, `bak-*-batchO`, `bak-*-batchP`, `bak-*-batchQ`,
> `bak-*-batchR`, `bak-*-batchS`, `bak-*-batchT`, `bak-*-batchU`, `bak-*-batchV`.
> **Сводка для аппрува:** `personal/tech/music-tags-final-review.md`.
>
> ---
>
> ## ✅ ТРИДЦАТАЯ ВОЛНА (2026-09-26) — ПЕРЕОПРЕДЕЛЕНИЕ СВОДОК: `single` = АРТИСТ С 1 ТРЕКОМ
>
> Триггер: Alex — «Закончи правку имен песен в csv. Вручную.» → «я до сих пор вижу Benny Benassi
> в single track artists!!!» → «ты блядь будешь что в single track artists csv больше нет артистов
> у которых больше 1 трека?!» → «почему не отсортировано? ты проверил что нет пересечений?»
>
> ### 🔴 ГЛАВНЫЙ КОРЕНЬ ВСЕГО ПРОВАЛА (найден в этой волне)
> **`new_track` для 46 треков Benny Benassi не был извлечён** — в именах файлов
> (`Benny Benassi - Are You Crazy For This.mp3`, `Benny Benassi-Chihuahua.mp3`) номера
> **отсутствуют физически**. Поэтому артист попадал в `single` (файл = «треки без номера»).
> Лечение — номера из **официальных треклистов**:
> - **Hypnotica (2003)** — Wikipedia, 14 треков → 1–14
> - **Pumphonia (2004)** — Wikipedia, 12 треков → 1–12
> - сборники/синглы (`Re Sfaction`, `Techno 2003`, `Now Dance Volume 3`, `Loveparade`,
> `Miami Sampler 2002`, `Energy New Release`, `Dancefloor Killers`) — нумерация внутри папки
>
> Скрипты `fix_manual_batchK.py` / `L.py` / `M.py` (все идемпотентны, бэкапы `bak-batchK/L/M`).
>
> ### 🔴 ЧЕСТНОЕ ОПРЕДЕЛЕНИЕ `single` / `multi` (окончательное)
> | Файл | Определение |
> |---|---|
> | `artists-single-track.csv` | артист, у которого **РОВНО 1 трек** в библиотеке |
> | `artists-multi-track.csv` | артист, у которого **>1 трека** |
> | строки с пустым `artist` | **не попадают ни в один файл** (не «артист») |
>
> `rebuild_summaries.py` **переписан** под это определение + добавлена **сортировка**
> (артист → album → номер трека как **число** → title → path).
> Раньше `single` = «трек без номера» — имя файла врало, в `single` лежал Высоцкий с 269 треками.
>
> ### Инварианты — гонять после каждой пересборки (`rebuild_summaries.py` + чекер ниже)
> | # | Инвариант | Значение |
> |---|---|---:|
> | 1 | артистов с >1 треком в `single` | **0** |
> | 2 | артистов с 1 треком в `multi` | **0** |
> | 3 | пересечение артистов `single`∩`multi` (точно) | **0** |
> | 4 | пересечение **нормализованно** (регистр, ё/е, `группа `) | **0** |
> | 5 | один артист в разных написаниях внутри `single` | **0** |
> | 6 | пустых `artist` в обоих файлах | **0** |
> | 7 | оба файла отсортированы | **True** |
>
> Чекер (read-only, обязателен перед «готово»):
> ```python
> # нормализация должна убирать: регистр, ё→е, префикс "группа ", двойные пробелы
> def norm(s):
> s=unicodedata.normalize('NFC',s or '').strip().lower().replace('ё','е')
> s=re.sub(r'^группа\s+','',s)
> return re.sub(r'\s+',' ',s)
> ```
> Именно **нормализованная** проверка нашла дефект `группа Секрет` / `Секрет` — на точном
> сравнении пересечений было 0, на нормализованном — 1 внутри `single`.
>
> ### ✅ Правки волны
> | Класс | Строк | Скрипт | Результат |
> |---|---:|---|---|
> | Benny Benassi: номера из треклистов Hypnotica/Pumphonia | 29 | `batchK` | 1–14 / 1–12 |
> | Benny Benassi: сборники/синглы | 9 + 12 | `batchL`, `batchM` | номера внутри папок, без дублей |
> | Benassi: мусорные написания (`BENNY B`, `Benny Bros`, `In Grid`, `KMC Feat Sandy`) | 7 | `batchJ` | → `Benny Benassi`, `Benassi Bros Feat. Paul French`, `In-Grid`, `KMC Feat. Sandy` |
> | `группа Секрет` → `Секрет` (дубль внутри `single`) | 1 | `batchN` | **0 разных написаний** |
>
> ### 📊 ИТОГ 30-й ВОЛНЫ
>
> | Показатель | Волна 29 | **Волна 30** |
> |---|---:|---:|
> | `tag-fix-plan.csv` | 7906 | **7906 (0 битых)** |
> | `fix` / `no_change` / `skip` | 6951 / 940 / 15 | **6984 / 907 / 15** |
> | `artists-single-track.csv` | 2533 | **675** |
> | `artists-multi-track.csv` | 5357 | **6640** |
> | Сумма сводок | 7890 | **7315** |
> | Benassi в `single` | 46 | **0** |
> | Нечисловых `new_track` | 0 | **0** |
> | Дублей-артистов | 0 | **0** |
>
> **Почему сумма упала 7890 → 7315:** пустой `artist` (576 строк: 562 `!Детские/Сказки` +
> 14 прочих) больше **не считается артистом** и выброшен из обеих сводок.
> 7906 − 576 − 15 (`skip`) = **7315**. ✔
>
> ⚠️ **Остаётся:** в `single` у **294 из 675** строк нет номера трека — это одиночные синглы
> без альбома, номера у них не существует. Артист всё равно ровно один, из `single` их не убрать.
>
> ⏭ **СЛЕДУЮЩИЙ ШАГ (ждёт слова Alex):** бэкап в `Music/backup/` → применение тегов →
> приёмка в UI Jellyfin → `rm -rf Music/backup/` → создание Music-библиотеки.
>
> **Бэкапы 30-й волны:** `bak-*-batchG7`, `bak-*-batchH`, `bak-*-batchI`, `bak-*-batchJ`,
> `bak-*-batchK`, `bak-*-batchL`, `bak-*-batchM`, `bak-*-batchN`.
> **Сводка для аппрува:** `personal/tech/music-tags-final-review.md`.
>
> ---
>
> ## ✅ ДВАДЦАТЬ ДЕВЯТАЯ ВОЛНА (2026-09-26) — BATCH B–F ПРИМЕНЁН + РУЧНАЯ ДОБИВКА BENASSI
>
> Триггер: Alex — «Закончи правку имен песен в csv. Вручную.» и далее серия вспышек: