goon/scripts/prune_category_performers.py
goon-foss d9177febf7 fix(performers): zdejmij przypisania kategorii udajacych ludzi
"Natural Tits", "Big Black", "Anal Milf", "Cum Swallow", "Fat Ass" i 94
inne siedzialy w tabeli performers z tagow tubow wciaganych jako obsada.
Ugryzly 2026-08-14 przy rozplataniu zlepkow: regula uznala "Big Black" za
osobe wymieniona w tytule i dala mu 124 sceny.

TRZY OCZYWISTE HEURYSTYKI SPRAWDZONE I ODRZUCONE, kazda z kontrprzykladem:

  1. "nazwa jest tez tagiem"  -> prawdziwe dla Lauren Phillips, Julii Ann,
     Nikki Benz (tuby robia tagi z nazwisk), 7625 z 74150 nazw
  2. "nie ma kanonu"          -> TPDB SAM MA "Big Black" i "Lesbian Love"
     jako performerow, a realna "Mena Mason" refow nie ma
  3. "tag >> performer"       -> dziala dla "Natural Tits" (2147x) i "Rough
     Sex" (1552x), ale "Big Black" ma 0,2x, "Lesbian Love" 0,0x

Kryterium jest wiec STRUKTURALNE i nie zalezy od zadnej z tych baz: cala
nazwa zlozona wylacznie ze slow opisujacych tresc. Kandydatow 101, czyli
na tyle malo, ze lista zostala PRZEJRZANA RECZNIE.

