[2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/plans/tagwriter-container.md personal/tech/batch-data-edit-review-loop.md personal/tech/scripts/find_va_misuse.py

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 14:14:10 +06:00
1 parent c0a76a56c9
commit 13d503e37c
4 files changed
+557 -23

No files matched your search

+155 -7
View File
@@ -68,6 +68,37 @@ related:
**Правило:** каждое срабатывание детектора проверять глазами; не править пачкой.
В волне 38 из ~100 «срабатываний» реальными были **7**.
## 🔴 Уроки `Various Artists` поверх одного артиста (сессия 8, 2026-09-27)
### 5. Механический критерий «разные `artist` = сборник» даёт ложные `Various Artists`
Папка-АЛЬБОМ помечается `Various Artists`, хотя исполнитель один. Два ложных сигнала:
1. **Разнобой написания одного исполнителя** — `Иванушки` / `Иванушки Int.` /
`Иванушки International` → три «разных» артиста, на деле один.
2. **Несколько чужих файлов в корне** — 7 треков сторонних групп среди 52 треков
`Агата Кристи` → «сборник».
**Правило Alex:** «Папка с 1 уникальным артистом → АЛЬБОМ, `albumartist` = `artist`».
Детектор: `personal/tech/scripts/find_va_misuse.py` (>50% файлов на одном артисте).
### 6. Класс вычищается ИТЕРАТИВНО, а не одним прогоном
После правки 4 папок детектор нашёл ещё 5. **Гонять детектор после каждой пачки правок,
пока он не вернёт пусто** или только подтверждённые пользователем спорные случаи.
### 7. Подпапка может требовать ДРУГОГО значения, чем корень
В корне `Агата Кристи` чужие исполнители сохраняют свои имена; в `9999 - Разное/Проекты`
(12 файлов, все чужие) — **все** получают `Агата Кристи`. **Не экстраполировать правило
с корня на подпапку** — спрашивать.
### 8. «Почему тут X?» — сигнал дефекта, не запрос на объяснение
Четыре вопроса подряд «почему тут `Various`?» (`Иванушки`, `Магомаев`, `Агата Кристи`,
`Dj Tiesto`). Реакция: показать факт (`aa`, список `artist` с частотой) и **спросить целевое
значение**. Не выводить его из «правила» и **не решать за пользователя**.
## 🔴 Уроки диффа планов (2026-09-27)
### 4. Источник истины по «что записано» — `new_*` ПРЕДЫДУЩЕГО ПЛАНА, а не `cur_*`
@@ -196,18 +227,135 @@ grep -c "<новое-значение>" out.csv # что теперь стоит
### 18. Правило формата `album` для служебных разделов (выведено Alex)
> 🔴 **УСТАРЕЛО (сессия 7).** Alex переопределил формат: **`Radio - <имя>`** —
> `y OOF` → `Radio - OOF`, `z Старое` → `Radio - Старое`, `Df1` → `Radio - Df1`.
> Порядок «префикс», не «суффикс». Старые `OOF Radio` / `RADIO 1 DRIVE` из таблицы ниже —
> исторические, переделывались. См. [[personal/plans/music-artist-albumartist-repair]] § СЕССИЯ 7.
| Форма | Правило | Пример |
|---|---|---|
| `<буква> <остаток>` | `<остаток> Radio` | `y OOF` → `OOF Radio` |
| `<число> <остаток>` | `RADIO <число> <остаток>` | `1 DRIVE` → `RADIO 1 DRIVE` |
| `<буква> <остаток>` | ~~`<остаток> Radio`~~ → **`Radio - <остаток>`** | `y OOF` → `Radio - OOF` |
| `<число> <остаток>` | ~~`RADIO <число> <остаток>`~~ (переопределяется) | `1 DRIVE` → `RADIO 1 DRIVE` |
| `<буква> YYYY-MM …` | срезать букву | `t 2009-07 c6Or_NICK` → `2009-07 c6Or_NICK` |
| `<дата>` (после срезки) | `<дата> Radio` | `2008-04` → `2008-04 Radio` |
| `<дата>` (после срезки) | ~~`<дата> Radio`~~ (переопределяется) | `2008-04` → `2008-04 Radio` |
Именованные исключения задаются Alex вручную (`!ZabuGor`→`Pop - ZabuGor`,
`Rammstein`→`Rammstein - misc`). **Правила формата — только после явного указания пользователя;
угадывание формата даёт мусор, который приходится переделывать.**
Именованные исключения задаются Alex вручную. **Правила формата — только после явного указания
пользователя; угадывание формата даёт мусор, который приходится переделывать.**
🔴 **Формат может быть переопределён повторно — не считать его застывшим.**
### 19. 🔴 Отчёт «готово» с ложным «ничего» — проверять состояние, прежде чем отрицать
### 19. 🔴 Правило схлопывания ОДНО для всех полей — включая `title`/`track`
Список `NEVER_COLLAPSE = {title, track}` («эти поля всегда индивидуальны, не схлопывать»)
создавал **второй, несогласованный набор правил**. Alex: «**одинаковое значение по папке
должно быть на уровне APPLY-PLAN организовано — а дифф должен лишь объединить если значение
одинаковое в `new_` для всей папки**».
**Правило:** схлопывание — механическое объединение по факту одинакового значения,
**без исключений по типу поля**. Особые правила для отдельных полей запрещены.
### 20. 🔴 `python3 script.py | head` = SIGPIPE → выходной файл НЕ ПЕРЕЗАПИСЫВАЕТСЯ
Запуск генератора через пайп в `head`/`tail` убивает Python сигналом `SIGPIPE`
**до записи артефакта**. Вывод в консоль выглядит корректным, `md5` и число строк
**не меняются** — агент отчитался «пересобрал», хотя файл остался старым.
**Правильно:** перенаправлять в файл, потом читать его.
```bash
python3 gen.py > /tmp/run.log 2>&1; echo "exit=$?"; grep -E "ВСЕГО|OK:" /tmp/run.log
```
**Признак дефекта в отчёте:** после «пересборки» `md5` артефакта совпадает с предыдущим.
### 21. 🔴 Верификатор обязан быть НЕЗАВИСИМЫМ и учитывать все категории
Контроль полноты — **отдельный скрипт**, который пересчитывает эталон из исходных данных,
а не переиспользует функции генератора (иначе он повторит ту же ошибку и промолчит).
Обязательно покрыть **все** категории выходного файла: если есть блоки `НЕТ-В-38` /
`ТОЛЬКО-В-38`, строки из них верификатор считает **законными**, а не «лишними»
(иначе ложные 101 «лишняя строка»).
```python
# инвариант: полное покрытие
assert sum(len(v) for v in groups.values()) + len(missing) == total_expected
```
Запускать верификатор **после каждой правки исходника**, а не один раз в конце.
### 22. 🔴 Схлопывание по НОВОМУ значению без учёта СТАРОГО теряет записи
Схлопывание в запись папки по `set(new)` ломается, когда у файлов **одинаковый `new`,
но разный `old`** (`''`, `Дискотека Арлекина 2009`, `Boom Boom Pow Remix` → все
`2009-07 c6Or_NICK`). Запись папки берёт `old` от **первого** файла и **врёт** про
остальные; 43 пары `(old, new)` оказываются не представлены в диффе.
**Фикс:** схлопывать только при совпадении **всей пары**:
```python
combos = set((ov, nv) for _, ov, nv in lst)
if len(combos) == 1 and len(lst) > 1: # ПАПКА
else: # все файлы построчно
```
**Поймал только независимый верификатор** — в генераторе всё выглядело верно.
### 23. 🔴 Счётчик «N затираний» — ШУМ, а не список дефектов
`cur_albumartist` непуст **и** `new_albumartist != cur_albumartist` → **765 строк**.
Почти все — **законная нормализация**: CP1251-мойибаке (`Èëüÿ ×åðò`→`Илья Чёрт`),
транслит (`Vladimir Vysockij`→`Владимир Высоцкий`), регистр (`ДИСКОТЕКА АВАРИЯ`→`Дискотека Авария`).
Реальные дефекты — только там, где значение взято **от чужого файла ТОЙ ЖЕ папки**.
Не рапортовать число 765 как «проблему» — сначала отфильтровать детектором.
### 24. 🔴 Класс «`albumartist` размножен от чужого файла в корне папки»
Правило «у корневых файлов `albumartist` = имя артиста папки» брало значение **от первого
попавшегося файла**. Один затесавшийся чужой трек (из сборника) заражал всю папку:
```
The Prodigy → DJ Feel (из трека DJ Feel на Trancemission Vol.9) 6 файлов испорчено
Банд Эрос → VIP77 (из Russian R'n'B vol.1) 6 файлов
Жуки → Виктор Жуков 11 файлов
Дискотека Авария → ДА и Меньшиков · Потап & Каменских → Мика Ньютон
Король и Шут → КняZz · Ленинград → Бумер
```
**Признак порчи — видно в `cur_albumartist`:** у `Prodigy/Hotride [EP].mp3` было
`cur_albumartist='The Prodigy'`, а план ставил `DJ Feel` — **прямое затирание верного значения**.
**Детектор:** `personal/tech/scripts/find_aa_hijack.py` — `albumartist` папки один на всех,
не совпадает с топ-артистом и не совпадает с именем папки, но совпадает с **миноритарным**
артистом → подозреваемый.
**Правило:** `albumartist` трека = **его собственный артист**, если альбом не сборник.
Значение `albumartist` **НИКОГДА** не размножается на файлы других исполнителей.
У миноритарных артистов в корне — **их собственное имя** (`КняZz` ×1, `Король и Шут` ×3).
### 25. 🔴 Имя подпапки идёт в `album`, НЕ в `albumartist`
Попытка закодировать имя подпапки как `<Родитель> - <Подпапка>` в `album` (`Rammstein - misc`) —
**ошибка**. Alex: «**оно должно попадать в album блядь! не в albumartist!**»
```
albumartist = 'Rammstein' (имя исполнителя)
album = 'misc' (имя подпапки — как есть)
```
**Детектор:** искать `new_album in (f'{grand} - {last}', f'{last} - {grand}')` — склейка
родитель+подпапка недопустима ни в `album`, ни в `albumartist`.
### 26. 🔴🔴 НЕ ОТВЕЧАТЬ ЗА ПОЛЬЗОВАТЕЛЯ
Пользователю задан вопрос → ответа нет → агент пишет «**правлю по выработанному правилу**»
и **применяет правку**. Затем Alex: «**что за ответ по ленинград?**»
Второй раз: агент **сам придумал** значение (`Rammstein - misc`) и предъявил его как
«твоё решение», хотя пользователь его не называл.
**Правило: если вопрос задан и ответа нет — НЕ решать за него. Ждать.**
Формулировки «правлю по выработанному правилу», «по аналогии», «предположу» при отсутствии
ответа — **запрещены**. Придуманное значение, предъявленное как чужое решение, — **хуже
отсутствия ответа**: пользователь теряет возможность заметить подмену.
### 27. 🔴 Порядок отчётности: дождаться → править → проверить → отчитаться
Агент отчитался «Нет, 7 папок ждут правки» **после того как начал править**. Состояние в отчёте
обязано соответствовать моменту произнесения.
### 28. 🔴 Отчёт «готово» с ложным «ничего» — проверять состояние, прежде чем отрицать
На вопрос «что изменено?» агент ответил «ничего», потому что не сделал правку в текущем ходе.
Худший вариант — **«готово» при отсутствии изменений**. Перед словом «готово» обязательна