aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/tmdb.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/tmdb.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/tmdb.py58
1 files changed, 42 insertions, 16 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/tmdb.py b/packages/meshbay-node/src/meshbay_node/tmdb.py
index a530660..c9623df 100644
--- a/packages/meshbay-node/src/meshbay_node/tmdb.py
+++ b/packages/meshbay-node/src/meshbay_node/tmdb.py
@@ -51,24 +51,47 @@ def _normalize(s: str) -> str:
return re.sub(r"\s+", " ", s).strip()
-def _best_match(query_title: str, results: list[dict], keys: tuple[str, ...]) -> tuple[dict | None, float]:
+def _release_year_of(item: dict) -> int | None:
+ d = str(item.get("release_date") or item.get("first_air_date") or "")
+ return int(d[:4]) if d[:4].isdigit() else None
+
+
+def _ratio_against(qn: str, item: dict, keys: tuple[str, ...]) -> float:
+ best = 0.0
+ for k in keys:
+ val = item.get(k)
+ if val:
+ best = max(best, difflib.SequenceMatcher(None, qn, _normalize(str(val))).ratio())
+ return best
+
+
+def _best_match(
+ query_title: str, results: list[dict], keys: tuple[str, ...],
+ year: int | None = None,
+) -> tuple[dict | None, float]:
"""
Trusts TMDB's own ranking (§3.3's last row — a locally-recomputed
- re-rank picked a coincidentally-closer-looking wrong show once): only
- the top result is considered. The similarity ratio is returned purely
- as a confidence signal for the caller's fallback decision, never used
- to pick a different candidate.
+ re-rank picked a coincidentally-closer-looking wrong show once): the
+ top result is what's returned. The similarity ratio rides along purely
+ as a confidence signal for the caller's fallback decision.
+
+ One narrow exception (§10.1/V9): when the top result is *not* a
+ confident textual hit (ratio < 0.6) and a `year` was requested, a
+ different result of that **exact** release year is preferred. TMDB
+ already year-filtered the search, so an entry landing on the requested
+ year is a hard corroboration, not the fuzzy string re-rank §3.3 warns
+ against — and this never overrides a confident top hit.
"""
if not results:
return None, 0.0
- top = results[0]
qn = _normalize(query_title)
- best_ratio = 0.0
- for k in keys:
- val = top.get(k)
- if val:
- best_ratio = max(best_ratio, difflib.SequenceMatcher(None, qn, _normalize(str(val))).ratio())
- return top, best_ratio
+ top = results[0]
+ top_ratio = _ratio_against(qn, top, keys)
+ if year is not None and top_ratio < 0.6:
+ for item in results:
+ if _release_year_of(item) == year:
+ return item, _ratio_against(qn, item, keys)
+ return top, top_ratio
class TmdbClient:
@@ -138,12 +161,15 @@ class TmdbClient:
params["year"] = year
data = await self._get("search/movie", params)
results = (data or {}).get("results", [])
- return _best_match(title, results, ("title", "original_title"))
+ return _best_match(title, results, ("title", "original_title"), year=year)
- async def search_tv(self, title: str) -> tuple[dict | None, float]:
- data = await self._get("search/tv", {"query": title})
+ async def search_tv(self, title: str, year: int | None = None) -> tuple[dict | None, float]:
+ params = {"query": title}
+ if year:
+ params["first_air_date_year"] = year
+ data = await self._get("search/tv", params)
results = (data or {}).get("results", [])
- return _best_match(title, results, ("name", "original_name"))
+ return _best_match(title, results, ("name", "original_name"), year=year)
async def search_movie_results(self, title: str) -> list[dict]:
"""