447 lines
17 KiB
Python
447 lines
17 KiB
Python
"""
|
|
Recommendation Service für ML-basierte Empfehlungen
|
|
"""
|
|
|
|
import numpy as np
|
|
from django.db.models import Q, Count, Avg
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
from .models import UserBehavior, UserProfile, ProductSimilarity, Recommendation, RecommendationModel
|
|
from products.models import Product
|
|
import json
|
|
|
|
|
|
class RecommendationService:
|
|
"""Service für ML-basierte Empfehlungen"""
|
|
|
|
def __init__(self):
|
|
self.collaborative_weight = 0.4
|
|
self.content_based_weight = 0.3
|
|
self.popular_weight = 0.2
|
|
self.trending_weight = 0.1
|
|
|
|
def get_recommendations_for_user(self, user, limit=10):
|
|
"""Personalisiertes Empfehlungen für User"""
|
|
|
|
recommendations = []
|
|
|
|
# Get user profile
|
|
profile, created = UserProfile.objects.get_or_create(user=user)
|
|
|
|
# 1. Collaborative Filtering
|
|
collaborative_recs = self._get_collaborative_recommendations(user, limit=limit//2)
|
|
recommendations.extend(collaborative_recs)
|
|
|
|
# 2. Content-based Filtering
|
|
content_recs = self._get_content_based_recommendations(user, profile, limit=limit//2)
|
|
recommendations.extend(content_recs)
|
|
|
|
# 3. Popular Products (fallback)
|
|
if len(recommendations) < limit:
|
|
popular_recs = self._get_popular_recommendations(limit=limit-len(recommendations))
|
|
recommendations.extend(popular_recs)
|
|
|
|
# 4. Trending Products (fallback)
|
|
if len(recommendations) < limit:
|
|
trending_recs = self._get_trending_recommendations(limit=limit-len(recommendations))
|
|
recommendations.extend(trending_recs)
|
|
|
|
# Remove duplicates and sort by confidence
|
|
unique_recs = {}
|
|
for rec in recommendations:
|
|
product_id = rec['product']['id']
|
|
if product_id not in unique_recs or rec['confidence_score'] > unique_recs[product_id]['confidence_score']:
|
|
unique_recs[product_id] = rec
|
|
|
|
# Sort by confidence score
|
|
final_recommendations = sorted(
|
|
unique_recs.values(),
|
|
key=lambda x: x['confidence_score'],
|
|
reverse=True
|
|
)[:limit]
|
|
|
|
# Save recommendations to database
|
|
self._save_recommendations(user, final_recommendations)
|
|
|
|
return final_recommendations
|
|
|
|
def _get_collaborative_recommendations(self, user, limit=5):
|
|
"""Collaborative Filtering basierend auf ähnlichen Usern"""
|
|
|
|
recommendations = []
|
|
|
|
# Get users with similar behavior
|
|
similar_users = self._find_similar_users(user)
|
|
|
|
# Get products liked by similar users
|
|
for similar_user in similar_users[:5]:
|
|
liked_products = UserBehavior.objects.filter(
|
|
user=similar_user,
|
|
behavior_type__in=['purchase', 'cart_add', 'wishlist_add']
|
|
).values_list('product', flat=True).distinct()
|
|
|
|
for product_id in liked_products:
|
|
product = Product.objects.filter(id=product_id, is_active=True).first()
|
|
if product and not self._user_has_interacted(user, product):
|
|
recommendations.append({
|
|
'product': {
|
|
'id': product.id,
|
|
'name': product.name,
|
|
'price': float(product.price),
|
|
'image_url': product.image.url if product.image else None,
|
|
'category': product.category,
|
|
},
|
|
'confidence_score': self.collaborative_weight,
|
|
'recommendation_type': 'collaborative',
|
|
'reason': f'Ähnlich zu {similar_user.username}'
|
|
})
|
|
|
|
if len(recommendations) >= limit:
|
|
break
|
|
|
|
return recommendations
|
|
|
|
def _get_content_based_recommendations(self, user, profile, limit=5):
|
|
"""Content-based Filtering basierend auf User Preferences"""
|
|
|
|
recommendations = []
|
|
|
|
# Get user preferences
|
|
preferred_categories = profile.preferred_categories
|
|
preferred_fursuit_types = profile.preferred_fursuit_types
|
|
preferred_price_range = profile.preferred_price_range
|
|
|
|
# Build query based on preferences
|
|
query = Q(is_active=True)
|
|
|
|
if preferred_categories:
|
|
query &= Q(category__in=preferred_categories)
|
|
|
|
if preferred_fursuit_types:
|
|
query &= Q(fursuit_type__in=preferred_fursuit_types)
|
|
|
|
if preferred_price_range:
|
|
min_price = preferred_price_range.get('min', 0)
|
|
max_price = preferred_price_range.get('max', float('inf'))
|
|
query &= Q(price__gte=min_price, price__lte=max_price)
|
|
|
|
# Get products matching preferences
|
|
matching_products = Product.objects.filter(query).exclude(
|
|
behaviors__user=user
|
|
).distinct()[:limit*2]
|
|
|
|
for product in matching_products:
|
|
confidence = self._calculate_content_based_confidence(product, profile)
|
|
|
|
recommendations.append({
|
|
'product': {
|
|
'id': product.id,
|
|
'name': product.name,
|
|
'price': float(product.price),
|
|
'image_url': product.image.url if product.image else None,
|
|
'category': product.category,
|
|
},
|
|
'confidence_score': confidence,
|
|
'recommendation_type': 'content_based',
|
|
'reason': 'Basierend auf deinen Präferenzen'
|
|
})
|
|
|
|
# Sort by confidence and return top results
|
|
recommendations.sort(key=lambda x: x['confidence_score'], reverse=True)
|
|
return recommendations[:limit]
|
|
|
|
def _get_popular_recommendations(self, limit=5):
|
|
"""Beliebte Produkte basierend auf Verhalten"""
|
|
|
|
recommendations = []
|
|
|
|
# Get popular products
|
|
popular_products = Product.objects.annotate(
|
|
purchase_count=Count('behaviors', filter=Q(behaviors__behavior_type='purchase')),
|
|
view_count=Count('behaviors', filter=Q(behaviors__behavior_type='view')),
|
|
).filter(
|
|
is_active=True,
|
|
purchase_count__gt=0
|
|
).order_by('-purchase_count', '-view_count')[:limit]
|
|
|
|
for product in popular_products:
|
|
popularity_score = (product.purchase_count * 2 + product.view_count) / 100
|
|
confidence = min(0.8, popularity_score)
|
|
|
|
recommendations.append({
|
|
'product': {
|
|
'id': product.id,
|
|
'name': product.name,
|
|
'price': float(product.price),
|
|
'image_url': product.image.url if product.image else None,
|
|
'category': product.category,
|
|
},
|
|
'confidence_score': confidence,
|
|
'recommendation_type': 'popular',
|
|
'reason': 'Beliebt bei anderen Kunden'
|
|
})
|
|
|
|
return recommendations
|
|
|
|
def _get_trending_recommendations(self, limit=5):
|
|
"""Trending Produkte (recent activity)"""
|
|
|
|
recommendations = []
|
|
|
|
# Get recent activity (last 7 days)
|
|
recent_date = timezone.now() - timedelta(days=7)
|
|
|
|
trending_products = Product.objects.annotate(
|
|
recent_purchases=Count('behaviors', filter=Q(
|
|
behaviors__behavior_type='purchase',
|
|
behaviors__created_at__gte=recent_date
|
|
)),
|
|
recent_views=Count('behaviors', filter=Q(
|
|
behaviors__behavior_type='view',
|
|
behaviors__created_at__gte=recent_date
|
|
)),
|
|
).filter(
|
|
is_active=True,
|
|
recent_purchases__gt=0
|
|
).order_by('-recent_purchases', '-recent_views')[:limit]
|
|
|
|
for product in trending_products:
|
|
trending_score = (product.recent_purchases * 3 + product.recent_views) / 50
|
|
confidence = min(0.7, trending_score)
|
|
|
|
recommendations.append({
|
|
'product': {
|
|
'id': product.id,
|
|
'name': product.name,
|
|
'price': float(product.price),
|
|
'image_url': product.image.url if product.image else None,
|
|
'category': product.category,
|
|
},
|
|
'confidence_score': confidence,
|
|
'recommendation_type': 'trending',
|
|
'reason': 'Trending diese Woche'
|
|
})
|
|
|
|
return recommendations
|
|
|
|
def _find_similar_users(self, user, limit=10):
|
|
"""Finde ähnliche User basierend auf Verhalten"""
|
|
|
|
# Get user's behavior
|
|
user_behaviors = UserBehavior.objects.filter(user=user).values_list('product', 'behavior_type')
|
|
|
|
if not user_behaviors:
|
|
return []
|
|
|
|
# Find users with similar behavior
|
|
similar_users = []
|
|
|
|
for behavior in user_behaviors:
|
|
product_id, behavior_type = behavior
|
|
|
|
# Find users who had similar behavior with this product
|
|
similar_behaviors = UserBehavior.objects.filter(
|
|
product_id=product_id,
|
|
behavior_type=behavior_type
|
|
).exclude(user=user).values_list('user', flat=True)
|
|
|
|
similar_users.extend(similar_behaviors)
|
|
|
|
# Count occurrences and get top users
|
|
from collections import Counter
|
|
user_counts = Counter(similar_users)
|
|
|
|
return [user_id for user_id, count in user_counts.most_common(limit)]
|
|
|
|
def _user_has_interacted(self, user, product):
|
|
"""Prüfe ob User bereits mit Produkt interagiert hat"""
|
|
|
|
return UserBehavior.objects.filter(
|
|
user=user,
|
|
product=product,
|
|
behavior_type__in=['purchase', 'cart_add', 'wishlist_add', 'view']
|
|
).exists()
|
|
|
|
def _calculate_content_based_confidence(self, product, profile):
|
|
"""Berechne Confidence Score für Content-based Filtering"""
|
|
|
|
confidence = 0.5 # Base confidence
|
|
|
|
# Category match
|
|
if product.category in profile.preferred_categories:
|
|
confidence += 0.2
|
|
|
|
# Fursuit type match
|
|
if product.fursuit_type in profile.preferred_fursuit_types:
|
|
confidence += 0.2
|
|
|
|
# Price range match
|
|
if profile.preferred_price_range:
|
|
min_price = profile.preferred_price_range.get('min', 0)
|
|
max_price = profile.preferred_price_range.get('max', float('inf'))
|
|
|
|
if min_price <= product.price <= max_price:
|
|
confidence += 0.1
|
|
|
|
return min(0.9, confidence)
|
|
|
|
def _save_recommendations(self, user, recommendations):
|
|
"""Speichere Empfehlungen in Datenbank"""
|
|
|
|
# Delete old recommendations
|
|
Recommendation.objects.filter(user=user).delete()
|
|
|
|
# Create new recommendations
|
|
for rec in recommendations:
|
|
Recommendation.objects.create(
|
|
user=user,
|
|
product_id=rec['product']['id'],
|
|
recommendation_type=rec['recommendation_type'],
|
|
confidence_score=rec['confidence_score'],
|
|
reason=rec['reason'],
|
|
expires_at=timezone.now() + timedelta(days=7)
|
|
)
|
|
|
|
def update_product_similarities(self):
|
|
"""Update Produkt-Ähnlichkeitsmatrix"""
|
|
|
|
products = Product.objects.filter(is_active=True)
|
|
|
|
for product in products:
|
|
# Find similar products based on various criteria
|
|
similar_products = self._find_similar_products(product)
|
|
|
|
# Update similarity matrix
|
|
for similar_product, similarity_score, similarity_type in similar_products:
|
|
ProductSimilarity.objects.update_or_create(
|
|
product=product,
|
|
similar_product=similar_product,
|
|
defaults={
|
|
'similarity_score': similarity_score,
|
|
'similarity_type': similarity_type
|
|
}
|
|
)
|
|
|
|
def _find_similar_products(self, product):
|
|
"""Finde ähnliche Produkte"""
|
|
|
|
similar_products = []
|
|
|
|
# Category-based similarity
|
|
category_similar = Product.objects.filter(
|
|
category=product.category,
|
|
is_active=True
|
|
).exclude(id=product.id)[:5]
|
|
|
|
for similar_product in category_similar:
|
|
similarity_score = 0.7
|
|
if similar_product.fursuit_type == product.fursuit_type:
|
|
similarity_score += 0.2
|
|
if abs(similar_product.price - product.price) / product.price < 0.3:
|
|
similarity_score += 0.1
|
|
|
|
similar_products.append((similar_product, similarity_score, 'category'))
|
|
|
|
# Price-based similarity
|
|
price_range = product.price * 0.3
|
|
price_similar = Product.objects.filter(
|
|
price__range=(product.price - price_range, product.price + price_range),
|
|
is_active=True
|
|
).exclude(id=product.id)[:5]
|
|
|
|
for similar_product in price_similar:
|
|
similarity_score = 0.6
|
|
if similar_product.category == product.category:
|
|
similarity_score += 0.2
|
|
if similar_product.fursuit_type == product.fursuit_type:
|
|
similarity_score += 0.2
|
|
|
|
similar_products.append((similar_product, similarity_score, 'price_range'))
|
|
|
|
return similar_products
|
|
|
|
def train_recommendation_model(self, model_type='hybrid'):
|
|
"""Trainiere ML Model für Empfehlungen"""
|
|
|
|
# Create model record
|
|
model = RecommendationModel.objects.create(
|
|
model_type=model_type,
|
|
model_name=f"{model_type}_model",
|
|
model_version="1.0",
|
|
is_active=False
|
|
)
|
|
|
|
# Get training data
|
|
behaviors = UserBehavior.objects.all()
|
|
training_data_size = behaviors.count()
|
|
|
|
# Simple training (in production, use proper ML library)
|
|
accuracy_score = 0.75 # Placeholder
|
|
precision_score = 0.70
|
|
recall_score = 0.65
|
|
f1_score = 0.67
|
|
|
|
# Update model
|
|
model.training_data_size = training_data_size
|
|
model.accuracy_score = accuracy_score
|
|
model.precision_score = precision_score
|
|
model.recall_score = recall_score
|
|
model.f1_score = f1_score
|
|
model.trained_at = timezone.now()
|
|
model.is_active = True
|
|
model.save()
|
|
|
|
return model
|
|
|
|
def update_analytics(self):
|
|
"""Update Recommendation Analytics"""
|
|
|
|
today = timezone.now().date()
|
|
analytics, created = RecommendationAnalytics.objects.get_or_create(date=today)
|
|
|
|
# Calculate today's metrics
|
|
today_recommendations = Recommendation.objects.filter(
|
|
created_at__date=today
|
|
)
|
|
|
|
analytics.total_recommendations = today_recommendations.count()
|
|
analytics.total_clicks = today_recommendations.filter(is_clicked=True).count()
|
|
analytics.total_purchases = today_recommendations.filter(is_purchased=True).count()
|
|
|
|
if analytics.total_recommendations > 0:
|
|
analytics.click_through_rate = (analytics.total_clicks / analytics.total_recommendations) * 100
|
|
|
|
if analytics.total_clicks > 0:
|
|
analytics.conversion_rate = (analytics.total_purchases / analytics.total_clicks) * 100
|
|
|
|
# Calculate revenue
|
|
purchased_recommendations = today_recommendations.filter(is_purchased=True)
|
|
total_revenue = sum(
|
|
rec.product.price for rec in purchased_recommendations
|
|
)
|
|
analytics.revenue_from_recommendations = total_revenue
|
|
|
|
# Average recommendation score
|
|
if analytics.total_recommendations > 0:
|
|
avg_score = today_recommendations.aggregate(
|
|
avg_score=Avg('confidence_score')
|
|
)['avg_score'] or 0
|
|
analytics.avg_recommendation_score = avg_score
|
|
|
|
# Per recommendation type
|
|
analytics.collaborative_recommendations = today_recommendations.filter(
|
|
recommendation_type='collaborative'
|
|
).count()
|
|
analytics.content_based_recommendations = today_recommendations.filter(
|
|
recommendation_type='content_based'
|
|
).count()
|
|
analytics.popular_recommendations = today_recommendations.filter(
|
|
recommendation_type='popular'
|
|
).count()
|
|
analytics.personalized_recommendations = today_recommendations.filter(
|
|
recommendation_type='personalized'
|
|
).count()
|
|
|
|
analytics.save()
|
|
|
|
return analytics |