Przeglad wylapal falszywki: "Dick Nasty" ma refy z szesciu zrodel, w tym
StashDB, i jest realnym meskim performerem (obsada "Sugar Daddy 17" obok
Dave'a Cummingsa). "King Cock" wykluczony z ostroznosci. Oba w
_JEDNAK_LUDZIE, z uzasadnieniem w kodzie.

Wynik: 11 793 przypisania zdjete od 99 kategorii, do archiwum. Same rekordy
performerow ZOSTAJA: scene_count spadl do 0, wiec i tak wypadaja z list
z only_with_content, a skasowanie rekordu zerwaloby refy kanoniczne przy
czyms, co TPDB u siebie trzyma jako performera.

Kontrola: Big Black/Natural Tits/Anal Milf/Fat Ass -> 0 scen, Lauren
Phillips 2146 i Dick Nasty 69 nietkniete.
2026-08-14 11:27:00 +02:00

147 lines
6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Zdejmuje przypisania „performerów", którzy są nazwami KATEGORII, nie ludźmi.
W tabeli `performers` siedzą pozycje w rodzaju „Natural Tits", „Big Black", „Anal Milf",
„Cum Swallow" czy „Fat Ass". Biorą się z tagów tubów wciąganych jako obsada. Ugryzły
2026-08-14 przy rozplataniu zlepków: reguła uznała „Big Black" za osobę wymienioną
w tytule i dała mu 124 sceny.
**Trzy oczywiste heurystyki sprawdzone i ODRZUCONE**, każda z konkretnym kontrprzykładem:
1. „nazwa jest też tagiem" — prawdziwe dla Lauren Phillips, Julii Ann i Nikki Benz,
bo tuby robią tagi z nazwisk (7625 z 74150 nazw).
2. „nie ma refów kanonicznych" — **TPDB SAM MA** „Big Black" i „Lesbian Love" jako
performerów, a realna „Mena Mason" refów nie ma.
3. „tag ma dużo więcej scen niż performer" — działa dla „Natural Tits" (2147×) i
„Rough Sex" (1552×), ale „Big Black" ma 0,2×, a „Lesbian Love" 0,0×.
Dlatego kryterium jest STRUKTURALNE i nie zależy od żadnej z tych baz: **cała nazwa
składa się wyłącznie ze słów opisujących treść**. „Big Black" to big + black, „Natural
Tits" to natural + tits, a „Lauren Phillips" nie ma ani jednego takiego członu.
Kandydatów wychodzi 101, łącznie ~12 tys. przypisań, i lista jest na tyle krótka, że
została **przejrzana ręcznie**. Stąd `_JEDNAK_LUDZIE`: słownik trafia też w prawdziwe
osoby o „opisowych" pseudonimach.
Uruchomienie (kontener worker):
python -m scripts.prune_category_performers # dry-run
python -m scripts.prune_category_performers --yes # wykonaj
"""
from __future__ import annotations
import argparse
import logging
import re
from sqlalchemy import text
from app.db import session_scope
log = logging.getLogger(__name__)
#: Słowa opisujące TREŚĆ, nie osobę. Świadomie bez imion i nazwisk.
_SLOWA_KATEGORII = set(
"""
anal ass tits boobs cock dick pussy cum cumshot creampie blowjob handjob footjob
milf teen mature granny mom stepmom stepsis sister brother wife girlfriend
black white asian latina ebony indian arab russian czech japanese korean thai
big small huge tiny massive giant thick fat skinny petite busty curvy
hardcore softcore rough gentle deep double triple first best hot sexy horny nasty
lesbian gay straight bi trans shemale ladyboy
fuck fucking fucked sex porn xxx video clip scene movie
blonde brunette redhead ginger
threesome foursome gangbang orgy bukkake facial swallow
squirt squirting shower golden piss pee
bdsm bondage domination submission fetish kink
natural fake real amateur pro professional
queen king lover girl boy babe slut whore bitch
pov casting audition massage yoga gym office nurse teacher student
young old
""".split()
)
#: Wyjątki znalezione przy ręcznym przeglądzie 101 kandydatów. To PRAWDZIWE osoby,
#: które mają pecha do pseudonimu:
#: „Dick Nasty" — refy z SZEŚCIU źródeł, w tym StashDB; realny męski performer,
#: widziany w obsadzie „Sugar Daddy 17" obok Dave'a Cummingsa.
#: „King Cock" — cztery źródła i niejednoznaczne sceny; wykluczony z ostrożności,
#: bo koszt błędu (skasowanie realnej osoby) jest wyższy niż zysk.
_JEDNAK_LUDZIE = frozenset({"dick nasty", "king cock"})
def _norm(s: str | None) -> str:
return re.sub(r"[^a-z0-9]+", " ", (s or "").lower()).strip()
def _kandydaci(session) -> list[tuple]:
out = []
for pid, nm in session.execute(
text("SELECT id, canonical_name FROM performers")
).all():
n = _norm(nm)
toks = n.split()
if len(toks) < 2 or n in _JEDNAK_LUDZIE:
continue
if all(t in _SLOWA_KATEGORII for t in toks):
out.append((pid, nm))
return out
def run(*, apply: bool) -> dict[str, int]:
with session_scope() as session:
kand = _kandydaci(session)
ids = [k[0] for k in kand]
if not ids:
print("brak kandydatow")
return {"performers": 0, "attributions": 0}
n = session.execute(
text(
"SELECT count(*) FROM scene_performers WHERE performer_id = ANY(:ids)"
),
{"ids": ids},
).scalar_one()
print(f"kategorii: {len(kand)} | ich przypisan: {n}")
if not apply:
for _pid, nm in sorted(kand, key=lambda x: x[1])[:12]:
print(f" {nm}")
print(" ... (dry-run)")
return {"performers": len(kand), "attributions": int(n)}
# Archiwum przed kasowaniem, ta sama tabela co reszta sprzatania.
session.execute(
text(
"""
INSERT INTO scene_performers_pruned
(scene_id, performer_id, source_id, performer_name, scene_title)
SELECT sp.scene_id, sp.performer_id, sp.source_id, p.canonical_name, s.title
FROM scene_performers sp
JOIN performers p ON p.id = sp.performer_id
JOIN scenes s ON s.id = sp.scene_id
WHERE sp.performer_id = ANY(:ids)
ON CONFLICT DO NOTHING
"""
),
{"ids": ids},
)
deleted = session.execute(
text("DELETE FROM scene_performers WHERE performer_id = ANY(:ids)"),
{"ids": ids},
).rowcount
# Same rekordy performerow ZOSTAJA (scene_count spadnie do 0 przy najblizszym
# `refresh_taxonomy_counts`, wiec i tak wypadna z list z `only_with_content`).
# Kasowanie rekordu zerwaloby refy kanoniczne, a tego nie chcemy przy czyms,
# co TPDB u siebie trzyma jako performera.
print(f"zdjeto {deleted} przypisan od {len(kand)} kategorii")
return {"performers": len(kand), "attributions": int(deleted or 0)}
def main() -> None:
logging.basicConfig(level=logging.INFO, format="%(message)s")
ap = argparse.ArgumentParser()
ap.add_argument("--yes", action="store_true", help="wykonaj (bez tego dry-run)")
run(apply=ap.parse_args().yes)
if __name__ == "__main__":
main()