goon/app/verticals.py
goon-foss a1f604a7a4 fix(verticals): przenos wertykal przy przejsciu do tagu/studia/performera
Zgloszenia 195ae42e i e9de414c: klikniecie studia w anime nic nie robilo, a tag
kliniety w anime pokazywal sceny spoza anime.

Jedna przyczyna: TagScenes/StudioScenes/PerformerScenes wolaly listScenes BEZ
parametru vertical, a backend bez niego wyklucza wszystkie wertykaly. Studio
animacyjne mialo wiec zero scen (pusty ekran = wyglada na martwy klik), a tag
zwracal wylacznie sceny glownego katalogu.

SceneOut dostaje pole "vertical" liczone z originow zrodel, czyli jedno zrodlo
prawdy w app/verticals.py zamiast dublowania listy originow w apce. W trybie
light dociagamy origin z tego samego wiersza co miniaturka, wiec zero dodatkowych
zapytan. SceneDetail przekazuje wartosc dalej w paramach nawigacji.

Dotyczy tak samo sekcji JAV, ktora miala ten sam blad.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 11:42:47 +02:00

78 lines
3.1 KiB
Python

"""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