--- title: Batch Data Edits with Human Review Loop (skill mirror) created: '2026-09-26' updated: '2026-09-26' type: tech namespace: personal tags: - skill - data - csv - idempotency - music related: - '[[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 "<старое>" ` → `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**. ## Связанное - [[personal/tech/music-tags-repair-truenas]] — волновой лог канонического инстанса - [[personal/plans/music-tags-wave38-fixes]] — план/результат волны 38 - [[personal/tech/music-tags-rename-audit]] — аудит, нашедший эти дефекты