feat: implemented algorythm and indexed more fields

This commit is contained in:
ITQ
2025-02-21 07:12:46 +03:00
parent e85df62859
commit 42aee455ba
2 changed files with 113 additions and 27 deletions
@@ -1,4 +1,4 @@
# Generated by Django 5.1.6 on 2025-02-17 18:16
# Generated by Django 5.1.6 on 2025-02-21 03:50
import apps.campaign.models
import django.core.validators
@@ -28,12 +28,12 @@ class Migration(migrations.Migration):
('ad_title', models.TextField()),
('ad_text', models.TextField()),
('ad_image', models.ImageField(blank=True, max_length=256, null=True, upload_to=apps.campaign.models.Campaign.ad_image_directory_path)),
('start_date', models.PositiveIntegerField()),
('end_date', models.PositiveIntegerField()),
('gender', models.CharField(blank=True, choices=[('MALE', 'MALE'), ('FEMALE', 'FEMALE'), ('ALL', 'ALL')], max_length=6, null=True)),
('age_from', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(100)])),
('age_to', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(100)])),
('location', models.TextField(blank=True, null=True, validators=[django.core.validators.MinLengthValidator(1)])),
('start_date', models.PositiveIntegerField(db_index=True)),
('end_date', models.PositiveIntegerField(db_index=True)),
('gender', models.CharField(blank=True, choices=[('MALE', 'MALE'), ('FEMALE', 'FEMALE'), ('ALL', 'ALL')], db_index=True, max_length=6, null=True)),
('age_from', models.PositiveSmallIntegerField(blank=True, db_index=True, null=True, validators=[django.core.validators.MaxValueValidator(100)])),
('age_to', models.PositiveSmallIntegerField(blank=True, db_index=True, null=True, validators=[django.core.validators.MaxValueValidator(100)])),
('location', models.TextField(blank=True, db_index=True, null=True, validators=[django.core.validators.MinLengthValidator(1)])),
('advertiser', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='campaigns', to='advertiser.advertiser')),
],
options={
@@ -45,7 +45,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('price', models.FloatField()),
('date', models.PositiveIntegerField()),
('date', models.PositiveIntegerField(db_index=True)),
('campaign', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='clicks', to='campaign.campaign')),
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='clicks', to='client.client')),
],
@@ -58,7 +58,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('price', models.FloatField()),
('date', models.PositiveIntegerField()),
('date', models.PositiveIntegerField(db_index=True)),
('campaign', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='impressions', to='campaign.campaign')),
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='impressions', to='client.client')),
],
+100 -14
View File
@@ -48,32 +48,36 @@ class Campaign(BaseModel):
ad_text = models.TextField()
ad_image = models.ImageField(
max_length=256,
null=True,
blank=True,
null=True,
upload_to=ad_image_directory_path,
)
start_date = models.PositiveIntegerField()
end_date = models.PositiveIntegerField()
start_date = models.PositiveIntegerField(db_index=True)
end_date = models.PositiveIntegerField(db_index=True)
gender = models.CharField(
max_length=6,
null=True,
blank=True,
null=True,
db_index=True,
choices=GenderChoices,
)
age_from = models.PositiveSmallIntegerField(
null=True,
blank=True,
null=True,
db_index=True,
validators=[MaxValueValidator(100)],
)
age_to = models.PositiveSmallIntegerField(
null=True,
blank=True,
null=True,
db_index=True,
validators=[MaxValueValidator(100)],
)
location = models.TextField(
null=True,
blank=True,
null=True,
db_index=True,
validators=[MinLengthValidator(1)],
)
@@ -273,19 +277,101 @@ class Campaign(BaseModel):
| models.Q(age_from__isnull=True)
) & (models.Q(age_to__gte=client.age) | models.Q(age_to__isnull=True))
queryset = cls.objects.filter(
queryset = (
cls.objects.filter(
date_filter,
location_filter,
gender_filter,
age_filter,
).prefetch_related("clicks", "impressions")
)
.prefetch_related("clicks", "impressions")
.select_related("advertiser")
)
return queryset
@classmethod
def suggest(cls, client: Client) -> Self:
aboba = cls.get_revelant(client).all()
return aboba[0]
def suggest(cls, client: Client) -> None | Self:
available_campaigns = Campaign.get_available_campaigns(client)
if not available_campaigns or available_campaigns == []:
return None
campaign_ids = [c.id for c in available_campaigns]
impressions_counts = (
CampaignImpression.objects.filter(campaign_id__in=campaign_ids)
.values("campaign_id")
.annotate(total=models.Count("id"))
)
impressions_dict = {
item["campaign_id"]: item["total"] for item in impressions_counts
}
clicks_counts = (
CampaignClick.objects.filter(campaign_id__in=campaign_ids)
.values("campaign_id")
.annotate(total=models.Count("id"))
)
clicks_dict = {
item["campaign_id"]: item["total"] for item in clicks_counts
}
existing_impressions = CampaignImpression.objects.filter(
client=client, campaign_id__in=campaign_ids
).values_list("campaign_id", flat=True)
existing_impressions_set = set(existing_impressions)
advertisers = {c.advertiser_id for c in available_campaigns}
ml_scores = models.Mlscore.objects.filter(
client=client, advertiser_id__in=advertisers
).values("advertiser_id", "score")
ml_score_dict = {ms["advertiser_id"]: ms["score"] for ms in ml_scores}
max_ml = max(ml_score_dict.values(), default=0)
valid_campaigns = []
for campaign in available_campaigns:
current_impressions = impressions_dict.get(campaign.id, 0)
if current_impressions >= campaign.impressions_limit:
continue
if campaign.id in existing_impressions_set:
continue
valid_campaigns.append(campaign)
prioritized = []
for campaign in valid_campaigns:
ml_score = ml_score_dict.get(campaign.advertiser_id, 0)
remaining_impressions = (
campaign.impressions_limit
- impressions_dict.get(campaign.id, 0)
)
weight_imp = (
remaining_impressions / campaign.impressions_limit
if campaign.impressions_limit > 0
else 1.0
)
current_clicks = clicks_dict.get(campaign.id, 0)
remaining_clicks = campaign.clicks_limit - current_clicks
if remaining_clicks <= 0:
cpc_contribution = 0.0
else:
click_availability = (
(remaining_clicks / campaign.clicks_limit)
if campaign.clicks_limit > 0
else 1.0
)
prob_click = (ml_score / max_ml) if max_ml != 0 else 0.0
cpc_contribution = (
campaign.cost_per_click * prob_click * click_availability
)
expected_profit = campaign.cost_per_impression + cpc_contribution
priority = ml_score * expected_profit * weight_imp
prioritized.append((campaign, priority))
prioritized.sort(key=lambda x: -x[1])
return prioritized[0]
class CampaignImpression(BaseModel):
@@ -300,7 +386,7 @@ class CampaignImpression(BaseModel):
related_name="impressions",
)
price = models.FloatField()
date = models.PositiveIntegerField()
date = models.PositiveIntegerField(db_index=True)
def __str__(self) -> str:
return f"{self.client.login} > {self.campaign.ad_title}"
@@ -324,7 +410,7 @@ class CampaignClick(BaseModel):
related_name="clicks",
)
price = models.FloatField()
date = models.PositiveIntegerField()
date = models.PositiveIntegerField(db_index=True)
def __str__(self) -> str:
return f"{self.client.login} > {self.campaign.ad_title}"