From 3946ccd352be49320616135a5fba16d778ba595d Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 15:50:21 +0600 Subject: [PATCH] [2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/plans/tagwriter-container.md personal/tech/id3-tag-repair.md --- .../plans/music-artist-albumartist-repair.md | 91 +++++++++++++- personal/plans/tagwriter-container.md | 112 +++++++++++++++++- personal/tech/id3-tag-repair.md | 59 +++++++++ 3 files changed, 250 insertions(+), 12 deletions(-) diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 0ec8850c..c2c479e2 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -19,7 +19,7 @@ related: - '[[personal/tech/music-tags-repair-truenas]]' - '[[personal/plans/tagwriter-container]]' - '[[family/how-to/navidrome-truenas-android]]' -status: manual-pass-17-defects-pending-approval +status: session17-gluefix-applied-awaiting-alex-check # Music `artist` + `albumartist` Repair — правила Alex и ход работ @@ -42,7 +42,7 @@ status: manual-pass-17-defects-pending-approval > ⚠️ Бэкап **не** снимался: проход был read-only (ни одного байта не изменено). Бэкап — > обязателен только перед записью. > -> ## РЕЗУЛЬТАТ ПОВТОРНОГО ПРОХОДА: 3 реальных дефекта (101 строка), НЕ 9 +> ## РЕЗУЛЬТАТ ПОВТОРНОГО ПРОХОДА: 3 реальных дефекта (102 строки), НЕ 9 — ✅ ИСПРАВЛЕНЫ > > 6 детекторов (`~/Downloads/music-tags/manual_scan.py`, `manual_scan2.py`), полный вывод — > `~/Downloads/music-tags/manual_scan_out.txt`. @@ -53,9 +53,10 @@ status: manual-pass-17-defects-pending-approval > | 2 | `album` = склейка «Категория - Подборка» | 77 | `Radio - 2009-10 Oktoberfest` → `2009-10 Oktoberfest` | `!Radio/*` | > | 3 | `album` = технический префикс папки | 6 | `(2001) Infected Mushroom - B.P. Empire` → `B.P. Empire` | `Infected Mushroom/*` | > -> ⚠️ **Статус: ждут апрува Alex. В файл НЕ записано.** Правило: `album` = имя **подпапки**, -> склейка `Родитель - Папка` запрещена (A3/D22). Технические префиксы — год издания -> `(2001)`, имя артиста — не часть названия альбома (ср. A6: `(CD1)`/`(Disc 2)`). +> ✅ **ПРИМЕНЕНО** (Alex: «Да, поправь все 3») — см. ниже «ПРИМЕНЕНО (сессия 17, вторая +> половина)». Правило: `album` = имя **подпапки**, склейка `Родитель - Папка` запрещена (A3/D22). +> Технические префиксы — год издания `(2001)`, имя артиста — не часть названия альбома +> (ср. A6: `(CD1)`/`(Disc 2)`). > > ### ⚪ Ложные срабатывания детекторов (проверено фактами, НЕ дефекты) > @@ -73,7 +74,85 @@ status: manual-pass-17-defects-pending-approval > Прежде чем называть класс дефектом — проверять его **против `cur_album`** (план не выдумывал?) > и **по соседним файлам папки** (правило A2/D22). -## 🟢 АКТУАЛЬНО НА КОНЕЦ СЕССИИ 16 (2026-09-27) — читать это первым +> ### ✅ ПРИМЕНЕНО (сессия 17, вторая половина) — 102 строки +> +> Alex: «**Да, поправь все 3**». Правка выполнена скриптом `~/Downloads/music-tags/fix_glue.py` +> (dry-run по умолчанию, `--apply` пишет). Два прогона: 101 строка, затем +1 (см. питфолл тире). +> +> | # | дефект | строк | было → стало | где | +> |---|---|---:|---|---| +> | 1 | склейка «Категория - Подпапка» | **18** | `Бард - Авангард` → `Бард-авангард` | `!Рэп/Трофим/07 Бард-авангард` | +> | 2 | склейка «Категория - Подборка» | **77** | `Radio - Старое` / `Radio - Df1` / `Radio - 2009-10 Oktoberfest` → имя подпапки | `!Radio/*` | +> | 3 | технический префикс папки | **7** | `(2001) Infected Mushroom - B.P. Empire` → `B.P. Empire` | `Infected Mushroom/*` | +> +> #### ⚠️ ПОПРАВКА К ПРЕДЛОЖЕНИЮ: `Бард - Авангард` — НЕ категория +> В первой версии отчёта я записал `Бард - Авангард` как склейку «категория - подпапка» с +> целевым значением `Разное`. **Неверно — и это был бы выдуманный дефект (A2/D22).** +> Проход по фактам: папка = `!Рэп/Трофим/07 Бард-авангард`, то есть `Бард-авангард` — **название +> альбома Трофима** (2001). Значение через дефис с пробелами — опечатка-разделитель; норма — +> как в имени папки: `Бард-авангард` (дефис без пробелов). **Данные Alex не давал — выведено +> из пути, по правилу «факты важнее догадки».** +> +> #### 🔴 ПИТФОЛЛ: длинное тире `–` не поймано регуляркой по дефису +> Первый прогон исправил 6 из 7 IM-строк. Осталась +> `(2012) Infected Mushroom – U r so fucked (GANDI Remix)` — **en-dash `–` U+2013**, а не +> дефис. Регулярка `-` её не матчила, верификатор показал `BAD … 1`. Починка — класс тире: +> +> ```python +> DASH = r"[-–—]" # hyphen, en-dash, em-dash +> RE_IM = re.compile(r"^\(\d{4}\)\s+Infected Mushroom\s+" + DASH + r"\s+(.+)$") +> ``` +> **Правило: везде, где ищем «разделитель» в значении из ID3, матчить ВСЕ три тире.** +> Это тот же класс, что A6 (`(CD1)` суффиксы): технический шум папки/тега прячется за +> разными вариантами одного символа. +> +> #### Форма записи и контрольные суммы +> ``` +> APPLY-PLAN.csv 2269466bbc08c018482b35c855569a8f → 42d32ede85fef1b2fad6e2ce87012b7f +> DIFF-new-vs-38.csv 4e9e43318fb1c9dd353f96e3d125919c → 4e073fe50c191cc50015d3f15f6a6d28 (2852 строки) +> бэкапы: APPLY-PLAN.csv.bak-gluefix-20260927-154547 (101) · …-154606 (1) +> ``` +> Порядок соблюдён: **коммит ДО → правка → бэкап → ПРОВЕРКА** (ждёт Алекса). `changed=yes` +> проставлен всем 102 строкам. Скрипт пишет через `tmp` + ревалидацию полей (16) + `os.replace`, +> по схеме защиты от обрезки CSV (§5 `id3-tag-repair`). +> +> #### 🔴 УРОК: 101 строка прошла RC-ревью незамеченной — доказать фактом +> Alex спросил: «**В rc этого не было.. это новые дефекты?**» Прямая проверка скриптом +> `were_these_in_rc.py` против снапшота `milestones/RC-20260927/`: +> ``` +> RC: 7861 строк | текущий: 7861 | одинаковых строк: 7861 | отличий: 0 +> Бард - X RC=18 сейчас=18 +> Radio - X RC=77 сейчас=77 +> (год) IM - X RC= 6 сейчас= 6 +> ``` +> → **дефекты НЕ новые, они присутствовали в RC в тех же количествах и были пропущены.** +> RC-проход искал срабатывания по имени папки-артиста и не проверял значение на **склейку**. +> **Вывод для будущих ревью: дифф проверять и на форму значения (склейка, префикс), не только +> на согласованность с папкой.** +> +> #### ✅ Верификация после правки (вывод по САМОМУ файлу, `verify_glue.py`) +> ``` +> Бард - Авангард (старое написание) 0 album='Бард-авангард' 18 +> Radio - X (склейка) 0 album='Старое' (Radio) 20 +> (год) IM - X (склейка) 0 +> ``` +> Контроль полноты (`verify_diff_completeness.py`): «ПОТЕРЯНО» = 881, **но это не потери** — +> скрипт `loss_kind.py` показал: у всех 881 новое значение ПРИСУТСТВУЕТ в строке ПАПКА, +> отличается только `old` (``, схлопывание D16). **Правок, пропавших из диффа: 0.** +> `ЛИШНИХ строк в диффе: 0`. +> +> #### Артефакты сессии 17 +> | Файл | Назначение | +> |---|---| +> | `~/Downloads/music-tags/manual_scan.py`, `manual_scan2.py` | 6 детекторов по плану/диффу | +> | `~/Downloads/music-tags/manual_scan_out.txt` | полный список кандидатов (3422) | +> | `~/Downloads/music-tags/were_these_in_rc.py` | сверка со снапшотом RC (ответ на вопрос Alex) | +> | `~/Downloads/music-tags/fix_glue.py` | правка 3 классов склейки (`--apply`) | +> | `~/Downloads/music-tags/verify_glue.py` | верификация значений по файлу | +> | `~/Downloads/music-tags/loss_kind.py` | разбор «881 потерь» → 0 реальных | +> | `~/Downloads/music-tags/diff_new_vs_38.py` | генератор диффа (перегенерация) | + +## 🟢 АКТУАЛЬНО НА КОНЕЦ СЕССИИ 16 (2026-09-27) > ``` > APPLY-PLAN.csv 7861 строка md5 2269466bbc08c018482b35c855569a8f diff --git a/personal/plans/tagwriter-container.md b/personal/plans/tagwriter-container.md index 2f595e5a..6123090b 100755 --- a/personal/plans/tagwriter-container.md +++ b/personal/plans/tagwriter-container.md @@ -4,7 +4,7 @@ created: '2026-09-26' updated: '2026-09-27' type: plan namespace: personal -status: v16-diff-session2-3594-rows-album-filled-radio-format-done-track-open +status: session17-applyscript-audited-3-questions-open-no-run tags: - music - truenas @@ -20,10 +20,92 @@ related: --- # tagwriter — применение тегов к MP3 (TrueNAS) -> 🔴 **СТАТУС: БОЕВОЙ ПРОГОН НЕ ЗАПУЩЕН.** План правится в сессиях 8-9 -> (дифф против 38-го прогона) — подробности в [[personal/plans/music-artist-albumartist-repair]] § «СЕССИЯ 8/9». -> Актуальные MD5: `APPLY-PLAN.csv` = `d1fa36dc0fba72faeff08856369fca46` (7861 стр), -> `DIFF-new-vs-38.csv` = `374ed1fed3abb7d9ec397025c0c88a2b` (4945 стр). +## 🔴 СЕССИЯ 17: АУДИТ APPLY-СКРИПТА ПЕРЕД БОЕВЫМ ПРОГОНОМ — ЧИТАТЬ ПЕРВЫМ + +> Alex: «**Проверяй ещё раз руками apply script. Скажи алгоритм которым он будет применять +> правки и найди если есть какие-то проблемы.**» +> +> Аудит read-only, ничего не запускалось. Скрипты: +> `~/Downloads/music-tags/audit_apply_script.py`, `audit_details.py`, `audit_critical.py`. +> +> ### АЛГОРИТМ (порядок исполнения, проверен по коду) +> +> | # | Шаг | Что делает | +> |---|---|---| +> | 1 | `_self_check()` | до записи: каждый фрейм `FIELD_MAP` создаётся в `_frames()` и покрыт `expected()`, иначе ABORT | +> | 2 | `load_plan()` | читает `/app/data/apply-plan.csv` | +> | 3 | `_check_changed_consistency()` | `changed=yes` ⟺ есть расхождение после нормализации | +> | 4 | `build_disk_index()` | обход `/music`, **только `.mp3`**, папки `backup*` исключены | +> | 5 | `build_jobs()` | `changed=no` → пропуск; `map_path()` (не выпускает за `/music`); нет в индексе → `missing` | +> | 6 | `cmd_apply()` | нет `/music/backup` → `exit 2` | +> | 7 | `apply_one()` ×N | `_preserved_frames()` (TDRC/COMM из v2→v1) → `delall` безусловно 6 фреймов → `add` непустых → `save(disk, v1=0, v2_version=3)` | +> | 8 | `--verify` | read-back всех, MISMATCH → `/app/data/apply-report.csv` | +> +> ### ✅ ЧТО ПРОВЕРЕНО И БЕЗОПАСНО +> +> ``` +> колонки: 16/16, отсутствующих нет · дублей path: 0 · пробелов в new_*: 0 +> changed='no' c расхождением (потеря правки): 0 +> TRCK-мусор в new_track: 0 · new_title пуст → удаление TIT2: 0 +> save() в тот же путь · v1=0 · фильтр .mp3 · map_path не выпускает за /music · probe="1" +> ``` +> +> ### 🔴 ПРОБЛЕМЫ, НАЙДЕННЫЕ АУДИТОМ +> +> **1. ОБРАЗ `tagwriter` НЕ СОБРАН ВООБЩЕ.** `docker images` пуст — ни `tagwriter:latest`, +> ни `tagwriter:v2`. На `2026-09-27` **билд выполняется впервые**. Обязателен `docker cp` +> актуального `apply_tags_v2.py` (питфолл W12). +> +> **2. ДВА DOCKERFILE — РАЗНЫЕ СКРИПТЫ. Брать только `Dockerfile.v2`.** +> +> | Файл | `COPY` | Вердикт | +> |---|---|---| +> | `Dockerfile` | `apply_tags.py` (v1, 5990 Б) | 🔴 **устаревшая v1**: без `TRCK`, без `TPE2`, без фильтра не-mp3 — та версия, что испортила 16 файлов | +> | `Dockerfile.v2` | `apply_tags_v2.py` | ✅ `python:3.11-slim` + `mutagen==1.47.0` | +> +> Копии `apply_tags_v2.py` (`~/Automation/...` и `~/Downloads/.../tagwriter/`) **совпадают по коду**, +> различие только в docstring (`APPLY-PLAN-FINAL2.csv` vs `APPLY-PLAN.csv`). +> +> **3. 🟡 12 строк `changed=yes` без реальных правок** — все 6 полей `cur == new`: +> `The Prodigy - Girls (Rex The Dog Remix)` · `Ночные Снайперы/Армия (2009)` (9 файлов) · +> `Infected Mushroom` (2). Тег перезапишется тем же значением: данные не портятся, но это +> лишняя запись и ложная строка в `field_changes`. **Ждёт решения Alex — снять флаг или нет.** +> +> **4. 🟡 `--force` переворачивает проблему 3.** Обработает 212 `changed=no`, у которых +> расхождений **0** (проверено) → лишние перезаписи. `--force` не нужен. +> +> **5. 🟡 TRCK канонизируется: `01` → `1`, `03/12` → `3/12`.** 11 файлов (Infected Mushroom, +> Ночные Снайперы) имеют **корректный** тег в форме с ведущим нулём, но будут перезаписаны +> из-за нормализации. Семантически эквивалентно, но это запись без изменения смысла. +> **Ждёт решения Alex — канонизировать или нет.** +> +> ### 🔴 ПИТФОЛЛ АУДИТА: два скрипта дали 21 vs 12 — сравнивать track НАДО нормализованно +> +> `audit_apply_script.py` сравнивал `track` сырыми строками → `'01' != '1'` → **21 ложная** +> строка «лишний changed». Строгий `audit_critical.py` нормализует `track` через тот же +> `normalize_track()` → **12 реальных**. **Любое сравнение `track` — только после канонизации.** +> +> ### 🔴 ПИТФОЛЛ: `any(c != (r[c] or "").strip() and r[c] for c in r...)` — переменная `c` из `r` в генераторе +> +> Детектор пробелов в `new_*` сравнивал значение **само с собой** и вернул ложные «7861». +> Перепроверка отдельным скриптом — **0 пробелов**. **Генератор по `r` внутри условия, которое +> зависит от `r[c]`, даёт тождество. Писать условие явным циклом.** +> +> ### ⏳ СТАТУС ПОСЛЕ АУДИТА +> +> Три вопроса к Alex (пп. 3 и 5 — решение, п. 1/2 — действие перед прогоном): +> 1. образ собрать — **билд впервые**, `Dockerfile.v2`; +> 2. `changed=yes` без правок (12 строк) — снять флаг? +> 3. `track` с ведущим нулём (11) — канонизировать в `1`/`3/12`? +> +> Боевой прогон не запущен. + +## 🔴 СТАТУС: БОЕВОЙ ПРОГОН НЕ ЗАПУЩЕН + +> План правится в сессиях 8-17 +> (дифф против 38-го прогона) — подробности в [[personal/plans/music-artist-albumartist-repair]] § «СЕССИЯ 17». +> Актуальные MD5 (обновлены в сессии 17 после gluefix): `APPLY-PLAN.csv` = `42d32ede85fef1b2fad6e2ce87012b7f` (7861 стр), +> `DIFF-new-vs-38.csv` = `4e073fe50c191cc50015d3f15f6a6d28` (2852 стр). > **Класс `Various Artists` поверх одного артиста — вычищен полностью** (детектор > `find_va_misuse.py` → `ПАПОК-КАНДИДАТОВ: 0`), корень `z Другое…` откачен на собственный `artist`. > Открыто: `track` (56 файлов с `238` + 9 с пустым при непустом `cur_track`), контейнер `snapcur` на NAS. @@ -517,16 +599,31 @@ docker start -a tw && docker rm -f tw 22. **«Транслит в поле» ≠ дефект.** `Absolution`, `Sehnsucht`, `The Marshall Mathers LP` — настоящие названия. Дефект только транслит **русских** слов (`Sbornik_Novij_Rok`). Фильтр должен отделять язык-источник, а не «есть латиница». +23. **Сравнение `track` только ПОСЛЕ нормализации.** `audit_apply_script.py` сравнивал сырые + строки → `'01' != '1'` дало **21 ложную** строку «лишний `changed=yes`»; строгий + `normalize_track()` дал **12 реальных**. Любой детектор по `track` обязан канонизировать + обе стороны, иначе завышает объём работы в разы. +24. **Генератор по `r` внутри условия, зависящего от `r[c]`, даёт тождество.** + `any(c != (r[c] or "").strip() and r[c] for c in r if c.startswith("new_"))` сравнивает + значение **само с собой** → ложные «7861 строк с пробелами». Реальность: **0**. + **Писать такое условие явным `for`-циклом, а не comprehension.** +25. **Два `Dockerfile` в одном каталоге = два разных скрипта внутри образа.** + `Dockerfile` копирует `apply_tags.py` (v1), `Dockerfile.v2` — `apply_tags_v2.py`. + Прогон по неверному файлу = возврат дефектов P1 (`TRCK`) и D3 (не-mp3). + **Сверять `COPY` в Dockerfile с именем боевого скрипта перед `build`.** ## 9. Что осталось | # | Шаг | Статус | |---|---|---| +| 0 | **Собрать образ `tagwriter:v2`** — 🔴 **БИЛД ВЫПОЛНЯЕТСЯ ВПЕРВЫЕ** (образа на Mac нет вообще). Только `Dockerfile.v2` (не `Dockerfile` — в нём устаревшая v1). Затем `docker cp` актуального скрипта | ⏳ **новая задача сессии 17** | | 0a | **Правка 20 файлов в CSV** (`fix_20_files.py`) | ⏳ **скрипт написан, НЕ ЗАПУЩЕН** (решения Alex получены) | | 0b | Вопрос по `title='Вивальди А. - Зима (рок-обработка)'` — срезать префикс? | ⏳ открыт | | 0c | **Транслит русских слов в `new_album`** (204 файла: `Sbornik_Novij_Rok` 200 и др.) | ⏳ ждёт решения Alex | | 0d | `Филипок` / `Маленький_Мук` — `album='Сказочный сборник'` / транслит | ⏳ войдёт в 0c | -| 1 | Боевой `--apply --verify` по 7861 | 🔴 **ЖДЁТ «go»** (после 0a–0c) | +| 0e | **12 строк `changed=yes` без реальных правок** — снять флаг? | ⏳ **вопрос сессии 17** | +| 0f | **`track` с ведущим нулём** (`01`, `03/12`) — канонизировать в `1`/`3/12`? 11 файлов | ⏳ **вопрос сессии 17** | +| 1 | Боевой `--apply --verify` по 7861 | 🔴 **ЖДЁТ «go»** (после 0, 0a–0f) | | 2 | Приёмка Alex в UI (Navidrome/Jellyfin) | ⏳ | | 3 | Откат 40 из `backup_conv40/` | ⏳ | | 4 | Удаление 29 битых/DRM ([[personal/tech/music-broken-toload]]) | ⏳ | @@ -548,6 +645,9 @@ docker start -a tw && docker rm -f tw | `~/Automation/music-tags/Dockerfile.v2` | python:3.11-slim + mutagen 1.47.0 | | `~/Downloads/music-tags/tagwriter/rename_apply_plan.py` | раскладка имён CSV (одноразовый) | | **`~/Downloads/music-tags/tagwriter/fix_20_files.py`** | **правка 20 файлов в боевом CSV — НЕ ЗАПУЩЕН** | +| `~/Downloads/music-tags/audit_apply_script.py` | аудит apply-скрипта против боевого CSV (сессия 17) | +| `~/Downloads/music-tags/audit_details.py` | разбор `changed=yes` без правок + пробелы в `new_*` | +| `~/Downloads/music-tags/audit_critical.py` | строгий аудит: `track` через `normalize_track()` (12 vs 21) | ### Бэкапы CSV diff --git a/personal/tech/id3-tag-repair.md b/personal/tech/id3-tag-repair.md index 1a1156b0..5bdf46a8 100755 --- a/personal/tech/id3-tag-repair.md +++ b/personal/tech/id3-tag-repair.md @@ -61,6 +61,11 @@ related: > править выхваченные строки (сессия 15: 413 из 420 «нарушений» были ложными). > Полные формулировки — в `SKILL.md` скилла. +> ⚠️ **Обновлён до v2.1.0 (сессия 17):** **A7** (склейка `Родитель - Папка` и технический префикс +> в `album`; 🔴 разделитель бывает ТРЁХ видов — `-`/`–`/`—`, матчить все) · **A8** (в контроле +> полноты «ПОТЕРЯНО: 881» = смена ФОРМЫ записи при схлопывании D16, реальных потерь 0 — +> сверять НОВОЕ значение, а не пару `old,new`). Полные формулировки — ниже. + ## 📁 Артефакты проекта **Путь:** `~/Developer/personal-os/tools/music-tags/` @@ -716,6 +721,60 @@ cur_album='Нечетный воин 2' → new_album='Нечётный Воин **Общий принцип:** технические суффиксы (`(CD1)`, `(Disc 2)`) — не часть названия альбома; перед записью значения сверять написание серии с нормой (ё, регистр). +### 🔴 A7 — СКЛЕЙКА «Родитель - Папка» / технический префикс в `album` (сессия 17) + +Отдельный класс от A3 (имя подпапки) и A6 (суффиксы серии): `album` содержит **склейку** +`Категория - Подпапка`, где левая часть — не артист альбома, а корень пути. Найдено 102 строки: + +``` +Бард - Авангард -> Бард-авангард (18) !Рэп/Трофим/07 Бард-авангард +Radio - Старое -> Старое (77) !Radio/* — album = имя подборки +Radio - Df1 -> Df1 +(2001) Infected Mushroom - B.P. Empire -> B.P. Empire (7) +``` + +Три формы склейки: **префикс-категория** (`Radio - `), **префикс-год+артист** (`(2001) IM - `), +**название с ошибочным разделителем** (`Бард - Авангард` → норма `Бард-авангард`, как в имени +папки). Правило: значение в `album` не должно содержать `Родитель - Ребёнок`; целевое значение +брать из **имени подпапки** или **пути**, не выдумывать (A2/D22). + +🔴 **Питфолл: разделитель бывает ТРЕМЯ разными символами.** Первый прогон исправил 6 из 7 +IM-строк — осталась `(2012) Infected Mushroom – U r so fucked` с **en-dash `–` (U+2013)**. +Регулярка с дефисом её не матчит, и это **не даёт ошибки** — просто строка остаётся. + +```python +DASH = r"[-–—]" # hyphen U+002D, en-dash U+2013, em-dash U+2014 +RE_IM = re.compile(r"^\(\d{4}\)\s+Infected Mushroom\s+" + DASH + r"\s+(.+)$") +``` + +**Любой поиск разделителя в значении из ID3 — матчить все три тире.** Соседний случай: NC-vs-NFD +в именах папок (§8) — тот же класс «визуально одинаковое, побайтово разное». + +🔴 **Питфолл ревью: дифф проверять не только на согласованность с папкой, но и на ФОРМУ значения.** +RC-проход сверял `new_*` с именем папки-артиста и согласованностью по папке — **склейка внутри +значения не проверялась**, 102 строки прошли ревью незамеченными. Детектор по форме (`re.search` +разделителя в `album`) даёт много ложных (222 попадания на 101 реальных): отсеивать по `cur_album` +и по соседним файлам папки. + +**Скрипты:** `~/Downloads/music-tags/fix_glue.py` (`--apply`), `verify_glue.py`, +`were_these_in_rc.py`, `loss_kind.py`. + +### 🔴 A8 — «ПОТЕРЯНО» в контроле полноты может быть сменой ФОРМЫ записи, а не потерей + +`verify_diff_completeness.py` после схлопывания диффа (D16) выдал `ПОТЕРЯНО: 881`. Реальных +потерь — **0**. Скрипт строит эталон `(path, field, old, new)` и требует точного совпадения +пары `(old, new)`; но при схлопывании у папки записывается `old = ''`, поэтому +**`old` не совпадает**, хотя `new` тот же. + +```python +# проверка: новое значение ПРИСУТСТВУЕТ в строке ПАПКА (не важно, какое там old) +if any(c[1] == nv for c in coll[(folder_of(rel), field)]): + form_change += 1 # смена формы записи, НЕ потеря +``` + +**Правило:** при разборе «потерь» сверять **новое значение**, а не пару `(old, new)` — иначе +отчёт пугает цифрой, за которой ничего нет. Скрипт `loss_kind.py`. + ### 🔴 A5 — имя ПРОЕКТА вместо артиста + вложенные скобки в имени файла (сессия 9) Класс: `artist` (и `albumartist`) = **название музыкального проекта**, а реальные исполнители