fix(plex,audit): E30 review round 1 — the people job keeps going, HU genres everywhere, results as data

- people_enrich: a failed TMDB lookup or a dead headshot is skipped, not the end of the run (a dead
  file is marked and left for a week; three failures in a row end it); portraits wait for the Plex
  module switch; a no-Plex original keeps its own cache key, so the resize still replaces it; the job
  entry lives in the plex domain (run_people_enrich)
- person-image takes only a headshot-shaped CDN url (no query variants of one file)
- every synced row gets Hungarian genres, and a HU genre filter matches the word as picked too;
  migration 0094 fills the rows no overlay reached
- a job run's audit row carries its result as data; audit_clear / demo_reset store their counts
- one credit-column list for the prune and the enrichment order; _overlay_pass typed
- tests: batch skip + stop, HU bio stored, dead headshots, module switch, the person-image route,
  the job body, genre filter + facet write + twin fallback, tagline/summary fallbacks, audit data,
  the reader's alphabet at every remaining collated() call site
This commit is contained in:
2026-09-30 03:26:57 +02:00
parent 59e6cc027d
commit ab963c81d2
15 changed files with 571 additions and 118 deletions
+15 -4
View File
@@ -15,6 +15,7 @@ dead `enabled` column) is a separate follow-up. Cast + intro/credit markers are
item-detail/player path (P2), not here.
"""
from collections.abc import Iterable
from datetime import datetime, timezone
from sqlalchemy import func, select
@@ -449,6 +450,10 @@ def _apply_facet_fields(row, meta: dict) -> None:
row.studio = (meta.get("studio") or None) and str(meta.get("studio"))[:255]
row.originally_available_at = _date(meta.get("originallyAvailableAt"))
row.genres = _tags(meta, "Genre")
# Every row has Hungarian genres, not only one with an HU overlay twin (the overlay pass, which runs
# after this, puts the twin's own list over it): the HU genre facets and filter read this column,
# and an English word there could never match a filter read in Hungarian.
row.genres_hu = hu_genres(row.genres)
row.directors = _tags(meta, "Director")
# The listing carries only the top ~20 cast, so re-applying it would DOWNGRADE an already-enriched
# full cast (up to 24, grown by the rich backfill/info-page enrich), silently breaking the
@@ -702,13 +707,19 @@ def _sync_overlay(
def _overlay_pass(
db: Session, plex: PlexClient, lib: PlexLibrary, item_type: int, rows, stats: dict
):
db: Session,
plex: PlexClient,
lib: PlexLibrary,
item_type: int,
rows: Iterable[PlexItem] | Iterable[PlexShow],
stats: dict,
) -> None:
"""One kind of the overlay (movies, shows or episodes) — see `_sync_overlay`."""
# guid -> the EN rows carrying it (a list: two editions of one film can share a guid; both overlay).
by_guid: dict[str, list] = {}
by_guid: dict[str, list[PlexItem | PlexShow]] = {}
for r in rows:
by_guid.setdefault(r.guid, []).append(r)
if r.guid: # the passes select guid-bearing rows only
by_guid.setdefault(r.guid, []).append(r)
# Reconcile step: clear HU fields on all guid-bearing rows. Only rows that actually held HU data
# become dirty (SQLAlchemy skips a no-net-change UPDATE), so this is cheap; matches are re-set below.
# The genres are the exception: every row gets Hungarian ones, from its HU twin or, lacking one, from