Files
obsidian-vault/personal/plans/watchlist-tmdb-missing-marker.md
T

15 KiB
Raw Blame History

Watchlist — автопоиск TMDB + маркер «нет в базе» (без ручных правок)

Дата: 2026-09-22 Статус: ПЛАН УТВЕРЖДЁН Alex — в работе Связано: personal/plans/lampac-watchlist-plugin, family/projects/watchlist-automation, family/projects/watchlist-sync


Требование Alex (дословно)

«Нет блядь, нахуя мне в плеере заглушки?! Нормальный автопоиск! Никаких БЛЯДЬ ручных правок! То чего нет для просмотра - того нет БЛЯДЬ в списке! И ставится вопрос. Все остальное из watchlist должно оказаться в списке!!»

«В смысле блядь прописал соответствия вручную?! Че за нахуй?! Убирай нахуй! Это блядь скрипт должен делать!»

«Скрипт должен поставить вопрос в списке напротив такого (кроме тех что ещё не вышли, там календарь стоит)»


Инварианты (нарушение = провал задачи)

  1. НИКАКИХ ручных словарей-соответствий в коде. Убрано 2026-09-22 (MANUAL_TMDB).
  2. НИКАКИХ заглушек в плеере. Позиция без TMDB не рисуется в каталоге вообще.
  3. Всё, что реально существует и смотрибельно, — ОБЯЗАНО попасть в каталог. Автопоиск обязан дотягивать то, что не находится по RU-названию.
  4. Того, чего нет для просмотра, нет в списке. Вместо позиции ставится ❓.
  5. Исключение — upcoming. У вышедших позже маркер 🗓️ (календарь) не трогать.

Разграничение по маркерам — ключевое

Маркер Значение Действие
🗓️ фильм ещё не вышел НЕ трогать. Календарь = ждём. В каталоге не нужен
❓ есть/должен быть, но не нашли в базах оставить, ждёт правки названия пользователем
без маркера вышел, должен смотреть обязан попасть в каталог

Сейчас 🗓️ ставится в discover для upcoming. Позиции с 🗓️ не должны получать ❓.


Что установлено фактами (2026-09-22)

Проблема 1: генератор не умеет дотягивать не-RU названия

gen_watchlist_v2.py::tmdb_search() ищет только title как есть, по ru-RU и en-US, типы movie/tv. Если в списке русское название, которого TMDB не знает (премьеры 2025–2026, локальные алиасы) — возвращает None → нет tmdb_id → нет постера → нет карточки.

Готовая механика уже есть в watchlist_sync/resolver.py (строки 186–219) — cross-reference через Кинопоиск:

# 1. TMDB по названию как есть
candidates = _search_tmdb(search_title, tmdb_key)

# 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
films 360 310 314
new 33 31 31
cartoons 3 3 3
anime 7 7 7
итого 403 351 355

Без 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 — автопоиск через Кинопоиск в генераторе каталога

Файл: /Users/admin/lampac-tmp/gen_watchlist_v2.py

1.1. Перенести cross-reference из резолвера. Новая функция:

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 = ждём)
    иначе если маркер ❓ уже есть  -> не дублировать
    иначе                          -> поставить ❓ перед названием

Формат строки (как в файле уже принято):

  - [ ] ❓ Название фильма

Питфоллы 1.3:

  • 🗓️ — явное исключение, не трогать (требование Alex);
  • не трогать [x] (просмотренные);
  • не дублировать существующий ❓;
  • backup .md перед записью, проверка размера после;
  • писать атомарно — файл в git, может быть открыт в Obsidian.

Шаг 2 — генератор НЕ отдаёт в каталог позиции без TMDB

Файл: gen_watchlist_v2.py, сборка JSON.

Сейчас позиция без tmdb_id уходит в JSON с href = поиск Кинопоиска → в плеере клик даёт «здесь пусто» (заглушка, которую Alex запретил).

Новое: позиция без tmdb_id не попадает в JSON каталога вообще. Она остаётся в .md с ❓ — Alex видит её в списке и поправит название.

Питфолл: счётчик count в JSON и stats в выводе должны отражать реальное число, а не исходное. Проверять: films уменьшится на число нерезолвленных.

Шаг 3 — discover не добавляет то, чего нет в TMDB

Файл: /Users/admin/Developer/watchlist-sync/watchlist_sync/discover.py

Добавить проверку после KP-фильтра: TMDB-поиск (с cross-reference через alternativeName) → если не найдено и фильм вышел — не добавлять в список.

Для upcoming (🗓️) — оставить как есть (они и так не резолвятся, календарь информативен).

Питфолл: не ломать существующую дедупликацию _all_watchlist_titles().


Порядок работ

# Шаг Файл Проверка
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 мусор не добавляется

Приёмка (формулировка Alex):

  1. Хамнет, WeWork, Астрал 2 — постеры есть, найдены автопоиском, не словарём.
  2. В плеере нет ни одной позиции, дающей «здесь пусто».
  3. Всё остальное из watchlist — в каталоге.
  4. Позиции, которых нет в TMDB и которые НЕ upcoming — с ❓ в списке.
  5. Позиции с 🗓️ — не тронуты.

Приёмка = видно в UI плеера, не «API отвечает».


Питфоллы, подтверждённые ранее (не повторять)

  • 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).