[2026-09-22] eagle: personal/plans/watchlist-tmdb-missing-marker.md
This commit is contained in:
1 parent
1d2876df06
commit
42ac730a46
1 file changed
+204
-74
@@ -1,61 +1,96 @@
|
||||
|
||||
# Watchlist — маркер «нет в TMDB» + заглушка карточки
|
||||
# Watchlist — автопоиск TMDB + маркер «нет в базе» (без ручных правок)
|
||||
|
||||
**Дата:** 2026-09-22
|
||||
**Статус:** черновик, ждёт апрува
|
||||
**Связано:** [[personal/plans/lampac-watchlist-plugin]], [[family/projects/watchlist-automation]]
|
||||
**Статус:** ПЛАН УТВЕРЖДЁН Alex — в работе
|
||||
**Связано:** [[personal/plans/lampac-watchlist-plugin]], [[family/projects/watchlist-automation]], [[family/projects/watchlist-sync]]
|
||||
|
||||
---
|
||||
|
||||
## Задача (дословно Alex)
|
||||
## Требование Alex (дословно)
|
||||
|
||||
> «Их нет?! Тогда какого хуя они БЛЯДЬ в списке вообще?! Скрипт должен поставить вопрос
|
||||
> в списке напротив такого (кроме тех что ещё не вышли, там календарь стоит)»
|
||||
> «Нет блядь, нахуя мне в плеере заглушки?! Нормальный автопоиск! Никаких БЛЯДЬ ручных
|
||||
> правок! То чего нет для просмотра - того нет БЛЯДЬ в списке! И ставится вопрос.
|
||||
> Все остальное из watchlist должно оказаться в списке!!»
|
||||
|
||||
> «В смысле блядь прописал соответствия вручную?! Че за нахуй?! Убирай нахуй!
|
||||
> Это блядь скрипт должен делать!»
|
||||
|
||||
> «Скрипт должен поставить вопрос в списке напротив такого (кроме тех что ещё не вышли,
|
||||
> там календарь стоит)»
|
||||
|
||||
---
|
||||
|
||||
## Что установлено фактами
|
||||
## Инварианты (нарушение = провал задачи)
|
||||
|
||||
### 1. Ручные соответствия — УБРАНЫ (2026-09-22)
|
||||
1. **НИКАКИХ ручных словарей-соответствий в коде.** Убрано 2026-09-22 (`MANUAL_TMDB`).
|
||||
2. **НИКАКИХ заглушек в плеере.** Позиция без TMDB не рисуется в каталоге вообще.
|
||||
3. **Всё, что реально существует и смотрибельно, — ОБЯЗАНО попасть в каталог.**
|
||||
Автопоиск обязан дотягивать то, что не находится по RU-названию.
|
||||
4. **Того, чего нет для просмотра, нет в списке.** Вместо позиции ставится `❓`.
|
||||
5. **Исключение — upcoming.** У вышедших позже маркер `🗓️` (календарь) не трогать.
|
||||
|
||||
Я вписал в генератор словарь `MANUAL_TMDB` (Хамнет → 858024, WeWork → 795622,
|
||||
Астрал 2 → 1291595) и «починил» 3 фильма руками. **Откачено по требованию Alex.**
|
||||
Причина корректная: подмена результатов — не решение, а маскировка проблемы.
|
||||
### Разграничение по маркерам — ключевое
|
||||
|
||||
⚠️ **Побочный эффект отката:** постеры у Хамнета, WeWork и Астрала 2 вернутся к «НЕТ»,
|
||||
пока не будет автоматического механизма. Данные в `/tmp/wl-out` пока содержат ручные
|
||||
результаты (задеплоено) — при следующем прогоне генератора они исчезнут.
|
||||
| Маркер | Значение | Действие |
|
||||
|---|---|---|
|
||||
| `🗓️` | фильм ещё **не вышел** | НЕ трогать. Календарь = ждём. В каталоге не нужен |
|
||||
| `❓` | есть/должен быть, но **не нашли в базах** | оставить, ждёт правки названия пользователем |
|
||||
| без маркера | вышел, должен смотреть | **обязан** попасть в каталог |
|
||||
|
||||
### 2. 4 фильма отсутствуют в TMDB физически
|
||||
Сейчас `🗓️` ставится в `discover` для upcoming. Позиции с `🗓️` **не должны** получать `❓`.
|
||||
|
||||
| Фильм | Кинопоиск-ID | Маркер | TMDB |
|
||||
|---|---|---|---|
|
||||
| Рассекая волны: экспедиция «Лас Бальсас» (2025) | 12432920 | 🗓️ | нет |
|
||||
| Цена вершины (2025) | 11888712 | 🗓️ | нет |
|
||||
| О добре и зле (2025) | 8184798 | 🗓️ | нет |
|
||||
| Эхопраксия | — | ❓ | id=1650284 есть, но **без постера** |
|
||||
---
|
||||
|
||||
Проверено запросами: RU-название, EN-алиас, разные годы — пусто.
|
||||
## Что установлено фактами (2026-09-22)
|
||||
|
||||
### 3. Корень: `discover` не проверяет наличие в TMDB
|
||||
### Проблема 1: генератор не умеет дотягивать не-RU названия
|
||||
|
||||
`discover.py` тянет новинки из Кинопоиска и фильтрует **только по рейтингу и голосам**
|
||||
(IMDb ≥ 7.0 / KP ≥ 8.5 для РФ, голосов ≥ 1000/500). Проверки «есть ли фильм в TMDB»
|
||||
**нет**. Поэтому в `## Новинки` попадают позиции, которых TMDB не знает →
|
||||
`resolve` их не может сопоставить → нет `tmdb_id` → нет постера → клик даёт «здесь пусто».
|
||||
`gen_watchlist_v2.py::tmdb_search()` ищет только `title` как есть, по `ru-RU` и `en-US`,
|
||||
типы `movie`/`tv`. Если в списке русское название, которого TMDB не знает (премьеры
|
||||
2025–2026, локальные алиасы) — возвращает `None` → нет `tmdb_id` → нет постера → нет карточки.
|
||||
|
||||
### 4. Почему маркер не сработал
|
||||
**Готовая механика уже есть** в `watchlist_sync/resolver.py` (строки 186–219) —
|
||||
cross-reference через Кинопоиск:
|
||||
|
||||
`🗓️` (upcoming) ставится **при добавлении** в `discover` для ещё не вышедших фильмов.
|
||||
`resolve` **пропускает** позиции с `❓`/`🗓️` (флаг `ambiguous` в `WatchItem`).
|
||||
Итог: маркер есть, но он про «не вышел», а не про «нет в TMDB» — и повторно
|
||||
позиция не проверяется никогда. Для `Эхопраксии` стоит `❓` с прошлых прогонов,
|
||||
но резолвер её не тронет, пока маркер не снят (`--recheck`).
|
||||
```python
|
||||
# 1. TMDB по названию как есть
|
||||
candidates = _search_tmdb(search_title, tmdb_key)
|
||||
|
||||
### 5. Текущие цифры (после деплоя)
|
||||
# 2. если пусто / скор низкий — идём в Кинопоиск
|
||||
if not candidates or candidates[0].score < AMBIG_SCORE_THRESHOLD:
|
||||
kp = _search_kp(search_title, kp_key) # KP /movie/search
|
||||
# 3. берём alternativeName (EN) из KP-документа
|
||||
for doc in kp_docs[:1]:
|
||||
alt_en = doc.get("alternativeName") or ""
|
||||
kp_year = doc.get("year")
|
||||
if alt_en and alt_en != search_title:
|
||||
tmdb_via_en = _search_tmdb(alt_en, tmdb_key) # ← ЭТО КЛЮЧ
|
||||
if tmdb_via_en and tmdb_via_en[0].score > 0.5:
|
||||
best = tmdb_via_en[0]
|
||||
# 4. ПРОВЕРКА ГОДА (±1) — защита от ложных совпадений
|
||||
if kp_year and best.year and abs(best.year - kp_year) > 1:
|
||||
pass # год не сошёлся — пропускаем
|
||||
else:
|
||||
candidates = [best] + candidates
|
||||
```
|
||||
|
||||
KP API: `https://api.poiskkino.dev/v1.4/movie/search`, ключ `KP_KEY` из `.env`.
|
||||
⚠️ НЕ `api.kinopoisk.dev` — отдаёт 301. Нужен `follow_redirects=True`.
|
||||
|
||||
### Проблема 2: `discover` не проверяет наличие в TMDB
|
||||
|
||||
`discover.py` фильтрует новинки **только по рейтингу и голосам**
|
||||
(IMDb ≥ 7.0 / KP ≥ 8.5 для РФ; голосов ≥ 1000 / ≥ 500). Проверки «есть ли фильм
|
||||
в TMDB» нет. Поэтому в `## Новинки` попадают позиции, которых TMDB не знает.
|
||||
|
||||
### Проблема 3: почему маркер не срабатывал
|
||||
|
||||
`🗓️` ставится **при добавлении** для upcoming. `resolve` **пропускает** позиции
|
||||
с `❓`/`🗓️` (флаг `ambiguous` в `WatchItem`, `parser.py`). Итог: маркер есть, но он
|
||||
про «не вышел», а не про «нет в TMDB» — повторной проверки никогда не происходит.
|
||||
|
||||
### Текущие цифры (задеплоено 2026-09-22)
|
||||
|
||||
| Категория | Позиций | Постеров | TMDB-ID |
|
||||
|---|---|---|---|
|
||||
@@ -65,80 +100,175 @@
|
||||
| anime | 7 | 7 | 7 |
|
||||
| **итого** | **403** | **351** | **355** |
|
||||
|
||||
Без TMDB-ID: **48 позиций**.
|
||||
Без TMDB-ID: **48 позиций** (в т.ч. 4 подтверждённо отсутствующих в TMDB).
|
||||
|
||||
### 🔴 Регресс от откатa ручных правок
|
||||
|
||||
При откате `MANUAL_TMDB` постеры у **Хамнета**, **WeWork**, **Астрала. Могильный
|
||||
ритуал 2** вернутся к «НЕТ». Это НЕ потеря — это корректное состояние до того, как
|
||||
заработает автопоиск (Шаг 1). **Автопоиск обязан их найти сам:**
|
||||
|
||||
| Фильм | TMDB-цель | Как находится |
|
||||
|---|---|---|
|
||||
| Хамнет | `858024` «Гамнет» (2025) | KP → `alternativeName` = "Hamnet" → TMDB |
|
||||
| WeWork | `795622` (2021) | KP → EN-алиас → TMDB |
|
||||
| Астрал. Могильный ритуал 2 | `1291595` (2026) | KP → EN-алиас → TMDB |
|
||||
|
||||
Если Шаг 1 не найдёт их сам — задача НЕ выполнена.
|
||||
|
||||
---
|
||||
|
||||
## ПЛАНА РАБОТ (ждёт апрува)
|
||||
## ПЛАН РАБОТ
|
||||
|
||||
### Шаг 1 — маркер `❓` автоматически в генераторе
|
||||
### Шаг 1 — автопоиск через Кинопоиск в генераторе каталога
|
||||
|
||||
**Файл:** `gen_watchlist_v2.py` (на Маке), функция `enrich` + новый writer в watchlist.
|
||||
**Файл:** `/Users/admin/lampac-tmp/gen_watchlist_v2.py`
|
||||
|
||||
Логика:
|
||||
**1.1. Перенести cross-reference из резолвера.** Новая функция:
|
||||
|
||||
```python
|
||||
def tmdb_via_kp(title, year, tmdb_key, kp_key, cache):
|
||||
"""RU-название -> Кинопоиск -> alternativeName (EN) -> TMDB.
|
||||
|
||||
Возвращает (tmdb_id, media_type, poster_path, kp_id) или None.
|
||||
"""
|
||||
# 1. KP search по русскому названию
|
||||
docs = kp_search(title, kp_key) # /movie/search, limit 5
|
||||
if not docs:
|
||||
return None
|
||||
# 2. перебираем документы, ищем попарное совпадение названия
|
||||
for doc in docs[:3]:
|
||||
alt_en = doc.get("alternativeName") or ""
|
||||
kp_year = doc.get("year")
|
||||
kp_id = doc.get("id")
|
||||
for q in (alt_en, doc.get("name") or ""):
|
||||
if not q or q == title:
|
||||
continue
|
||||
res = tmdb_search(q, kp_year or year, tmdb_key, cache)
|
||||
if not res:
|
||||
continue
|
||||
tid, mtype, pp = res
|
||||
# 3. ПРОВЕРКА ГОДА (±1) — обязательно, иначе ложные совпадения
|
||||
det = tmdb_details(tid, mtype, tmdb_key, details_cache)
|
||||
ty = (det or {}).get("year") or None
|
||||
if kp_year and ty and abs(int(ty) - int(kp_year)) > 1:
|
||||
continue # год не сошёлся
|
||||
return (tid, mtype, pp, kp_id)
|
||||
return None
|
||||
```
|
||||
|
||||
**1.2. Подключить в `enrich()` как шаг 2b** (после TMDB-поиска, перед `miss`):
|
||||
|
||||
```
|
||||
1) Radarr по названию
|
||||
2) TMDB поиск (как сейчас)
|
||||
2b) Кинопоиск cross-reference ← НОВОЕ
|
||||
3) если всё пусто -> miss
|
||||
```
|
||||
|
||||
**1.3. Маркер `❓` для тех, кого не нашли.** После прогона — записать обратно
|
||||
в `movies-watchlist.md`:
|
||||
|
||||
```
|
||||
для каждой позиции:
|
||||
если tmdb_id найден -> как сейчас (постер + карточка)
|
||||
иначе если маркер 🗓️ (upcoming) -> НЕ трогать (календарь = ещё не вышел)
|
||||
иначе -> поставить ❓ в movies-watchlist.md
|
||||
tmdb_id найден -> ничего не менять
|
||||
иначе если маркер 🗓️ -> НЕ ТРОГАТЬ (upcoming = ждём)
|
||||
иначе если маркер ❓ уже есть -> не дублировать
|
||||
иначе -> поставить ❓ перед названием
|
||||
```
|
||||
|
||||
Что писать в `.md`: заменить/добавить `❓` перед названием, как это делает `resolve`.
|
||||
Формат (как уже принято в файле):
|
||||
Формат строки (как в файле уже принято):
|
||||
```
|
||||
- [ ] ❓ Название фильма
|
||||
```
|
||||
|
||||
Питфоллы:
|
||||
- не дублировать `❓`, если он уже стоит;
|
||||
- **не трогать** позиции с `🗓️` — явное исключение по требованию Alex;
|
||||
**Питфоллы 1.3:**
|
||||
- `🗓️` — явное исключение, не трогать (требование Alex);
|
||||
- не трогать `[x]` (просмотренные);
|
||||
- писать атомарно: backup `.md` → запись → проверка размера.
|
||||
- не дублировать существующий `❓`;
|
||||
- backup `.md` перед записью, проверка размера после;
|
||||
- писать атомарно — файл в git, может быть открыт в Obsidian.
|
||||
|
||||
### Шаг 2 — заглушка карточки для позиций без TMDB
|
||||
### Шаг 2 — генератор НЕ отдаёт в каталог позиции без TMDB
|
||||
|
||||
Сейчас клик по такому элементу → `cards/card-movie-<id>.json` не существует →
|
||||
«здесь пусто». Решение: генерировать карточку-заглушку с тем, что есть.
|
||||
**Файл:** `gen_watchlist_v2.py`, сборка JSON.
|
||||
|
||||
Содержимое заглушки: название, год, рейтинг, ссылка на Кинопоиск, пометка
|
||||
«нет данных TMDB».
|
||||
Сейчас позиция без `tmdb_id` уходит в JSON с `href` = поиск Кинопоиска → в плеере
|
||||
клик даёт «здесь пусто» (заглушка, которую Alex запретил).
|
||||
|
||||
⚠️ **Пункт под вопросом** — Alex не подтвердил. Возможно, достаточно маркера `❓`
|
||||
в списке (он сам увидит список и поправит названия). **Уточнить перед реализацией.**
|
||||
**Новое:** позиция без `tmdb_id` **не попадает в JSON каталога вообще**.
|
||||
Она остаётся в `.md` с `❓` — Alex видит её в списке и поправит название.
|
||||
|
||||
**Питфолл:** счётчик `count` в JSON и `stats` в выводе должны отражать реальное число,
|
||||
а не исходное. Проверять: `films` уменьшится на число нерезолвленных.
|
||||
|
||||
### Шаг 3 — `discover` не добавляет то, чего нет в TMDB
|
||||
|
||||
**Файл:** `discover.py` в `~/Developer/watchlist-sync/`.
|
||||
**Файл:** `/Users/admin/Developer/watchlist-sync/watchlist_sync/discover.py`
|
||||
|
||||
Добавить проверку в `_has_enough_votes()`-цепочку: после KP-фильтра делать
|
||||
TMDB-поиск и **пропускать** фильм, если TMDB его не знает. Тогда мусор не попадает
|
||||
в список вообще.
|
||||
Добавить проверку после KP-фильтра: TMDB-поиск (с cross-reference через
|
||||
`alternativeName`) → если не найдено **и фильм вышел** — не добавлять в список.
|
||||
|
||||
⚠️ **Риск:** фильмы, которых ещё нет в TMDB, но которые реально существуют, будут
|
||||
потеряны для списка. Для upcoming (`🗓️`) можно оставить — они и так не резолвятся.
|
||||
Для **upcoming** (`🗓️`) — оставить как есть (они и так не резолвятся, календарь
|
||||
информативен).
|
||||
|
||||
**Уточнить:** применять ли эту проверку к upcoming тоже.
|
||||
**Питфолл:** не ломать существующую дедупликацию `_all_watchlist_titles()`.
|
||||
|
||||
---
|
||||
|
||||
## Порядок работ
|
||||
|
||||
| # | Шаг | Файл | Апрув |
|
||||
| # | Шаг | Файл | Проверка |
|
||||
|---|---|---|---|
|
||||
| 1 | Маркер `❓` в генераторе | `gen_watchlist_v2.py` | ⏳ |
|
||||
| 2 | Заглушка карточки (?) | `gen_watchlist_v2.py` | ⏳ требует уточнения |
|
||||
| 3 | Фильтр TMDB в `discover` | `discover.py` | ⏳ |
|
||||
| 1.1 | `tmdb_via_kp()` — cross-reference | `gen_watchlist_v2.py` | dry-run на 4 проблемных |
|
||||
| 1.2 | подключить в `enrich` | `gen_watchlist_v2.py` | постеры 351 → ? |
|
||||
| 1.3 | маркер `❓` в `.md` | `gen_watchlist_v2.py` | `🗓️` не тронуты |
|
||||
| 2 | не отдавать в JSON без TMDB | `gen_watchlist_v2.py` | «здесь пусто» исчезло |
|
||||
| 3 | TMDB-фильтр в `discover` | `discover.py` | мусор не добавляется |
|
||||
|
||||
**Приёмка:** открыть список — напротив фильмов, которых нет в TMDB и которые НЕ
|
||||
upcoming, стоит `❓`. Фильмы с `🗓️` не тронуты.
|
||||
**Приёмка (формулировка Alex):**
|
||||
1. Хамнет, WeWork, Астрал 2 — постеры есть, **найдены автопоиском**, не словарём.
|
||||
2. В плеере нет ни одной позиции, дающей «здесь пусто».
|
||||
3. Всё остальное из watchlist — в каталоге.
|
||||
4. Позиции, которых нет в TMDB и которые НЕ upcoming — с `❓` в списке.
|
||||
5. Позиции с `🗓️` — не тронуты.
|
||||
|
||||
**Приёмка = видно в UI плеера**, не «API отвечает».
|
||||
|
||||
---
|
||||
|
||||
## Открытые вопросы (нужен ответ Alex)
|
||||
## Питфоллы, подтверждённые ранее (не повторять)
|
||||
|
||||
1. **Заглушка карточки** — нужна вообще, или достаточно маркера `❓` в списке?
|
||||
2. **`discover`** — фильтровать по TMDB только для вышедших, или для upcoming тоже?
|
||||
3. **Откат 3 постеров** — принять откат (Хамнет/WeWork/Астрал без постера, пока нет
|
||||
автоматики), или сделать автопоиск по альтернативным названиям через Кинопоиск
|
||||
(`alternativeName`), как уже делается в `watchlist-sync` резолвере?
|
||||
- **TMDB `/search/multi` сортирует по популярности**, не по релевантности → короткие
|
||||
названия матчат нерелевантные фильмы. Отсюда обязательная проверка года (±1).
|
||||
- **`ModInit.cs:285` возвращает любое `JValue` СТРОКОЙ** → в `card_args` нужен
|
||||
`format: "long.TryParse(value, out long v) ? v : 0"`.
|
||||
- **`tmdb_id` ДОЛЖЕН быть числом** — `plugin.js` делает `if(json.tmdb_id)`.
|
||||
- **Кириллица в ключе каталога** → `ModInit.goInit` чистит `[^a-z0-9\-]` → пустая
|
||||
строка → 400. Ключ обязан быть `plugin: watchlist`.
|
||||
- **Кэш `catalog:card:*` в ПАМЯТИ** переживает `docker restart lampac` — проверять
|
||||
на незакэшированном `uri`.
|
||||
- **Кэш TMDB генератора:** `~/.lampac_tmdb_cache.json` — `None` в кэше означает
|
||||
«не нашли»; после добавления cross-reference старые `None` **надо инвалидировать**,
|
||||
иначе новые запросы не пойдут. ⚠️ **Критично для Шага 1** — иначе автопоиск
|
||||
не сработает на уже закэшированных промахах.
|
||||
- **macOS `tar`** кладёт AppleDouble `._*` → `tar --no-mac-metadata`.
|
||||
- **Права `root:root 600`** → dotnet `Permission denied` → статика молча 404.
|
||||
Нужно `1000:1000` + `644`.
|
||||
|
||||
---
|
||||
|
||||
## Артефакты
|
||||
|
||||
| Файл | Роль |
|
||||
|---|---|
|
||||
| `/Users/admin/lampac-tmp/gen_watchlist_v2.py` | генератор каталога |
|
||||
| `/Users/admin/lampac-tmp/deploy_lampac.py` | заливка на Кра́кен |
|
||||
| `/Users/admin/lampac-tmp/check_fixed2.py` | проверка постера по названию |
|
||||
| `~/Developer/watchlist-sync/watchlist_sync/resolver.py` | ЭТАЛОН cross-reference |
|
||||
| `~/Developer/watchlist-sync/watchlist_sync/discover.py` | фильтр новинок |
|
||||
| `~/.lampac_tmdb_cache.json` | кэш поиска TMDB |
|
||||
| `~/.lampac_tmdb_details.json` | кэш деталей карточки |
|
||||
|
||||
**Ключи:** `TMDB_KEY`, `KP_KEY` — из `~/Developer/watchlist-sync/.env` (на Маке).
|
||||
API: `https://api.poiskkino.dev/v1.4/movie/search` (с `follow_redirects=True`).
|
||||
Reference in new issue
Block a user