furry/recommendations/services.py

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