Siedem performerow o nazwie JEDNOCZLONOWEJ ("James", "Blue", "Moon",
"Crystal", "Cruz", "Starr", "Knight") zbieralo sceny wielu roznych osob
dzielacych ten czlon: "Blue" mial sceny Anity, Delilah i Bonnie Blue,
"Starr" mial Violet, Natashe i Bobbi Starr.
prune_token_overlap_attributions z zalozenia ich NIE RUSZAL i to nie byl
niedopatrzenie: dla nazwy jednoczlonowej za dowod uznaje samo wystapienie
tego slowa w tytule, wiec "James" jest "udokumentowany" przez kazda scene
wspominajaca jakiegokolwiek Jamesa. Stad 0 skasowanych z tej siodemki.
To NIE bylo kasowanie, tylko ODZYSKIWANIE: w probce 14/14 wlasciwa osoba
NIE byla przypisana do sceny, czyli Bonnie Blue nie miala tych scen na
swojej stronie.
Kryterium: przypisanie ze scrapera (kanon nietykalny — z 605 przypisan
tych siedmiu 518 jest ze scrapera) + istnieje performer, ktorego nazwa
konczy sie tym czlonem po spacji + ta PELNA nazwa stoi w tytule sceny
jako ciag calych slow. Ostatni warunek odroznia odzyskanie od zgadywania.
Wynik: dolozono 271 wlasciwych przypisan, zdjeto 268 mononimow (roznica
to sceny, gdzie w tytule stoja DWIE takie osoby, np. Bonnie i Mick Blue —
obie sa dokladane, mononim schodzi raz).
Zyskali m.in.: Violet Starr +22 sceny, Mick Blue +16, Stacy Cruz +15,
Rachel Starr +13, Alexis Crystal +12, Natasha Starr +8.
Mononimy spadly: Starr 93->28, Cruz 45->8, Moon 64->23, Blue 113->73.
Reszta zostaje nietknieta, bo nie ma w tytule wskazanego wlasciciela —
"Blue" i "Cruz" maja refy StashDB, wiec czesc z nich moze byc prawdziwa.
Zdjete wiersze ida do scene_performers_pruned, cofniecie to INSERT..SELECT.
132 lines
5.5 KiB
Python
132 lines
5.5 KiB
Python
"""Przypisuje WŁAŚCIWĄ osobę tam, gdzie scena dostała sam człon jej nazwiska.
|
|
|
|
W bazie siedzi siedem performerów o nazwie jednoczłonowej („James", „Blue", „Moon",
|
|
„Crystal", „Cruz", „Starr", „Knight"), którzy zbierają sceny wielu różnych osób
|
|
dzielących ten człon: „Blue" ma sceny Anity Blue, Delilah Blue i Bonnie Blue, „Starr"
|
|
ma Violet, Natashę i Bobbi Starr.
|
|
|
|
**To NIE jest ten sam przypadek co `prune_token_overlap_attributions`** i tamten skrypt
|
|
z założenia ich nie rusza: dla nazwy jednoczłonowej za dowód uznaje samo wystąpienie
|
|
tego słowa w tytule, więc „James" jest „udokumentowany" przez każdą scenę wspominającą
|
|
jakiegokolwiek Jamesa. Stąd 0 skasowanych z tych siedmiu przy sprzątaniu 2026-08-13.
|
|
|
|
**I nie jest to kasowanie, tylko ODZYSKIWANIE.** W próbce 14/14 właściwa osoba NIE była
|
|
przypisana do sceny, czyli Bonnie Blue nie miała tych scen na swojej stronie. Skrypt
|
|
dokłada właściwego performera i zdejmuje mononim.
|
|
|
|
Kryterium (wszystkie warunki naraz):
|
|
|
|
1. przypisanie mononimu pochodzi ze scrapera (kanon nietykalny; z 605 przypisań tych
|
|
siedmiu 518 jest ze scrapera, reszta z TPDB/StashDB i tej nie dotykamy),
|
|
2. w bazie istnieje performer, którego nazwa KOŃCZY SIĘ tym członem po spacji
|
|
(„% blue" łapie „Bonnie Blue", nie łapie „Blue"),
|
|
3. ta pełna nazwa występuje w tytule sceny jako ciąg całych słów.
|
|
|
|
Warunek 3 jest tym, co odróżnia odzyskanie od zgadywania: nie wystarczy, że ktoś taki
|
|
istnieje, jego nazwisko musi stać w tytule tej konkretnej sceny.
|
|
|
|
Gdy w tytule stoją DWIE takie osoby („Bonnie Blue and Mick Blue"), dokładamy obie
|
|
i zdejmujemy mononim raz. To poprawne, bo scena faktycznie ma ich obu.
|
|
|
|
Uruchomienie (kontener worker):
|
|
python -m scripts.fix_mononym_attributions # dry-run
|
|
python -m scripts.fix_mononym_attributions --yes # wykonaj
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import logging
|
|
|
|
from sqlalchemy import text
|
|
|
|
from app.db import session_scope
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
#: Nazwy jednoczłonowe zbierające cudze sceny. Lista jawna, NIE heurystyka: mononimy
|
|
#: bywają prawdziwe (StashDB ma performerki „Blue" i „Cruz"), więc nie chcemy tu trafić
|
|
#: przypadkiem w kogoś, kto naprawdę występuje pod jednym imieniem.
|
|
MONONYMS = ("James", "Blue", "Moon", "Crystal", "Cruz", "Starr", "Knight")
|
|
|
|
_BUILD = """
|
|
CREATE TEMP TABLE kand ON COMMIT DROP AS
|
|
SELECT sp.scene_id,
|
|
sp.performer_id AS mono_id,
|
|
sp.source_id,
|
|
p2.id AS wlasciwy_id
|
|
FROM scene_performers sp
|
|
JOIN sources src ON src.id = sp.source_id AND src.name = 'tube-scraper'
|
|
JOIN performers m ON m.id = sp.performer_id AND m.canonical_name = ANY(:names)
|
|
JOIN scenes s ON s.id = sp.scene_id
|
|
JOIN performers p2
|
|
ON lower(p2.canonical_name) LIKE '% ' || lower(m.canonical_name)
|
|
AND p2.id <> m.id
|
|
-- pełna nazwa musi stać w tytule jako ciąg CAŁYCH słów; `strpos` po znormalizowanym,
|
|
-- obustronnie odspacjowanym tekście, żeby „Mia Blue" nie łapało się w „Miami Blues"
|
|
AND strpos(' ' || regexp_replace(lower(s.title), '[^a-z0-9]+', ' ', 'g') || ' ',
|
|
' ' || regexp_replace(lower(p2.canonical_name), '[^a-z0-9]+', ' ', 'g') || ' ') > 0
|
|
"""
|
|
|
|
#: Dokładamy właściwą osobę. `ON CONFLICT DO NOTHING`, bo bywa już przypisana.
|
|
#: `source_id` przepisujemy z wiersza mononimu: przypisanie pochodzi z tego samego
|
|
#: scrapera, tylko trafiło w zły rekord.
|
|
_ATTACH = """
|
|
INSERT INTO scene_performers (scene_id, performer_id, source_id)
|
|
SELECT DISTINCT scene_id, wlasciwy_id, source_id FROM kand
|
|
ON CONFLICT DO NOTHING
|
|
"""
|
|
|
|
_ARCHIVE = """
|
|
INSERT INTO scene_performers_pruned
|
|
(scene_id, performer_id, source_id, performer_name, scene_title)
|
|
SELECT DISTINCT sp.scene_id, sp.performer_id, sp.source_id, p.canonical_name, s.title
|
|
FROM kand k
|
|
JOIN scene_performers sp ON sp.scene_id = k.scene_id AND sp.performer_id = k.mono_id
|
|
JOIN performers p ON p.id = sp.performer_id
|
|
JOIN scenes s ON s.id = sp.scene_id
|
|
ON CONFLICT DO NOTHING
|
|
"""
|
|
|
|
_DETACH = """
|
|
DELETE FROM scene_performers sp
|
|
USING (SELECT DISTINCT scene_id, mono_id FROM kand) d
|
|
WHERE sp.scene_id = d.scene_id AND sp.performer_id = d.mono_id
|
|
"""
|
|
|
|
_REPORT = """
|
|
SELECT m.canonical_name AS mononim,
|
|
count(*) AS przypisan,
|
|
count(DISTINCT k.wlasciwy_id) AS roznych_osob
|
|
FROM kand k JOIN performers m ON m.id = k.mono_id
|
|
GROUP BY m.canonical_name ORDER BY 2 DESC
|
|
"""
|
|
|
|
|
|
def run(*, apply: bool) -> dict[str, int]:
|
|
with session_scope() as session:
|
|
session.execute(text(_BUILD), {"names": list(MONONYMS)})
|
|
rows = session.execute(text(_REPORT)).all()
|
|
for name, n, people in rows:
|
|
print(f" {name:9} {n:>4} przypisań → {people} różnych osób", flush=True)
|
|
total = sum(r[1] for r in rows)
|
|
|
|
if not apply:
|
|
print(f"\nRAZEM: {total} do naprawy (dry-run)")
|
|
return {"matched": total, "attached": 0, "detached": 0}
|
|
|
|
attached = session.execute(text(_ATTACH)).rowcount or 0
|
|
session.execute(text(_ARCHIVE))
|
|
detached = session.execute(text(_DETACH)).rowcount or 0
|
|
print(f"\nRAZEM: dołożono {attached} właściwych przypisań, zdjęto {detached} mononimów")
|
|
return {"matched": total, "attached": attached, "detached": detached}
|
|
|
|
|
|
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()
|