8.7 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-26 | 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. Проверять сумму, не только группы. - Не выдавать выборку за полную проверку.
🔴 Уроки волны 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.
🔴 Уроки диффа планов (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).
Связанное
- 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-го прогона