import os import numpy as np import pandas as pd import requests import joblib import concurrent.futures from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity # === Setup Surprise data folder before importing Surprise === surprise_data_dir = os.path.join(os.getcwd(), "surprise_data") os.makedirs(surprise_data_dir, exist_ok=True) os.environ["SURPRISE_DATA_FOLDER"] = surprise_data_dir from surprise import Reader, Dataset, SVD from surprise.model_selection import cross_validate class Model: def __init__(self, model_name): self.model_name = '' self.model_data = '' if model_name == 'Movie': self.model_name = model_name self.model_data = pd.read_csv('data/MovieBasedRecommender.csv') elif model_name == 'User': self.model_name = model_name self.model_data = pd.read_csv('data/MovieBasedRecommender.csv') self.model_svd = joblib.load('model/SVD.joblib') self.model_id = pd.read_csv('data/movie_id.csv') self.model_ratings = pd.read_csv('data/ratings_small.csv') elif model_name == 'User & Movie': self.model_name = model_name self.model_data = pd.read_csv('data/MovieBasedRecommender.csv') self.model_svd = joblib.load('model/SVD.joblib') self.model_id = pd.read_csv('data/movie_id.csv') self.model_ratings = pd.read_csv('data/ratings_small.csv') self.model_feature = self.model_data['model_feature'].fillna('') self.tfidf_matrix = self.calculate_tfidf(self.model_feature) def api_poster(self, movie_id): url = f"https://api.themoviedb.org/3/movie/{movie_id}?api_key=6b8ac0d80d854198821514621e36ae32&language=en-US" headers = {"accept": "application/json"} response = requests.get(url, headers=headers).json() full_path = 'data/No-Image-Placeholder.png' if 'poster_path' in response and response['poster_path']: full_path = "https://image.tmdb.org/t/p/w500/" + response['poster_path'] return full_path def calculate_tfidf(self, data): tfidf = TfidfVectorizer(analyzer='word', ngram_range=(1, 2), min_df=0.0, stop_words='english') return tfidf.fit_transform(data) def combine_recommendation(self, title, movie_indices): data = self.model_data.reset_index() idx = data[data['title'] == title].index[0] cosine_sim = cosine_similarity(self.tfidf_matrix[int(idx)], self.tfidf_matrix) similarity_scores = sorted(list(enumerate(cosine_sim[0])), key=lambda x: x[1], reverse=True)[1:501] movie_indices_combine = [i[0] if i[0] in movie_indices else None for i in similarity_scores] names = set() for i in movie_indices_combine: if i is not None: names.add(self.model_data['title'].iloc[i]) return names, set(movie_indices_combine) def hybrid_svd(self, userId, title): data = self.model_data.reset_index() idx = data[data['title'] == title].index[0] cosine_sim = cosine_similarity(self.tfidf_matrix[int(idx)], self.tfidf_matrix) sim_scores = sorted(list(enumerate(cosine_sim[0])), key=lambda x: x[1], reverse=True)[1:20] movie_indices = [i[0] for i in sim_scores] movies = self.model_data.iloc[movie_indices][['title', 'id']] tempLinks = self.model_id[self.model_id['tmdbId'].isin(movies['id'].tolist())] l = [self.model_svd.predict(userId, item).est for item in tempLinks["movieId"]] tempData = pd.DataFrame({"title": movies['title'], "est": l, "id": movies['id']}) tempData = tempData.sort_values('est', ascending=False) tempData = tempData[tempData["est"] >= 3] return tempData def recommender(self, title=None, userId=None, similarity_weight=0.9, top_n=1000): if self.model_name == 'Movie': data = self.model_data.reset_index() idx = data[data['title'] == title].index[0] cosine_sim = cosine_similarity(self.tfidf_matrix[int(idx)], self.tfidf_matrix) similarity = cosine_sim[0].T sim_data = pd.DataFrame(similarity, columns=['similarity']) final_data = pd.concat([data, sim_data], axis=1) final_data['final_score'] = final_data['score']*(1-similarity_weight) + final_data['similarity']*similarity_weight final_data_sorted = final_data.sort_values(by='final_score', ascending=False).head(top_n) self_index = final_data_sorted[final_data_sorted['title'] == title].index final_data_sorted.drop(self_index, inplace=True) remove_indices = final_data_sorted[final_data_sorted['similarity'] < 0.01].index final_data_sorted.drop(remove_indices, inplace=True) movies_indices = final_data_sorted.index.tolist() posters = [self.api_poster(self.model_data['id'].iloc[i]) for i in movies_indices[:10]] names = [self.model_data['title'].iloc[i] for i in movies_indices[:10]] return names, posters, movies_indices elif self.model_name == 'User': tempRatings = self.model_ratings[self.model_ratings['userId'] == userId] tempRatings = tempRatings[tempRatings["rating"] >= 3.5] tempLinks = self.model_id[self.model_id['tmdbId'].isin(tempRatings['movieId'].tolist())] titlesData = self.model_data[self.model_data["id"].isin(tempLinks["tmdbId"])] resultDataFrame = pd.DataFrame() with concurrent.futures.ThreadPoolExecutor() as executor: futures = [executor.submit(self.hybrid_svd, userId, i) for i in titlesData["title"]] for future in concurrent.futures.as_completed(futures): try: resultDataFrame = pd.concat([future.result(), resultDataFrame], ignore_index=True) except: print('connect time out') return resultDataFrame.sort_values('est', ascending=False) elif self.model_name == 'User & Movie': return self.hybrid_svd(userId=userId, title=title)