mtoft20 commited on
Commit
ff04a4c
·
verified ·
1 Parent(s): 110654f

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +51 -128
src/streamlit_app.py CHANGED
@@ -3,10 +3,8 @@ import requests
3
  import pandas as pd
4
  from together import Together
5
  import os
6
- from sklearn.feature_extraction.text import TfidfVectorizer
7
- from sklearn.preprocessing import StandardScaler
8
- from sklearn.cluster import KMeans
9
- import numpy as np
10
 
11
  # =============================================================================
12
  # CONFIGURATION - Using Secrets Management
@@ -251,128 +249,54 @@ def extract_unique_names(content_list, field):
251
  unique_names.add(cleaned_name)
252
  return sorted(list(unique_names))
253
 
254
- @st.cache_data(ttl=3600) # Cache feature vectors for 1 hour
255
- def prepare_content_features(content_list):
256
- """Prepare and cache content features for similarity matching"""
257
- # Convert to DataFrame for easier processing
258
- df = pd.DataFrame(content_list)
259
-
260
- # Create TF-IDF vectorizer for text features
261
- tfidf = TfidfVectorizer(
262
- stop_words='english',
263
- max_features=1000, # Limit features to most important ones
264
- ngram_range=(1, 2) # Include bigrams for better context
265
- )
266
-
267
- # Combine relevant text fields with weights
268
- df['text_features'] = (
269
- df['listed_in'].fillna('') + ' ' + # Genres
270
- df['description'].fillna('') + ' ' + # Plot
271
- df['cast'].fillna('').apply(lambda x: ' '.join(x.split(',')[:3])) + ' ' + # Top 3 cast members
272
- df['director'].fillna('') # Director
273
- )
274
-
275
- # Get text features
276
- text_features = tfidf.fit_transform(df['text_features'])
277
-
278
- # Prepare numerical features
279
- df['release_year'] = pd.to_numeric(df['release_year'], errors='coerce').fillna(0)
280
-
281
- # Extract duration in minutes for movies
282
- def extract_duration_minutes(duration):
283
- if pd.isna(duration) or 'Season' in str(duration):
284
- return 0
285
- try:
286
- return int(str(duration).split()[0])
287
- except:
288
- return 0
289
-
290
- df['duration_minutes'] = df['duration'].apply(extract_duration_minutes)
291
-
292
- # Scale numerical features
293
- scaler = StandardScaler()
294
- numerical_features = scaler.fit_transform(df[['release_year', 'duration_minutes']])
295
-
296
- # Convert sparse matrix to dense for easier calculations
297
- text_features_dense = text_features.toarray()
298
-
299
- # Combine features with appropriate weights
300
- combined_features = np.hstack([
301
- text_features_dense * 0.8, # 80% weight to text features
302
- numerical_features * 0.2 # 20% weight to numerical features
303
- ])
304
-
305
- return combined_features, df
306
-
307
- @st.cache_resource # Cache the KMeans model
308
- def get_kmeans_model(n_clusters=50):
309
- """Get or create cached KMeans model"""
310
- return KMeans(
311
- n_clusters=n_clusters,
312
- random_state=42,
313
- n_init=10, # Number of times to run with different centroid seeds
314
- )
315
-
316
- @st.cache_data(ttl=3600) # Cache cluster assignments for 1 hour
317
- def get_content_clusters(features, n_clusters=50):
318
- """Get cluster assignments for all content"""
319
- kmeans = get_kmeans_model(n_clusters)
320
- return kmeans.fit_predict(features)
321
-
322
- def find_similar_content(content, all_content_list, n_clusters=50, n_recommendations=5):
323
- """Find similar content using optimized k-means clustering"""
324
  try:
325
- # Get cached features and cluster assignments
326
- features, df = prepare_content_features(all_content_list)
327
- cluster_labels = get_content_clusters(features, n_clusters)
328
-
329
- # Find the index of the selected content
330
- selected_idx = df[df['title'] == content['title']].index[0]
331
- selected_cluster = cluster_labels[selected_idx]
332
-
333
- # Get indices of content in the same cluster
334
- cluster_indices = np.where(cluster_labels == selected_cluster)[0]
335
-
336
- # If cluster is too small, adjust n_clusters and retry
337
- if len(cluster_indices) < n_recommendations + 1:
338
- cluster_labels = get_content_clusters(features, n_clusters // 2)
339
- selected_cluster = cluster_labels[selected_idx]
340
- cluster_indices = np.where(cluster_labels == selected_cluster)[0]
341
-
342
- # Calculate distances to all points in the same cluster
343
- cluster_features = features[cluster_indices]
344
- selected_features = features[selected_idx].reshape(1, -1)
345
 
346
- # Use euclidean distance for similarity
347
- distances = np.linalg.norm(cluster_features - selected_features, axis=1)
 
 
 
348
 
349
- # Get indices of most similar content (excluding self)
350
- similar_indices = cluster_indices[np.argsort(distances)][1:n_recommendations+1]
351
 
352
- # Convert to list of content dictionaries
353
- similar_content = []
354
- for idx in similar_indices:
355
- row = df.iloc[idx]
356
- similarity_score = 1 / (1 + distances[np.where(cluster_indices == idx)[0][0]])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
- content_dict = {
359
- 'title': row['title'],
360
- 'type': row['type'],
361
- 'release_year': row['release_year'],
362
- 'listed_in': row['listed_in'],
363
- 'description': row['description'],
364
- 'streaming_service': row['streaming_service'],
365
- 'rating': row['rating'],
366
- 'duration': row['duration'],
367
- 'cast': row['cast'],
368
- 'director': row['director'],
369
- 'similarity': f"{similarity_score:.2%}"
370
- }
371
- similar_content.append(content_dict)
372
-
373
- return similar_content
374
  except Exception as e:
375
- st.error(f"Error finding similar content: {str(e)}")
376
  return []
377
 
378
  # =============================================================================
@@ -630,7 +554,7 @@ def main():
630
  similar_button = st.button(f"🔍 Find Similar Content", key=f"similar_{i}")
631
  if similar_button:
632
  with st.spinner("Finding similar content..."):
633
- similar_content = find_similar_content(content, all_content)
634
 
635
  if similar_content:
636
  # Create tabs for different aspects of recommendations
@@ -656,14 +580,13 @@ def main():
656
  with sim_tab2:
657
  st.write("**Why these recommendations?**")
658
  st.write("""
659
- These titles were selected based on our k-means clustering algorithm which groups similar content together using:
660
- - Genre matching and themes (80% weight)
661
- - Release year proximity (10% weight)
662
- - Duration similarity (10% weight)
663
- - Cast and director overlap
664
- - Content description similarity
665
 
666
- The percentage match indicates how close each title is to your selected content within its cluster.
667
  """)
668
  else:
669
  st.info("No similar content found.")
 
3
  import pandas as pd
4
  from together import Together
5
  import os
6
+ import json
7
+ from collections import defaultdict
 
 
8
 
9
  # =============================================================================
10
  # CONFIGURATION - Using Secrets Management
 
249
  unique_names.add(cleaned_name)
250
  return sorted(list(unique_names))
251
 
252
+ def get_similar_content(content, n_recommendations=5):
253
+ """Get pre-computed similar content from database"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  try:
255
+ # Get database credentials
256
+ api_token, _ = get_api_credentials()
257
+ headers = {
258
+ "xc-token": api_token,
259
+ "accept": "application/json"
260
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
+ # Query the similarities table
263
+ similarity_table_url = "https://mtoft20-potm.hf.space/api/v1/db/data/noco/p9pozkcw81t9aee/content_similarities"
264
+ params = {
265
+ "where": f"(title,eq,\"{content['title']}\")"
266
+ }
267
 
268
+ response = requests.get(similarity_table_url, headers=headers, params=params)
 
269
 
270
+ if response.status_code == 200:
271
+ data = response.json()
272
+ if data and len(data.get('list', [])) > 0:
273
+ # Get similar items from stored data
274
+ similar_items = json.loads(data['list'][0]['similar_items'])
275
+
276
+ # Limit to requested number
277
+ similar_items = similar_items[:n_recommendations]
278
+
279
+ # Get full content details for each similar item
280
+ similar_content = []
281
+ for item in similar_items:
282
+ # Query main content table for full details
283
+ content_params = {
284
+ "where": f"(title,eq,\"{item['title']}\")"
285
+ }
286
+ content_response = requests.get(NOCODB_URL, headers=headers, params=content_params)
287
+
288
+ if content_response.status_code == 200:
289
+ content_data = content_response.json()
290
+ if content_data and len(content_data.get('list', [])) > 0:
291
+ content_dict = content_data['list'][0]
292
+ content_dict['similarity'] = f"{item['similarity']:.2%}"
293
+ similar_content.append(content_dict)
294
+
295
+ return similar_content
296
 
297
+ return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  except Exception as e:
299
+ st.error(f"Error fetching similar content: {str(e)}")
300
  return []
301
 
302
  # =============================================================================
 
554
  similar_button = st.button(f"🔍 Find Similar Content", key=f"similar_{i}")
555
  if similar_button:
556
  with st.spinner("Finding similar content..."):
557
+ similar_content = get_similar_content(content)
558
 
559
  if similar_content:
560
  # Create tabs for different aspects of recommendations
 
580
  with sim_tab2:
581
  st.write("**Why these recommendations?**")
582
  st.write("""
583
+ These recommendations are pre-computed using advanced content analysis:
584
+ - Genre and theme matching
585
+ - Plot similarity analysis
586
+ - Cast and director relationships
587
+ - Release year proximity
 
588
 
589
+ The percentage match indicates how similar each title is to your selection.
590
  """)
591
  else:
592
  st.info("No similar content found.")