"""Wertykały: treść, która żyje w osobnej sekcji apki, nie w głównym feedzie. Wertykał to zbiór originów, których treść **nie miesza się** z zachodnim katalogiem: osobny namespace tytułów, osobne słownictwo tagów, brak dedupu między wertykałem a resztą. Dlatego nie chcemy ich w jednym strumieniu, tak samo jak nie chcemy mieszać JAV z zachodnimi scenami. **Dlaczego brama po originie jest pewna, a nie heurystyczna.** Scena z wertykału ma WYŁĄCZNIE originy tego wertykału, bo nie ma się z czym zdeduplikować. Zmierzone 2026-08-13: 9507 scen animowanych, **zero** z originem spoza listy. To znaczy, że `EXISTS(origin IN ...)` dzieli katalog dokładnie, bez fałszywych trafień w obie strony. **Dlaczego nie osobna baza.** Rozważane i odrzucone 2026-08-13: skoro rozdzielenie po originie jest dokładne, osobna baza kosztowałaby rozjazd migracji, backupów i wspólnych tabel performerów/tagów/dedupu, nie dając nic ponad to, co daje ta lista. **Tagi to samodzielny powód rozdzielenia.** Z 3116 tagów występujących przy scenach animowanych tylko 452 pojawiają się też poza animacją, czyli **85% to słownictwo wyłącznie animacyjne** (nazwy postaci, serie, gatunki rysunkowe). Bez rozdzielenia lista tagów w głównym feedzie ciągnie 2664 pozycje, których nie da się tam użyć. """ from __future__ import annotations #: JAV: kody typu BKD-368, tytuły po japońsku. Osobna zakładka od 2026-07. JAV_ORIGINS = ( "tube:javflix", "tube:javguru", "tube:vjav", "tube:supjav", ) #: Hentai i 3D. Dla animacji nie istnieje kanon (TPDB/StashDB nie mają rysowanych #: postaci), więc te sceny nie mają obsady i nigdy nie zmatchują się z zachodnimi. ANIMATION_ORIGINS = ( "tube:hentaimama", "tube:xanimeporn", "tube:hentaigasm", "tube:hentaiplay", "tube:rule34video", "tube:naughtymachinima", ) #: Nazwa wertykału → originy. Klucze są częścią kontraktu API (`?vertical=`). VERTICALS: dict[str, tuple[str, ...]] = { "jav": JAV_ORIGINS, "animation": ANIMATION_ORIGINS, } #: Wszystko, co ma NIE wchodzić do głównego feedu. ALL_VERTICAL_ORIGINS: tuple[str, ...] = JAV_ORIGINS + ANIMATION_ORIGINS def origins_for(vertical: str | None) -> tuple[str, ...]: """Originy wertykału. `None` → wszystkie originy wertykałowe (do wykluczania).""" if vertical is None: return ALL_VERTICAL_ORIGINS return VERTICALS.get(vertical, ()) #: Odwrotna mapa origin → wertykał. Budowana raz, bo `vertical_for_origins` leci #: dla każdej sceny w odpowiedzi. _ORIGIN_TO_VERTICAL: dict[str, str] = { origin: name for name, origins in VERTICALS.items() for origin in origins } def vertical_for_origins(origins) -> str | None: """Wertykał sceny na podstawie originów jej źródeł, albo None dla głównego katalogu. Scena wertykałowa ma WYŁĄCZNIE originy swojego wertykału (patrz docstring modułu), więc wystarczy pierwszy trafiony. Kolejność wyniku jest deterministyczna, bo pytamy o konkretną scenę, a nie o zbiór. """ for origin in origins: vertical = _ORIGIN_TO_VERTICAL.get(origin) if vertical is not None: return vertical return None