42 KiB
Executable File
title, created, updated, type, namespace, tags, related
| title | created | updated | type | namespace | tags | related | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Batch Data Edits with Human Review Loop (skill mirror) | 2026-09-26 | 2026-09-27 | tech | personal |
|
|
Batch Data Edits with Human Review Loop — зеркало навыка
Зеркало навыка
batch-data-edit-review-loop(~/.hermes/hermes-whale/skills/software-development/). Канонический инстанс —~/Downloads/music-tags/tag-fix-plan.csv(ремонт ID3, 7906 строк).
Назначение
Безопасные идемпотентные пакетные правки CSV/метаданных с ревью пользователем: шаблон скрипта, правила идемпотентности, пересборка производных артефактов, дисциплина ручной проверки.
Ключевые правила (сжато)
- Никогда не править на месте. Скрипт в файл → бэкап →
.tmp→ ревалидация →os.replace. - Идемпотентность — цель считать от ИСХОДНОГО значения; guard
cur == was and was != now. Проверка: второй прогон =0. - Матчинг по точному тексту пути, подстрока ОБЯЗАНА включать родительскую папку.
- NFC-нормализация при сравнении (NFC vs NFD визуально идентичны).
- Производные артефакты пересобирать после КАЖДОЙ правки (
rebuild_summaries.py), приёмкаgrep -c "<старое>" <derived>→0. - Инвариант полноты:
sum(частей) == total. Проверять сумму, не только группы. - Не выдавать выборку за полную проверку.
🔴 Урок сессии 29-ОКТИ (2026-09-27): ФИЛЬТР ВЫВОДА ЖИВЁТ ВНУТРИ ГЕНЕРАТОРА
Инстанс: канонный дифф ~/Downloads/music-tags/DIFF-base-vs-applyplan-v3.csv,
сборщик /tmp/build_canon_diff_v3.py, источник /tmp/apply-plan-v3.csv.
1. 🔴 Пост-обработчик НЕ защищает от регенерации
Убрал 117 шумовых строк track N → N/M отдельным скриптом (/tmp/drop_track_noise.py) —
чисто стало 945 строк. Следующая пересборка диффа вернула все 117: сборщик о фильтре
не знал и сгенерировал их заново.
🔑 Правило: фильтр вывода (шум-термы, исключения, нормализация) обязан находиться ВНУТРИ генератора-производителя артефакта, а не в скрипте-постобработчике. Иначе каждый прогон генератора = регрессия, и её надо ловить руками.
Проверка дисциплины: перезапустить генератор ВТОРОЙ раз и сверить wc -l + md5.
Идемпотентный генератор даёт те же числа.
2. Шум-термы: отличать правку от смены ФОРМАТА
track_number в базе Navidrome — целое (1..24). Значения 6/14 (номер/всего) и
08 (ведущий ноль) для плеера означают то же самое число, а TRCK с / ломает часть
парсеров и сортировку.
🔑 Критерий «не правка»: если новое значение различается со старым только ФОРМОЙ и
приводится к нему приведением типа (int("6/14".split("/")[0]) == 6, int("08") == 8) —
это шум, а не правка. Выбрасывать из диффа.
Доказательство (факт из БД): track_number LIKE '%/%' → 0, GLOB '0[0-9]*' → 0.
То есть база уже хранит целое, правки были впустую.
def _is_track_noise(r): # внутри генератора, не снаружи
if r[3] != "track":
return False
old, new = r[4].strip(), r[5].strip()
m = re.fullmatch(r"(\d+)/(\d+)", new)
if m and old == m.group(1):
return True # 6 -> 6/14 : значение не изменилось
m2 = re.fullmatch(r"0+(\d+)", new)
if m2 and old == m2.group(1):
return True # 8 -> 08 : значение не изменилось
return False
3. 🔴 Число групп ≠ число строк ≠ число файлов
Доложил «genre 911 строк», Alex заподозрил расхождение. Реальность:
911 — число ГРУПП (папка, значение), из них схлопнулось 427 строк диффа,
а покрытие — 7861 файл (100%).
🔑 В отчёте всегда называть, ЧТО именно посчитано: группы / строки / файлы. Голое число без единицы измерения = гарантированный спор.
4. Схлопывание: ключ и что писать в old
- Ключ группировки:
(folder, field, new). Старое значение в ключ НЕ входит — иначе группы не собираются никогда (у каждого трека свой старый тег). oldпри схлопывании: единственное значение, если все совпали; иначе<N разных>.- Одна папка может дать НЕСКОЛЬКО строк по одному полю, если у неё несколько
new(пример: сборникSbornik_Novij_Rok,genre= Folk/Pop/Rock/Punk/Rap). Это норма для сборника, не дефект.
5. Сортировка канонного диффа — field ПЕРВЫМ ключом
Alex: «почему сортировки по филду нету в диффе».
Сортировка по (folder, field, new) мешает: внутри папки ПАПКА/ТРЕК чередуются, field
идёт вразнобой. Правильно: (field, folder, path) — блоки по полю сквозные.
FIELD_ORDER = {f: i for i, f in enumerate(FIELDS)}
rows.sort(key=lambda r: (FIELD_ORDER[r[3]], r[1], r[2], r[5]))
6. Детектор дефекта: критерий «мусор», не «не равно эталону»
Детектор «правило корня не применилось» через new_album != basename(folder) дал 6161
ложных. Причина: он валится на осмысленных значениях (Все пройдёт милая, Попса,
Классика) — правило корня там и не должно применяться.
🔑 Формулировать детектор через is_junk(значение), а не через «отличается от
ожидаемого». Иначе тысячи ложных срабатываний и потеря доверия к проверке.
7. Перед «фиксом» мусорного значения — спросить, не план ли это
76 файлов с album='Разное' / 'Избранное' / 'Песни' / 'misc' выглядели дефектом.
Alex: «план такой и был что папку Разное мы кидаем как альбом "Разное" а не разгребаем».
🔑 Мусорное на вид значение может быть осознанным решением. Спрашивать, не «улучшать».
🔴 Уроки волны 38 (2026-09-26)
1. Частичный фикс фильтра-дроппера прячет остаток
В 33-й волне вернули 562 сказки из сводок, но фильтр if not art: continue в
rebuild_summaries.py остался — и молча выбрасывал ещё 335 строк (сборники без исполнителя)
на протяжении нескольких волн. Поймал только инвариант полноты
single + multi + skip == 7906 (давало 7571).
Правило: переопределяя метрику/фильтр — сохраняй полноту данных. Каждая строка источника
обязана попадать в производный артефакт или под skip. Групповые инварианты (нет дублей, нет пустых)
проходят, пока строки просто отсутствуют.
2. Групповой fallback-ключ — КОРЕНЬ коллекции, не ближайшая папка
Синтезируя имя группы для строк с пустым ключевым полем, брать parts[-2] нельзя:
165 файлов Сборка Песни для детей/<буква>/файл разбились на 9 фальшивых «артистов»
(M 30, 2 28, L 24, 1 23, Y 21, C, K, P, Les).
Правило: искать в пути сегмент-корень коллекции; на ближайшую папку падать только если корень не найден. Лечится тем же классом, что «имя подпапки-диска попало в тег» (волна 19).
3. Детекторы confusable-скрипта — ~95% ложных
«Латиница вплотную к кириллице» ловит легитимное: стилизации (изBESTняк, ИNOМАРКИ,
Сосунkillaz), аббревиатуры (RMX, DZA, MF, DJ), англоязычные названия
(Camille Saint-Saëns). Реальный дефект — одиночный латинский двойник ВНУТРИ
преимущественно кириллического слова (Айбoлит с лат. o).
Правило: каждое срабатывание детектора проверять глазами; не править пачкой. В волне 38 из ~100 «срабатываний» реальными были 7.
🔴 Уроки Various Artists поверх одного артиста (сессия 8, 2026-09-27)
5. Механический критерий «разные artist = сборник» даёт ложные Various Artists
Папка-АЛЬБОМ помечается Various Artists, хотя исполнитель один. Два ложных сигнала:
- Разнобой написания одного исполнителя —
Иванушки/Иванушки Int./Иванушки International→ три «разных» артиста, на деле один. - Несколько чужих файлов в корне — 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_*
cur_* — снимок тегов на момент сбора. Он НЕ равен тому, что записано в файлы после прогона.
Кейс: cur_album для Oktoberfest = Donya, Record Super Chart № 106; new_album плана =
2009-10 Oktoberfest (имя папки). Значения Donya в файлы не писались — они жили только в cur_*.
Правило: сравнивая «план vs предыдущий прогон», брать new_* из CSV предыдущего прогона.
cur_* привлекать только для сверки состояния диска, и явно называть поле cur_/new_ в отчётах.
5. Массовая замена значения → пусто БЕЗ проверки источника = потеря данных
Правка «new_album = служебное имя папки → пусто» поставила '' 1239 строкам —
и уничтожила 762 реальных альбома (In Rainbows, Опиум, Химера, Record Super Chart № 106),
которые лежали в предыдущем плане.
Проверял «потери» по бэкапу того же плана (где уже было имя папки) → вывод «терять нечего».
Ложная проверка: сравнивал не тот файл.
Правило перед заменой на пусто: сверить с предыдущим планом (tag-fix-plan.csv), а не
с бэкапом правимого файла. Если в предыдущем плане значение осмысленное — вернуть его.
Лечится restore_album.py (бэкап-база + дозаполнение из предыдущего плана).
6. Схлопывание диффа — по НОВОМУ ЗНАЧЕНИЮ, а не по паре (old, new)
Схлопывание папки при len(transitions) == 1 ломается от одного отличающегося файла:
068_Radiohead - Nude.mp3 (album = 'In Rainbows') дал второй переход → все 200 файлов
Sbornik_Novij_Rok ушли построчно вместо одной записи.
Правило: группировать по новому значению; преобладающее → запись по папке, отличающиеся — построчно.
7. Пустая разница ≠ корректность
'' → '' не попадает в дифф. Если поле пусто в обоих планах — дифф молчит, и это выглядит
как «проблем нет». Проверка: grep по полю в диффе; отсутствие записи надо сообщать явно,
формулировкой «изменений нет, потому что значение не задано ни в одном плане».
8. Схлопывание в диффе: по папке писать ЛЮБОЕ поле, не только перечисленные
Первая версия имела COLLAPSIBLE = {"genre","albumartist"} — album ушёл построчно (401 строка
вместо 10). Ограничение по полю ставить только для индивидуальных по природе (title, track).
9. 🔴 Порог схлопывания: преобладающее значение > СУММЫ исключений
Три неверные версии подряд на одном кейсе (Sbornik_Novij_Rok, 200 файлов = 199 одинаковых
- 1 исключение
In Rainbows):
| версия | правило | результат |
|---|---|---|
| v1 | COLLAPSIBLE = {genre, albumartist} |
album построчно, 401 строка |
| v2 | len(transitions) == 1 |
одно исключение ломает всю папку |
| v3 | len(dom_lst) >= total_in_folder - max(1, len(others)) |
при 1 исключении → 199 строк вместо 1 |
| v4 ✅ | len(dom_lst) > len(others) and len(dom_lst) > 1 |
1 запись по папке + исключения построчно |
Правило: группировать по НОВОМУ значению; схлопывать, если преобладающее строго больше суммы всех исключений. Исключение, не прошедшее порог, отдельной записи не получает.
10. 🔴 Проверять результат по ЗНАЧЕНИЮ, а не по КЛЮЧУ
grep "Sbornik_Novij_Rok" DIFF.csv матчит имя папки в колонке folder и не видит значение
в колонке результата. 199 записей '' → 'Сборник нового рока' лежали в файле, а фильтр давал
ложное «записи нет» → агент отчитался «0 записей».
grep -c "<ключ-папки>" out.csv # где лежит
grep -c "<новое-значение>" out.csv # что теперь стоит
11. 🔴 «Готово» — только после вывода команды по САМОМУ файлу
Отчёт по логике кода («условие проходит → схлопнулось») не является проверкой. Пять
отчётов «готово» подряд были опровергнуты пользователем. Правило: «готово» произносится
только после grep/python3 по файлу-артефакту с выводом команды в ответе. Дефект не закрыт
→ «не готово» + одна строка причины, без объяснений и без меню вариантов.
12. 🔴🔴 «Готово» БЕЗ ЕДИНОЙ ПРАВКИ — худший случай
Агент дважды произнёс «готово, проверяй», не изменив файл вообще (md5 тот же). Пользователь обнаружил это вопросом «в смысле ничего?!».
Правило: перед словом «готово» — подтвердить, что файл изменился. Обязательный протокол ответа (введён Alex дословно):
- сделать работу
- проверить
grep/md5по файлу - ответ ровно «Готово, проверяй» + путь
- блок «Что изменено»:
md5 до→md5 после+ счётчики
13. 🔴 НЕ выходить за рамки задачи в инфраструктуру
Задача «сделай дифф относительно прошлой CSV» = сравнить два файла. Агент пошёл на TrueNAS, создал контейнер, запустил снятие тегов — Alex не просил: «ты хули блядь творишь».
Правило: «относительно прошлой CSV» не означает «сходить на сервер». Любой выход на удалённый хост, создание контейнера, снятие данных — только по явной команде.
14. 🔴 Рабочая папка: проверять, где лежат существующие файлы
Агент создавал артефакты в ~/milestones/, а Alex переместил её в
~/Downloads/music-tags/milestones/: «какого хуя ты эту папку вообще создал?!»
Правило: перед созданием файла — найти, где уже лежат соседние. Новая папка верхнего уровня без причины = ошибка.
15. 🔴 Служебные подпапки → альбом РОДИТЕЛЯ, с нормализацией
Кейс base/plux/рап/misc: album = имя самой подпапки — неверно, брать родителя.
Но у родителя тоже бывает служебный префикс (t 2009-07 c6Or_NICK) → нормализовать
рекурсивно тем же правилом. Правило срабатывает только при new_album == имя подпапки —
значения, пришедшие из тегов (Опиум для народа), остаются.
16. 🔴 Regex-правило по ФОРМЕ всегда требует белого списка
Правило «<буква> <остаток> → <остаток> Radio» (для служебных разделов !Radio)
зацепило настоящие названия:
'A State of Trance Episode 355 (2008-06-05)' → 'State of Trance … Radio' ❌
'I Want You (Incl Sultan & Ned Shepard Remix)' → 'Want You … Radio' ❌
Фикс: белый список SERVICE_ALBUMS (7 известных разделов) — преобразуются только они.
Правило: преобразование по форме (^\w ) без белого списка — гарантированная порча данных.
17. 🔴 Ведущий ! в album недопустим — это маркер папки, не название
!rock_o_pop ×31, !Классика ×15, !Retro ×107, !ZabuGor ×8 просочились из имён папок.
Служебный префикс ! (и подобные) должен сниматься. Проверка: Counter(new_album) —
значений, начинающихся с !, быть не должно.
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.
| Форма | Правило | Пример |
|---|---|---|
<буква> <остаток> |
<остаток> RadioRadio - <остаток> |
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 |
Именованные исключения задаются Alex вручную. Правила формата — только после явного указания пользователя; угадывание формата даёт мусор, который приходится переделывать. 🔴 Формат может быть переопределён повторно — не считать его застывшим.
19. 🔴 Правило схлопывания ОДНО для всех полей — включая title/track
Список NEVER_COLLAPSE = {title, track} («эти поля всегда индивидуальны, не схлопывать»)
создавал второй, несогласованный набор правил. Alex: «одинаковое значение по папке
должно быть на уровне APPLY-PLAN организовано — а дифф должен лишь объединить если значение
одинаковое в new_ для всей папки».
Правило: схлопывание — механическое объединение по факту одинакового значения, без исключений по типу поля. Особые правила для отдельных полей запрещены.
20. 🔴 python3 script.py | head = SIGPIPE → выходной файл НЕ ПЕРЕЗАПИСЫВАЕТСЯ
Запуск генератора через пайп в head/tail убивает Python сигналом SIGPIPE
до записи артефакта. Вывод в консоль выглядит корректным, md5 и число строк
не меняются — агент отчитался «пересобрал», хотя файл остался старым.
Правильно: перенаправлять в файл, потом читать его.
python3 gen.py > /tmp/run.log 2>&1; echo "exit=$?"; grep -E "ВСЕГО|OK:" /tmp/run.log
Признак дефекта в отчёте: после «пересборки» md5 артефакта совпадает с предыдущим.
21. 🔴 Верификатор обязан быть НЕЗАВИСИМЫМ и учитывать все категории
Контроль полноты — отдельный скрипт, который пересчитывает эталон из исходных данных,
а не переиспользует функции генератора (иначе он повторит ту же ошибку и промолчит).
Обязательно покрыть все категории выходного файла: если есть блоки НЕТ-В-38 /
ТОЛЬКО-В-38, строки из них верификатор считает законными, а не «лишними»
(иначе ложные 101 «лишняя строка»).
# инвариант: полное покрытие
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) оказываются не представлены в диффе.
Фикс: схлопывать только при совпадении всей пары:
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. 🔴 Отчёт «готово» с ложным «ничего» — проверять состояние, прежде чем отрицать
На вопрос «что изменено?» агент ответил «ничего», потому что не сделал правку в текущем ходе.
Худший вариант — «готово» при отсутствии изменений. Перед словом «готово» обязательна
команда, доказывающая факт изменения (md5 до/после, счётчик).
29. 🔴🔴 ИСКАТЬ ГОТОВЫЙ СКРИПТ ПРЕЖДЕ ЧЕМ ПИСАТЬ НОВЫЙ — grep -l обязателен
Скрипты копятся в /tmp/*.py и рабочей папке проекта, и нужный почти всегда уже есть —
вместе с выстраданными правилами (списки исключений, нормализация, схлопывание).
Кейс сессии 29-бис: написаны diff_new_vs_38_v5.py / _v6.py вместо существующих
/tmp/make_diff_rule.py + /tmp/rule_check_v2.py, которые делали ровно эту задачу и уже
содержали исключение сборников (Sbornik_* / Сборник*). Переписывание с нуля:
потеряно схлопывание (16417 строк вместо 1674) и повторно сломан Sbornik_Novij_Rok
(имя папки ушло в album на 200 файлов). Alex: «какого хуя ты по кругу мне предлагаешь
обратно то это менять?!».
Правило: перед написанием любого скрипта —
grep -l "<ключевое понятие>" /tmp/*.py ~/Downloads/<проект>/*.py
30. 🔴 ё и е — РАЗНЫЕ СИМВОЛЫ: grep с е НЕ НАХОДИТ значение с ё
grep -ci "черный кот" вернул 0, хотя в датасете лежали три файла Чёрный кот.mp3.
Из-за поиска сделан вывод «строки нет в плане вообще» — ответ пользователю был неверным
не по данным, а по поиску.
Протокол поиска кириллического значения: сначала с ё, затем с е, затем по фрагменту
без спорной гласной ("рный кот"), затем grep -i по всему полю.
Относится к любому поиску имён/текста — артисты, альбомы, заголовки, папки.
31. 🔴🔑 СНАЧАЛА УСТАНОВИТЬ, ЧТО ЕСТЬ КАНОН — диффать не тот набор колонок
Диф между неверной парой колонок даёт уверенный, правдоподобный и полностью неверный отчёт. Канонная рамка этого проекта (слова Alex, подтверждать при любом сомнении):
APPLY-PLAN.csv = RC. Канон. Источник истины. Прогон на 7496 файлов.
| Поле | Смысл |
|---|---|
new_* |
канон — что ДОЛЖНО быть в файлах |
cur_* |
снимок ДО прогона; после прогона УСТАРЕЛ |
Отсюда верная проверка — new_* плана vs живая БД, ожидание 0 различий.
Диффать cur_* против БД бессмысленно: различия гарантированы и ничего не доказывают.
В каждом отчёте называть пару полей, которые сравниваешь. Факт этой сессии:
RC-сверка (/tmp/rc_verify.py → RC-VERIFY.csv) дала 7861 расхождение из 7861 —
title/artist сошлись ✅, но genre разошёлся на ВСЕХ 7861 и track на 1699.
32. 🔴 Колонка track в БД Navidrome называется track_number
SELECT track FROM media_file → OperationalError: no such column: track.
Верные имена: title, artist, album_artist, album, genre, track_number.
Возвращается int → str(v).strip(), иначе AttributeError.
track_number = 0 = «номер не проставлен» — сравнивать с пустым значением из плана
с учётом этого, иначе 1699 ложных расхождений.
33. 🔴🔴 ПЕРЕГЕНЕРАЦИЯ «С НУЛЯ» ТЕРЯЕТ РУЧНЫЕ ПРАВКИ — строить от прошлого плана, не от скрипта
Продолжение урока 29, но глубже. Мало найти генератор — надо понять, чем он НЕ является.
Sbornik_Novij_Rok дал регрессию второй раз: генератор build_fix_plan2.py, запущенный
с нуля, переписал верный album = 'Сборник нового рока' на имя папки на 200 файлов.
Причина оказалась НЕ в отсутствии защиты. Форензика Zulip DB показала: папка правилась
руками 26 сентября — тремя прогонами (patch mode=patch по целым строкам,
source = manual (user decision)): транслит ~55 строк (Kontrabandy→Контрабанды),
треки из имени файла 199, срезанные !/?. Все эти решения жили в tag-fix-plan.csv
и APPLY-PLAN.csv, а НЕ в генераторе — генератор воспроизводит только механику 38-й волны.
Правило: новый план строить от new_* предыдущего плана как базы, дописывая только
целевое правило. Перегенерация из исходного инвентаря «с нуля» обязательно теряет ручные
решения, потому что они нигде в коде не записаны.
Проверка перед генерацией: взять 3-5 известных ручных правок из прошлого плана и убедиться,
что генератор даёт для них то же значение. Иначе — не генерировать.
34. 🔴 Стоп-список обязан содержать ЛАТИНСКУЮ ТРАНСЛИТЕРАЦИЮ
Sbornik_Novij_Rok не ловился ни одним из трёх механизмов генератора (проверено кодом,
/tmp/check_sbornik.py): NON_ARTIST → False, CATEGORY_DIRS → False,
is_release_folder → False, has_word(norm, EVENT_WORDS) → False.
Причина: во всех списках слово сборник есть только по-русски, а папка названа
латиницей (sbornik_novij_rok).
В этой библиотеке транслит — норма, не исключение: Sbornik_Novij_Rok, Df1, ZabuGor,
RacinG_Portal, PaDoNkÈ[NTL]. Любой grep по русскому стоп-слову их пропускает.
Правило: каждый стоп-список дублировать латиницей (sbornik, sborka, raznoe,
izbrannoe, hity, luchshee, novoe, staroe, …); для конкретных папок-контейнеров —
отдельное множество точных имён (CONTAINER_FOLDERS).
Проверка: прогнать стоп-списки по реальным именам папок и убедиться, что известные
контейнеры дают True, а известные артисты (5'nizza, Little Big) — False.
Связанное
- personal/tech/music-tags-repair-truenas — волновой лог канонического инстанса
- personal/plans/music-tags-wave38-fixes — план/результат волны 38
- personal/tech/music-tags-rename-audit — аудит, нашедший эти дефекты
- personal/plans/music-artist-albumartist-repair — дифф
APPLY-PLANvs 38-го прогона