Files
obsidian-vault/personal/tech/batch-data-edit-review-loop.md
T

14 KiB
Executable File
Raw Blame History

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
skill
data
csv
idempotency
music
personal/tech/music-tags-repair-truenas
personal/plans/music-tags-wave38-fixes

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/метаданных с ревью пользователем: шаблон скрипта, правила идемпотентности, пересборка производных артефактов, дисциплина ручной проверки.

Ключевые правила (сжато)

  1. Никогда не править на месте. Скрипт в файл → бэкап → .tmp → ревалидация → os.replace.
  2. Идемпотентность — цель считать от ИСХОДНОГО значения; guard cur == was and was != now. Проверка: второй прогон = 0.
  3. Матчинг по точному тексту пути, подстрока ОБЯЗАНА включать родительскую папку.
  4. NFC-нормализация при сравнении (NFC vs NFD визуально идентичны).
  5. Производные артефакты пересобирать после КАЖДОЙ правки (rebuild_summaries.py), приёмка grep -c "<старое>" <derived> → 0.
  6. Инвариант полноты: sum(частей) == total. Проверять сумму, не только группы.
  7. Не выдавать выборку за полную проверку.

🔴 Уроки волны 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).

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 дословно):

  1. сделать работу
  2. проверить grep/md5 по файлу
  3. ответ ровно «Готово, проверяй» + путь
  4. блок «Что изменено»: 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 == имя подпапки — значения, пришедшие из тегов (Опиум для народа), остаются.

Связанное