fix(dedup): bezpiecznik na pary drugiego rzedu w merge_phash_exact_dupes
Po scaleniu 4647 par zostalo 7 nowych, POWSTALYCH przez ten przebieg. Merge przenosi odciski na keepera, wiec scalona scena zbiera phashe wszystkich wchlonietych (jedna miala ich 14) i zaczyna kolidowac z obcymi scenami przez odziedziczony phash, ktory nie przedstawia nawet jej tresci. Ocena recznie po obsadzie i studiach: 5 z 7 (71%) to byly falszywki, m.in. Brazzers vs Reality Kings i AdultTime vs Teen Curves. W parach pierwszego rzedu bylo 0 falszywek na 18. Czyli ponowne odpalenie skryptu bez zmian scalaloby blednie. Rozdziela je podobienstwo tytulu z duzym marginesem: prawdziwe duplikaty 92,1 i 84,8, wszystkie falszywki 23,0-52,4. Prog 0,70 lezy w srodku luki. Prefiks studia progu nie rusza, bo token_set_ratio traktuje goly tytul jako podzbior tytulu z prefiksem. Po wpieciu: 2 prawdziwe scalone, 5 odrzuconych, kolejny przebieg nie generuje nowych par. Zbiezne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3de8489ffa
commit
121e6aa3f5
1 changed files with 30 additions and 3 deletions
|
|
@ -14,6 +14,18 @@ UWAGA: merge KASUJE zdublowaną scenę (refs/sources/tags łączone do keepera)
|
||||||
NIEODWRACALNE. Domyślnie dry-run; --yes wykonuje. Per-para re-fetch → obsługuje klastry
|
NIEODWRACALNE. Domyślnie dry-run; --yes wykonuje. Per-para re-fetch → obsługuje klastry
|
||||||
(scena znika po wcześniejszym merge'u w tym samym klastrze).
|
(scena znika po wcześniejszym merge'u w tym samym klastrze).
|
||||||
|
|
||||||
|
**Bezpiecznik na pary drugiego rzędu (dodany 2026-07-27).** Merge PRZENOSI odciski na
|
||||||
|
keepera, więc scalona scena zbiera phashe wszystkich wchłoniętych — jedna miała ich 14.
|
||||||
|
Taki konglomerat zaczyna potem kolidować z obcymi scenami przez odziedziczony phash,
|
||||||
|
który nie przedstawia nawet jego treści. Po przebiegu na 4647 parach zostało 7 nowych,
|
||||||
|
POWSTAŁYCH przez ten przebieg, i 5 z nich (71%) było fałszywkami: Brazzers vs Reality
|
||||||
|
Kings, AdultTime vs Teen Curves. W parach pierwszego rzędu było 0 fałszywek na 18.
|
||||||
|
|
||||||
|
Rozdziela je podobieństwo tytułu, i to z dużym marginesem — zmierzone na tych 7 parach:
|
||||||
|
prawdziwe duplikaty 92,1 i 84,8, wszystkie fałszywki 23,0-52,4. Stąd próg 0,70, który
|
||||||
|
leży w środku luki. Prefiks studia progu nie rusza, bo `token_set_ratio` traktuje
|
||||||
|
„Bursting The Bag" jako podzbiór „FakehubOriginals – Sophia Locke – Bursting The Bag".
|
||||||
|
|
||||||
Uruchomienie:
|
Uruchomienie:
|
||||||
python -m scripts.merge_phash_exact_dupes # dry-run
|
python -m scripts.merge_phash_exact_dupes # dry-run
|
||||||
python -m scripts.merge_phash_exact_dupes --yes # wykonaj
|
python -m scripts.merge_phash_exact_dupes --yes # wykonaj
|
||||||
|
|
@ -22,6 +34,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
from rapidfuzz import fuzz
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
|
|
||||||
from app.db import session_scope
|
from app.db import session_scope
|
||||||
|
|
@ -29,6 +42,10 @@ from app.models.scene import Scene
|
||||||
from app.resolve.scene_merge import merge_scenes
|
from app.resolve.scene_merge import merge_scenes
|
||||||
from app.scheduler.bulk_dedup import _pick_keep_drop
|
from app.scheduler.bulk_dedup import _pick_keep_drop
|
||||||
|
|
||||||
|
# Patrz docstring: zmierzona luka to 52,4 (najgorsza fałszywka) ↔ 84,8 (najsłabszy
|
||||||
|
# prawdziwy duplikat). 0.70 siedzi w jej środku.
|
||||||
|
_MIN_TITLE_SIM = 0.70
|
||||||
|
|
||||||
# UWAGA (2026-07-27): bez wykluczenia `phash_blacklist` ten self-join jest NIEBEZPIECZNY.
|
# UWAGA (2026-07-27): bez wykluczenia `phash_blacklist` ten self-join jest NIEBEZPIECZNY.
|
||||||
# Jedna zaślepka była dzielona przez 892 sceny → sam ten klaster daje ~397 tys. par, a
|
# Jedna zaślepka była dzielona przez 892 sceny → sam ten klaster daje ~397 tys. par, a
|
||||||
# każde dwie z nich o zbliżonej długości i wspólnym performerze zostałyby scalone, mimo
|
# każde dwie z nich o zbliżonej długości i wspólnym performerze zostałyby scalone, mimo
|
||||||
|
|
@ -64,7 +81,7 @@ def main() -> None:
|
||||||
pairs = [(r[0], r[1]) for r in s.execute(text(_SAFE_PAIRS_SQL))]
|
pairs = [(r[0], r[1]) for r in s.execute(text(_SAFE_PAIRS_SQL))]
|
||||||
print(f"safe high-confidence duplicate pairs: {len(pairs)}")
|
print(f"safe high-confidence duplicate pairs: {len(pairs)}")
|
||||||
|
|
||||||
merged = skipped = 0
|
merged = skipped = rejected = 0
|
||||||
for sa, sb in pairs:
|
for sa, sb in pairs:
|
||||||
with session_scope() as s:
|
with session_scope() as s:
|
||||||
a = s.get(Scene, sa)
|
a = s.get(Scene, sa)
|
||||||
|
|
@ -72,6 +89,14 @@ def main() -> None:
|
||||||
if a is None or b is None or a.id == b.id:
|
if a is None or b is None or a.id == b.id:
|
||||||
skipped += 1 # już zmergowane w tym klastrze
|
skipped += 1 # już zmergowane w tym klastrze
|
||||||
continue
|
continue
|
||||||
|
# Bezpiecznik na pary drugiego rzędu (patrz docstring): sam wspólny phash
|
||||||
|
# nie wystarcza, gdy któraś strona jest konglomeratem po wcześniejszym
|
||||||
|
# merge'u i niesie odziedziczone odciski obcych scen.
|
||||||
|
sim = fuzz.token_set_ratio(a.title_normalized or "", b.title_normalized or "") / 100.0
|
||||||
|
if sim < _MIN_TITLE_SIM:
|
||||||
|
rejected += 1
|
||||||
|
print(f" SKIP (tytuł {sim:.2f}) '{(a.title or '')[:34]}' vs '{(b.title or '')[:34]}'")
|
||||||
|
continue
|
||||||
keep, drop = _pick_keep_drop(s, a, b)
|
keep, drop = _pick_keep_drop(s, a, b)
|
||||||
print(f" {'MERGE' if args.yes else '[dry] MERGE'} keep={str(keep.id)[:8]} "
|
print(f" {'MERGE' if args.yes else '[dry] MERGE'} keep={str(keep.id)[:8]} "
|
||||||
f"'{(keep.title or '')[:32]}' ({keep.duration_sec}s) <- drop={str(drop.id)[:8]} "
|
f"'{(keep.title or '')[:32]}' ({keep.duration_sec}s) <- drop={str(drop.id)[:8]} "
|
||||||
|
|
@ -82,9 +107,11 @@ def main() -> None:
|
||||||
merged += 1
|
merged += 1
|
||||||
|
|
||||||
if args.yes:
|
if args.yes:
|
||||||
print(f"\nAPPLIED: merged={merged} skipped(already-gone)={skipped}")
|
print(f"\nAPPLIED: merged={merged} skipped(already-gone)={skipped} "
|
||||||
|
f"rejected(tytuł<{_MIN_TITLE_SIM})={rejected}")
|
||||||
else:
|
else:
|
||||||
print(f"\n(dry-run — {len(pairs)} par; uruchom z --yes aby scalić. NIEODWRACALNE)")
|
print(f"\n(dry-run — {len(pairs)} par, z tego {rejected} odrzuconych na tytule; "
|
||||||
|
f"uruchom z --yes aby scalić. NIEODWRACALNE)")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue