cinematch-api / main.py
Leo
Update main.py
04dcc39 verified
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware
from pinecone import Pinecone
from sentence_transformers import SentenceTransformer
from thefuzz import process, fuzz
import random
import os
from supabase import create_client, Client
PINECONE_API_KEY = os.environ.get("PINECONE_API_KEY")
INDEX_NAME = "cine-match"
if not PINECONE_API_KEY:
env_path = os.path.join(os.path.dirname(__file__), ".env")
if os.path.exists(env_path):
with open(env_path, "r", encoding="utf-8") as f:
for line in f:
if line.strip().startswith("PINECONE_API_KEY"):
parts = line.split("=", 1)
if len(parts) > 1:
PINECONE_API_KEY = parts[1].strip().strip('"').strip("'")
break
if not PINECONE_API_KEY:
raise RuntimeError(
"PINECONE_API_KEY not set. Add it to environment or ml-engine/.env"
)
SUPABASE_URL = os.environ.get("SUPABASE_URL")
SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
print("⏳ Loading AI Model...")
model = SentenceTransformer('all-MiniLM-L6-v2')
pc = Pinecone(api_key=PINECONE_API_KEY)
index = pc.Index(INDEX_NAME)
print("✅ Brain Online!")
class SearchRequest(BaseModel):
query: str
filter_type: str = "All"
class QuizRequest(BaseModel):
genre: str
class FinalRecommendationRequest(BaseModel):
mood: str
selected_titles: list[str]
genre: str
def log_search(query_text: str, search_type: str):
try:
supabase.table("logs").insert({
"query": query_text,
"type": search_type
}).execute()
except Exception as e:
print(f"Error logging search: {e}")
@app.post("/search")
def semantic_search(req: SearchRequest):
log_search(req.query, "search_bar")
try:
query_vector = model.encode(req.query).tolist()
filter_dict = {}
if req.filter_type != "All":
filter_dict = {"type": req.filter_type}
results = index.query(
vector=query_vector,
top_k=80,
include_metadata=True,
filter=filter_dict if filter_dict else None
)
candidates = []
for match in results['matches']:
meta = match['metadata']
candidates.append({
"id": meta['original_id'],
"title": meta['title'],
"type": meta['type'],
"score": match['score'],
"rating": meta.get('rating', 0)
})
final_results = []
for item in candidates:
fuzzy_score = fuzz.ratio(req.query.lower(), item['title'].lower())
if fuzzy_score > 85:
item['score'] += 2.0
elif fuzz.partial_ratio(req.query.lower(), item['title'].lower()) > 90:
item['score'] += 0.5
final_results.append(item)
final_results.sort(key=lambda x: x['score'], reverse=True)
return {"results": final_results[:20]}
except Exception as e:
print(f"Error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/mood")
def mood_search(mood: str):
mood_map = {
"Happy": "Feel good movie, comedy, lighthearted, happy ending",
"Dark": "Dark, psychological thriller, disturbing, gritty, noir",
"Adrenaline": "High stakes action, fast paced, car chases, explosions",
"Mind-Bending": "Confusing plot, time travel, philosophy, deep thoughts",
"Romantic": "Love story, romance, heartbreak, relationship",
"Scary": "Horror, ghosts, jump scares, terrifying"
}
search_query = mood_map.get(mood, mood)
return semantic_search(SearchRequest(query=search_query))
@app.post("/get-quiz-items")
def get_quiz_items(req: QuizRequest):
query = f"Popular, famous, high rated {req.genre} movies or anime"
vector = model.encode(query).tolist()
results = index.query(
vector=vector, top_k=20, include_metadata=True,
filter={"type": "Anime" if req.genre == "Anime" else "Movie"}
)
items = [{"id": m['metadata']['original_id'], "title": m['metadata']['title'], "type": m['metadata']['type'], "poster": None} for m in results['matches']]
return {"items": items}
@app.post("/hybrid-recommend")
def hybrid_recommend(req: FinalRecommendationRequest):
joined_titles = ", ".join(req.selected_titles)
semantic_query = f"{req.mood} {req.genre} similar to {joined_titles}"
log_search(semantic_query, "hybrid_wizard")
query_vector = model.encode(semantic_query).tolist()
results = index.query(vector=query_vector, top_k=60, include_metadata=True)
recommendations = []
for match in results['matches']:
meta = match['metadata']
if meta['title'] in req.selected_titles: continue
reason = f"Because you liked {random.choice(req.selected_titles)} and wanted something {req.mood}."
recommendations.append({
"id": meta['original_id'],
"title": meta['title'],
"type": meta['type'],
"score": match['score'],
"rating": meta.get('rating', 0),
"reason": reason
})
return {"results": recommendations}
@app.get("/lucky")
def lucky_pick():
vector = model.encode("Masterpiece, highly rated, famous, classic, 5 stars").tolist()
results = index.query(vector=vector, top_k=50, include_metadata=True)
if not results['matches']: raise HTTPException(status_code=404, detail="No movies found")
match = random.choice(results['matches'])
meta = match['metadata']
return {
"id": meta['original_id'], "title": meta['title'], "type": meta['type'],
"rating": meta.get('rating', 0), "reason": "Serendipity ✨"
}