[2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/tech/id3-tag-repair.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 14:54:41 +06:00
1 parent 6576b53834
commit 85f0f009dd
2 files changed
+171 -21

No files matched your search

+66
View File
@@ -35,6 +35,10 @@ related:
> раздельных циклах бесполезна); **D11** (`new_track` теряется при пустом `cur_track`);
> **D12** (`new_track` «ни к чему не привязан» → сдвинутый ряд пересчитать, иначе убрать).
>
> ⚠️ **Обновлён до v1.8.0 (сессия 13, вторая половина):** **D16** (схлопывание диффа: ключ
> `(папка, поле, НОВОЕ значение)`, `old` в ключ не входит), **D17** (`album` унаследован от
> `cur_album` для подборок `!Radio` — 203 строки; метод: имя подборки = значение большинства).
>
> ⚠️ **Обновлён до v1.7.0 (сессия 13):** **D12 ОТКАЧЕН** — см. **D12-R** ниже (сквозная
> нумерация многодисковых изданий НЕ трогать). Добавлены **D13** (жанр унаследован от
> `cur_genre` без проверки), **D14** (статус «привязан/сирота» для `track`), **D15** (поиск
@@ -531,6 +535,68 @@ lost = [r for r in rows if r["field"] == "track"
Скрипт: `/tmp/classify_track.py`.
### 🔴 D16 — схлопывание диффа: ключ `(папка, поле, НОВОЕ значение)`, НЕ `old` (сессия 13)
Первая версия схлопывала только когда у **всех** файлов папки совпадал **и `old`, и `new`**:
```python
combos = set((ov, nv) for _, ov, nv in lst)
if len(combos) == 1 and len(lst) > 1: collapse...
else: для каждого файла отдельная строка
```
Это **почти никогда не срабатывало**: `old_38` у каждого файла свой (`'Дискотека Арлекина 2009'`,
`'Record Super Chart ¹ 90 (30.05.2009)'`, `''`, `'Ticker / Sensor'` …). Папка из 43 файлов
давала 43 строки при **одном** новом значении.
Alex: «схлопывание по `field+value` делается, а не по `old` — очевидно что он блядь у всех разный!»
**Правильно — группировать по новому значению внутри `(папка, поле)`:**
```python
groups = defaultdict(list)
for rel, ov, nv in lst:
groups[nv].append((rel, ov)) # ключ НЕ включает old
for nv, members in groups.items():
if len(members) > 1:
olds = sorted({ov for _, ov in members})
label = olds[0] if len(olds) == 1 else f"<{len(olds)} разных>"
collapse_lines.append((fld, fname, label, nv, len(members)))
else:
rel, ov = members[0]
file_lines.append((fld, rel, fname, ov, nv))
```
🔴 **Питфолл отчёта:** в колонку `old_38` схлопнутой строки **нельзя** подставлять одно из
нескольких старых значений — это враньё в отчёте. Если старых значений больше одного, писать
`<N разных>`.
**Эффект:** `!Radio/t 2009-07 c6Or_NICK` `field=album` — 43 строки → 1 (`'<7 разных>' -> '2009-07 c6Or_NICK'`).
Дифф целиком 4996 → 2864 строк (`ПАПКА` 1123→1485 · `ТРЕК` 3747→1253).
Контроль полноты на трек-уровне не меняется (`assert covered + len(file_lines) == sum(field_counts)`).
### 🔴 D17 — `album` унаследован от `cur_album` для подборок `!Radio` (сессия 13, 203 строки)
Тот же корень, что и **D13**: план копирует значение из тега файла, не проверяя, уместно ли оно.
Здесь — `new_album` ← `cur_album`. Для подборок `!Radio` **`album` = имя папки-подборки**
(правило сессии 7), а не альбом, из которого трек взят.
```
!Radio/t 2009-07 c6Or_NICK/plux/Би-2 и Агата Кристи - Теряю свет.mp3
cur_album='Нечетный воин 2' → new_album='Нечётный Воин 2' ❌
правильно: '2009-07 c6Or_NICK'
```
Один и тот же трек, но в **папке-альбоме** `!Rock&Folk/Би-2/Нечетный Воин/2008 Проект…`
(`class='АЛЬБОМ'`) — там `album='Нечётный Воин 2'` и `albumartist='Би-2'` **верны**.
**Метод фикса (без выдумывания значений):** целевое имя папки-подборки берётся как значение,
которое **уже стоит у большинства** файлов папки (значит оно проставлено ранее правилом), плюс
требование `cnt >= 2`. Папки, где такого большинства нет либо `class='АЛЬБОМ'` — **пропускать**.
**Детектор разнобоя:** для каждой папки `Counter(new_album)`; `len(vals) > 1` = дефект.
Скрипты: `/tmp/audit_radio_album.py` (поиск), `/tmp/fix_radio_album.py` (правка, сухой прогон по умолчанию).
До: 19 папок / 469 строк. После: 2 папки (обе `3 SHANSON/Трофим` — там альбомы Трофима, пропущены намеренно).
🔴 **Не путать с правильными случаями:** `z Старое/disk/N NAME` → `'RADIO N NAME'` — это
**одно** значение на папку и оно верно; дефект только там, где значения разные.
### 🔴 D15 — поиск по бэкапам: фильтр `.csv` отсекает `APPLY-PLAN.csv.bak-*` (сессия 13)
`glob(f"{B}/APPLY-PLAN.csv*")` + условие `p.endswith(".csv")` **выбрасывает все бэкапы**,