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:
parent
cfe72195ed
commit
27309d24f2
3 changed files with 60 additions and 5 deletions
33
alembic/versions/20260930_0051_movies_tpdb_checked_at.py
Normal file
33
alembic/versions/20260930_0051_movies_tpdb_checked_at.py
Normal 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")
|
||||||
|
|
@ -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,7 +45,11 @@ from app.resolve.studio_resolver import resolve_studio
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
_DASH = str.maketrans({"–": "-", "—": "-", "‑": "-"})
|
# 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({"–": "-", "—": "-", "‑": "-"})
|
||||||
|
|
||||||
|
|
||||||
def _cand_year(raw: dict) -> int | None:
|
def _cand_year(raw: dict) -> int | None:
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue