goon/app/resolve/studio_resolver.py
goon-foss f167c491c8
Some checks are pending
Backend tests / test (push) Waiting to run
feat(movies): NEW przy filmach ulubionych, scalanie duplikatow przytrzymaniem
Filmy performerki z ulubionych dodane od ostatniej wizyty maja odznake NEW
i ida na gore. Przytrzymanie filmu otwiera scalanie jak przy scenach
(POST /movies/{keep}/merge/{drop}). Ocena dopasowania liczy cyfry rzymskie
jak arabskie i uznaje za zgodne studia o tej samej nazwie bez spacji;
resolver studiow nie zaklada juz blizniaka "EvilAngel" obok "Evil Angel".
Skrypt remerge_movies_roman_studio.py scala istniejace takie pary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:56:26 +02:00

138 lines
4.2 KiB
Python

"""M2: prosty resolver studia.
Ścieżka 1: exact external ref → update.
Ścieżka 2: name_normalized exact match → reuse + dopnij external_ref.
Ścieżka 3: insert nowego.
(Fuzzy alias matching dochodzi w M3.)
"""
from __future__ import annotations
import logging
import uuid
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.models.studio import Studio, StudioAlias, StudioExternalRef
from app.normalize.scenes import NormalizedStudio
from app.normalize.text import slugify
log = logging.getLogger(__name__)
def _compact_name_twin(session: Session, name_normalized: str) -> Studio | None:
"""Jedno studio o tej samej nazwie bez spacji ("evilangel" = "evil angel").
xtapes podaje studia sklejone ("[EvilAngel]", "[TeamSkeet]"), a reszta zrodel ze
spacja; osobne studio psulo dopasowanie filmow (studio "rozne" obnizalo ocene, np.
"Hardcore Gangbangs 8" i "... VIII", zgloszenie f8c9486e). Tylko przy jednym trafieniu.
"""
compact = (name_normalized or "").replace(" ", "")
if len(compact) < 4:
return None
rows = session.execute(
select(Studio)
.where(func.replace(Studio.name_normalized, " ", "") == compact)
.limit(2)
).scalars().all()
return rows[0] if len(rows) == 1 else None
def resolve_studio(
session: Session,
*,
norm: NormalizedStudio,
source_id: uuid.UUID,
) -> Studio:
if norm.external_id:
ref = session.execute(
select(StudioExternalRef).where(
StudioExternalRef.source_id == source_id,
StudioExternalRef.external_id == norm.external_id,
)
).scalar_one_or_none()
if ref is not None:
studio = session.get(Studio, ref.studio_id)
assert studio is not None
_update_studio_fields(studio, norm)
return studio
studio = session.execute(
select(Studio).where(Studio.name_normalized == norm.name_normalized)
).scalar_one_or_none()
if studio is None:
studio = _compact_name_twin(session, norm.name_normalized)
if studio is None:
studio = Studio(
name=norm.name,
name_normalized=norm.name_normalized,
slug=_unique_slug(session, norm.slug or slugify(norm.name) or "studio"),
network=norm.network,
homepage_url=norm.homepage_url,
)
session.add(studio)
session.flush()
log.debug("studio create id=%s name=%s", studio.id, studio.name)
else:
_update_studio_fields(studio, norm)
if norm.external_id:
existing_ref = session.execute(
select(StudioExternalRef).where(
StudioExternalRef.source_id == source_id,
StudioExternalRef.external_id == norm.external_id,
)
).scalar_one_or_none()
if existing_ref is None:
session.add(
StudioExternalRef(
source_id=source_id,
external_id=norm.external_id,
studio_id=studio.id,
confidence=1.0,
)
)
return studio
def _update_studio_fields(studio: Studio, norm: NormalizedStudio) -> None:
if norm.network and not studio.network:
studio.network = norm.network
if norm.homepage_url and not studio.homepage_url:
studio.homepage_url = norm.homepage_url
def _unique_slug(session: Session, base: str) -> str:
candidate = base
n = 1
while session.execute(select(Studio.id).where(Studio.slug == candidate)).first():
n += 1
candidate = f"{base}-{n}"
return candidate
def add_alias_if_missing(
session: Session,
*,
studio_id: uuid.UUID,
alias: str,
alias_normalized: str,
source_id: uuid.UUID | None,
) -> None:
existing = session.execute(
select(StudioAlias).where(
StudioAlias.studio_id == studio_id,
StudioAlias.alias_normalized == alias_normalized,
)
).scalar_one_or_none()
if existing is None:
session.add(
StudioAlias(
studio_id=studio_id,
alias=alias,
alias_normalized=alias_normalized,
source_id=source_id,
)
)