TasteEngine / recommender /explainer.py
Abdallah4z's picture
Add user creation API and enhance user preference handling in the frontend
28e5413
class Explainer:
def __init__(self, products_df, users_df):
self.products = products_df
self.users = users_df
self._product_cache = {}
self._user_cache = {}
for _, row in products_df.iterrows():
self._product_cache[row["product_id"]] = row
for _, row in users_df.iterrows():
self._user_cache[row["user_id"]] = row
def _get_product(self, product_id):
return self._product_cache.get(product_id, {})
def _get_user(self, user_id):
return self._user_cache.get(user_id, {})
def _fmt_score(self, score):
if score >= 0.9:
return "Excellent match"
elif score >= 0.7:
return "Strong match"
elif score >= 0.5:
return "Good match"
elif score >= 0.3:
return "Moderate match"
else:
return "Partial match"
def _pref_list(self, user, key):
raw = user.get(key, "")
if raw is None or isinstance(raw, float):
return []
if isinstance(raw, str):
return [p.strip() for p in raw.split(",") if p.strip()][:2]
return list(raw)[:2] if raw else []
def explain_cf(self, method, user_id, product_id, details=None):
product = self._get_product(product_id)
user = self._get_user(user_id)
pname = product.get("name", f"Item #{product_id}")
cat = product.get("category", "")
brand = product.get("brand", "")
price = product.get("price", 0)
details = details or {}
sim = min(details.get('sim_score', 0), 1.0)
pref_cats = self._pref_list(user, "preferred_categories")
templates = {
"user_based": f"Users with {cat} taste also liked {pname}",
"item_based": f"Matches your {cat} preferences — similar to items you've rated highly",
"svd": f"Fits your profile: {brand}'s {cat} — top latent factor match" if brand and cat else f"Fits your profile: {cat} — top latent factor match",
"knn": f"Popular among peers who also like {cat}" if cat else f"Popular among users with similar taste",
"slope_one": f"Frequently chosen by users who liked the same {cat} products" if cat else f"Frequently chosen by users with your taste",
}
return templates.get(method, f"Recommended based on collaborative filtering")
def explain_content(self, method, user_id, product_id, details=None):
product = self._get_product(product_id)
user = self._get_user(user_id)
pname = product.get("name", f"Item #{product_id}")
cat = product.get("category", "")
brand = product.get("brand", "")
sub = product.get("subcategory", "")
details = details or {}
score = min(details.get('score', 0), 1.0)
pref_cats = self._pref_list(user, "preferred_categories")
templates = {
"tfidf": f"Content matches: {cat} / {sub} — strongly aligns with your past likes" if sub else f"Content matches your preferred {cat} items",
"feature_match": f"Matches your preferences: {cat}, {brand}" if brand else f"Matches your preferred category: {cat}",
}
return templates.get(method, f"Recommended based on item features")
def explain_knowledge(self, method, user_id, product_id, details=None):
product = self._get_product(product_id)
user = self._get_user(user_id)
details = details or {}
pname = product.get("name", f"Item #{product_id}")
cat = product.get("category", "")
brand = product.get("brand", "")
price = product.get("price", 0)
budget_max = details.get("budget_max", 0)
templates = {
"constraint": f"Fits your criteria: ${price:.0f} within budget · {cat} · {brand}" if brand else f"Fits your criteria: ${price:.0f} within budget · {cat}",
"rule": f"{cat} buyers commonly add {pname} — cross-sell match" if cat else f"Customers also purchased {pname}",
"utility": f"Top utility score: {cat} × {brand} matches your preference weights" if brand else f"Top utility score: {cat} matches your preference weights",
}
return templates.get(method, f"Recommended based on your requirements")
def get_explanation(self, approach, method, user_id, product_id, details=None):
if approach == "cf":
return self.explain_cf(method, user_id, product_id, details)
elif approach == "content":
return self.explain_content(method, user_id, product_id, details)
elif approach == "knowledge":
return self.explain_knowledge(method, user_id, product_id, details)
return ""