[2026-09-20] eagle: family/how-to/arr-stack-kraken.md family/projects/watchlist-automation.md family/projects/watchlist-sync.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-20 19:39:57 +06:00
1 parent d7ce0a9a70
commit bd743da99c
3 files changed
+234 -30

No files matched your search

+72 -1
View File
@@ -81,15 +81,31 @@ Prowlarr → Radarr + Sonarr → Transmission → Jellyfin
## Cron'ы на Kraken (crontab)
⚠️ **Фактическое состояние (проверено 2026-09-20): строки watchlist в crontab ОТСУТСТВУЮТ.** Watchlist pipeline мёртв с 2026-06-20 — детали и разбор в [[family/projects/watchlist-automation]].
Фактически в `crontab -l` юзера `kraken` сейчас:
```
0 * * * * bash ~/scripts/sync-vault.sh # Obsidian sync
0 * * * * pgs-to-srt.sh /media/downloads # PGS→SRT новые
0 * * * * pgs-to-srt.sh /media/downloads/complete # PGS→SRT новые
0 3 * * * pgs-to-srt.sh /media # PGS→SRT вся библиотека
```
Должно быть (НЕ восстановлено, ждёт решения Alex):
```
0 1 * * * bash ~/scripts/watchlist-nightly.sh # watchlist nightly
0 9 * * 0 bash ~/scripts/watchlist-discover.sh # watchlist discover
*/10 * * * * bash .../media-pipeline/media-router.sh # media router (Telegram)
```
Точная проверка наличия любого крон-джоба (не верить доке, смотреть систему):
```bash
crontab -l # юзер kraken
sudo crontab -l # root
sudo ls -la /var/spool/cron/crontabs/ # все юзерские
sudo grep -rl watchlist /var/spool/cron/ /etc/cron.d/ /etc/crontab
systemctl --user list-timers --all ; systemctl list-timers --all | grep -i watchlist
```
## API Keys
| Service | API Key |
@@ -432,9 +448,64 @@ sudo docker inspect <name> -f '{{index .Config.Labels "com.docker.compose.projec
Все сервисы стека (`radarr`, `sonarr`, `prowlarr`, `flaresolverr`, `transmission`) **плюс `jellyfin` и `portainer`** размечены label `com.centurylinklabs.watchtower.enable=true` → обновляются автоматически (7 контейнеров, проверено `scanned=7`). Полный разбор и список того, что НЕ обновляется: [[family/how-to/kraken-watchtower]]. Теория: [[personal/tech/watchtower-docker-updates]].
## Watchlist-sync: ручной запуск и баг дублирования (2026-09-20)
> Детальный разбор бага и полная механика — в [[family/projects/watchlist-automation]]. Здесь — операционная выжимка.
### Ручной запуск (пока крон не восстановлен)
```bash
ssh kraken
bash ~/scripts/watchlist-nightly.sh # pull → process-thumbs → resolve → sync-down → sync-up → push
```
Отдельные шаги:
```bash
cd /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync
uv run --project . watchlist-sync <resolve|sync-down|sync-up|process-thumbs|discover> [--apply] [--recheck] [--batch N]
```
`uv` = `~/.local/bin/uv`. Без `--apply` — dry-run.
### 🔴 Баг `mark_ambiguous()` — исправлен
`watchlist_sync/parser.py`, функция `mark_ambiguous()`: паттерн снимал **только саму `❓`-строку**, а замена вставляла новые кандидаты **перед** старыми → под-буллеты дублировались **на каждом прогоне** `resolve` (+3 строки / +100 байт за раз). `❓`-строки при этом НЕ дублировались — только под-буллеты под ними.
```python
# ФИКС: захват всего блока — маркер + под-буллеты
_SUB_BULLETS = r'(?:\n[ \t]{4,}-[ \t]*(?!\[[ xX]\])[^\n]*)'
pattern = re.compile(
r'(^[ \t]*-[ \t]*\[ \][ \t]*)[❓]?[ \t]*(' + escaped + r')[ \t]*'
r'(?P<subs>' + _SUB_BULLETS + r'*)',
re.MULTILINE
)
```
**Правило для парсеров этого файла:** под-буллет кандидата = отступ **≥4 пробела** + буллет + **НЕ чекбокс** (`- [ ]`/`- [x]`). Строки фильмов имеют отступ **2 пробела** — их надо исключать явно. Внутри `*`-группы **перенос строки обязателен** (`(?:` + `\n` + ...) + `*`): если сделать `\n?` опциональным, regex перешагивает на следующую строку и **проглатывает соседние фильмы** (проверено — так потеряли `❓ The kill room`, `The burial`, `Past lives`).
**Верификация:** прогнать `mark_ambiguous()` 3× на свежей копии → размер обязан быть **идентичным**. Плюс проверка, что 0 не-кандидатных строк потеряно.
### Скрипт дедупа кандидатов
Одноразовая чистка накопленных дублей: под каждой `❓`/`🗓️`-строкой оставить уникальные под-буллеты (ключ `title|year|rating`), порядок не менять, строки фильмов не трогать.
```bash
python3 /tmp/wl_dedupe.py # dry-run
python3 /tmp/wl_dedupe.py --apply # запись + авто-бэкап .dedupe-backup-<ts>
```
Прогон 2026-09-20: **168 дублей в 15 блоках**, 26430 → 21032 байт (838 → 670 строк).
⚠️ **Скрипты этой сессии — в `/tmp` на Кракене** (`wl_dedupe.py`, `wl_fix_parser*.py`, `wl_verify*.py`); `/tmp` чистится при перезагрузке. Для постоянной копии — перенести в `~/scripts/`.
### Sonarr 400 «This series has already been added» — не сбой
При `sync-down` уже существующей серии Sonarr отвечает `400 SeriesExistsValidator` («Волшебный участок» = «Magical Police Department», tvdbId=442038). `arr.py::sonarr_add()` **не проверяет дубли перед POST** — в отличие от `radarr_add()`. Шумит в логе, данные не портит. Диагноз смотреть в теле ответа: `errorCode: SeriesExistsValidator`.
### Zulip-пинг не настроен
`_zulip_ping()` (`cli.py`) требует `ZULIP_URL` / `ZULIP_API_KEY` / `ZULIP_BOT_EMAIL`, которых **нет в `.env`** репо → `[warn] not set — skipping ping`. Уведомления о нерезолвленных items до Alex **не доходят** (59 штук ушли молча).
## Notes
- See [[arr-stack-taiga]] for Taiga (secondary) arr stack
- Media pipeline pitfalls: [[media-pipeline-pitfalls]]
- FlareSolverr / Cloudflare / Prowlarr-питфоллы: [[prowlarr-flaresolverr-cloudflare]]
- Watchlist pipeline (полный flow, cron, баги): [[family/projects/watchlist-automation]]
- Синтез: [[concepts/kraken-media-stack]]
+132 -10
View File
@@ -1,7 +1,7 @@
---
created: '2026-05-20'
updated: '2026-09-20'
status: broken
status: degraded
tags:
- kraken
- watchlist
@@ -81,7 +81,95 @@ Jellyfin (8096) → rescan → виден контент
**Побочный эффект — файл засран `❓`-мусором.** Так как `resolve --recheck` не бегает, ambiguous-строки копят дампы кандидатов: «Крестный отец» ×4 блока по 6 строк, «Nightmare Alley» ×31 строка, «Stand by me» ×40 строк, «Фарго» ×30. Секции `👍/👎` не обрабатываются — 40+ позиций висят в «Новинки», не переехав в основной список.
Аудит завершён без изменений на Кракане — ничего не трогалось. Три варианта для Alex: (1) восстановить крон, (2) сначала почистить `❓`-мусор, (3) оставить как есть.
> ⚠️ **Уточнение (2026-09-20, позже в тот же день):** изначальная гипотеза «мусор скопился от многомесячного простоя» **НЕВЕРНА**. Дубли создавались на **каждом** прогоне `resolve` из-за бага в `mark_ambiguous()` (см. ниже). 33 строки у «Nightmare Alley» = 11 прогонов. Простой лишь позволил мусору накопиться незамеченным.
**Крон по-прежнему НЕ восстановлен** — Alex не просил, прогон делался вручную (см. «Ручной прогон»).
## ✅ Баг дублирования кандидатов в `mark_ambiguous()` — НАЙДЕН И ИСПРАВЛЕН (2026-09-20)
### Симптом
Под каждой `❓`-строкой копились одинаковые дампы кандидатов. Рост ровно **+100 байт за прогон** (3 строки кандидатов) — подтверждено экспериментом: 26430 → 26530 → 26630 → 26730 байт.
### Корень (точная механика)
В `watchlist_sync/parser.py`, функция `mark_ambiguous()`:
```python
pattern = re.compile(
r'(^\s*-\s*\[ \]\s*)[❓]?\s*(' + escaped + r')\s*$', # ← матчит ТОЛЬКО строку ❓
re.MULTILINE
)
...
replacement = rf'\1❓ \2\n{cand_lines}' # ← новые кандидаты
new_text = pattern.sub(replacement, text, count=1) # ← ВСТАВЛЯЮТСЯ перед старыми
```
Паттерн снимал **только саму `❓`-строку**. Замена вставляла `\n` + 3 новых кандидата, а старые 33 строки оставались **ниже** — реплейс не знал о них. Итог: каждый прогон добавляет ещё один блок кандидатов поверх существующих.
**Важно:** `❓`-строки НЕ дублировались (каждая — одна). Дублировались **под-буллеты** под ними.
### Фикс
Паттерн теперь захватывает **весь блок**: строку-маркер ПЛЮС её под-буллеты, и перезаписывает целиком.
```python
# Candidate sub-bullet: indent of 4+ spaces, a bullet, NOT a checkbox line.
# Each iteration REQUIRES its own leading newline -- otherwise `*` walks onto
# the following line and swallows neighbouring items.
_SUB_BULLETS = r'(?:\n[ \t]{4,}-[ \t]*(?!\[[ xX]\])[^\n]*)'
pattern = re.compile(
r'(^[ \t]*-[ \t]*\[ \][ \t]*)[❓]?[ \t]*(' + escaped + r')[ \t]*'
r'(?P<subs>' + _SUB_BULLETS + r'*)',
re.MULTILINE
)
```
### ⚠️ Две НЕУДАЧНЫЕ версии паттерна (не повторять)
1. **v1** — `(?:\n[ \t]+-[^\n]*\n?)*`: `\n?` опционален, поэтому `*` перешагивал на следующую строку **без разделителя** и проглатывал соседние фильмы. Потеряны `❓ The kill room`, `The burial`, `Past lives`. Откачено.
2. **v2** — то же плюс негативный lookahead: не помогло, `[^\n]*` из предыдущей итерации уже захватил хвост следующей строки.
3. **v3 (рабочая)** — перенос обязателен внутри каждой итерации (`(?:` + `\n` + ...) + `*`), якорь не нужен.
**Правило для парсеров этого файла:** под-буллет кандидата = отступ **≥4 пробела**, это буллет и он **НЕ чекбокс** (`- [ ]` / `- [x]`). Строки фильмов имеют отступ 2 пробела — именно поэтому их надо исключать явно.
### Проверка фикса
На чистой копии (md5 `40295fac...`) — 3 прогона `mark_ambiguous()`:
```
run 1: size=25430
run 2: size=25430
run 3: size=25430
```
Размер стабилен. Потеряно **0** не-кандидатных строк. `Past lives` на месте. 838 → 808 строк (минус 30 = старые дубли, 33 → 3).
### Чистка существующих дублей
Скрипт `/tmp/wl_dedupe.py` (дедуп по `title|year|rating`, первое вхождение сохраняется, порядок не меняется; строки фильмов не трогаются). Прогон `--apply`:
- **168 дублей в 15 блоках**, 26430 → 21032 байт (838 → 670 строк).
- Топ: `Nightmare alley` 33→3, `Stand by me` 30→3, `Фарго` 30→3, `Left behind` 21→4, `Крестный отец` 12→3, `Godfathers` 12→3.
### Ручной прогон `watchlist-nightly.sh` (от юзера `kraken`)
```
bash ~/scripts/watchlist-nightly.sh
```
Прошёл до `Done`, без падений. Результат: **270 resolved (38 horror), 37 ambiguous, 22 not found**. `sync-down` добавил 4 фильма в Radarr (Американская история X, Мастерская сатаны, Я ругаюсь, Гладиатор), `Scream` уже был → помечен `⬇️`. `sync-up`: 7 played items из Jellyfin, 1 отмечен. Vault закоммичен и запушен (`Committed: family/documents/movies-watchlist.md`, `Pushed ✓`).
**После прогона дублей: 0** (проверено отдельным скриптом). Секции целы: Новинки / Фильмы / Мультфильмы / Аниме / Новинки👎 / Связанные заметки.
### 🟡 Побочная находка: Zulip-пинг не настроен на Кракене
`_zulip_ping()` требует `ZULIP_URL`, `ZULIP_API_KEY`, `ZULIP_BOT_EMAIL`, но их **нет в `.env`** репо watchlist-sync. При 59 нерезолвленных items → `[warn] ... not set — skipping ping`. Уведомления о нерезолвленных до Alex не доходят.
### 🟡 Побочная находка: Sonarr 400 «This series has already been added»
При `sync-down` для «Волшебный участок»: `400 Bad Request` на `/sonarr/api/v3/series`. Причина безобидная — **серия уже в Sonarr** (tvdbId=442038, «Magical Police Department»). `arr.py::sonarr_add()` не проверяет дубли перед POST, поэтому Sonarr отвечает `SeriesExistsValidator`. Не сбой, но шумит в логе — стоит добавить pre-check (как в `radarr_add`).
### Бэкапы (перед правками)
```
~/obsidian/family/documents/movies-watchlist.md.bak-20260920-192909 (838 строк, 26430 б)
~/obsidian/family/documents/movies-watchlist.md.dedupe-backup-20260920-193111
/srv/.../watchlist-sync/watchlist_sync/parser.py.bak-20260920-192909
```
### Статус на конец сессии
- ✅ Фикс `parser.py` применён на Кракене, записан на диск, **`ast.parse` OK**.
- 🔴 **Фикс НЕ закоммичен** в репо watchlist-sync (git status показывает только `?? arr.py.bak-web4k-...`). Нужен коммит.
- ✅ Файл watchlist вычищен и закоммичен в vault.
- 🔴 Крон по-прежнему отсутствует — pipeline живёт только вручную.
## Cron Jobs (на Кракене с 2026-05-27)
@@ -102,7 +190,26 @@ Jellyfin (8096) → rescan → виден контент
**Пути на Кракене (HDD UUID 49e8f586-3839-4c5d-a1e1-58bfc3579ade):**
- `/home/kraken/obsidian` → symlink на `/srv/dev-disk-by-uuid-.../obsidian`
- `~/Developer/watchlist-sync` → symlink. **Реальный корень репо** (проверено 2026-09-04): `/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/watchlist-sync` — находится **в корне диска, НЕ под `Developer/`** (в доке ранее путь был ошибочный). Внутри: `watchlist_sync/` (cli.py, arr.py, sync_down.py, sync_up.py, resolver.py, parser.py, discover.py, jellyfin.py), `.venv` (py3.11+httpx+click), `.resolve_cache.json`, pyproject.toml.
- `/home/kraken/Developer/watchlist-sync` → symlink на `/srv/dev-disk-by-uuid-49e8f586-.../watchlist-sync`
✅ **Путь репо в скриптах ВЕРНЫЙ** (проверено 2026-09-20). В `watchlist-nightly.sh` и `watchlist-discover.sh`:
```
WS=/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync
UV=~/.local/bin/uv
```
`ls -la` подтверждает: `/srv/.../watchlist-sync` существует, внутри `watchlist_sync/`, `.venv/`, `.resolve_cache.json`, `pyproject.toml`, `.env` (есть, содержит TMDB_KEY/KP_KEY/RADARR_*/SONARR_*; **нет** ZULIP_*).
> Ранее в доке фигурировал путь `.../6194539b-.../watchlist-sync` «как реальный корень». На 2026-09-20 рабочий путь — **`49e8f586`**, и он же используется в скриптах. Не «исправлять» его обратно.
**Ещё одно подтверждённое отличие:** `~/scripts/` — **реальная директория** (`drwxr-xr-x root root`), а `~/.hermes/` на Кракане содержит **только** `skills/`. Скрипты лежат в `~/scripts/`, не в `~/.hermes/scripts/`.
### Как запускать CLI вручную (рабочая команда)
```bash
ssh kraken
cd /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync
uv run --project . watchlist-sync <команда> # resolve / sync-down / sync-up / process-thumbs / discover
```
Или целиком пайплайн: `bash ~/scripts/watchlist-nightly.sh`
## Discover — Фильтрация
@@ -161,19 +268,34 @@ Jellyfin library mapping: Movies → `movies`, Cartoons → `mixed` (movies + ep
### KP API Endpoint
`api.kinopoisk.dev` редиректит на `api.poiskkino.dev/v1.4/movie`. Использовать destination redirect напрямую.
### 🔴 Жёстко зашитый `qualityProfileId: 1` в arr.py (корень жирных remux)
Найдено 2026-09-04. В `watchlist_sync/arr.py` **`qualityProfileId: 1` захардкожен** и для Radarr, и для Sonarr:
### 🔴 Жёстко зашитый `qualityProfileId` в arr.py (корень жирных remux) — РЕШЕНО
Найдено 2026-09-04. В `watchlist_sync/arr.py` изначально был захардкожен **`qualityProfileId: 1`** и для Radarr, и для Sonarr:
```python
# radarr_add (~стр. 77) / sonarr_add (~стр. 112)
# radarr_add (~стр. 77) / sonarr_add (~стр. 112)
"qualityProfileId": 1,
```
Профиль 1 (Radarr) = **'Any'** — разрешает ВСЁ вплоть до Remux-2160p и BR-DISK. Поэтому **каждый новый фильм/сериал, добавляемый watchlist-sync, попадает в «Any»** и Radarr скачивает самый жирный доступный релиз (60GB remux'ы). Готового профиля «2160p, но без Remux» на Kraken нет (нужно создавать). Решение по стратегии — открыто (см. ниже). Чтобы новые шли в поддерживаемое качество: либо создать профиль «2160p без Remux/BR-DISK» и поменять `qualityProfileId` в `arr.py` (Radarr+Sonarr), либо сузить сам профиль 1, либо зашить `4` (HD-1080p).
Профиль 1 (Radarr) = **'Any'** — разрешает ВСЁ вплоть до Remux-2160p и BR-DISK, поэтому каждый новый фильм/сериал попадал в «Any» и Radarr скачивал 60GB remux'ы.
✅ **Решение принято и применено 2026-09-04:** создан профиль **`WEB-4K (no remux)`** → Radarr id **7**, Sonarr id **7**; `arr.py` теперь пишет `qualityProfileId: 7`. Подробности профиля — в [[arr-stack-kraken]], секция «Качество загрузки для НОВЫХ фильмов/сериалов».
### ✅ Баг накопления кандидатов в `mark_ambiguous()` (найден и исправлен 2026-09-20)
Кандидаты-под-буллеты под `❓`-строками дублировались **на каждом прогоне** `resolve` (+3 строки за раз), потому что `re.sub` снимал только саму `❓`-строку, а новые кандидаты вставлялись **перед** старыми. Паттерн исправлен на захват всего блока (маркер + под-буллеты). Полная механика, две неудачные версии паттерна и чистка 168 дублей — в [[family/projects/watchlist-automation]].
**Правило для парсеров watchlist:** под-буллет кандидата = отступ **≥4 пробела** + буллет + **НЕ чекбокс** (`- [ ]`/`- [x]`). Строки фильмов имеют отступ 2 пробела. Внутри `*`-группы перенос строки обязателен — иначе regex проглатывает соседние фильмы.
## Open Tasks
- 🔴 **[2026-09-20] Восстановить cron для watchlist pipeline** — pipeline мёртв с 20 июня (см. раздел выше). Решение за Alex: восстановить крон / сначала почистить `❓`-мусор / оставить как есть.
- 🔴 **[2026-09-04] Решить стратегию качества загрузки новых фильмов** (см. профили в [[arr-stack-kraken]]). Корень — захардкоженный `qualityProfileId: 1` (`Any`) в `watchlist_sync/arr.py`. Пока НЕ менялось (НИЧЕГО не правилось, ждём решение Alex). Вопрос юзеру: сохранить 4K (→ создать профиль «2160p без Remux/BR-DISK» и сменить id в arr.py) или опустить до 1080p (id 4); и менять ли и для Sonarr (сериалы) тоже.
- 🟡 Почистить `❓`-мусор в movies-watchlist.md (повторяющиеся дампы кандидатов после многомесячного простоя)
- 🔴 **[2026-09-20] Закоммитить фикс `parser.py`** в репо watchlist-sync. Правка применена на Кракене, но не в git — при следующем `git pull`/пересоздании `.venv` можно потерять. Файл: `watchlist_sync/parser.py` (функция `mark_ambiguous`, `_SUB_BULLETS`).
- 🔴 **[2026-09-20] Восстановить cron для watchlist pipeline** — pipeline мёртв с 20 июня. Скрипты готовы и проверены вручную (прогон 2026-09-20 прошёл чисто), нужно вернуть в crontab юзера `kraken`:
```
0 1 * * * bash ~/scripts/watchlist-nightly.sh
0 9 * * 0 bash ~/scripts/watchlist-discover.sh
```
⚠️ В [[arr-stack-kraken]] эти строки в секции «Cron'ы на Kraken» указаны как действующие, хотя фактически их НЕТ — док в той части расходится с реальностью.
- 🟡 **[2026-09-20] Настроить Zulip-пинг** — `ZULIP_URL` / `ZULIP_API_KEY` / `ZULIP_BOT_EMAIL` отсутствуют в `.env` watchlist-sync, 59 нерезолвленных items ушли в никуда без уведомления.
- 🟡 **[2026-09-20] Добавить pre-check дублей в `sonarr_add()`** — сейчас при уже существующей серии прилетает `400 SeriesExistsValidator` и шумит в логе. В `radarr_add()` проверка есть.
- 🔴 **[2026-09-04] Решить стратегию качества загрузки новых фильмов** — уже РЕШЕНО 2026-09-04 (профиль `WEB-4K (no remux)`, id 7 для Radarr и Sonarr). Осталось только убедиться, что старые `⬇️`-позиции, добавленные до фикса, не тянут remux.
- 🟡 Оставшиеся `❓`-позиции (71 маркер) — **намеренно не чистятся вручную** (решение Alex 2026-09-20). Разбираются через `resolve --recheck` при работающем кроне.
- 🟡 Передавать Transmission locations через router script при каждом импорте (сейчас вручную)
- 🔴 rsync невыполненных фильмов с HTPC на Кракен (`Фильмы не перенесенные с htpc на кракен.md`)
- 🟡 Watchlist sync-up: добавить перенесённые фильмы в нужные разделы
+30 -19
View File
@@ -1,7 +1,7 @@
---
created: '2026-05-18'
updated: '2026-05-18'
status: planning
updated: '2026-09-20'
status: active
tags:
- kraken
- jellyfin
@@ -16,6 +16,8 @@ tags:
Репо: `~/Developer/watchlist-sync` → GitHub `mallexxx/watchlist-sync`
> ℹ️ **Эта заметка — исходный дизайн-документ проекта (Stage 1/2).** Актуальное состояние, рабочие пути, cron-статус и все найденные баги — в [[family/projects/watchlist-automation]]. Реализовано всё, кроме Stage 2 (рекомендации). Ниже сохранён дизайн; расхождения с реальностью помечены.
---
## Архитектура
@@ -113,8 +115,12 @@ WATCHLIST_PATH=/vault/family/documents/movies-watchlist.md
| Job | Расписание | Скрипт |
|-----|-----------|--------|
| `watchlist-nightly` | ежедн. 01:00 | `~/.hermes/scripts/watchlist-nightly.sh` |
| `watchlist-discover` | вс 09:00 | `~/.hermes/scripts/watchlist-discover.sh` |
| `watchlist-nightly` | ежедн. 01:00 | `~/scripts/watchlist-nightly.sh` |
| `watchlist-discover` | вс 09:00 | `~/scripts/watchlist-discover.sh` |
⚠️ Фактический путь — `~/scripts/`, **не** `~/.hermes/scripts/` (проверено 2026-09-20). Мигрировали с Eagle на Кракен 2026-05-27.
⚠️🔴 **Cron НЕ восстановлен после 2026-06-20** — pipeline запускается только вручную. См. [[family/projects/watchlist-automation]].
Используют `~/scripts/sync-vault.sh` для всех git-операций (stash/pull/pop/commit/push). **Не агентные** — скрипты вызывают CLI напрямую, никакого LLM в pipeline.
@@ -208,28 +214,33 @@ WATCHLIST_PATH=/vault/family/documents/movies-watchlist.md
## Имплементация: очерёдность задач
### Фаза 1 (MVP)
- [ ] Инициализировать репо `~/Developer/watchlist-sync`, push на GitHub
- [ ] `parser.py` — парсинг .md, классификация строк
- [ ] `resolver.py` — TMDB `/search/multi` + KP fallback
- [ ] `sync_down.py` — add to Radarr/Sonarr (батч 5), пометить `⬇️`
- [ ] CLI: `watchlist-sync resolve --dry-run` и `--apply`
- [ ] CLI: `watchlist-sync sync-down --batch 5`
> Статусы проставлены 2026-09-20. Фазы 1-3 и cron реализованы и работают (проверено ручным прогоном `watchlist-nightly.sh`). Stage 2 (Фаза 4) — не начата.
### Фаза 2
- [ ] `sync_up.py` — Jellyfin → `[x]` + сортировка
- [ ] CLI: `watchlist-sync sync-up`
- [ ] Cron на Кракене: sync-up каждые 24ч
### Фаза 1 (MVP) — ✅ РЕАЛИЗОВАНО
- [x] Инициализировать репо `~/Developer/watchlist-sync`, push на GitHub
- [x] `parser.py` — парсинг .md, классификация строк
- [x] `resolver.py` — TMDB `/search/multi` + KP fallback
- [x] `sync_down.py` — add to Radarr/Sonarr (батч 5), пометить `⬇️`
- [x] CLI: `watchlist-sync resolve --dry-run` и `--apply`
- [x] CLI: `watchlist-sync sync-down --batch 5`
### Фаза 3
- [ ] AI disambiguation job (Hermes Kraken cron)
- [ ] Уточнение у Alex через Zulip для нерезолвленных
### Фаза 2 — ✅ РЕАЛИЗОВАНО
- [x] `sync_up.py` — Jellyfin → `[x]` + сортировка
- [x] CLI: `watchlist-sync sync-up`
- [x] Cron на Кракене: sync-up каждые 24ч — ⚠️ был, **умер 2026-06-20**, не восстановлен
### Фаза 4 (Stage 2)
### Фаза 3 — ✅ РЕАЛИЗОВАНО (кроме Zulip)
- [x] CLI `resolve --recheck` для повторного разбора `❓`
- [ ] AI disambiguation job (Hermes Kraken cron) — не делался, вместо него детерминированный `--recheck`
- [ ] Уточнение у Alex через Zulip для нерезолвленных — код есть (`_zulip_ping()` в `cli.py`), но `ZULIP_*` не в `.env` → молча скипается
### Фаза 4 (Stage 2) — ❌ НЕ НАЧАТА
- [ ] `recommend.py` — профиль + TMDB recommendations
- [ ] Три curated списка с подтверждением
- [ ] Еженедельный cron job
> Файловая структура в разделе «Структура проекта» — тоже дизайн: реально `radarr.py`/`sonarr.py` объединены в `arr.py`, добавлены `discover.py`, `sync_down.py`, `sync_up.py`, `jellyfin.py`, `parser.py`, `resolver.py`, `cli.py`. Точный список — в [[family/projects/watchlist-automation]].
---
## Ключи и секреты