fix(movies): kolejka wzbogacania z TPDB pamieta, co juz sprawdzila

Zadanie (co 6 h po 200) bralo zawsze 200 najnowszych kandydatow. Przebieg
o 05:00 wzbogacal 14-19 swiezych filmow, trzy kolejne mielily te same 200
bez trafienia z wynikiem zero, a 12,9 tys. starszych kandydatow nie bylo
sprawdzonych ani razu. Teraz movies.tpdb_checked_at (migracja 0051): najpierw
niesprawdzone, film bez trafienia wraca po 30 dniach, blad nie oznacza.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-09-30 20:39:12 +02:00
parent cfe72195ed
commit 27309d24f2
3 changed files with 60 additions and 5 deletions

View file

@ -0,0 +1,33 @@
"""movies.tpdb_checked_at: kiedy film ostatnio szukano w TPDB (kolejka wzbogacania).
Wzbogacanie filmow z TPDB (co 6 h po 200) bralo zawsze 200 NAJNOWSZYCH kandydatow i nie
pamietalo, ktore juz sprawdzilo. Przebieg o 05:00, po dziennym ingescie, wzbogacal 14-19
nowych filmow, a trzy kolejne mielily te same 200 bez trafienia i konczyly z zerem.
12,9 tys. kandydatow starszych niz miesiac nie doczekalo sie ani jednego sprawdzenia
(pomiar 2026-09-30). Z data sprawdzenia kolejka idzie od niesprawdzonych, a film bez
trafienia wraca najwczesniej po 30 dniach.
Kolumna nullable bez DEFAULT, ALTER natychmiastowy; NULL = jeszcze nie sprawdzany.
Revision ID: 0051_movies_tpdb_checked_at
Revises: 0050_tag_vertical_counts_no_fk
Create Date: 2026-09-30
"""
from __future__ import annotations
from alembic import op
revision = "0051_movies_tpdb_checked_at"
down_revision: str | None = "0050_tag_vertical_counts_no_fk"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute("SET LOCAL lock_timeout = '5s'")
op.execute("ALTER TABLE movies ADD COLUMN IF NOT EXISTS tpdb_checked_at timestamptz")
def downgrade() -> None:
op.execute("SET LOCAL lock_timeout = '5s'")
op.execute("ALTER TABLE movies DROP COLUMN IF EXISTS tpdb_checked_at")

View file

@ -25,10 +25,10 @@ from __future__ import annotations
import logging import logging
import uuid import uuid
from datetime import UTC, datetime from datetime import UTC, datetime, timedelta
from rapidfuzz import fuzz from rapidfuzz import fuzz
from sqlalchemy import func, select from sqlalchemy import func, select, text
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.connectors.tpdb import TPDBConnector, _parse_movie from app.connectors.tpdb import TPDBConnector, _parse_movie
@ -45,6 +45,10 @@ from app.resolve.studio_resolver import resolve_studio
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Film bez trafienia w TPDB wraca do kolejki dopiero po tym czasie (TPDB dopisuje filmy,
# wiec kiedys warto sprawdzic ponownie, ale nie cztery razy dziennie).
_RECHECK_AFTER = timedelta(days=30)
_DASH =str.maketrans({"–": "-", "—": "-", "‑": "-"}) _DASH =str.maketrans({"–": "-", "—": "-", "‑": "-"})
@ -282,7 +286,12 @@ def enrich_movie(
def _candidate_movies(session: Session, *, source_id: uuid.UUID, limit: int) -> list[Movie]: def _candidate_movies(session: Session, *, source_id: uuid.UUID, limit: int) -> list[Movie]:
"""Filmy jeszcze nie wzbogacone TPDB, priorytet: mają żywy playback (są grywalne) """Filmy jeszcze nie wzbogacone TPDB, priorytet: mają żywy playback (są grywalne)
i brak obsady LUB brak studia (największa luka). Reszta później.""" i brak obsady LUB brak studia (największa luka). Reszta później.
Kolejność: najpierw nigdy nie sprawdzane (najnowsze pierwsze), potem te sprawdzone
najdawniej, ale nie wcześniej niż _RECHECK_AFTER od ostatniego razu. Bez tego każdy
przebieg brał te same 200 najnowszych bez trafienia, a starszych nie ruszał wcale
(migracja 0051)."""
has_tpdb = ( has_tpdb = (
select(MovieExternalRef.movie_id) select(MovieExternalRef.movie_id)
.where(MovieExternalRef.source_id == source_id) .where(MovieExternalRef.source_id == source_id)
@ -307,7 +316,11 @@ def _candidate_movies(session: Session, *, source_id: uuid.UUID, limit: int) ->
Movie.id.in_(has_playback), Movie.id.in_(has_playback),
) )
.where((perf_count == 0) | (Movie.studio_id.is_(None))) .where((perf_count == 0) | (Movie.studio_id.is_(None)))
.order_by(Movie.created_at.desc()) .where(
Movie.tpdb_checked_at.is_(None)
| (Movie.tpdb_checked_at < datetime.now(UTC) - _RECHECK_AFTER)
)
.order_by(Movie.tpdb_checked_at.asc().nulls_first(), Movie.created_at.desc())
.limit(limit) .limit(limit)
) )
return list(session.execute(stmt).scalars().all()) return list(session.execute(stmt).scalars().all())
@ -345,6 +358,12 @@ def run_tpdb_movie_enrich(
outcome = enrich_movie( outcome = enrich_movie(
session, movie, connector=connector, source_id=source_id, min_title=min_title session, movie, connector=connector, source_id=source_id, min_title=min_title
) )
# Surowy UPDATE: nie rusza updated_at, a po merge skasowany film po prostu
# nie ma czego oznaczyc. Blad (wyjatek nizej) nie oznacza, wroci w nastepnym
# przebiegu, bo moze to byc chwilowa awaria TPDB.
session.execute(
text("UPDATE movies SET tpdb_checked_at = now() WHERE id = :id"), {"id": mid}
)
session.commit() session.commit()
counters[outcome] = counters.get(outcome, 0) + 1 counters[outcome] = counters.get(outcome, 0) + 1
except Exception as e: # pragma: no cover - defensywnie, jeden film nie wywala batcha except Exception as e: # pragma: no cover - defensywnie, jeden film nie wywala batcha

View file

@ -46,6 +46,9 @@ class Movie(UUIDPKMixin, TimestampMixin, Base):
poster_url: Mapped[str | None] = mapped_column(String(2048)) poster_url: Mapped[str | None] = mapped_column(String(2048))
backdrop_url: Mapped[str | None] = mapped_column(String(2048)) backdrop_url: Mapped[str | None] = mapped_column(String(2048))
rating: Mapped[float | None] = mapped_column(Float) rating: Mapped[float | None] = mapped_column(Float)
# Ostatnie szukanie w TPDB (app/enrich/tpdb_movies.py), NULL = jeszcze nie. Kolejka
# wzbogacania idzie od niesprawdzonych, bez trafienia wraca po 30 dniach (migracja 0051).
tpdb_checked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
class MovieExternalRef(Base): class MovieExternalRef(Base):