308 lines
12 KiB
Python
308 lines
12 KiB
Python
"""
|
|
Recommendation Engine Models für ML-basierte Empfehlungen
|
|
"""
|
|
|
|
from django.db import models
|
|
from django.contrib.auth.models import User
|
|
from django.utils import timezone
|
|
from products.models import Product
|
|
from auction.models import Auction
|
|
import uuid
|
|
|
|
|
|
class UserBehavior(models.Model):
|
|
"""User Behavior Tracking für Empfehlungen"""
|
|
|
|
BEHAVIOR_TYPE_CHOICES = [
|
|
('view', 'Produkt angesehen'),
|
|
('cart_add', 'Zum Warenkorb hinzugefügt'),
|
|
('cart_remove', 'Aus Warenkorb entfernt'),
|
|
('purchase', 'Gekauft'),
|
|
('wishlist_add', 'Zur Wunschliste hinzugefügt'),
|
|
('wishlist_remove', 'Von Wunschliste entfernt'),
|
|
('search', 'Gesucht'),
|
|
('auction_bid', 'Bei Auktion geboten'),
|
|
('auction_watch', 'Auktion beobachtet'),
|
|
('review', 'Bewertung abgegeben'),
|
|
('share', 'Geteilt'),
|
|
('like', 'Geliked'),
|
|
('dislike', 'Nicht gemocht'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='behaviors')
|
|
behavior_type = models.CharField(max_length=20, choices=BEHAVIOR_TYPE_CHOICES)
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True, blank=True, related_name='behaviors')
|
|
auction = models.ForeignKey(Auction, on_delete=models.CASCADE, null=True, blank=True, related_name='behaviors')
|
|
session_id = models.CharField(max_length=100, blank=True)
|
|
ip_address = models.GenericIPAddressField(null=True, blank=True)
|
|
user_agent = models.TextField(blank=True)
|
|
metadata = models.JSONField(default=dict) # Additional data
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
verbose_name = 'User Behavior'
|
|
verbose_name_plural = 'User Behaviors'
|
|
indexes = [
|
|
models.Index(fields=['user', 'behavior_type', 'created_at']),
|
|
models.Index(fields=['product', 'behavior_type', 'created_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.user.username} - {self.get_behavior_type_display()}"
|
|
|
|
|
|
class UserProfile(models.Model):
|
|
"""User Profile für Personalisierung"""
|
|
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='recommendation_profile')
|
|
|
|
# Preferences
|
|
preferred_categories = models.JSONField(default=list)
|
|
preferred_fursuit_types = models.JSONField(default=list)
|
|
preferred_price_range = models.JSONField(default=dict)
|
|
preferred_colors = models.JSONField(default=list)
|
|
|
|
# Behavior patterns
|
|
avg_session_duration = models.FloatField(default=0) # in minutes
|
|
total_purchases = models.IntegerField(default=0)
|
|
total_views = models.IntegerField(default=0)
|
|
total_searches = models.IntegerField(default=0)
|
|
|
|
# Engagement metrics
|
|
last_active = models.DateTimeField(auto_now=True)
|
|
engagement_score = models.FloatField(default=0) # 0-100
|
|
loyalty_level = models.CharField(max_length=20, choices=[
|
|
('new', 'Neu'),
|
|
('regular', 'Regulär'),
|
|
('loyal', 'Loyal'),
|
|
('vip', 'VIP'),
|
|
], default='new')
|
|
|
|
# ML features
|
|
feature_vector = models.JSONField(default=dict) # ML feature vector
|
|
last_updated = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
verbose_name = 'User Profile'
|
|
verbose_name_plural = 'User Profiles'
|
|
|
|
def __str__(self):
|
|
return f"Profile für {self.user.username}"
|
|
|
|
def update_engagement_score(self):
|
|
"""Engagement Score basierend auf Verhalten aktualisieren"""
|
|
behaviors = self.user.behaviors.all()
|
|
|
|
# Calculate engagement score
|
|
view_weight = 1
|
|
cart_weight = 3
|
|
purchase_weight = 10
|
|
search_weight = 2
|
|
|
|
total_score = 0
|
|
total_actions = 0
|
|
|
|
for behavior in behaviors:
|
|
if behavior.behavior_type == 'view':
|
|
total_score += view_weight
|
|
elif behavior.behavior_type == 'cart_add':
|
|
total_score += cart_weight
|
|
elif behavior.behavior_type == 'purchase':
|
|
total_score += purchase_weight
|
|
elif behavior.behavior_type == 'search':
|
|
total_score += search_weight
|
|
|
|
total_actions += 1
|
|
|
|
if total_actions > 0:
|
|
self.engagement_score = min(100, (total_score / total_actions) * 10)
|
|
|
|
# Update loyalty level
|
|
if self.engagement_score >= 80:
|
|
self.loyalty_level = 'vip'
|
|
elif self.engagement_score >= 60:
|
|
self.loyalty_level = 'loyal'
|
|
elif self.engagement_score >= 30:
|
|
self.loyalty_level = 'regular'
|
|
else:
|
|
self.loyalty_level = 'new'
|
|
|
|
self.save()
|
|
|
|
|
|
class ProductSimilarity(models.Model):
|
|
"""Produkt-Ähnlichkeitsmatrix für Collaborative Filtering"""
|
|
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='similarities')
|
|
similar_product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='similar_to')
|
|
similarity_score = models.FloatField() # 0-1
|
|
similarity_type = models.CharField(max_length=20, choices=[
|
|
('category', 'Kategorie'),
|
|
('fursuit_type', 'Fursuit Typ'),
|
|
('price_range', 'Preisbereich'),
|
|
('color', 'Farbe'),
|
|
('collaborative', 'Collaborative'),
|
|
('content_based', 'Content-based'),
|
|
])
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
unique_together = ['product', 'similar_product']
|
|
ordering = ['-similarity_score']
|
|
verbose_name = 'Product Similarity'
|
|
verbose_name_plural = 'Product Similarities'
|
|
|
|
def __str__(self):
|
|
return f"{self.product.name} ~ {self.similar_product.name} ({self.similarity_score:.2f})"
|
|
|
|
|
|
class Recommendation(models.Model):
|
|
"""Generierte Empfehlungen für User"""
|
|
|
|
RECOMMENDATION_TYPE_CHOICES = [
|
|
('collaborative', 'Collaborative Filtering'),
|
|
('content_based', 'Content-based Filtering'),
|
|
('popular', 'Beliebte Produkte'),
|
|
('trending', 'Trending'),
|
|
('personalized', 'Personalisiert'),
|
|
('similar', 'Ähnliche Produkte'),
|
|
('frequently_bought', 'Häufig zusammen gekauft'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='recommendations')
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='recommendations')
|
|
recommendation_type = models.CharField(max_length=20, choices=RECOMMENDATION_TYPE_CHOICES)
|
|
confidence_score = models.FloatField() # 0-1
|
|
reason = models.CharField(max_length=200, blank=True)
|
|
is_clicked = models.BooleanField(default=False)
|
|
is_purchased = models.BooleanField(default=False)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
expires_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['-confidence_score', '-created_at']
|
|
verbose_name = 'Recommendation'
|
|
verbose_name_plural = 'Recommendations'
|
|
indexes = [
|
|
models.Index(fields=['user', 'recommendation_type', 'created_at']),
|
|
models.Index(fields=['product', 'recommendation_type', 'created_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.user.username} - {self.product.name} ({self.get_recommendation_type_display()})"
|
|
|
|
|
|
class RecommendationModel(models.Model):
|
|
"""ML Model für Empfehlungen"""
|
|
|
|
MODEL_TYPE_CHOICES = [
|
|
('collaborative', 'Collaborative Filtering'),
|
|
('content_based', 'Content-based Filtering'),
|
|
('hybrid', 'Hybrid Model'),
|
|
('neural_network', 'Neural Network'),
|
|
('matrix_factorization', 'Matrix Factorization'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
model_type = models.CharField(max_length=20, choices=MODEL_TYPE_CHOICES)
|
|
model_name = models.CharField(max_length=100)
|
|
model_version = models.CharField(max_length=20)
|
|
model_file = models.FileField(upload_to='recommendation_models/', null=True, blank=True)
|
|
model_parameters = models.JSONField(default=dict)
|
|
training_data_size = models.IntegerField(default=0)
|
|
accuracy_score = models.FloatField(default=0)
|
|
precision_score = models.FloatField(default=0)
|
|
recall_score = models.FloatField(default=0)
|
|
f1_score = models.FloatField(default=0)
|
|
is_active = models.BooleanField(default=False)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
trained_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
verbose_name = 'Recommendation Model'
|
|
verbose_name_plural = 'Recommendation Models'
|
|
|
|
def __str__(self):
|
|
return f"{self.model_name} v{self.model_version} ({self.get_model_type_display()})"
|
|
|
|
|
|
class ABTest(models.Model):
|
|
"""A/B Testing für Empfehlungen"""
|
|
|
|
TEST_STATUS_CHOICES = [
|
|
('draft', 'Entwurf'),
|
|
('running', 'Läuft'),
|
|
('paused', 'Pausiert'),
|
|
('completed', 'Abgeschlossen'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
test_name = models.CharField(max_length=100)
|
|
description = models.TextField()
|
|
test_type = models.CharField(max_length=50)
|
|
variant_a = models.JSONField() # Control group
|
|
variant_b = models.JSONField() # Test group
|
|
traffic_split = models.FloatField(default=0.5) # % für Variante B
|
|
status = models.CharField(max_length=20, choices=TEST_STATUS_CHOICES, default='draft')
|
|
start_date = models.DateTimeField(null=True, blank=True)
|
|
end_date = models.DateTimeField(null=True, blank=True)
|
|
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ab_tests')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
# Results
|
|
variant_a_conversion = models.FloatField(default=0)
|
|
variant_b_conversion = models.FloatField(default=0)
|
|
statistical_significance = models.FloatField(default=0)
|
|
winner = models.CharField(max_length=10, choices=[
|
|
('a', 'Variant A'),
|
|
('b', 'Variant B'),
|
|
('none', 'Kein Gewinner'),
|
|
], blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
verbose_name = 'A/B Test'
|
|
verbose_name_plural = 'A/B Tests'
|
|
|
|
def __str__(self):
|
|
return f"{self.test_name} ({self.get_status_display()})"
|
|
|
|
|
|
class RecommendationAnalytics(models.Model):
|
|
"""Analytics für Empfehlungen"""
|
|
|
|
date = models.DateField()
|
|
total_recommendations = models.IntegerField(default=0)
|
|
total_clicks = models.IntegerField(default=0)
|
|
total_purchases = models.IntegerField(default=0)
|
|
click_through_rate = models.FloatField(default=0)
|
|
conversion_rate = models.FloatField(default=0)
|
|
revenue_from_recommendations = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
|
avg_recommendation_score = models.FloatField(default=0)
|
|
|
|
# Per recommendation type
|
|
collaborative_recommendations = models.IntegerField(default=0)
|
|
content_based_recommendations = models.IntegerField(default=0)
|
|
popular_recommendations = models.IntegerField(default=0)
|
|
personalized_recommendations = models.IntegerField(default=0)
|
|
|
|
class Meta:
|
|
unique_together = ['date']
|
|
ordering = ['-date']
|
|
verbose_name = 'Recommendation Analytics'
|
|
verbose_name_plural = 'Recommendation Analytics'
|
|
|
|
def __str__(self):
|
|
return f"Recommendation Analytics - {self.date}"
|
|
|
|
@classmethod
|
|
def get_or_create_today(cls):
|
|
"""Heutige Analytics erstellen oder abrufen"""
|
|
today = timezone.now().date()
|
|
analytics, created = cls.objects.get_or_create(date=today)
|
|
return analytics |