Skip to content

Commit 6571add

Browse files
chore: improve recommendation weights and refactor
1 parent cf277a0 commit 6571add

File tree

7 files changed

+333
-59
lines changed

7 files changed

+333
-59
lines changed

app/services/recommendation_service.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,13 @@ async def _fetch_details(tmdb_id: int):
155155
# Extract IMDB ID from external_ids
156156
external_ids = details.get("external_ids", {})
157157
imdb_id = external_ids.get("imdb_id")
158-
tmdb_id = details.get("id")
158+
# tmdb_id = details.get("id")
159159

160160
# Prefer IMDB ID, fallback to TMDB ID
161-
stremio_id = imdb_id if imdb_id else f"tmdb:{tmdb_id}"
161+
if imdb_id:
162+
stremio_id = imdb_id
163+
else: # skip content if imdb id is not available
164+
continue
162165

163166
# Construct Stremio meta object
164167
title = details.get("title") or details.get("name")
@@ -179,6 +182,7 @@ async def _fetch_details(tmdb_id: int):
179182

180183
meta_data = {
181184
"id": stremio_id,
185+
"imdb_id": stremio_id,
182186
"type": "series" if media_type in ["tv", "series"] else "movie",
183187
"name": title,
184188
"poster": poster_url,

app/services/row_generator.py

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from pydantic import BaseModel
44

55
from app.models.profile import UserTasteProfile
6-
from app.services.tmdb.genre import movie_genres, series_genres
6+
from app.services.tmdb.countries import COUNTRY_ADJECTIVES
7+
from app.services.tmdb.genre import GENRE_ADJECTIVES, movie_genres, series_genres
78
from app.services.tmdb_service import TMDBService
89

910

@@ -32,45 +33,6 @@ class RowGeneratorService:
3233
def __init__(self, tmdb_service: TMDBService | None = None):
3334
self.tmdb_service = tmdb_service or TMDBService()
3435

35-
# Adjectives to spice up titles based on genres
36-
GENRE_ADJECTIVES = {
37-
28: ["Adrenaline-Pumping", "Explosive", "Hard-Hitting"], # Action
38-
12: ["Epic", "Globe-Trotting", "Daring"], # Adventure
39-
878: ["Mind-Bending", "Futuristic", "Dystopian"], # Sci-Fi
40-
27: ["Bone-Chilling", "Nightmarish", "Terrifying"], # Horror
41-
53: ["Edge-of-your-Seat", "Suspenseful", "Slow-Burn"], # Thriller
42-
10749: ["Heartwarming", "Passionate", "Bittersweet"], # Romance
43-
35: ["Laugh-Out-Loud", "Witty", "Feel-Good"], # Comedy
44-
18: ["Critically Acclaimed", "Powerful", "Emotional"], # Drama
45-
14: ["Magical", "Otherworldly", "Enchanting"], # Fantasy
46-
9648: ["Mysterious", "Puzzle-Box", "Twisted"], # Mystery
47-
80: ["Gritty", "Noir", "Underworld"], # Crime
48-
}
49-
50-
# Country Name Map
51-
COUNTRY_NAMES = {
52-
"US": "American",
53-
"GB": "British",
54-
"FR": "French",
55-
"DE": "French",
56-
"JP": "Japanese",
57-
"KR": "Korean",
58-
"IN": "Indian",
59-
"CN": "Chinese",
60-
"ES": "Spanish",
61-
"IT": "Spanish",
62-
"CA": "Canadian",
63-
"AU": "Australian",
64-
"HK": "Hong Kong",
65-
"TW": "Hong Kong",
66-
"RU": "Russian",
67-
"BR": "Brazilian",
68-
"MX": "Brazilian",
69-
"SE": "Swedish",
70-
"DK": "Swedish",
71-
"NO": "Danish",
72-
}
73-
7436
async def generate_rows(self, profile: UserTasteProfile, content_type: str = "movie") -> list[RowDefinition]:
7537
"""
7638
Generate a diverse set of 3-5 thematic rows.
@@ -91,12 +53,15 @@ def get_gname(gid):
9153
return genre_map.get(gid, "Movies")
9254

9355
def get_cname(code):
94-
return self.COUNTRY_NAMES.get(code, "")
56+
adjectives = COUNTRY_ADJECTIVES.get(code, [])
57+
if adjectives:
58+
return random.choice(adjectives)
59+
return ""
9560

9661
# Strategy 1: Genre + Mood (Adjective)
9762
if top_genres:
9863
g_id = top_genres[0][0]
99-
adj = random.choice(self.GENRE_ADJECTIVES.get(g_id, ["Essential"]))
64+
adj = random.choice(GENRE_ADJECTIVES.get(g_id, ["Essential"]))
10065
rows.append(
10166
RowDefinition(
10267
title=f"{adj} {get_gname(g_id)}",

app/services/scoring.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ class ScoringService:
99
It consumes raw dictionary data or Pydantic models and returns enriched ScoredItems.
1010
"""
1111

12-
# TODO: Handle this for series
12+
# TODO: Make this a bit more complex based on more parameters.
13+
# Rewatch, How many times? Watched but duration?? What if user stopped watching in middle?
1314

1415
# Weights for different factors
15-
WEIGHT_WATCH_PERCENTAGE = 0.35
16-
WEIGHT_REWATCH = 0.15
17-
WEIGHT_RECENCY = 0.15
16+
WEIGHT_WATCH_PERCENTAGE = 0.25
17+
WEIGHT_REWATCH = 0.20
18+
WEIGHT_RECENCY = 0.20
1819
WEIGHT_EXPLICIT_RATING = 0.3
1920
ADDED_TO_LIBRARY_WEIGHT = 0.05
2021

app/services/stremio_service.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import asyncio
22

33
import httpx
4+
from async_lru import alru_cache
45
from loguru import logger
56

67
from app.core.config import settings
78

89
BASE_CATALOGS = [
910
{"type": "movie", "id": "watchly.rec", "name": "Top Picks for You", "extra": []},
1011
{"type": "series", "id": "watchly.rec", "name": "Top Picks for You", "extra": []},
11-
{"type": "movie", "id": "watchly.trending", "name": "Trending Movies", "extra": []},
12-
{"type": "series", "id": "watchly.trending", "name": "Trending Series", "extra": []},
13-
{"type": "movie", "id": "watchly.gems", "name": "Hidden Gems", "extra": []},
14-
{"type": "series", "id": "watchly.gems", "name": "Hidden Gems", "extra": []},
1512
]
1613

1714

@@ -137,6 +134,24 @@ async def is_loved(self, auth_key: str, imdb_id: str, media_type: str) -> tuple[
137134
)
138135
return False, False
139136

137+
@alru_cache(maxsize=1000, ttl=3600)
138+
async def get_loved_items(self, auth_token: str, media_type: str) -> list[dict]:
139+
async with httpx.AsyncClient() as client:
140+
url = f"https://likes.stremio.com/addons/loved/movies-shows/{auth_token}/catalog/{media_type}/stremio-loved-{media_type.lower()}.json" # noqa: E501
141+
response = await client.get(url)
142+
response.raise_for_status()
143+
metas = response.json().get("metas", [])
144+
return [meta.get("id") for meta in metas]
145+
146+
@alru_cache(maxsize=1000, ttl=3600)
147+
async def get_liked_items(self, auth_token: str, media_type: str) -> list[dict]:
148+
async with httpx.AsyncClient() as client:
149+
url = f"https://likes.stremio.com/addons/liked/movies-shows/{auth_token}/catalog/{media_type}/stremio-liked-{media_type.lower()}.json" # noqa: E501
150+
response = await client.get(url)
151+
response.raise_for_status()
152+
metas = response.json().get("metas", [])
153+
return [meta.get("id") for meta in metas]
154+
140155
async def get_library_items(self) -> dict[str, list[dict]]:
141156
"""
142157
Fetch library items from Stremio once and return both watched and loved items.

0 commit comments

Comments
 (0)