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 <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-07-28 09:23:14 +02:00
parent ef8c255e1f
commit 9af5fd4eb4

View file

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