Merge remote-tracking branch 'origin/main'

This commit is contained in:
Taiga committed 2026-09-26 09:12:37 +00:00
commit ca471c21af
1 file changed
+76 -31
+76 -31
View File
@@ -18,9 +18,11 @@ related:
---
# Music Tags Repair — TrueNAS (7906 файлов)
> **Статус: ЭТАПЫ 1–2 ЗАВЕРШЕНЫ (2026-09-26). CSV v3 + ноты ревью отданы Alex. Правки файлов НЕ начаты.**
> **Статус: ЭТАПЫ 1–2 ЗАВЕРШЕНЫ (2026-09-26). CSV v4 + ноты ревью отданы Alex. Правки файлов НЕ начаты.**
> Следующий шаг — апрув Alex по нотам ревью, затем бэкап + применение.
> Перед выдачей проведён **полный аудит всех 4774 строк** (`audit_plan.py` + `final_gate.py`) — найдено и исправлено 6 багов.
> Прогонялись три волны проверок: полный аудит всех строк (`audit_plan.py` + `final_gate.py`) → 6 багов,
> затем третья волна по замечаниям Alex → 3 бага (en-dash + подстроковый поиск стоп-слов).
> Итог v4: 4777 `fix`, mojibake = 0, 80 тестов PASS, блок «требует решения» 296 → 28.
## 📌 ПЛАН ТРЕТЬЕЙ ВОЛНЫ (2026-09-26, апрув Alex) — «сам проставь, потом на апрув»
@@ -124,32 +126,45 @@ Alex: **«Сам проверил весь файл на адекватност
**Оставшиеся флаги аудита — все проверены вручную и законны:** `album == artist` (49, одноимённые альбомы),
`translit_kept` (83, реальная латиница), `mixed_script` (19, `NTL ft. MC Молодой`), `numeric_title` (15, `007`/`48`).
## 📡 SYNCTHING: ноты доехали, но найдено расхождение в расписании
## 📡 SYNCTHING: ноты доехали — но по пути нашлись ДВА бага
Проверка по просьбе Alex («Проверь что syncthing всосал доку»). Первая проверка показала:
`obsidian-syncthing` **отстал на 3 коммита**, нот в нём нет.
**Причина — не Syncthing.** Цепочка «bare repo → git-клон `obsidian-syncthing` → Syncthing → телефон»
рвётся на **втором** шаге: job `sync-vault` в контейнере `hermes-taiga` расписан **`0 * * * *` — раз в час**,
хотя в `[[family/how-to/vault-git-sync]]` записано «каждые 5 минут».
**Причина №1 — не Syncthing.** Цепочка «bare repo → git-клон `obsidian-syncthing` → Syncthing → телефон»
рвётся на **втором** шаге: клон обновляет Taiga, а не Syncthing. Лечится прогоном job вручную:
**Что сделано:** job триггернут вручную (`hermes cron run 29851c413903`). Проверено:
```bash
docker exec hermes-taiga bash /opt/data/sync-vault-taiga.sh
```
> ⚠️ **Именно `bash`, не `sh`** — скрипт использует `set -o pipefail`, который `dash`/`sh` не понимает
> (`Illegal option -o pipefail`). Путь **внутри контейнера**: `/opt/data/sync-vault-taiga.sh`
> (= `/mnt/RED_2TB/docker/hermes/config/sync-vault-taiga.sh` на хосте).
**Причина №2 (настоящая, нашлась после «Pushed ✓»):** файлы **не уходили на телефон**, хотя git
рапортовал `Pushed ✓`. Это питфолл прав — см. следующий раздел.
**Итог после обоих фиксов:**
| Проверка | Значение |
|---|---|
| Клон `obsidian-syncthing` | ✅ HEAD `e200596`, 4 файла на месте |
| Syncthing: очередь | ✅ `needFiles: 0`, `needItems: 0`, `needDeletes: 0` |
| Синк с телефоном | ✅ `completion: 100`, `remoteState: valid` |
| Ошибки | ✅ `errors: 0` |
| Клон `obsidian-syncthing` | ✅ HEAD `7564ae2`, все 4 ноты на месте |
| Syncthing: очередь | ✅ `needFiles: 0`, `needBytes: 0`, `errors: 0` |
| Состояние | ✅ `state: idle` |
| Версия Syncthing | `v2.1.5 "Hafnium Hornet"`, папка `k5vyy-gzdgj` «Obsidian Vault» |
> 🔴 **ПРАВИЛО ПРИЁМКИ СИНКА: `needFiles: 0` + `errors: 0`, а НЕ «Pushed ✓».**
> `Pushed` — это про git. Файл может быть в коммите и при этом не читаться Syncthing'ом.
**Питфоллы синка этой сессии:**
- 🔴 **Порт Syncthing `8384` слушает ТОЛЬКО внутри контейнера** — `curl` с хоста TrueNAS отдаёт пустоту.
Запрос API делать через `docker exec syncthing curl -s -H "X-API-Key: …" http://localhost:8384/rest/…`.
- ⚠️ **`obsidian-syncthing` — git-клон (remote `/vault.git`), а не Syncthing-папка.** Обновляет его
Taiga по крону; Syncthing лишь раздаёт то, что уже в клоне. «Syncthing здоров» ≠ «док доехал».
Taiga; Syncthing лишь раздаёт то, что уже в клоне. «Syncthing здоров» ≠ «док доехал».
- ⚠️ **Скрипт диагностики синка** — `check_syncthing2.sh` (в `tools/music-tags/`): версия, статус папки,
connections, completion для телефона. Писать в файл, а не инлайн — иначе `APIKEY=` в heredoc режется.
- ⚠️ **Ключ Syncthing** добывается изнутри: `docker exec syncthing grep -o "<apikey>[^<]*" /var/syncthing/config/config.xml | cut -c9-`.
### 🔴 ПИТФОЛЛ: права файлов в git-клоне блокируют Syncthing
@@ -488,19 +503,21 @@ if cyr < 2: # одна буква — совпадение
| # | Шаг | Статус |
|---|---|---|
| 1 | **Инвентаризация** read-only: `ffprobe` по 7906 файлам → CSV | ✅ **ГОТОВО (v2)** — `tag_inventory2.py`, `probe_failed`=0 |
| 2 | **CSV с предлагаемым исправлением** → Alex | ✅ **ГОТОВО (v3)** — `~/Downloads/music-tags/tag-fix-plan.csv`, 4774 `fix` |
| 2a | **Ноты для ревью в Obsidian** → Alex (запрос Alex) | ✅ **ГОТОВО** — `personal/plans/music-tags-review/`, синкнуто + доехало до телефона |
| 2 | **CSV с предлагаемым исправлением** → Alex | ✅ **ГОТОВО (v4)** — `~/Downloads/music-tags/tag-fix-plan.csv`, 4777 `fix` / 3129 `no_change` |
| 2a | **Ноты для ревью в Obsidian** → Alex (запрос Alex) | ✅ **ГОТОВО (v4)** — `personal/plans/music-tags-review/`, пересинкнуто |
| 2b | **Полный аудит всех строк** (`audit_plan.py` + `final_gate.py`) → 6 багов исправлено | ✅ **ГОТОВО** (запрос Alex «Сам проверил весь файл?») |
| 3 | **Апрув Alex по нотам ревью** → только после этого правки | ⏳ **ЖДЁМ** (v1 и v2 отклонены, v3 + ноты отданы) |
| 2c | **Третья волна** по замечаниям Alex: чекбоксы, en-dash, подстроковый поиск стоп-слов | ✅ **ГОТОВО** — 3 бага, блок «требует решения» 296→28 |
| 3 | **Апрув Alex по нотам ревью** → только после этого правки | ⏳ **ЖДЁМ** (v1–v3 отклонены, v4 + ноты отданы) |
| 4 | **Бэкап** — копировать **только перезаписываемые** файлы в `Music/backup/` | ⏳ |
| 5 | **Ремонт** — перекодировка mojibake + восстановление из имён путей + чистка мусора | ⏳ |
| 6 | **Приёмка** — Alex слушает выборку | ⏳ |
> 📌 **Что именно ждёт апрува:** 4774 `fix` из 7906. Alex проходит по трём нотам ревью,
> 📌 **Что именно ждёт апрува:** 4777 `fix` из 7906. Alex проходит по трём нотам ревью,
> отмечает чекбоксы. Ничего не применяется до его «go».
>
> 🔴 **Открытый вопрос к Alex:** расписание job `sync-vault` в Taiga — `0 * * * *` (раз в час),
> хотя доки говорят «каждые 5 минут». Менять на `*/5 * * * *` или оставить?
> 📌 **Открытый вопрос про расписание синка** — снят, см. раздел «SYNCTHING» ниже: job зовётся
> `docker exec hermes-taiga bash /opt/data/sync-vault-taiga.sh`, расхождение было в описании,
> а не в работе. Причина недоехавших нот оказалась в **правах файлов**, не в расписании.
### Ключевые решения по плану (правки Alex)
@@ -538,7 +555,7 @@ if cyr < 2: # одна буква — совпадение
| `id3_direct.py` | **Прямой парсер ID3v2** из байтов файла (минуя `ffprobe`). Поддержка ID3v2.2/2.3/2.4, кодировок 0–3, extended header. Используется как fallback и как отладочный инструмент |
| `analyse_inventory.py` | Анализ CSV инвентаря: доли вердиктов, проверка обратимости mojibake, разбор `lost`/`probe_failed` |
| `build_fix_plan.py` | **Этап 2 (v1, устарел).** Содержал ранний выход по `probe_failed` — из-за него ДДТ отдавались пустыми |
| `build_fix_plan2.py` | **Этап 2 (v3, актуальный).** Пословный `fix_encoding`, `NON_ARTIST`-контейнеры, `is_release_folder`, `clean_folder`, `prettify_translit`, срезание ведущего номера. Функции: `_try_convert`, `has_release_in_path`, `fix_encoding`, `clean_value`, `clean_folder`, `repair`, `split_artist_title`, `title_from_name`, `derive_from_path`, `is_release_folder`, `prettify_translit` |
| `build_fix_plan2.py` | **Этап 2 (v4, актуальный).** Пословный `fix_encoding`, `NON_ARTIST`-контейнеры, `is_release_folder`, `clean_folder`, `prettify_translit`, срезание ведущего номера. **v4 добавил:** `find_separator()` (разделители `-`/`–`/`—`, включая приклеенный слева дефис) и `has_word()` (токенный поиск стоп-слов вместо подстрокового). Функции: `_try_convert`, `has_word`, `has_release_in_path`, `fix_encoding`, `clean_value`, `clean_folder`, `repair`, `find_separator`, `split_artist_title`, `title_from_name`, `derive_from_path`, `is_release_folder`, `prettify_translit` |
| `audit_plan.py` | **ПОЛНЫЙ аудит плана по всем строкам** (не сэмплы). Классы: `damaged_output`, `mixed_script_artist`, `translit_kept`, `watermark`, `numeric_title`, `artist_equals_title`, `generic_artist`, `album_equals_artist`, `one_char_title`, `whitespace`, `no_visible_change` |
| `final_gate.py` | **Гейт полноты перед выдачей.** Проверяет: совпадение числа строк с инвентарём, отсутствие заглушек (`probe_failed`/`investigation`), пустых title, U+FFFD, правок без `source`. Печатает PASS или точные строки |
| `deepdive_audit.py` | Углублённый разбор крупнейших классов (`generic_artist`, `translit_kept`) — по папкам и сырым значениям |
@@ -551,12 +568,13 @@ if cyr < 2: # одна буква — совпадение
| `diagnose_probe_failures.py` | Анализ заголовков файлов, упавших в `probe_failed`: ID3-версия, `hdr_ok`, размеры |
| `test_encoding_guard.py` | **32 регресс-теста (v1).** Защита от порчи корректного UTF-8 (`Björk`, `Saint-Saëns`, `Noël`) + `split_artist_title` |
| `test_plan2.py` | **65 регресс-тестов (v3, актуальный).** Пословная конверсия смешанных строк, короткие токены (`7Á`→`7Б`), контейнеры не становятся артистом, release-папки, отказ сплита на event-именах |
| `render_grouped_doc.py` | **Генератор нот для Obsidian.** CSV → 3 блока по уверенности + обзор, группировка по папкам, чекбоксы, оглавление |
| `render_grouped_doc.py` | **Генератор нот для Obsidian.** CSV → 3 блока по уверенности + обзор, группировка по папкам, чекбоксы, оглавление. **v4:** чекбоксы без префикса `> ` (иначе Obsidian парсит их как цитату и они не кликаются); `ROOT = /storage/Music/` (пути в CSV относительные от `/storage`, не от `/mnt/...`); блок «требует решения» = `нет artist И нет album` |
| `test_separators.py` | **15 регресс-тестов (v4).** Разделители `–`/`—`/приклеенный дефис; отказ сплита на `Рок-н-ролл`, `БИ-2`; event-имена (`НАШЕСТВИЕ 2009 - …`) не расщепляются; `VA - Title` и `Сказка - Теремок` возвращаются целиком. Зовётся `python3 test_separators.py` в `tools/music-tags/` |
| Артефакт | Где | Что |
|---|---|---|
| CSV-инвентарь v2 | `/mnt/RED_2TB/storage/tmp/tag-inventory2.csv` (NAS) → `~/Downloads/music-tags/tag-inventory.csv` (Mac) | 7906 строк, колонка `source`, `probe_failed`=0 |
| **CSV-план правок v3** | `~/Downloads/music-tags/tag-fix-plan.csv` (Mac, ~2.2 МБ) | **Отдан Alex 2026-09-26 (финал).** 4774 `fix` / 3132 `no_change` / 0 `probe_failed` |
| **CSV-план правок v4** | `~/Downloads/music-tags/tag-fix-plan.csv` (Mac, ~2.2 МБ) | **Отдан Alex 2026-09-26 (финал).** 4777 `fix` / 3129 `no_change` / 0 `probe_failed` |
| Копия БД Jellyfin | `/tmp/jf-copy.db` (Mac) | Для чтения API-ключа (см. `[[family/how-to/jellyfin-config]]`) |
| Копия плана на NAS | `/tmp/tag-fix-plan.csv` (NAS) | Для запуска `id3_direct.py`/`check_ddt_album.py` с хостовым Python |
| **Ноты ревью в vault** | `personal/plans/music-tags-review/` (4 файла) | Обзор + 3 блока по уверенности; **вход для апрува Alex**, синкнуто |
@@ -614,15 +632,32 @@ if cyr < 2: # одна буква — совпадение
- ⚠️ **Правка скриптов только локально → `scp` заново.** `sed -i` на NAS применён быть не должен (была такая ошибка, исправлена).
- ⚠️ **Кириллица в аргументах `ssh`-команд** может поднять tirith `confusable_text`. Держать русские пути внутри файла-скрипта.
- ⚠️ **Долгий инвентарь — запускать в фоне** (`docker run -d`) и поллить статус, а не держать `for`-цикл ожидания дольше 600 с (таймаут foreground).
- 🔴 **Файлы, созданные/обновлённые из контейнера `hermes-taiga` (там root), Syncthing НЕ читает.** Владелец становится `root`, а Syncthing работает под uid 950. Итог: `noPermissions: true`, `needFiles` висит вечно, на телефон ничего не уходит. **Фикс из контейнера** (у `truenas_admin` нет sudo без пароля):
```bash
docker exec hermes-taiga chown -R 950:950 <path> && docker exec hermes-taiga chmod -R 770 <path>
```
Эталон рабочих файлов клона: `truenas_admin:truenas_admin`, `drwxrwx---` (770). Проверять после каждого синка новых файлов.
- 🔴 **`Pushed ✓` ≠ «доехало».** Git-коммит и читаемость файла для Syncthing — разные вещи. Приёмка: `needFiles: 0` + `errors: 0` через Syncthing API.
- ⚠️ **`sync-vault-taiga.sh` запускать через `bash`, не `sh`** — скрипт использует `set -o pipefail` (в `sh`/`dash` → `Illegal option -o pipefail`). Путь внутри контейнера: `/opt/data/sync-vault-taiga.sh`.
- 🔴 **Чекбоксы в Obsidian не должны иметь префикс `> `.** Строка `> - [ ]` внутри выноски парсится как **цитата**, галочки не кликаются. Выноску (`> [!info]`) оставлять, но чекбоксы и подпункты — без `> `.
- 🔴 **Стоп-слова проверять ТОЛЬКО по токенам, не подстрокой.** `any(w in text)` для `"va"` матчит `Ivan`, `"set"` матчит `Sunset`, `"mix"` матчит `Mixes`. Теряются реальные исполнители (`DJ Ivan Scratchin'`). Фикс — `has_word()` с токенизацией.
- ⚠️ **Разделитель artist/title бывает не только `" - "`.** Встречаются en-dash `–`, em-dash `—` и дефис, приклеенный слева (`Arno Cost & Norman Doray- Apocalypse`). Последний принимать только если слева больше одного слова — иначе `БИ-2`/`Рок-н-ролл` расщепятся.
### 🧠 ГЛАВНЫЙ ПРОЦЕССНЫЙ УРОК ЭТОЙ СЕССИИ
Alex: **«Ты бы хоть постеснялся не проверив мне файл сувать!»**
Alex (вторая волна): **«Сам проверил весь файл на адекватность? Чтобы я в очевидные вещи тебя не тыкал.»**
Alex (третья волна): **«Давай ка дорогой пройди по файлу и сам проставь нормально все, а только потом мне на апрув, я не собираюсь очевидные вещи руками лопать.»**
**Ключевое различие, которое стоило двух итераций: выборочная проверка ≠ полный аудит.**
После первой правки были «пройдены все проверки» — но это были 5 метрик на сэмплах. Полный аудит
по всем 4774 строкам нашёл ещё **6 багов**, крупнейший — 238 файлов с `artist = Сказки`.
**Два различия, которые в сумме стоили трёх итераций:**
1. **Выборочная проверка ≠ полный аудит.** После первой правки были «пройдены все проверки» — но это
были 5 метрик на сэмплах. Полный аудит по всем строкам нашёл ещё **6 багов**, крупнейший — 238 файлов
с `artist = Сказки`.
2. **«Требует решения» — не оправдание для очевидного.** Отдавать Alex блок как «требует решения» можно
только когда решения **действительно нет**. 268 из 296 файлов решались автоматически
(сборники и сказки: album есть, artist и не нужен). Пользователь не должен вручную разгребать то,
что выводится кодом.
Порядок работы, который себя оправдал:
@@ -630,16 +665,21 @@ Alex (вторая волна): **«Сам проверил весь файл н
2. Прогнать **автотесты** на краевых случаях (латиница с умляутами, дефисы, номера, смешанные строки).
3. **Прогнать ПОЛНЫЙ аудит по всем строкам**, а не по сэмплам (`audit_plan.py`).
4. **Прогнать гейт полноты** (`final_gate.py`) — заглушки, пустые поля, правки без источника.
5. Посмотреть сэмплы «до/после» глазами по каждой проблемной группе.
6. Проверить **сырые данные конкретного файла**, прежде чем спорить «это баг / не баг».
7. Отделить **настоящие дефекты от законных случаев** (`separate_moji.py`, `review_classes.py`):
5. **Пройти по КАЖДОЙ строке плана глазами** — не по сэмплам. Именно так нашёлся склеенный title.
6. Свести блок «требует решения» к реально нерешаемому — всё выводимое проставить заранее.
7. Проверить **сырые данные конкретного файла**, прежде чем спорить «это баг / не баг».
8. Отделить **настоящие дефекты от законных случаев** (`separate_moji.py`, `review_classes.py`):
`NTL`/`Eagles` — латиница, `Би-2/2001 Би-2` — одноимённый альбом.
8. **Только потом** отдавать пользователю.
9. **Только потом** отдавать пользователю.
**Заглушка `needs investigation` в отчёте — это не результат, а недоработка.**
**«Требует решения» — законный исход, но должен быть явным, а не замаскированным мусором.**
**И он должен быть МИНИМАЛЬНЫМ: если значение выводится — выводить самому.**
### 🐞 Девять багов, пойманных ДО выдачи (файлы не пострадали)
**Правило Alex, зафиксировано:** если исполнитель неочевиден — **пусто**, не выдумывать
(пример: `detskiy_hor_benassi_satisfaction.mp3` — Alex сам оставил artist пустым).
### 🐞 Двенадцать багов, пойманных ДО выдачи (файлы не пострадали)
**Первая волна (3 бага, сэмплами):**
@@ -653,8 +693,13 @@ Alex (вторая волна): **«Сам проверил весь файл н
`artist = Сказки` (238), mojibake рядом с латиницей (127), имя радиовыпуска как артист (31),
номер склеен с названием (17), `album = '+'` (10), `©` как title (1).
> 🔑 **Методический вывод:** сэмплы ловят грубые ошибки, полный аудит — системные.
> Оба нужны. Без полного аудита CSV v2 ушёл бы Alex с 238 файлами «исполнитель Сказки».
**Третья волна (3 бага, по замечаниям Alex):** см. «ПЛАН ТРЕТЬЕЙ ВОЛНЫ» в начале —
чекбоксы с префиксом `> ` (не кликались), en-dash не распознавался как разделитель,
подстроковый поиск стоп-слов терял `DJ Ivan Scratchin'`.
> 🔑 **Методический вывод:** сэмплы ловят грубые ошибки, полный аудит — системные,
> а чтение глазами каждой строки — «очевидные», которые видны пользователю с первого взгляда.
> Все три уровня нужны.
## Связанные заметки