nabilah_argyanti_ardyningrum/backend/easycookapi-main/profiles/utils.py
2024-12-31 09:49:01 +07:00

362 lines
14 KiB
Python

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 []