fix(e5): address code-review findings (TMDB guid media_type, search ingest guard, discover keying)

Follow-ups from the epic-end /code-review of the E5 branch:

- tmdb.resolve_recs + people._resolve_known_for: match recommendations/
  credits to the library bucketed by media_type. TMDB movie ids and tv ids
  are independent sequences, so a bare `tmdb:<id>` match could deep-link a
  movie rec to an unrelated show (or vice-versa) on a numeric collision.
  Regression test added (test_resolve_recs_matches_by_media_type_not_bare_id).
- routes/search: wrap the _ingest_candidates call so a DB/enrich failure
  degrades to a 422 with a message instead of a bare 500 hidden behind the
  FE's generic "search failed" (extends the B-core-1 rationale); rollback on
  failure. Stamp enriched_at with its own timestamp taken after the enrich,
  not the stub-insert placeholder.
- routes/plex/discover: key serialized cards back by rating_key (globally
  unique in Plex) instead of id(obj); fix the stale rows 3-tuple annotation.
This commit is contained in:
2026-08-21 03:16:24 +02:00
parent ff45db70ae
commit 2b5de89eda
5 changed files with 118 additions and 42 deletions
+21 -9
View File
@@ -340,25 +340,37 @@ def run_enrich(db: Session) -> dict:
def resolve_recs(db: Session, recs: list[dict] | None) -> list[dict]:
"""Tag each recommendation with whether it's in OUR library (matched by guid `tmdb:<id>`) and, if so,
its rating_key — so the client can deep-link the ones we have and merely display the rest."""
its rating_key — so the client can deep-link the ones we have and merely display the rest.
TMDB movie ids and tv ids are independent sequences (id 603 can be BOTH a movie and a show), so the
match is bucketed by each rec's own media_type: a movie rec only matches a library movie, a tv rec
only a library show — otherwise a numeric-id collision would deep-link a rec to an unrelated title."""
if not recs:
return []
guids = [f"tmdb:{r['tmdb_id']}" for r in recs if r.get("tmdb_id")]
in_lib: dict[str, str] = {}
if guids:
movie_guids = [
f"tmdb:{r['tmdb_id']}" for r in recs if r.get("tmdb_id") and r.get("media_type") != "tv"
]
tv_guids = [
f"tmdb:{r['tmdb_id']}" for r in recs if r.get("tmdb_id") and r.get("media_type") == "tv"
]
movie_lib: dict[str, str] = {}
show_lib: dict[str, str] = {}
if movie_guids:
for guid, rk in db.execute(
select(PlexItem.guid, PlexItem.rating_key).where(
PlexItem.kind == "movie", PlexItem.guid.in_(guids)
PlexItem.kind == "movie", PlexItem.guid.in_(movie_guids)
)
):
in_lib[guid] = rk
movie_lib.setdefault(guid, rk)
if tv_guids:
for guid, rk in db.execute(
select(PlexShow.guid, PlexShow.rating_key).where(PlexShow.guid.in_(guids))
select(PlexShow.guid, PlexShow.rating_key).where(PlexShow.guid.in_(tv_guids))
):
in_lib.setdefault(guid, rk)
show_lib.setdefault(guid, rk)
out: list[dict] = []
for r in recs:
rk = in_lib.get(f"tmdb:{r.get('tmdb_id')}")
g = f"tmdb:{r.get('tmdb_id')}"
rk = show_lib.get(g) if r.get("media_type") == "tv" else movie_lib.get(g)
out.append({**r, "in_library": rk is not None, "rating_key": rk})
return out