mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 19:46:16 -08:00
fix(tmdb): a new series no longer matches its older, better-known namesake
TMDB returns titles in the REQUEST language, so a Cyrillic query issued with `ru-RU` collapses unrelated foreign shows onto ordinary local nouns: an older foreign series comes back under exactly the same localized name as a recent local-language one. `pickConfidentMatch` admitted the older row through the series "premiered earlier" tolerance — portals report the running season's year while TMDB reports the premiere — and then let `pickMostPopular` decide across every admitted candidate, discarding the year evidence that had just admitted them. 26 votes beat 4, and the 2026 series rendered the 2018 show's poster, cast, genres and rating. Rank admitted candidates by year evidence first (`yearEvidenceTier`: the provider's exact year, then a year off by one, then the series tolerance) and let popularity break ties only inside the strongest tier any candidate reached. The tolerance stays — three of eight real lookups from one install depend on it (asked 2026 / premiere 2025; asked 2025 / premiere 2023; asked 2026 / premiere 2023) — but it is a last resort, not an equal. Measured over 400 Cyrillic series titles sampled from a real catalog, 20 normalized keys had a same-titled older series and 16 of those were the more popular row. The mirror case is accepted knowingly: a long-running show whose stated season year happens to BE another same-titled show's premiere year now resolves to the newer show. Only the older show's season air dates could separate the two and a search response does not carry them, while that shape needs three coincidences at once against one that needs none. Search cache keys move to `|v4` with a matching startup cleanup, because a positive row naming the wrong show stays fresh for 30 days. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f80a21beef
commit
b87ea9a178
9 files changed
+267
-65
No files matched your search
@@ -141,7 +141,27 @@ Wrong metadata is worse than no metadata, so id resolution is conservative:
|
||||
provider year — portals report the current season's year while TMDB's
|
||||
`first_air_date` is the premiere. Without a year, the exact-title match
|
||||
must be unambiguous (single hit).
|
||||
4. No confident match → the provider data stays untouched, and the negative
|
||||
4. Several admitted results are ranked by **year evidence first**
|
||||
(`yearEvidenceTier`): the provider's exact year beats a year off by one,
|
||||
which beats the series "premiered earlier" tolerance. Popularity
|
||||
(`vote_count`, then `popularity`) only breaks ties inside the strongest
|
||||
tier any candidate reached.
|
||||
|
||||
The tolerance is a last resort, not an equal. Ranked alongside the
|
||||
exact-year tier it handed every new series its older, better-known
|
||||
namesake: because TMDB returns titles in the request language, an
|
||||
unrelated 2018 foreign series came back under the same `ru-RU` name as a
|
||||
2026 local-language series and outvoted it, rendering its poster, cast
|
||||
and genres. Over 400 Cyrillic series titles sampled from a real catalog,
|
||||
20 normalized keys had a same-titled older series and 16 of those were
|
||||
the more popular row.
|
||||
|
||||
The mirror case is accepted knowingly: a long-running show whose stated
|
||||
season year happens to BE another same-titled show's premiere year now
|
||||
resolves to the newer show. Separating those two needs the older show's
|
||||
season air dates, which a search response does not carry — and the shape
|
||||
needs three coincidences at once, against one that needs none.
|
||||
5. No confident match → the provider data stays untouched, and the negative
|
||||
verdict is cached (shorter TTL) so browsing back doesn't re-search.
|
||||
|
||||
The year filter is applied client-side rather than via TMDB's strict
|
||||
@@ -475,7 +495,7 @@ tmdb_metadata (
|
||||
media_type 'movie' | 'tv' | 'person',
|
||||
lookup_key 'id:<tmdbId>|v2' -- details payload row
|
||||
'id:<tmdbId>|season:<n>' -- season payload row
|
||||
'title:<query lowercased>|year:<y>|v3' -- search resolution row
|
||||
'title:<query lowercased>|year:<y>|v4' -- search resolution row
|
||||
'person:<personId>' -- person payload row
|
||||
'trending:week' -- trending list row
|
||||
'badProviderId:<tmdbId>' -- id confirmed 404 by TMDB
|
||||
@@ -490,19 +510,21 @@ tmdb_metadata (
|
||||
TTLs (enforced at read time in `TmdbCacheService.isFresh`): details and
|
||||
positive matches 30 days, negative matches 7 days.
|
||||
|
||||
Search keys carry a `|v3` and details keys a `|v2` version suffix
|
||||
Search keys carry a `|v4` and details keys a `|v2` version suffix
|
||||
(`buildSearchLookupKey` / `buildDetailsLookupKey` in `tmdb-matcher.ts`): for
|
||||
search rows so normalization or query changes cannot reuse stale positive or
|
||||
negative resolutions, for details rows because payloads now include videos
|
||||
via `append_to_response` and pre-videos cache rows had to be invalidated.
|
||||
Search v2 → v3 retired the rows written while the folded comparison key was
|
||||
also the wire query (see "Search query vs. comparison key" below). Database
|
||||
also the wire query (see "Search query vs. comparison key" below); v3 → v4
|
||||
retired the rows resolved before year evidence was tiered, where a positive
|
||||
row naming the wrong show would otherwise stay fresh for 30 days. Database
|
||||
startup deletes the rows of every retired search-key generation once, each
|
||||
under its own `app_state` marker so a skipped release still runs the cleanups
|
||||
it missed (`migration:tmdb-search-lookup-v2-cache-cleanup:v1` for the
|
||||
unversioned rows, `migration:tmdb-search-lookup-v3-cache-cleanup:v1` for the
|
||||
`|v2` rows; `LEGACY_TMDB_SEARCH_CACHE_CLEANUPS` in `connection.ts`); details
|
||||
and person cache rows are unaffected.
|
||||
unversioned rows, `…-v3-…` for the `|v2` rows, `…-v4-…` for the `|v3` rows;
|
||||
`LEGACY_TMDB_SEARCH_CACHE_CLEANUPS` in `connection.ts`); details and person
|
||||
cache rows are unaffected.
|
||||
|
||||
### Search query vs. comparison key
|
||||
|
||||
@@ -513,7 +535,7 @@ both sides fold the same way. `query` is `cleanTitleForSearch`
|
||||
stripping, but the letters left as the provider wrote them. The search's
|
||||
identity is the query with only its case removed (`searchQueryIdentity`):
|
||||
variants are deduplicated by it and every attempted variant is cached under
|
||||
its own key (`title:<identity>|year:<y>|v3`, in the language that variant
|
||||
its own key (`title:<identity>|year:<y>|v4`, in the language that variant
|
||||
was searched in), never by the folded key and never only under the first
|
||||
variant — "Феик" and "Фейк" fold to one key but are different searches with
|
||||
different answers, so a verdict for one must not be read back for the other;
|
||||
|
||||
Reference in new issue
Block a user