From d787749dd5ffc667098bf82a1e5d0101b27f2933 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 21:04:32 +0600 Subject: [PATCH] [2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/tech/batch-data-edit-review-loop.md personal/tech/whale-soul-answer-format.md --- .../plans/music-artist-albumartist-repair.md | 165 +++++++++++++++++- personal/tech/batch-data-edit-review-loop.md | 90 ++++++++++ personal/tech/whale-soul-answer-format.md | 80 +++++++++ 3 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 personal/tech/whale-soul-answer-format.md diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 86cb3a69..a60e2914 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -4,7 +4,7 @@ created: '2026-09-27' updated: '2026-09-27' type: plan namespace: personal -status: session29okti-CANON-DIFF-v3-BUILT-SORT-FIXED +status: session29okti-CANON-DIFF-v3-BUILT-GENRE-GAP-FOUND tags: - music - metadata @@ -21,6 +21,169 @@ related: - '[[family/how-to/navidrome-truenas-android]]' # Music `artist` + `albumartist` Repair — правила Alex и ход работ +## ✅ СЕССИЯ 29-ОКТИ (2026-09-27, вечер) — КАНОННЫЙ ДИФФ v3 СОБРАН: СОРТИРОВКА ПО `field`, ЖАНРЫ ПРОПУЩЕНЫ ПРОГОНОМ + +> **ТРИГГЕР:** «**диф где блядь**» → «**почему сортировки по филду нету в диффе**» → +> «**!Детские/Из чего же.mp3 - где альбом**» → «**Андервуд надо исправить еще**» → +> «**\"!rock_o_pop\" - вот это отщепилось**» → «**вот всегда бы ты так отвечал**» + +### 🎯 ГЛАВНЫЙ АРТЕФАКТ СЕССИИ + +``` +Файл: ~/Downloads/music-tags/DIFF-base-vs-applyplan-v3.csv +md5: bdb20c6ae00e7dfab4da2f76653fe084 +строк: 945 (ПАПКА=711, ТРЕК=234) +поля: album 28 · albumartist 6 · genre 427 +источник правок: /tmp/apply-plan-v3.csv (7861 строк) +сборщик: /tmp/build_canon_diff_v3.py +``` + +**Формат канонного диффа:** `scope,folder,path,field,old,new` (`scope` = ПАПКА | ТРЕК). + +### 🔴 СОРТИРОВКА ПО `field` — ТРЕБОВАНИЕ ALEX, ИСПРАВЛЕНО + +Первый сборщик сортировал по `(folder, field, new)` — `field` был **вторым** ключом, поэтому +внутри папки строки ПАПКА/ТРЕК чередовались, а `field` повторялся вразнобой. + +**Фикс:** ключ сортировки `(field, folder, path)` — `field` ПЕРВЫМ. +Порядок в файле теперь строго блочный: `album` → `albumartist` → `genre`. + +```python +FIELD_ORDER = {f: i for i, f in enumerate(FIELDS)} # title, artist, albumartist, album, genre, track +rows.sort(key=lambda r: (FIELD_ORDER[r[3]], r[1], r[2], r[5])) +``` + +### 🔴🔴 ГЛАВНЫЙ ДЕФЕКТ, ВСКРЫТЫЙ СЕССИЕЙ: ЖАНРЫ НЕ ЗАПИСАНЫ В ТЕГИ ВООБЩЕ + +Вопрос Alex: «**жанры почему пропали есть понимание?**» + +**Факт из базы Navidrome:** +``` +в базе genre пусто: 7861 из 7861 ← ВСЕ файлы +в базе genre непусто: 0 +в плане new_genre есть: 7861 из 7861 +расхождение: 7861 (не 911!) +``` + +**Причина — НЕ потеря, а пропуск при прогоне.** В `tagwriter_verify_full.log` из полей +присутствуют только `TALB` 29 · `TIT2` 25 · `TPE1` 98 · `TRCK` 102 — **`TCON` (genre) +отсутствует полностью**. Боевой прогон 27 сен (7649/7649 ok) `TCON` не записал → +Navidrome читает теги и видит пусто. + +🔑 **Это не «правка жанра», а ПОВТОРНАЯ ЗАПИСЬ того, что прогон пропустил.** Все 7861 файл +нужно записать заново по `genre`. + +**Ловушка числа 911:** я сначала доложил «genre 911 строк» — Alex-наблюдаемое расхождение. +Правильная формулировка: **911 — это число ГРУПП `(папка, жанр)`, а не строк диффа**. +Групп 911 → после схлопывания 427 строк (366 ПАПКА + 61 ТРЕК) → покрытие **7861 файл, 100%**. + +### 🔴 ТРЕКИ В БАЗЕ — ОК, ПРАВКИ `N → N/M` БЫЛИ ШУМОМ + +Вопрос Alex: «**с треками в базе navidrome в итоге все ок?**» + +``` +всего файлов: 7861 +track_number = '0': 1581 ← нет трека, это НОРМА +track_number заполнен: 6280 +track_number с '/': 0 ← чисто +track_number с ведущим 0: 0 ← чисто +``` + +**Решение Alex: `6` → `6/14` не править.** Обоснование согласовано: +1. `track_number` в ID3 — целое; `6` и `6/14` для плеера одно и то же значение +2. `TRCK` с `/` ломает часть парсеров и сортировку в Navidrome +3. Правилось бы 117 файлов ради нуля изменений по существу + +🔴 **ШУМ-ФИЛЬТР ВШИТ В САМ СБОРЩИК** (строки 99-114 `build_canon_diff_v3.py`), а не в +отдельный скрипт — иначе регрессия возвращается при каждой пересборке: + +```python +def _is_track_noise(r): # track N -> N/M и N -> 0N == не правка + 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 + m2 = re.fullmatch(r"0+(\d+)", new) + if m2 and old == m2.group(1): return True +``` + +✅ **Проверка идемпотентности:** повторный прогон сборщика → те же 945 строк. +Регрессия убрана, подтверждено 4 способами (фильтр в файле, `track`=0 в диффе, `N/M`=0 в +плане, повторный прогон стабилен). + +### 🔴 ПИТФОЛЛ: `track N → N/M` РЕГРЕССИРУЕТ, ЕСЛИ ФИЛЬТР СНАРУЖИ + +Первый раз я убрал 117 шумовых строк **отдельным скриптом** (`/tmp/drop_track_noise.py`). +Следующая пересборка диффа вернула их все — сборщик о фильтре не знал. +🔑 **Фильтр вывода живёт ВНУТРИ генератора, не в пост-обработчике.** + +### 🔴 ФИКСЫ, ВНЕСЁННЫЕ В ЭТОЙ СЕССИИ + +| # | Что | Было → Стало | Файл | +|---|---|---|---| +| 1 | Опечатка в имени папки протекла в `album` | `Андервуд` → **`Ундервуд`** | `!A Not sorted/SD-card-2026-05/Андервуд/Гагарин, я вас любила.mp3` | +| 2 | Битый `cur_album` дал ПУСТОЙ `new_album` | `Íåèçâåñòíîå íàçâàíèå` → **`Детские`** | `!Детские/Из чего же.mp3` | +| 3 | `!` не снесён → папка разъехалась на два значения | `!rock_o_pop` → **`rock_o_pop`** | `Рок-Группа - Попса.mp3`, `АлисА - Стать севера.mp3` (2 файла) | +| 4 | Альбом не совпадал с основным | `Руки` → **`Руки вверх`** | `!Retro/Руки/*` (5 файлов) | +| 5 | `track N/M` | `7/19` → **`7`** | 117 файлов | +| 6 | Сортировка диффа | по `folder` → **по `field`** | сборщик | + +Скрипты: `/tmp/fix_v3_typos.py`, `/tmp/fix_ruki.py`, `/tmp/fix_rockopop_track.py`, +`/tmp/drop_track_noise.py`, `/tmp/build_canon_diff_v3.py`. + +**Бэкапы:** `/tmp/apply-plan-v3.csv.bak-20260927-204950`, `-205732`, `-205822`, +`/tmp/DIFF-v3.bak-*.csv`, `/tmp/DIFF-v3.bak2-*.csv`. + +### 🔴 РЕШЕНИЕ ALEX: КЛАСС «ПАПКА-МУСОРКА» — ЭТО ПЛАН, НЕ ДЕФЕКТ + +Детектор «файл в корне + мусорный `album`» дал 76 файлов: +``` +44 !Барды/Владимир Высоцкий/Разное album='Разное' +14 !Rock&Folk/Пилот/Избранное album='Избранное' +10 !Rock&Folk/ДДТ/2003 - песни album='Песни' + 3 !Rock&Folk/Наутилус Помпилиус/2000 - песни album='песни' + 3 !Rock&Folk/Rammstein/misc album='misc' + 1 !A Not sorted/Агата Кристи album='Избранное' +``` +**Alex (дословно):** «**план такой и был что папку Разное мы кидаем как альбом \"Разное\" +а не разгребаем**». + +🔑 **Класс ЗАКРЫТ, действий не требуется.** Папка-мусорка остаётся альбомом. +**Правило:** перед «фиксом» мусорного `album` — спросить, не план ли это. + +### 🔴 ПИТФОЛЛ ДЕТЕКТОРА: ``-ЛОГИКА ДАЁТ ТЫСЯЧИ ЛОЖНЫХ СРАБАТЫВАНИЙ + +Мой детектор «правило корня не применилось» (`folder == parent`, `new_album != basename(folder)`) +выдал **6161** файл — это шум. Он валится на файлах, где `new_album` **осмысленный** +(`Все пройдёт милая`, `Попса`, `Классика`) — правило корня там и **не должно** применяться. + +🔑 **Правило корня нужно только там, где `new_album` мусорный/пустой.** +Правильная формулировка детектора: `is_junk(new_album)` — словарь заглушек +(`non spécifié`, `null`, `no title`, `album`, `mix`, `misc`, `песни`, `избранное`, `разное`, +`[unknown album]`, `неизвестное название`...) + подстроки водяных знаков (`muzmo.ru`, +`melody.az`) + `\d{4}` (только год). Скрипт: `/tmp/diag_root_junk.py`. + +### 🔑 ФОРМАТ ОТЧЁТА, ОДОБРЕННЫЙ ALEX + +Alex: «**вот всегда бы ты так отвечал**» — про ответ, который начинался с артефакта. + +Записано в `~/.hermes/hermes-whale/SOUL.md` (строка 82, блок **Verified answer format**, ++17 строк, md5 `36c85871b9f94929dedc2a4979e3a119`, бэкап `SOUL.md.bak-20260927-205340`): + +1. **Строка 1 = артефакт.** Путь + `md5` + счёт из САМОГО файла (`wc -l`, `grep -c`). Ничего до него. +2. **Что изменено** — список `что → почему`, без хеджинга. +3. **Бэкапы.** + +Никогда не открывать «сейчас проверю». Факт-проверка ДО отчёта, числа из файла. +**Alex спорит с числом → не повторять цифру**, дать правило счёта + спросить +«какое число ждёшь и откуда». + +### 🔴 ПИТФОЛЛ: `grep` ПО ВСЕМУ ФАЙЛУ (повтор из сессии 29-СЕПТИ, но с новым лицом) + +Проверяя фиксы, я грепал `APPLY-PLAN.csv` целиком и получал «опечатки на месте». +Верный метод — **по КОЛОНКЕ через CSV-парсер**: `/tmp/check_shortlist_full.py`, +`/tmp/check_typo_fixes.py`. Файл один и тот же, опечатки живут в `title`. + ## ✅ СЕССИЯ 29-СЕПТИ (2026-09-27) — ВЕРИФИКАЦИЯ «КОРОТКОГО СПИСКА ОПЕЧАТОК»: ВСЁ УЖЕ СТОИТ, v3 НИЧЕГО НЕ ТЕРЯЕТ > **ТРИГГЕР:** «**учел фикс опечаток типа "Муммий тролль" "филосовские" из того же короткого списка??**» diff --git a/personal/tech/batch-data-edit-review-loop.md b/personal/tech/batch-data-edit-review-loop.md index ea5fbeb5..44a674d9 100755 --- a/personal/tech/batch-data-edit-review-loop.md +++ b/personal/tech/batch-data-edit-review-loop.md @@ -36,6 +36,96 @@ related: 6. **Инвариант полноты:** `sum(частей) == total`. Проверять **сумму**, не только группы. 7. **Не выдавать выборку за полную проверку.** +## 🔴 Урок сессии 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**. +То есть база уже хранит целое, правки были впустую. + +```python +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` при схлопывании: единственное значение, если все совпали; иначе ``. +- Одна папка может дать НЕСКОЛЬКО строк по одному полю, если у неё несколько `new` + (пример: сборник `Sbornik_Novij_Rok`, `genre` = Folk/Pop/Rock/Punk/Rap). Это норма + для сборника, не дефект. + +### 5. Сортировка канонного диффа — `field` ПЕРВЫМ ключом + +Alex: «**почему сортировки по филду нету в диффе**». +Сортировка по `(folder, field, new)` мешает: внутри папки ПАПКА/ТРЕК чередуются, `field` +идёт вразнобой. Правильно: `(field, folder, path)` — блоки по полю сквозные. + +```python +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. Частичный фикс фильтра-дроппера прячет остаток diff --git a/personal/tech/whale-soul-answer-format.md b/personal/tech/whale-soul-answer-format.md new file mode 100644 index 00000000..25b696fc --- /dev/null +++ b/personal/tech/whale-soul-answer-format.md @@ -0,0 +1,80 @@ +--- +title: Whale SOUL.md — формат ответа (Verified answer format) +namespace: personal +type: tech +created: '2026-09-27' +updated: '2026-09-27' +tags: + - hermes + - soul + - whale + - communication + - alex-preference +related: + - '[[personal/plans/music-artist-albumartist-repair]]' + - '[[personal/tech/alex-communication-patterns]]' +--- + +# Whale SOUL.md — формат ответа + +Файл: `~/.hermes/hermes-whale/SOUL.md` — системный промпт Кита (Whale). +Загрузка в каждый turn, поэтому правки там действуют сразу. + +## 🔴 Что добавлено (2026-09-27, вечер) + +**Триггер:** Alex — «**вот всегда бы ты так отвечал**» после ответа, который начинался +с артефакта (путь файла + md5 + число из самого файла), а не с рассказа о работе. + +**Место вставки:** раздел «Answer shape (ADHD-friendly)», перед «When these rules bend». + +``` +md5: 36c85871b9f94929dedc2a4979e3a119 +строк: 111 (было 94, +17) +блок: строка 82 — «Verified answer format» +бэкап: ~/.hermes/hermes-whale/SOUL.md.bak-20260927-205340 +``` + +## Содержание блока (дословно) + +**Verified answer format (Alex approved 2026-09-27: "вот всегда бы ты так отвечал"):** +When reporting finished work — artifact FIRST, not narration. Structure: + +1. **Line 1 = the artifact.** File path + `md5` + a count read from the file itself + (`wc -l`, `grep -c`). Nothing before it. +2. **What changed** — short list, each line `what → why`. No hedging. +3. **Backups** — paths of what was saved before the edit. + +Never open with "сейчас проверю" / "let me check". Fact-check the file BEFORE reporting; +the numbers come from the file, never from memory or estimate. Never invent a value Alex +did not supply. + +**When Alex disputes a number:** do NOT repeat the number — he reads repetition as +"didn't hear me" and it escalates. Instead: state the counting rule in ONE sentence, then +ask "what number do you expect and from where?" A mismatch is a defect signal, not a debate. + +**Answer length:** "всё готово, проверяй" is the target shape. Long reports are not read. +Defect not closed → "не готово" + ONE line of cause. No menus, no "делать?" — once a defect +is named, finish it in the same turn. + +## Почему именно так + +| Факт про Alex | Следствие в блоке | +|---|---| +| Не читает длинные отчёты | «всё готово, проверяй» — целевая форма | +| Спор о числе = «не услышал» → мат | не повторять цифру, дать правило счёта | +| Переспрашивание после названного дефекта = саботаж | «no "делать?", finish in the same turn» | +| Числа из памяти = выдумка | числа только из файла, факт-проверка ДО отчёта | +| Требует доказательств («Мой `read back` ≠ работает») | строка 1 = артефакт + md5 + счёт | + +## Связь с memory + +Дублирующая запись в USER.md профиля Кита — та же суть, компактнее. +SOUL.md — источник истины, memory — быстрый доступ. + +## Как править + +1. Бэкап: `cp ~/.hermes/hermes-whale/SOUL.md ~/.hermes/hermes-whale/SOUL.md.bak-$(date +%Y%m%d-%H%M%S)` +2. `patch` по уникальной строке-якорю (не перезаписывать целиком) +3. Проверить: `wc -l`, `grep -n "Verified answer format"`, `md5 -q` +4. 🔴 НЕ трогать секцию «Core Rules» и «Absolute rules» — там plan-first и + no-delete/no-kill, они главнее стиля