Pour toi : recommandations diverses et déjà-possédé réellement exclu

Deux défauts enchaînés : la requête Kitsu cumulait les genres en ET
(quatre catégories → 1 seul titre, précisément le téléchargement à
l'origine des genres), et l'exclusion échouait car « Titre - Saison 1 -
E3 » ne normalisait jamais vers « Titre » canonique.

- Une requête Kitsu par genre, fusion entrelacée (chaque genre contribue)
- normalize_title retire saison/épisode en boucle avec frontières de mots
  (« - Saison 1 - E3 », « S1 E1 » → titre canonique ; « Série 9 » intact)
- test de non-régession : le possédé avec marqueurs n'est plus recommandé
This commit is contained in:
Roman
2026-09-25 15:23:29 +00:00
parent d5fa546dd7
commit fed61adc56
3 changed files with 86 additions and 23 deletions
+28 -9
View File
@@ -303,15 +303,34 @@ class DiscoverService:
return result
top_genres = sorted(genre_counts, key=genre_counts.get, reverse=True)[:_MAX_GENRES]
slugs = [category_slug(genre) for genre in top_genres]
items = await self._kitsu_anime(
{
"filter[categories]": ",".join(slugs),
"sort": "-userCount",
"page[limit]": limit, # le déjà-possédé est filtré après
}
)
kept = [item for item in items if item["title"] and item["title"].casefold() not in owned]
# Une requête par genre (Kitsu cumule les catégories en ET : quatre genres
# ensemble ne laissent que quelques titres — parfois le déjà-possédé !).
# Fusion entrelacée : chaque genre contribue, doublons retirés.
pools = [
list(pool)
for pool in await asyncio.gather(*(
self._kitsu_anime(
{"filter[categories]": category_slug(g), "sort": "-userCount", "page[limit]": limit}
)
for g in top_genres
))
if pool
]
candidates: list[dict] = []
seen: set[str] = set()
while pools:
for pool in pools[:]:
item = pool.pop(0)
key = str(item.get("kitsu_id") or item.get("title", "").casefold())
if key not in seen:
seen.add(key)
candidates.append(item)
if not pool:
pools.remove(pool)
kept = [
item for item in candidates
if item["title"] and normalize_title(item["title"]).casefold() not in owned
][:limit]
result = {"based_on": top_genres, "items": kept}
self._cache.set(cache_key, result, _FOR_YOU_TTL_SECONDS)
return result