fix(performers): zdjecie scraperowych przypisan z nazw 1-2 znakowych

"A", "L", "Ro", "Be", "Js" mialy po 250-290 scen kazdy. To artefakty parsowania
tytulow przez tube-search, ale NIE wszystkie: "Cj", "PD" i "JD" to realne krotkie
pseudonimy z obsada potwierdzona w TPDB i StashDB, z prawdziwymi partnerami
scenicznymi (Cj z Lucy Belle i Denisem Martim, PD z Victoria Love).

Dlatego nie kasujemy tu performera, tylko przypisania, o ktorych WIEMY ze dokleil je
scraper. Reszta rozstrzyga sie sama: komu nic nie zostanie, ten znika. To pierwsze
uzycie provenance z migracji 0026 - bez niej nie dalo sie odroznic "Cj" z obsady
TPDB od "Cj" wylusknietego z tytulu.

Rozklad byl jednoznaczny: z 9404 przypisan 9232 (98,2 proc.) ze scrapera, 141 z
kanonu, 31 nieznanych. Wiersze z NULL-em zostawiamy, nie zgadujemy.

Wynik: 9232 przypisan zdjetych, 20 performerow zniknelo calkiem, 31 zostalo z sama
prawdziwa obsada (Cj 41, PD 38, JD 20, reszta <=10). Dopiete do joba junk_performers,
zeby nie odroslo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-07-27 15:18:38 +02:00
parent 034ddd644c
commit 8d92ecc51b

View file

@ -70,6 +70,13 @@ DEFAULT_MIN_TAG_RATIO = 2
#: Poniżej tego stosunek jest szumem — za mało scen, żeby cokolwiek przesądzać.
DEFAULT_MIN_SCENES_FOR_RATIO = 50
#: Nazwy 1-2 znakowe („A", „L", „Ro", „Js") to artefakty parsowania tytułów, ale
#: kilka to realne krótkie pseudonimy (Cj, PD, JD mają obsadę potwierdzoną w TPDB
#: i StashDB, z prawdziwymi partnerami scenicznymi). Dlatego NIE kasujemy tu
#: performera, tylko przypisania, o których WIEMY, że dokleił je scraper — reszta
#: rozstrzyga się sama: komu nic nie zostanie, ten znika.
MAX_ARTIFACT_NAME_LEN = 2
_SELECT_SQL = """
WITH junk AS (
SELECT pf.id, pf.canonical_name, pf.slug
@ -122,6 +129,40 @@ def find_junk_performers(
return list(ids)
_PRUNE_ARTIFACT_SQL = """
DELETE FROM scene_performers sp
USING sources s, performers p
WHERE p.id = sp.performer_id
AND length(trim(p.canonical_name)) <= :max_len
AND s.id = sp.source_id
AND s.kind = 'scraper'
"""
_DROP_EMPTY_ARTIFACT_SQL = """
DELETE FROM performers p
WHERE length(trim(p.canonical_name)) <= :max_len
AND NOT EXISTS (SELECT 1 FROM scene_performers sp WHERE sp.performer_id = p.id)
"""
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.
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.
Wiersze z NULL-em (sceny wieloźródłowe) zostawiamy nie zgadujemy."""
pruned = int(
session.execute(text(_PRUNE_ARTIFACT_SQL), {"max_len": max_len}).rowcount or 0
)
dropped = int(
session.execute(text(_DROP_EMPTY_ARTIFACT_SQL), {"max_len": max_len}).rowcount or 0
)
return pruned, dropped
def run_junk_performer_cleanup(
*, min_foreign: int = DEFAULT_MIN_FOREIGN_PERFORMERS, commit: bool = True
) -> dict[str, int]:
@ -132,15 +173,19 @@ def run_junk_performer_cleanup(
with session_scope() as session:
ids = find_junk_performers(session, min_foreign=min_foreign)
if not ids:
return {"found": 0, "deleted": 0}
deleted = 0
deleted = pruned = dropped = 0
if commit:
deleted = int(
session.execute(
text("DELETE FROM performers WHERE id = ANY(:ids)"), {"ids": ids}
).rowcount or 0
)
if ids:
deleted = int(
session.execute(
text("DELETE FROM performers WHERE id = ANY(:ids)"), {"ids": ids}
).rowcount or 0
)
pruned, dropped = prune_artifact_name_attributions(session)
session.commit()
log.info("junk performers: znaleziono %d, skasowano %d", len(ids), deleted)
return {"found": len(ids), "deleted": deleted}
log.info(
"junk performers: znaleziono %d, skasowano %d; nazwy 1-2 znakowe: zdjęto %d "
"przypisań, usunięto %d pustych",
len(ids), deleted, pruned, dropped,
)
return {"found": len(ids), "deleted": deleted, "pruned": pruned, "dropped": dropped}