from django.db.models import F from profiles.models import Pantry from recipes.models import Ingredient, UnitConversion import pandas as pd import numpy as np from sklearn.metrics.pairwise import cosine_similarity from .models import Favorite from django.db.models import Q def get_owned_raw_ingredients(user): pantry_ids = Pantry.objects.filter(user=user).values_list('id', flat=True) unit_conversions = UnitConversion.objects.select_related( 'higher_unit', 'lower_unit').all() owned_raw_ingredient_ids = set() for pantry_id in pantry_ids: pantry = Pantry.objects.select_related( 'unit', 'raw_ingredient').get(id=pantry_id) raw_ingredient_id = pantry.raw_ingredient_id ingredients = Ingredient.objects.filter( Q(raw_ingredient_id=raw_ingredient_id) & Q(raw_ingredient__pantry__user=user) ).select_related('unit') for ingredient in ingredients: if pantry.unit is None: owned_raw_ingredient_ids.add(raw_ingredient_id) elif ingredient.unit is None: continue else: if ingredient.unit == pantry.unit: if pantry.amount >= ingredient.amount: owned_raw_ingredient_ids.add(raw_ingredient_id) else: if ingredient.unit.type == "measured" and pantry.unit.type == "measured": if pantry.unit.hierarchy <= ingredient.unit.hierarchy: higher = pantry lower = ingredient else: higher = ingredient lower = pantry conversion = UnitConversion.objects.get( higher_unit_id=higher.unit.pk, lower_unit_id=lower.unit.pk) if conversion: converted_amount = conversion.value * higher.amount if higher == pantry and converted_amount >= lower.amount: owned_raw_ingredient_ids.add(raw_ingredient_id) elif higher == ingredient and lower.amount >= converted_amount: owned_raw_ingredient_ids.add(raw_ingredient_id) else: common_unit_id = find_common_unit( higher.unit.id, lower.unit.id, unit_conversions ) if common_unit_id: converted_higher_amount = convert_amount( higher.amount, higher.unit.id, common_unit_id, unit_conversions ) converted_lower_amount = convert_amount( lower.amount, lower.unit.id, common_unit_id, unit_conversions ) if converted_higher_amount >= converted_lower_amount: owned_raw_ingredient_ids.add( raw_ingredient_id) else: if ingredient.unit.category == pantry.unit.category: if pantry.unit.hierarchy < ingredient.unit.hierarchy: owned_raw_ingredient_ids.add(raw_ingredient_id) elif pantry.unit.hierarchy == ingredient.unit.hierarchy: if pantry.amount >= ingredient.amount: owned_raw_ingredient_ids.add( raw_ingredient_id) elif pantry.unit.category == 'item': owned_raw_ingredient_ids.add(raw_ingredient_id) return owned_raw_ingredient_ids def find_common_unit(higher_unit_id, lower_unit_id, unit_conversions): if higher_unit_id == lower_unit_id: return higher_unit_id conversion = unit_conversions.filter( higher_unit_id=higher_unit_id, lower_unit_id=lower_unit_id ).first() if conversion: return lower_unit_id conversion = unit_conversions.filter( higher_unit_id=lower_unit_id, lower_unit_id=higher_unit_id ).first() if conversion: return higher_unit_id for conversion in unit_conversions: if conversion.higher_unit_id == higher_unit_id: common_unit = find_common_unit( conversion.lower_unit_id, lower_unit_id, unit_conversions ) if common_unit: return common_unit return None def convert_amount(amount, from_unit_id, to_unit_id, unit_conversions): if from_unit_id == to_unit_id: return amount conversion = unit_conversions.filter( higher_unit_id=from_unit_id, lower_unit_id=to_unit_id ).first() if conversion: return amount * conversion.value conversion = unit_conversions.filter( higher_unit_id=to_unit_id, lower_unit_id=from_unit_id ).first() if conversion: return amount / conversion.value common_unit_id = find_common_unit( from_unit_id, to_unit_id, unit_conversions) if common_unit_id: return convert_amount( convert_amount(amount, from_unit_id, common_unit_id, unit_conversions), common_unit_id, to_unit_id, unit_conversions ) return None def get_owned_recipe_ingredients(user, recipe_id): pantry_ids = Pantry.objects.filter(user=user).values_list('id', flat=True) unit_conversions = UnitConversion.objects.select_related( 'higher_unit', 'lower_unit').all() owned_ingredient_ids = set() for pantry_id in pantry_ids: pantry = Pantry.objects.select_related( 'unit', 'raw_ingredient').get(id=pantry_id) raw_ingredient_id = pantry.raw_ingredient_id ingredients = Ingredient.objects.filter( recipe_id=recipe_id, raw_ingredient_id=raw_ingredient_id, raw_ingredient__pantry__user=user).select_related('unit') for ingredient in ingredients: if pantry.unit is None: owned_ingredient_ids.add(ingredient.id) elif ingredient.unit is None: continue else: if ingredient.unit == pantry.unit: if pantry.amount >= ingredient.amount: owned_ingredient_ids.add(ingredient.id) else: if ingredient.unit.type == "measured" and pantry.unit.type == "measured": if pantry.unit.hierarchy <= ingredient.unit.hierarchy: higher = pantry lower = ingredient else: higher = ingredient lower = pantry conversion = UnitConversion.objects.get( higher_unit_id=higher.unit.pk, lower_unit_id=lower.unit.pk) if conversion: converted_amount = conversion.value * higher.amount if higher == pantry and converted_amount >= lower.amount: owned_ingredient_ids.add(ingredient.id) elif higher == ingredient and lower.amount >= converted_amount: owned_ingredient_ids.add(ingredient.id) else: common_unit_id = find_common_unit( higher.unit.id, lower.unit.id, unit_conversions ) if common_unit_id: converted_higher_amount = convert_amount( higher.amount, higher.unit.id, common_unit_id, unit_conversions ) converted_lower_amount = convert_amount( lower.amount, lower.unit.id, common_unit_id, unit_conversions ) if converted_higher_amount >= converted_lower_amount: owned_ingredient_ids.add(ingredient.id) else: if ingredient.unit.category == pantry.unit.category: if pantry.unit.hierarchy < ingredient.unit.hierarchy: owned_ingredient_ids.add(ingredient.id) elif pantry.unit.hierarchy == ingredient.unit.hierarchy: if pantry.amount >= ingredient.amount: owned_ingredient_ids.add(ingredient.id) elif pantry.unit.category == 'item': owned_ingredient_ids.add(ingredient.id) return owned_ingredient_ids def jaccard_similarity(owned, set1, set2): intersection = len(owned) union = len(set1.union(set2)) similarity = intersection / union return similarity def calculate_top_recipes_by_ingredients(user): pantry = set(Pantry.objects.filter(user=user).values_list('raw_ingredient_id', flat=True)) # Get valid recipes recipe_ingredients = Ingredient.objects.filter( raw_ingredient__pantry__user=user ).values('recipe_id', 'raw_ingredient_id', 'id', user_id=F('raw_ingredient__pantry__user_id')) data = {} for ingredient in recipe_ingredients: user_id = ingredient['user_id'] recipe_id = ingredient['recipe_id'] raw_ingredient_id = ingredient['raw_ingredient_id'] # ingredient_id = ingredient['id'] if recipe_id in data: data[recipe_id]['raw_ingredient_ids'].add(raw_ingredient_id) else: data[recipe_id] = { 'user_id': user_id, 'recipe_id': recipe_id, 'raw_ingredient_ids': {raw_ingredient_id} } for recipe_id, recipe_data in data.items(): raw_ingredient_ids = set(Ingredient.objects.filter(recipe_id=recipe_id).values_list('raw_ingredient_id', flat=True)) owned_raw_ingredient = set(get_owned_recipe_ingredients(user, recipe_id)) similarity = jaccard_similarity( owned_raw_ingredient, pantry, raw_ingredient_ids) recipe_data['similarity'] = similarity * 100 sorted_recipes = sorted( data.values(), key=lambda x: x['similarity'], reverse=True) return sorted_recipes def create_interaction_matrix(): favorites = Favorite.objects.all() user_ids = [] recipe_ids = [] for favorite in favorites: user_ids.append(favorite.user_id) recipe_ids.append(favorite.recipe_id) unique_user_ids = list(set(user_ids)) unique_recipe_ids = list(set(recipe_ids)) interaction_matrix = pd.DataFrame( 0, index=unique_user_ids, columns=unique_recipe_ids) for i in range(len(user_ids)): user_id = user_ids[i] recipe_id = recipe_ids[i] interaction_matrix.loc[user_id, recipe_id] = 1 return interaction_matrix def create_interaction_matrix_hybrid(): favorites = Favorite.objects.all() user_ids = [] recipe_ids = [] ratings = [] for favorite in favorites: user_ids.append(favorite.user_id) recipe_ids.append(favorite.recipe_id) ratings.append(favorite.rating) # Menambahkan rating unique_user_ids = list(set(user_ids)) unique_recipe_ids = list(set(recipe_ids)) interaction_matrix = pd.DataFrame( 0, index=unique_user_ids, columns=unique_recipe_ids) for i in range(len(user_ids)): user_id = user_ids[i] recipe_id = recipe_ids[i] rating = ratings[i] # Menggunakan rating sebagai nilai interaction_matrix.loc[user_id, recipe_id] = rating return interaction_matrix def calculate_cosine_similarity(): interaction_matrix = create_interaction_matrix() similarity_matrix = cosine_similarity(interaction_matrix.T) similarity_df = pd.DataFrame( similarity_matrix, index=interaction_matrix.columns, columns=interaction_matrix.columns) return similarity_df, interaction_matrix def get_recommendations_for_user(user_id): similarity_matrix, interaction_matrix = calculate_cosine_similarity() if user_id in interaction_matrix.index: scores = np.dot(interaction_matrix.values, similarity_matrix) recommended_items = [interaction_matrix.columns[i] for i in np.argsort(scores[interaction_matrix.index == user_id, :])[0][::-1]] recommendations = [] min_score = np.min(scores) max_score = np.max(scores) if min_score == max_score: return [] # Normalize the scores to a range of 0-100 normalized_scores = 100 * \ (scores - min_score) / (max_score - min_score) for item in recommended_items: score = scores[interaction_matrix.index == user_id, interaction_matrix.columns == item][0] similarity_score = normalized_scores[interaction_matrix.index == user_id, interaction_matrix.columns == item][0] if score > 0: recommendation = { 'recipe_id': item, 'similarity': round(similarity_score, 2), 'score': round(score, 2) } recommendations.append(recommendation) return recommendations else: return [] def get_hybrid_recommendation(user_id): similarity_matrix_cbf, _ = calculate_cosine_similarity() interaction_matrix = create_interaction_matrix() # Move this line here if user_id in interaction_matrix.index: similarity_matrix_ibcf = cosine_similarity(interaction_matrix.T) similarity_matrix = 0.6 * similarity_matrix_cbf + 0.4 * similarity_matrix_ibcf recommended_items = list(interaction_matrix.columns) scores = np.dot(interaction_matrix.values, similarity_matrix) scores_filtered = scores[interaction_matrix.index == user_id, :] sorted_indices = np.argsort(scores_filtered[0])[::-1] recommended_items_sorted = [interaction_matrix.columns[i] for i in sorted_indices if interaction_matrix.columns[i] in recommended_items] return recommended_items_sorted else: return []