From 8c8f22d388bf2e3ec7d8170848750623582e09ef Mon Sep 17 00:00:00 2001 From: goon-foss Date: Fri, 14 Aug 2026 12:28:47 +0200 Subject: [PATCH] feat(tags): zdejmij znaczniki jakosci + przytnij ogon tagow rule34video Znaczniki jakosci (hd, 4k, 720p, HD Videos, wzorzec ^\d{3,4}p$) trafiaja na stoplist i znikaja z katalogu: 323 379 przypisan, 16 tagow. Powod: apka ma osobny filtr jakosci liczony ze ZRODEL ODTWARZANIA, ktory jest wiarygodny, a tag jest deklaracja tuba i bywa falszywy. Dwa rownolegle sygnaly o tym samym, z ktorych jeden klamie, sa gorsze niz jeden. Ogon rule34video: 356 scen wnosilo 1112 roznych tagow do WSPOLNEJ listy uzywanej przez caly katalog, z czego 602 wystepowaly dokladnie raz (nazwy postaci i serii, tagowanie w stylu booru). Nowy job co 24h zostawia tagi z >=3 scen i najwyzej 15 na scene, uporzadkowane po czestosci w obrebie serwisu. Wazny szczegol reguly, wychwycony dopiero w symulacji: ochrona tagow wspolnych dotyczy TYLKO progu, nie limitu. Pierwsza wersja wylaczala tagi wspolne rowniez z limitu i przez to prawie nie dzialala, rekord spadal 161 -> 147 zamiast do 15, bo najbardziej obsypana scena skladala sie glownie z tagow wspolnych. Wynik na produkcji: srednia na scenie rule34video 13,5 -> 9,1, rekord 161 -> 15, zadna scena nie zostala bez tagow. Usuniete 1554 przypisania i 2475 osieroconych tagow. VACUUM ANALYZE po obu operacjach. Co-Authored-By: Claude Opus 5 --- app/config.py | 5 ++ app/normalize/tag_stoplist.py | 37 ++++++++--- app/scheduler/jobs.py | 26 ++++++++ app/scheduler/tag_tail_trim.py | 112 +++++++++++++++++++++++++++++++++ app/scheduler/worker.py | 2 + 5 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 app/scheduler/tag_tail_trim.py diff --git a/app/config.py b/app/config.py index 221f0b0..6d89214 100644 --- a/app/config.py +++ b/app/config.py @@ -150,6 +150,11 @@ class Settings(BaseSettings): sched_junk_performers_hours: int = Field( default=24, validation_alias="GOON_SCHED_JUNK_PERFORMERS_HOURS" ) + # Przycinanie ogona tagow rule34video (tagowanie booru: nazwy postaci/serii). + # 356 scen wnosilo 1112 tagow do WSPOLNEJ listy, 602 z nich uzyte raz. 24h. 0 = off. + sched_tag_tail_trim_hours: int = Field( + default=24, validation_alias="GOON_SCHED_TAG_TAIL_TRIM_HOURS" + ) # Ingest freshness watchdog, alert do Sentry gdy aktywny tube (origin # tube:) przestał dawać nowe sceny > próg. Łapie zamrożenie # pojedynczego origin, którego globalny monitor (jeden Source "tube-scraper") nie diff --git a/app/normalize/tag_stoplist.py b/app/normalize/tag_stoplist.py index 89167ff..e891c09 100644 --- a/app/normalize/tag_stoplist.py +++ b/app/normalize/tag_stoplist.py @@ -15,15 +15,18 @@ Kryterium wpisu: tag nie zawezaja wyszukiwania, bo pasuje do niemal wszystkiego w katalogu ("porn", "sex", "hot"), albo opisuje sposob dystrybucji, a nie tresc ("premium", "exclusive", "leaked", "download"). -Swiadomie NIE na liscie: - - `onlyfans` (71 972 przypisan) - to realny typ tresci, ludzie tego szukaja. - - `4k`, `hd`, `720p`, `1080p` (ok. 110 tys.) - niosa jakas informacje, choc - niepewna. Apka ma osobny, wiarygodny filtr jakosci liczony ze zrodel odtwarzania, - wiec te tagi sa zbedne, ale usuniecie ich to decyzja produktowa, nie sprzatanie - smieci. Do przegadania osobno. +Znaczniki jakosci (`hd`, `4k`, `720p`, `HD Videos`) tez sa na liscie, decyzja z 2026-08-14. +Powod: apka ma osobny filtr jakosci liczony ze ZRODEL ODTWARZANIA, ktory jest wiarygodny, +a tag jest deklaracja tuba i bywa falszywy. Dwa rownolegle sygnaly o tym samym, z ktorych +jeden klamie, sa gorsze niz jeden. Samo `HD Videos` mialo 205 488 przypisan. + +Swiadomie NIE na liscie: `onlyfans` (71 972 przypisan). To realny typ tresci, nie chwyt +marketingowy, ludzie tego szukaja. """ from __future__ import annotations +import re + #: Porownanie po slugu (lowercase, dywizy), bo ta sama rzecz przychodzi jako #: "Full HD", "full-hd" i "FullHD". JUNK_TAG_SLUGS: frozenset[str] = frozenset( @@ -65,10 +68,30 @@ JUNK_TAG_SLUGS: frozenset[str] = frozenset( "streaming", "online", "full-video", + # znaczniki jakosci, patrz docstring: dubluja filtr liczony ze zrodel i klamia + "hd", + "hd-porn", + "hd-videos", + "sd", + "fhd", + "uhd", + "full-hd", + "fullhd", + "ultra-hd", + "high-definition", + "hi-res", + "4k", + "8k", + "4k-porn", } ) +#: Rozdzielczosc jako tag: 240p, 480p, 720p, 1080p, 2160p... Wzorzec zamiast wyliczanki, +#: bo tuby wymyslaja kolejne warianty przy kazdym re-scrape. +_RESOLUTION_RE = re.compile(r"^\d{3,4}p$") + def is_junk_tag(slug: str) -> bool: """True dla tagu, ktorego nie chcemy trzymac w katalogu.""" - return slug.strip().lower() in JUNK_TAG_SLUGS + s = slug.strip().lower() + return s in JUNK_TAG_SLUGS or bool(_RESOLUTION_RE.match(s)) diff --git a/app/scheduler/jobs.py b/app/scheduler/jobs.py index ff8bf8f..83c4770 100644 --- a/app/scheduler/jobs.py +++ b/app/scheduler/jobs.py @@ -308,6 +308,21 @@ def _job_phash_blacklist() -> None: log.exception("[scheduler] phash blacklist failed") +def _job_tag_tail_trim() -> None: + """Przycina ogon tagow rule34video (booru: nazwy postaci, serii, autorow). + 356 scen wnosilo 1112 roznych tagow do wspolnej listy, 602 uzyte dokladnie raz. + Zostawia tagi z >=3 scen i najwyzej 15 na scene; tagi uzywane takze poza + rule34video sa chronione, bo naleza do wspolnego slownika.""" + log.info("[scheduler] tag tail trim starting") + try: + from app.scheduler.tag_tail_trim import run + + stats = run() + log.info("[scheduler] tag tail trim done: %s", stats) + except Exception: + log.exception("[scheduler] tag tail trim failed") + + def _job_junk_performers() -> None: """Kasuje kategorie i studia podszywające się pod performerów („Creampie", „Natural tits", „Brazzers"). Audyt 2026-07-27: 277 rekordów, 33 784 przypisania. @@ -550,6 +565,17 @@ def build_scheduler(cfg: dict[str, Any]) -> BlockingScheduler: cfg["phash_backfill_minutes"], _batch, ) + if cfg.get("tag_tail_trim_hours"): + sched.add_job( + _job_tag_tail_trim, + IntervalTrigger(hours=cfg["tag_tail_trim_hours"], start_date=INTERVAL_ANCHOR), + id="tag_tail_trim", + replace_existing=True, + max_instances=1, + coalesce=True, + ) + log.info("scheduler: tag tail trim every %dh", cfg["tag_tail_trim_hours"]) + if cfg.get("junk_performers_hours"): sched.add_job( _job_junk_performers, diff --git a/app/scheduler/tag_tail_trim.py b/app/scheduler/tag_tail_trim.py new file mode 100644 index 0000000..b2874b2 --- /dev/null +++ b/app/scheduler/tag_tail_trim.py @@ -0,0 +1,112 @@ +"""Przycinanie ogona tagow z rule34video (tagowanie w stylu booru). + +Skad problem: rule34video taguje jak booru, czyli nazwami postaci, serii i autorow. +Zmierzone 2026-08-14: **356 scen ma 1112 roznych tagow**, srednio 13,5 na scene, rekord +161 (zgloszenie uzytkownika o scenie "TEMPTATION - Episode 14"). Z tych 1112 tagow +**602 wystepuja dokladnie raz**, a 763 najwyzej dwa razy. To sa nazwy wlasne. + +Same w sobie nie sa bledne, ludzie ich szukaja, ale 356 scen wrzuca ponad tysiac pozycji +do WSPOLNEJ listy tagow, ktorej uzywa caly katalog (1,9 mln scen). Stad przyciecie, +a nie skasowanie zrodla. + +Dwie reguly, obie potrzebne (zmierzone osobno): + 1. **Prog czestosci**: tag musi wystepowac na >=3 scenach rule34video. Sam prog zbija + srednia 13,5 -> 10,9 i rekord 161 -> 104, wiec NIE wystarcza. + 2. **Limit na scene**: najwyzej 15 tagow, zostawiamy najczestsze w obrebie serwisu. + Dopiero to domyka rekord. + +Ochrona tagow wspolnych dotyczy TYLKO progu, nie limitu, i to jest istotne. Tag uzywany +takze poza rule34video (np. "Bondage") nalezy do wspolnego slownika, wiec nie kasujemy go +z powodu niskiej czestosci w obrebie serwisu. Ale limit na scene obejmuje go normalnie: +scena nie powinna miec 161 tagow niezaleznie od tego, czyje one sa. + +Pierwsza wersja tej reguly wylaczala tagi wspolne takze z limitu i przez to prawie nie +dzialala: rekord spadal 161 -> 147 zamiast do 15, bo najbardziej obsypana scena skladala +sie glownie z tagow wspolnych. + +Uwaga o zbieznosci: limit na scene obniza liczniki tagow, wiec kolejny przebieg moze +zejsc nieco nizej, zanim sie ustabilizuje. Liczniki bierzemy ze SNAPSHOTU sprzed zmian +w danym przebiegu, zeby jeden przebieg nie kaskadowal sam na sobie. +""" +from __future__ import annotations + +import logging + +from sqlalchemy import text + +from app.db import session_scope + +log = logging.getLogger(__name__) + +ORIGIN = "tube:rule34video" +MIN_SCENES_PER_TAG = 3 +MAX_TAGS_PER_SCENE = 15 + +_SQL = text( + """ +WITH r34 AS ( + SELECT DISTINCT p.scene_id AS id + FROM playback_sources p + WHERE p.origin = :origin +), +-- snapshot licznikow SPRZED zmian w tym przebiegu +freq AS ( + SELECT st.tag_id, count(*) AS c + FROM scene_tags st JOIN r34 ON r34.id = st.scene_id + GROUP BY st.tag_id +), +-- tag uzywany takze poza rule34video nalezy do wspolnego slownika, nie ruszamy +shared AS ( + SELECT DISTINCT st.tag_id + FROM scene_tags st + WHERE st.tag_id IN (SELECT tag_id FROM freq) + AND st.scene_id NOT IN (SELECT id FROM r34) +), +ranked AS ( + SELECT st.scene_id, st.tag_id, f.c AS cnt, + (st.tag_id IN (SELECT tag_id FROM shared)) AS is_shared, + row_number() OVER (PARTITION BY st.scene_id ORDER BY f.c DESC, st.tag_id) AS rn + FROM scene_tags st + JOIN r34 ON r34.id = st.scene_id + JOIN freq f ON f.tag_id = st.tag_id +), +doomed AS ( + SELECT scene_id, tag_id FROM ranked + -- limit obowiazuje wszystkie tagi; prog tylko te wylacznie rule34video + WHERE rn > :max_tags OR (cnt < :min_scenes AND NOT is_shared) +) +DELETE FROM scene_tags st +USING doomed d +WHERE st.scene_id = d.scene_id AND st.tag_id = d.tag_id +""" +) + +# Tagi, ktore po przycieciu nie wisza juz nigdzie. Zostawione zasmiecalyby liste tagow +# mimo zerowej liczby scen. +_SQL_ORPHANS = text( + """ +DELETE FROM tags t +WHERE NOT EXISTS (SELECT 1 FROM scene_tags st WHERE st.tag_id = t.id) + AND NOT EXISTS (SELECT 1 FROM movie_tags mt WHERE mt.tag_id = t.id) + AND NOT EXISTS (SELECT 1 FROM blacklisted_tags bt WHERE bt.tag_id = t.id) +""" +) + + +def run() -> dict[str, int]: + """Przytnij ogon i skasuj osierocone tagi. Zwraca licznik zmian.""" + with session_scope() as session: + removed = session.execute( + _SQL, + { + "origin": ORIGIN, + "min_scenes": MIN_SCENES_PER_TAG, + "max_tags": MAX_TAGS_PER_SCENE, + }, + ).rowcount + orphans = session.execute(_SQL_ORPHANS).rowcount + session.commit() + log.info( + "tag_tail_trim: usunieto %s przypisan, %s osieroconych tagow", removed, orphans + ) + return {"removed": removed or 0, "orphans": orphans or 0} diff --git a/app/scheduler/worker.py b/app/scheduler/worker.py index ebd9c7e..c1c189c 100644 --- a/app/scheduler/worker.py +++ b/app/scheduler/worker.py @@ -224,6 +224,8 @@ def run_forever() -> int: # Śmieciowi performerzy, kategorie/studia udające osoby (audyt 2026-07-27). # Kryterium wąskie celowo: szerokie kasuje realnych ludzi bez refa w TPDB. "junk_performers_hours": getattr(settings, "sched_junk_performers_hours", 24) or None, + # Ogon tagow rule34video (nazwy wlasne w stylu booru zalewajace liste tagow). + "tag_tail_trim_hours": getattr(settings, "sched_tag_tail_trim_hours", 24) or None, # Taxonomy scene_count refresh, denormalizacja liczników (perf fix 0019). "taxonomy_counts_hours": getattr(settings, "sched_taxonomy_counts_hours", 3) or None, # Ingest freshness watchdog, per-origin alert do Sentry (report 14f3a655).