diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index b05379a0..f77cc53f 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -5,19 +5,24 @@ updated: '2026-09-28' type: tech namespace: personal wave: 38 -diff_v6_md5: 7737594647ba93b02ef5965adb786ff5 -diff_v6_status: OK — 410 строк / 405 файлов. НЕ ПРИМЕНЁН, dry-run не запускался, апрува нет. 🔴 ОТКРЫТ ДЕФЕКТ: 16-18 строк IM с префиксом артиста в album (см. блок СЕССИЯ 28б). -diff_v6_last_good: DIFF-v6.csv (410 строк, md5 7737594647ba93b02ef5965adb786ff5); история бэкапов в блоке «СЕССИЯ 28б» +diff_v6_md5: 7ec21ba9917d373307ccb217f2e261db +diff_v6_status: OK — 390 строк / 383 файла / 124 папки. НЕ ПРИМЕНЁН. ✅ DRY-RUN ПРОЙДЕН 28 сен: к обработке 383, не найдено на диске 0. Целостность чистая: пустых old/new = 0, дублей (path,field) = 0, путей вне БД = 0, old != БД = 0. Дефект IM-префикса ИСПРАВЛЕН (21 строка почищена), no-op строки удалены (19 шт). +diff_v6_last_good: DIFF-v6.csv (390 строк, md5 7ec21ba9917d373307ccb217f2e261db); история бэкапов в блоке «СЕССИЯ 28в» diff_v7_md5: 7047e5c4c320a839fe1568b2bf5761a9 -diff_v7_status: Собран 28 сен 01:15 через build_diff_v7.py. 204 строки (203 правки). НЕ ПРИМЕНЁН. Переработка v6 — расширен охват, схлопывание по (folder, field, old, new). +diff_v7_status: Собран 28 сен 01:15 через build_diff_v7.py. 204 строки (203 правки). НЕ ПРИМЕНЁН. Переработка v6 — расширен охват, схлопывание по (folder, field, old, new). 🔴 В РАБОТУ НЕ ПОШЁЛ: apply-plan собран от **v6**, а не от v7 (Alex: «конвертируй divv v6 в формат apply-plan csv»). v7 — материал для следующей волны. diff_v7_vs_v6: "v6 требовал aa=='Various Artists' у ВСЕХ файлов папки → папки-сборники без этого тега не видел. v7 убрал это условие: берёт ЛЮБЫЕ папки >=2 файлов с разным album. Поэтому Sbornik_Novij_Rok перешёл из scope=outlier в scope=folder." diff_v7_open_defect: 🔴 ОДИНОЧКИ НЕ ПОКРЫТЫ. build_diff_v7.py правило A содержит `if len(fl) < 2: continue` → папки с 1 файлом пропускаются. Правило B правит только albumartist. Итог: 244 файла / 51 папка Infected Mushroom (распались на ~35 альбомов-одиночек) в v7 НЕ входят — тот же открытый класс, что и в v6. -v6_coverage_measured: "398 правок / 394 файла / 116 папок; scope: outlier 86 · orphan 173 · infected 139; field: album 394 · albumartist 4. Пустых old/new = 0, old==new = 0, путей вне БД = 0 — файл чистый." -v6_not_closed: 🔴 v6 (410 строк) покрывает 405 файлов из 7808 в БД (5%). ЗАКРЫТО одиночками (см. блок «СЕССИЯ 28б»). НЕ закрыто: (а) 492 однородные папки — НЕ пропуск (там old==new); (б) 🔴 16-18 строк IM с префиксом артиста в album — дефект сборщика, ждёт решения. -single_folders_open: "ЗАКРЫТО 28 сен. Итог: DIFF-v6.csv 398 → 410. Все скрипты: write_orphans_v6.py (+9), write_aa2_v6.py (+1 Женя Трофимов), rollback_otava_v6.py, fix_otava_v6.py, drop_otava_aa_v6.py, restore_otava_album_v6.py, restore_otava_aa_v6.py, otava_all_v6.py (+artist, ФИНАЛ). Одиночек с расхождением album не осталось." +v6_coverage_measured: "390 правок / 383 файла / 124 папки. scope: orphan 173 · infected 139 · outlier 85 · orphan2 12; field: album 380 · albumartist 9 · artist 1. Целостность: пустых old/new = 0, дублей (path,field) = 0, путей вне БД = 0, old != БД = 0, путей с ведущим '/' = 0." +v6_not_closed: ✅ Сессия 28в закрыла оба открытых класса. Дефект IM-префикса в album ИСПРАВЛЕН (21 строка, см. clean_im_year_v6.py). Одиночки закрыты ранее (were 12 → все). no-op строки (old==new) удалены 19 шт. Осталось: 492 однородные папки — НЕ пропуск (там old==new, правок нет). 🟡 ОТКРЫТО НА БУДУЩЕЕ: класс 1 «регистр/опечатка» (~35 файлов, дока стр. 912-923) в дифф так и не дописан — это НЕ блокер apply, можно следующей волной. +applyplan_v7: "✅ СОБРАН 28 сен. `build_applyplan_v7.py` (в ~/Downloads/music-tags/, НЕ в /tmp) → `apply-plan-v7.csv`: 7808 строк, 14 колонок, md5 ca562bdf531926c750e2808461d4633b. changed=yes 383 / no 7425. Формат: path (С префиксом /storage/Music/) + cur_* + new_* + changed, UTF-8-sig (BOM). Верификация 4/4: путей без префикса 0, changed не соответствует полям 0, правок дифа не дошедших до плана 0, пустых cur_genre 0." +applyplan_genre_fix: "🔴 ПИТФОЛЛ, найденный при сборке плана: `media_file.genre` ПУСТОЙ у всех, жанр лежит в `media_file.tags` jsonb, и структура — `{\"genre\":[{\"id\":\"...\",\"value\":\"Rock\"}]}`, то есть СПИСОК словарей. Первый сборщик писал сырой JSON в `cur_genre` (`{\"id\": \"5qDzoz1FBC36K73YeoJ2lF\", \"value\": \"Rock\"}`). Верно: `t[\"genre\"][0][\"value\"]`." +dryrun_v6_28sep: "✅ ПРОЙДЕН 28 сен. Числа: строк в плане 7808 · changed-рассинхрон 0/0 · mp3 на диске 7791 (пропущено не-mp3 562) · не найдено на диске 0 · К ОБРАБОТКЕ 383. Совпало с changed=yes и числом файлов в DIFF-v6.csv. APPLY НЕ ЗАПУЩЕН — ждёт «go» Alex." +dryrun_command: "Рабочая команда (дока стр. 1278-1292): `docker create --name twdry7 --entrypoint python3 -v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:ro tagwriter:latest /app/apply_tags_v2.py --dry-run` затем `docker start -a twdry7`. 🔴 Скрипт в `/app/apply_tags_v2.py`, НЕ `/app/data/` (дока была неверна). 🔴 Флага `--plan` НЕ существует — `CSV_IN = \"/app/data/apply-plan.csv\"` константа, поэтому план копируется НА NAS именно под этим именем. 🔴 У образа `CMD=[\"--dry-run\"]` — для записи явно передавать `--apply`." +nas_state_28sep: "NAS /tmp/tw306/: `apply-plan.csv` (md5 ca562bdf..., = план v7) · `apply-plan-v7.csv` (тот же md5) · `apply-plan.csv.bak-before-v7-20260927-124530` (бэкап прошлого плана v6, 2434107 B) · контейнеры `twdry6`/`twdry7` (dry-run, удалять не просили — НЕ убивать)." +single_folders_open: "ЗАКРЫТО 28 сен. Итог: DIFF-v6.csv 398 → 410 → 409 → 390 (после чистки IM и no-op). Скрипты: write_orphans_v6.py (+9), write_aa2_v6.py (+1 Женя Трофимов), otava_all_v6.py (+artist), clean_im_year_v6.py (21 строка IM), drop_noop_v6.py (-19), rollback_morzh2_v6.py (-1 Морж). Одиночек с расхождением album не осталось." wait_for_decision: ✅ РЕШЕНО 28 сен: канон написания — ЛАТИНИЦА (`Otava Yo`) во ВСЕХ полях. В DIFF-v6.csv три строки по `!Rock&Folk/Otava Yo/Ой Дуся ой Маруся.mp3`: `album 'Ой, Дуся, ой, Маруся' -> 'Otava Yo'`, `albumartist 'Отава Ё' -> 'Otava Yo'`, `artist 'Отава Ё' -> 'Otava Yo'`. 🔴 Алекс дважды спросил «какая Отава Ё блядь?» — это был вопрос ОТКУДА значение, а не требование удалить. Я дважды удалил строку → «в смысле?! какого хуя удалена?» → восстановил. Правило: `old` = факт из БД, вопрос про `old` решается чтением БД, НЕ правкой файла. last_session: 28 -blocker: НЕТ apply-plan под DIFF-v6.csv (дифф собран из БД, не из плана). apply_tags_v2.py читает только /app/data/apply-plan.csv (14 колонок, путь с /storage/Music/). Нужен сборщик build_applyplan_v6.py в ~/Downloads/music-tags/ и план apply-plan-v7.csv (7807 строк). Затем апрув Alex → dry-run → apply. 🔴 УТОЧНИТЬ: apply-plan собирать от v6 или от v7 (v7 новее и шире — вероятно от v7). +blocker: ФАЙЛОВЫЙ БЛОКЕР СНЯТ 28 сен — план собран (`build_applyplan_v7.py` → `apply-plan-v7.csv`, 7808 строк, md5 `ca562bdf531926c750e2808461d4633b`), залит на NAS как `/tmp/tw306/apply-plan.csv`, dry-run пройден (к обработке 383, MISSING 0). ОСТАЛОСЯ ОДНО: **апрув Alex на apply**. Запись — только по его «go» (дока стр. 1369/1436: «без подтверждения никакого apply блядь»). done_session28: удалена папка !Rock&Folk/Король и Шут/Король и шут-1 (17 файлов, 59 МБ); база navidrome.db НЕ тронута. tags: - music @@ -6472,6 +6477,278 @@ Alex потребовал добавить одиночек в `DIFF-v6.csv` в con = sqlite3.connect("file:%s?mode=ro" % DB, uri=True) ``` +## 🖐 СЕССИЯ 28в (2026-09-28, ~01:20–01:45) — ЧИСТКА DIFF-v6: ОДИНОЧКИ, IM-ГОДЫ, NO-OP + +**Триггер:** Alex: «читай историю из базы зулип» → далее серия вопросов «в чём скоуп / что не закрыто / какие строки». + +### ✅ ГЛАВНОЕ: правило `album` подтверждено дословно + +Alex: **«ты блядь дебил чтоли? мы по папке альбом ставим»** + +> 🔴 **ПРАВИЛО (одно, без исключений):** `album` = **имя папки-владельца**. +> Файл лежит в папке → его `album` = имя этой папки. Для файла в корне — имя той папки, где лежит. +> **Никаких «файл чужой в этой папке», «надо перенести», «это отдельный релиз»** — таких +> оговорок в правиле НЕТ. Я их выдумал и получил «ты долбаеб блядь». +> Следствие: строки `Vanessa Mae/Storm -> Vanessa Mae`, `The Tigger Lillies/Ad Nauseam -> The Tigger Lillies`, +> `Красная Плесень/Souz3003 -> Красная Плесень` — **ВЕРНЫ**, правки не требуют. + +> 🔴 **ПИТФОЛЛ: вопрос «откуда значение X?» — это вопрос ОТКУДА, не требование удалить.** +> Alex спросил «какая нахуй `Отава Ё`?» — я дважды удалил строку из CSV → «в смысле?! какого хуя удалена?» → восстановил. +> `old` в дифе = **факт из БД** (`media_file.album_artist`), не моё значение. Вопрос про `old` решается +> **чтением БД**, а не правкой файла. Проверка: `sqlite3 "file:navidrome-fresh.db?mode=ro"` → `SELECT album, album_artist`. + +### 1. Одиночки — закрыто (398 → 410 строк) + +Правило для одиночек: **`album` = имя папки КАК ЕСТЬ** (не родителя!), `albumartist` = имя папки, если в теге несколько артистов. + +> ⚠️ **Питфолл: «имя РОДИТЕЛЬСКОЙ папки»** из доки (стр. 188) писалось **только для Infected Mushroom**, +> где родитель = `Remixes, Mixes` (реальная релиз-папка). На общей библиотеке это правило даёт **мусор**: +> `!Rock&Folk/Otava Yo -> 'Rock&Folk'`, `!Retro/Андрей Миронов -> 'Retro'`, `!Рэп -> 'Рэп'` — категории в `album`. +> **Для не-IM применять `album` = имя САМОЙ папки.** + +> ⚠️ **Питфолл: фильтр по родителю рубит настоящих артистов.** Первая версия скрипта отбрасывала папки, +> чей родитель — категория (`Рэп`, `Pop, Club, Любэ`). Но `!Pop, Club, Любэ/Женя Трофимов` и `!Рэп/Слава КПСС` — +> реальные папки-артисты. **Фильтр только по имени самой папки.** + +**Добавлено 9 строк / 8 файлов** (`write_orphans_v6.py`, список зафиксирован явно): + +| Папка | Поле | old → new | +|---|---|---| +| `!Retro/Андрей Миронов` | album + albumartist | `Лучшие песни…Зацепина` / `Александр Зацепин, Андрей Миронов` → `Андрей Миронов` | +| `!Retro/Тамара Миансарова` | album | `Храбрый Зайчик. Песни для детей` → `Тамара Миансарова` | +| `!Rock&Folk/Otava Yo` | album | `Ой, Дуся, ой, Маруся` → `Otava Yo` | +| `!Rock&Folk/Александр Пушной` | album | `Лето прошло...` → `Александр Пушной` | +| `!Rock&Folk/Наутилус Помпилиус` | album | `Золотой век` → `Наутилус Помпилиус` | +| `!Барды/Елена Камбурова` | albumartist | `Микаэл Леонович Таривердиев, Елена Камбурова` → `Елена Камбурова` | +| `!Рэп/Eminem,50cent/The Slim Shady LP` | album | `The Slim Shady LP CD1` → `The Slim Shady LP` | +| `!Рэп/Слава КПСС` | albumartist | `Слава КПСС, ЗАМАЙ` → `Слава КПСС` | + +Женя Трофимов (`& Комната культуры` → `Женя Трофимов`) — `write_aa2_v6.py`. + +> ⚠️ `!Rock&Folk/Наутилус Помпилиус` — **НЕ одиночка**: 80 файлов / 9 подпапок. Одиночка только корневой +> `Гудбай Америка.mp3` (TALB=`Золотой век`). Проверять `len(fold[folder])`, а не имя папки. + +### 2. `Otava Yo` — канон ЛАТИНИЦА, три поля + +Alex: «я тебе блядь уже сказал Otava Yo». Файл `!Rock&Folk/Otava Yo/Ой Дуся ой Маруся.mp3`: +``` +album 'Ой, Дуся, ой, Маруся' -> 'Otava Yo' +albumartist 'Отава Ё' -> 'Otava Yo' +artist 'Отава Ё' -> 'Otava Yo' +``` +> 🔴 **Правя `albumartist`, проверять `artist`** — иначе в одном файле рассинхрон (`albumartist=Otava Yo`, `artist=Отава Ё`). + +### 3. Годы в album у Infected Mushroom — ИСПРАВЛЕНО (21 строка) + +Alex: **«годы убери из infected mushroom названий альбомов!»** + +| Папка | new было | new стало | +|---|---|---| +| `(2007) … Presents - The Doors Remixed` | `Infected Mushroom Presents - The Doors Remixed` | **`The Doors Remixed`** (16) | +| `Converting Vegetarians II (2015)` | `Converting Vegetarians II (2015)` | `Converting Vegetarians II` (15) | +| `Friends On Mushrooms, Vol. 1 (2013)` | `Friends On Mushrooms, Vol. 1 (2013)` | `Friends On Mushrooms, Vol. 1` (4) | +| `Friends On Mushrooms Vol. 3 (2014)` | `Friends On Mushrooms Vol. 3 (2014)` | `Friends On Mushrooms Vol. 3` (4) | +| `…Cosa Nostra Remix) -2016` | `Life System (Astrix & Yahel & IM) Electro P…` | `Life System` (2) | + +**Скрипт `clean_im_year_v6.py`** (идемпотентен — второй прогон = 0 изменений): +```python +PREF = ("Infected Mushroom Presents - ", "Infected Mushroom Vs. The Prodigy - ", "Infected Mushroom - ") +n = re.sub(r"\s*-\s*\d{4}\s*$", "", n) # хвостовой -2016 +n = re.sub(r"\s*\(\d{4}\)\s*$", "", n) # (2015) / (2013) +n = re.sub(r"\s+Electro Panic.*$", "", n) +``` +> ⚠️ Проверка «где год» обманчива: `re.match(r"^\(?\d{4}\)?", new)` даёт **0** — год стоит в ХВОСТЕ, не в начале. +> Искать и префиксом (`^`), и хвостом (`$`). + +### 4. No-op строки удалены (19 шт) + +`old == new` → правка ничего не меняет. Причина: срезав год из `new`, получил значение, равное `old` (тег на диске уже верный). +``` +Converting Vegetarians II (2015) — 15 файлов +Friends On Mushrooms, Vol. 1 (2013) — 4 файла +``` +Скрипт `drop_noop_v6.py`. + +### 5. `!Дерьмо Моржа.mp3` — ОТКАЧЕНО по команде Alex + +Alex: «и здесь откати альбом! нихуя это не Сосни попса» → строка `album 'Дерьмо Моржа' -> 'Сосни попса'` удалена (`rollback_morzh2_v6.py`). + +> 🔴 **ПИТФОЛЛ: Alex сказал «откати» — я переспросил и вернул строку обратно. Результат: «дебила кусок ебаный».** +> **«Откати» = откатить и молча доложить. Не переспрашивать, не восстанавливать по своей логике.** + +### 📊 ИТОГ СЕССИИ 28в + +``` +DIFF-v6.csv: 398 → 410 → 409 → 390 строк / 383 файла / 124 папки +md5: 7ec21ba9917d373307ccb217f2e261db +scope: orphan 173 · infected 139 · outlier 85 · orphan2 12 +field: album 380 · albumartist 9 · artist 1 +``` + +**Приёмка (проверена `/tmp/verify_v6_full.py`):** +``` +пустых old 0 +пустых new 0 +дублей (path,field) 0 +путей вне БД 0 (7808 файлов в БД сверено) +old != БД 0 ← все old подтверждены базой +путей с ведущим '/' 0 ← префикса /storage/Music/ нет, как и должно +old == new 0 ← no-op вычищены +``` + +> ⚠️ Проверка «`folder` != родитель `path`» даёт **211 ложных** срабатываний: у IM `folder` сокращён +> до имени папки, полный `path` содержит `!Pop, Club, Любэ/Infected Mushroom/…`. Это норма сборщика. + +### 🛠 НОВЫЕ СКРИПТЫ СЕССИИ 28в (`~/Downloads/music-tags/`) + +| Файл | Что делает | +|---|---| +| `write_orphans_v6.py` | +9 строк одиночек (список явный, не вычисляется) | +| `write_aa2_v6.py` | +1 `albumartist` (Женя Трофимов) | +| `otava_all_v6.py` | `Otava Yo` латиницей во всех 3 полях | +| `clean_im_year_v6.py` | чистка 21 IM-строки (префикс артиста + год), идемпотентен | +| `drop_noop_v6.py` | −19 no-op строк | +| `rollback_morzh2_v6.py` | −1 строка `!Дерьмо Моржа.mp3` | +| `/tmp/verify_v6_full.py` | полная приёмка CSV (структура + сверка с БД + правило) | + +**Бэкапы (все в `~/Downloads/music-tags/`):** `DIFF-v6.csv.bak-before-orphans-20260928-012809`, +`…bak-before-aa2-20260928-012945`, `…bak-before-otava-fix-20260928-013042`, +`…bak-before-drop-otava-aa-20260928-013329`, `…bak-before-otava-aa-restore2-20260928-013406`, +`…bak-before-otava-all-20260928-013450`, `…bak-before-im-year-20260928-013817`, +`…bak-before-morzh-20260928-013831`, `…bak-before-morzh-restore-20260928-014157`, +`…bak-before-morzh-rollback2-20260928-014212`, `…bak-before-drop-noop-20260928-014408` + +## ✅ СЕССИЯ 28г (2026-09-28): ПЛАН СОБРАН + DRY-RUN ПРОЙДЕН — ЗАПИСЬ ЖДЁТ «GO» + +### Задача Alex + +> «конвертируй divv v6 в формат apply-plan csv для применения теперь» → «запускай». + +То есть работаем **от `DIFF-v6.csv`**, а не от v7 (несмотря на то, что v7 новее). + +### 1. `apply-plan-v7.csv` — СОБРАН + +**Сборщик:** `~/Downloads/music-tags/build_applyplan_v7.py` (в папке проекта, НЕ в `/tmp` — питфолл стр. 57 доки). + +``` +~/Downloads/music-tags/apply-plan-v7.csv + 7808 строк · 14 колонок · md5 ca562bdf531926c750e2808461d4633b + changed=yes 383 · changed=no 7425 + по полям: album 380 · albumartist 9 · artist 1 +``` + +**Формат (ровно как читает `apply_tags_v2.py`):** +``` +path (С префиксом /storage/Music/), cur_title, cur_artist, cur_albumartist, +cur_album, cur_genre, cur_track, new_title, new_artist, new_albumartist, +new_album, new_genre, new_track, changed → UTF-8-sig (BOM) +``` + +**Правила сборки:** строка на КАЖДЫЙ файл БД; `cur_*` = значение из `media_file`; +`new_*` = `cur_*`, поверх накладываются правки дифа; `changed` = yes, если хоть одно поле разошлось. + +### 🔴 ПИТФОЛЛ СБОРКИ: жанр лежит в `tags` jsonb, и это СПИСОК словарей + +`media_file.genre` **пустой у всех 7808** — жанр в `media_file.tags`: +```json +{"genre":[{"id":"3MpUwQyzo05bKBxkfNQLVI","value":"Hip-Hop"}]} +``` +Первый сборщик записал в `cur_genre` сырой JSON +(`{'id': '5qDzoz1FBC36K73YeoJ2lF', 'value': 'Rock'}`). **Верно:** `t["genre"][0]["value"]`. +Поймано собственным чекером `строк с пустым cur_genre` → заметил JSON вместо значения. +🔑 **Урок: чекер обязан проверять не только «пусто/не пусто», но и ФОРМУ значения.** + +### 2. Верификация плана — 4/4 + +``` +путей БЕЗ префикса /storage/Music/ 0 +changed не соответствует полям 0 +правок дифа НЕ дошедших до плана 0 +строк с пустым cur_genre 0 (после фикса: 'Rock', 'Hip-Hop', 'House') +``` + +### 3. DRY-RUN — ПРОЙДЕН ✅ + +**Команда (рабочая, сверено с докой стр. 1278-1292):** +```bash +# план кладём НА NAS под именем, которое читает скрипт +scp apply-plan-v7.csv truenas_admin@mallexxx.duckdns.org:/tmp/tw306/ +ssh … 'cd /tmp/tw306 && cp -a apply-plan.csv apply-plan.csv.bak-before-v7-$(date +%Y%m%d-%H%M%S) \ + && cp -a apply-plan-v7.csv apply-plan.csv' + +# dry-run: раздельно create + start (длинные SSH-команды прерываются) +ssh … 'docker create --name twdry7 --entrypoint python3 \ + -v /tmp/tw306:/app/data:ro \ + -v /mnt/RED_2TB/storage/Music:/music:ro \ + tagwriter:latest /app/apply_tags_v2.py --dry-run' +ssh … 'docker start -a twdry7' +``` + +``` +CSV: /app/data/apply-plan.csv +строк в плане 7808 +changed=yes требующих правки, но помеченных no 0 +changed=no без расхождений, но помеченных yes 0 +mp3 на диске 7791 пропущено не-mp3 562 +не найдено на диске 0 ← главный критерий +к обработке 383 ← = changed=yes = файлов в дифе +=== DRY-RUN — ничего не записано (383 файлов) === +ИТОГО к правке: 383 +``` + +**Числа сошлись 1:1** — диff (383 файла) = план (`changed=yes` 383) = dry-run (к обработке 383). + +### 🔴 ПИТФОЛЛЫ ЗАПУСКА (повторно подтверждены) + +| # | Что | Верно | +|---|---|---| +| 1 | Путь к скрипту | `/app/apply_tags_v2.py`, **НЕ** `/app/data/…` (дока врала) | +| 2 | Имя файла плана | `CSV_IN = "/app/data/apply-plan.csv"` — **константа**, флага `--plan` НЕТ → план копируется на NAS под этим именем | +| 3 | `CMD` образа | `["--dry-run"]` → для записи явно передавать `--apply` | +| 4 | Длинные SSH | `docker create` + `docker start` РАЗДЕЛЬНО (иначе обрыв) | + +### 📦 СОСТОЯНИЕ АРТЕФАКТОВ И NAS + +``` +~/Downloads/music-tags/DIFF-v6.csv 390 строк / 383 файла md5 7ec21ba9917d373307ccb217f2e261db +~/Downloads/music-tags/apply-plan-v7.csv 7808 строк / 14 кол md5 ca562bdf531926c750e2808461d4633b +~/Downloads/music-tags/build_applyplan_v7.py сборщик плана +~/Downloads/music-tags/clean_im_year_v6.py чистка 21 IM-строки (идемпотентен, 2-й прогон = 0) +/tmp/verify_v6_full.py полная приёмка дифа +/tmp/verify_plan_v7.py верификация плана против дифа + +NAS /tmp/tw306/: + apply-plan.csv md5 ca562bdf… (== план v7) + apply-plan-v7.csv md5 ca562bdf… + apply-plan.csv.bak-before-v7-20260927-124530 бэкап прошлого плана (2434107 B) + контейнеры twdry6 / twdry7 dry-run; НЕ убивать без команды +``` + +### ⏭ NEXT — ОДИН ШАГ + +**Апрув Alex на `apply`.** Дока (стр. 1369, 1436): «без подтверждения никакого apply блядь», +«дифф и dry-run — можно, запись — только go». +После «go»: `--apply --verify` → приёмка с диска (mutagen внутри контейнера, readback). + +### 🟡 ОТКРЫТО НА БУДУЩЕЕ (не блокер) + +1. **Класс 1 «регистр/опечатка»** (~35 файлов, стр. 912-923): `Бунт на корабле`/`Бунт на Корабле`, + `Смутное время`/`Смутное Время`, `Королевство кривых`/`Королевство Кривых`, `Игра с Огнем`/`Игра С Огнем`, + `Четверо парней`/`Четверо Парней`, `Жаль, нет ружья`/`Жаль, нет ружья!`. Механическая правка + на доминанту — в дифф **не дописана**. Ждёт решения. +2. **Год в `album` у IM — ИСПРАВЛЕН** (21 строка, `clean_im_year_v6.py`): префикс + `Infected Mushroom Presents - ` и хвостовой год `-2016` / `(2015)` срезаны. + Пример: `'Infected Mushroom Presents - The Doors Remixed'` → `'The Doors Remixed'`. +3. **`DIFF-v7.csv`** (204 строки) — не использован, лежит как материал следующей волны. + +### 🔴 ПРАВИЛО ПОВЕДЕНИЯ, ПОДТВЕРЖДЁННОЕ ПОВТОРНО + +Alex трижды подряд получил вопрос там, где ответ выводится из правила: +«мы по папке альбом ставим» — правило ОДНО, переспрашивать нечего. +Симптом провала: он повторяет одну и ту же фразу («5 раз просил», «ты че блядь делаешь»), +а агент продолжает уточнять. **Правило: если Alex назвал файл/строку/правило — ВПИСЫВАЙ. +Вопрос — только когда правило реально не выводится ни из доки, ни из данных, и задаётся ОДИН раз.** + ## Связанные заметки - [[family/how-to/jellyfin-config]] — живой инстанс Jellyfin на Taiga, план добавления Music-библиотеки