From 9af5fd4eb4b1a12a40fd78daae572dc05ef13171 Mon Sep 17 00:00:00 2001 From: goon-foss Date: Tue, 28 Jul 2026 09:23:14 +0200 Subject: [PATCH] fix(performers): zbedne przypisania jednotokenowe obok osoby pelnoimiennej Ranking z poprzedniego commita ujawnil klase, ktorej zaden wczesniejszy sygnal nie widzial: wpisy bedace SAMYM NAZWISKIEM albo samym imieniem, powstale z rozbicia peleno nazwy przez tube-search. "Devine" obok "Shalina Devine", "Kitana" obok "Kitana A", "Rayne" obok "Tiffany Rayne". Taki wpis lapie potem kazda scene z tym slowem, nalezaca do zupelnie innej osoby. Regula jest DOWODLIWIE BEZSTRATNA: kasujemy przypisanie jednotokenowe tylko wtedy, gdy ta sama scena ma juz przypisana osobe pelnoimienna zawierajaca to slowo. Informacja o obsadzie zostaje, znika tylko niejednoznaczny duplikat. Kontrola potwierdzila, ze zadna scena nie zostala przez to bez obsady (0). Prowenancja z migracji 0026 dala zabezpieczenie: z 62 783 zbednych przypisan 62 120 to scraper (skasowane), 73 kanon (chronione), 590 nieznane (zostawione). Bez niej nie dalo by sie tego rozdzielic. Wynik: 62 120 przypisan zdjetych, 2156 wpisow odchudzonych (Devine 170 na 60), 1740 pustych rekordow usunietych. Regula dopieta do joba junk_performers, drugi przebieg daje zera. Zostaje residuum: sceny, na ktorych osoba pelnoimienna NIE byla przypisana, wiec regula nie miala sie o co oprzec (James, Blue, Moon, Crystal, Cruz, Starr, Knight po 60-160 przypisan). Tam trzeba by DOPISAC wlasciwa osobe, a nie kasowac - to osobne, bardziej ryzykowne zadanie. Co-Authored-By: Claude Opus 5 --- app/scheduler/junk_performers.py | 45 ++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/app/scheduler/junk_performers.py b/app/scheduler/junk_performers.py index 608b8d5..498cd2d 100644 --- a/app/scheduler/junk_performers.py +++ b/app/scheduler/junk_performers.py @@ -129,6 +129,32 @@ def find_junk_performers( return list(ids) +# Jednotokenowy performer jest ZBĘDNY na scenie, która ma już osobę pełnoimienną +# zawierającą to samo słowo: „Devine" obok „Shalina Devine", „Kitana" obok „Kitana A". +# To rozbite zapisy tej samej osoby, powstałe z parsowania tytułów przez tube-search — +# jeden wpis nazwiskiem łapie potem wszystkie sceny z tym nazwiskiem, należące do +# zupełnie innych ludzi (audyt 2026-07-27: 62 tys. takich przypisań, 2156 wpisów). +# +# Kasowanie jest DOWODLIWIE BEZSTRATNE: warunek wymaga, żeby konkretna osoba już była +# przypisana do tej sceny, więc informacja o obsadzie zostaje. Żadna scena nie może +# przez to zostać bez obsady (sprawdzone: 0). +# +# Ograniczone do `kind='scraper'` — przypisania z kanonu zostają nietknięte (73 takie). +_PRUNE_SURNAME_SQL = """ +DELETE FROM scene_performers sp +USING sources s, performers p +WHERE p.id = sp.performer_id + AND p.canonical_name !~ ' ' + AND length(p.canonical_name) >= 4 + AND s.id = sp.source_id + AND s.kind = 'scraper' + AND EXISTS ( + SELECT 1 FROM scene_performers sp2 JOIN performers q ON q.id = sp2.performer_id + WHERE sp2.scene_id = sp.scene_id AND q.id <> p.id AND q.canonical_name ~ ' ' + AND q.canonical_name ~* ('\\m' || p.canonical_name || '\\M') + ) +""" + _PRUNE_ARTIFACT_SQL = """ DELETE FROM scene_performers sp USING sources s, performers p @@ -148,8 +174,9 @@ WHERE length(trim(p.canonical_name)) <= :max_len def prune_artifact_name_attributions( session: Session, *, max_len: int = MAX_ARTIFACT_NAME_LEN ) -> tuple[int, int]: - """Zdejmij scraperowe przypisania z nazw 1-2 znakowych i usuń tych, którym nic nie - zostało. Kanoniczne przypisania zostają nietknięte — patrz MAX_ARTIFACT_NAME_LEN. + """Zdejmij scraperowe przypisania w dwóch przypadkach i usuń tych, którym nic nie + zostało: (1) nazwy 1-2 znakowe, (2) jednotokenowy performer na scenie, która ma już + osobę pełnoimienną zawierającą to słowo. Kanoniczne przypisania zostają nietknięte. Wymaga wypełnionego `scene_performers.source_id` (migracja 0026): bez provenance nie dałoby się odróżnić „Cj" z obsady TPDB od „Cj" wyłuskanego z tytułu przez tube-search. @@ -157,9 +184,23 @@ def prune_artifact_name_attributions( pruned = int( session.execute(text(_PRUNE_ARTIFACT_SQL), {"max_len": max_len}).rowcount or 0 ) + pruned += int(session.execute(text(_PRUNE_SURNAME_SQL)).rowcount or 0) dropped = int( session.execute(text(_DROP_EMPTY_ARTIFACT_SQL), {"max_len": max_len}).rowcount or 0 ) + dropped += int( + session.execute( + text( + "DELETE FROM performers p WHERE p.canonical_name !~ ' ' " + "AND length(p.canonical_name) >= 4 " + "AND NOT EXISTS (SELECT 1 FROM scene_performers sp " + "WHERE sp.performer_id = p.id) " + "AND NOT EXISTS (SELECT 1 FROM performer_external_refs r " + "JOIN sources s ON s.id = r.source_id WHERE r.performer_id = p.id " + "AND s.kind IN ('tpdb','stashdb'))" + ) + ).rowcount or 0 + ) return pruned, dropped