from django.db import models from django.contrib.auth.models import User class Brief(models.Model): owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="briefs") product = models.TextField() audience = models.TextField() usp = models.TextField(blank=True, null=True) benefits = models.JSONField(default=list, blank=True) constraints = models.TextField(blank=True, null=True) tone = models.CharField(max_length=120, blank=True, null=True) formats = models.JSONField(default=list) # client chooses formats variants_per_format = models.PositiveIntegerField(default=3) created_at = models.DateTimeField(auto_now_add=True) class TextVariant(models.Model): brief = models.ForeignKey(Brief, on_delete=models.CASCADE, related_name="variants") format = models.CharField(max_length=50) payload = models.JSONField(default=dict) placement_tips = models.TextField(blank=True, default="") expected_effect = models.TextField(blank=True, default="") created_at = models.DateTimeField(auto_now_add=True) class Test(models.Model): owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="tests") brief = models.ForeignKey(Brief, on_delete=models.CASCADE, related_name="tests") name = models.CharField(max_length=200, default="Тест") channel = models.CharField(max_length=120, blank=True, default="") duration_days = models.PositiveIntegerField(default=3) sample_size = models.PositiveIntegerField(default=0) objective = models.CharField(max_length=32, default="leads") # leads|conversions|clicks status = models.CharField(max_length=20, default="draft") created_at = models.DateTimeField(auto_now_add=True) class Segment(models.Model): test = models.ForeignKey(Test, on_delete=models.CASCADE, related_name="segments") name = models.CharField(max_length=200) description = models.TextField(blank=True, default="") class Assignment(models.Model): test = models.ForeignKey(Test, on_delete=models.CASCADE, related_name="assignments") segment = models.ForeignKey(Segment, on_delete=models.CASCADE, related_name="assignments") variant = models.ForeignKey(TextVariant, on_delete=models.CASCADE, related_name="assignments") class ResultEntry(models.Model): test = models.ForeignKey(Test, on_delete=models.CASCADE, related_name="results") segment = models.ForeignKey(Segment, on_delete=models.CASCADE, related_name="results") variant = models.ForeignKey(TextVariant, on_delete=models.CASCADE, related_name="results") date = models.DateField() impressions = models.PositiveIntegerField(default=0) clicks = models.PositiveIntegerField(default=0) conversions = models.PositiveIntegerField(default=0) leads = models.PositiveIntegerField(default=0) spend = models.FloatField(default=0.0) created_at = models.DateTimeField(auto_now_add=True) class MetricsSnapshot(models.Model): test = models.OneToOneField(Test, on_delete=models.CASCADE, related_name="snapshot") ranking = models.JSONField(default=list) recommendations = models.JSONField(default=list) created_at = models.DateTimeField(auto_now_add=True) class OptimizationPolicy(models.Model): """User-defined optimization rules for Agent #2. Supports two modes: - thresholds: structured KPI + thresholds - text: free-form query that describes ranking/thresholds """ MODE_CHOICES = [ ("thresholds", "thresholds"), ("text", "text"), ] KPI_CHOICES = [ ("cpa", "cpa"), ("cpl", "cpl"), ("cpc", "cpc"), ("ctr", "ctr"), ("cr", "cr"), ] test = models.OneToOneField("Test", on_delete=models.CASCADE, related_name="policy") mode = models.CharField(max_length=16, choices=MODE_CHOICES, default="thresholds") # Structured mode primary_kpi = models.CharField(max_length=8, choices=KPI_CHOICES, default="cpl") direction = models.CharField(max_length=8, default="min") # min or max good_threshold = models.FloatField(null=True, blank=True) ok_threshold = models.FloatField(null=True, blank=True) min_impressions = models.IntegerField(default=0) min_clicks = models.IntegerField(default=0) # Text mode query_text = models.TextField(blank=True, null=True) updated_at = models.DateTimeField(auto_now=True)