инвайт линк и подсчет отписок
This commit is contained in:
@@ -733,6 +733,47 @@ class Postgres(DatabaseBase):
|
||||
counts = {row['placement_id']: row['count'] for row in results}
|
||||
return {pid: counts.get(pid, 0) for pid in placement_ids}
|
||||
|
||||
@staticmethod
|
||||
async def count_unsubscriptions_by_placement_post_batch(
|
||||
placement_post_ids: list[uuid.UUID],
|
||||
) -> dict[uuid.UUID, int]:
|
||||
"""Count unsubscriptions for placement_posts by finding their parent placements."""
|
||||
if not placement_post_ids:
|
||||
return {}
|
||||
|
||||
from tortoise.functions import Count
|
||||
|
||||
# Get placement_ids from placement_posts
|
||||
placement_posts = await domain.PlacementPost.filter(id__in=placement_post_ids).all()
|
||||
placement_id_to_post_ids: dict[uuid.UUID, list[uuid.UUID]] = {}
|
||||
for pp in placement_posts:
|
||||
placement_id_to_post_ids.setdefault(pp.placement_id, []).append(pp.id)
|
||||
|
||||
placement_ids = list(placement_id_to_post_ids.keys())
|
||||
if not placement_ids:
|
||||
return dict.fromkeys(placement_post_ids, 0)
|
||||
|
||||
# Count unsubscriptions by placement (filter by UNSUBSCRIBED status)
|
||||
results = (
|
||||
await domain.Subscription.filter(
|
||||
placement_id__in=placement_ids, status=domain.SubscriptionStatus.UNSUBSCRIBED
|
||||
)
|
||||
.group_by('placement_id')
|
||||
.annotate(count=Count('id'))
|
||||
.values('placement_id', 'count')
|
||||
)
|
||||
|
||||
placement_counts = {row['placement_id']: row['count'] for row in results}
|
||||
|
||||
# Map back to placement_post_ids
|
||||
post_counts: dict[uuid.UUID, int] = {}
|
||||
for placement_id, post_ids in placement_id_to_post_ids.items():
|
||||
count = placement_counts.get(placement_id, 0)
|
||||
for post_id in post_ids:
|
||||
post_counts[post_id] = count
|
||||
|
||||
return {pid: post_counts.get(pid, 0) for pid in placement_post_ids}
|
||||
|
||||
@staticmethod
|
||||
async def has_placement_posts_for_creative(creative_id: uuid.UUID) -> bool:
|
||||
return await domain.PlacementPost.filter(placement__creative_id=creative_id).exists()
|
||||
|
||||
@@ -54,6 +54,7 @@ class Placement(TimestampedModel):
|
||||
comment = fields.TextField(null=True)
|
||||
|
||||
invite_link = fields.CharField(max_length=512, null=True)
|
||||
invite_link_created_at = fields.DatetimeField(null=True)
|
||||
invite_link_type = fields.CharEnumField(InviteLinkType)
|
||||
|
||||
project: fields.ForeignKeyRelation['Project'] = fields.ForeignKeyField(
|
||||
|
||||
@@ -47,6 +47,7 @@ class PlacementAnalyticsOutput(pydantic.BaseModel):
|
||||
placement_date: datetime.datetime
|
||||
cost: float | None
|
||||
subscriptions_count: int
|
||||
unsubscriptions_count: int
|
||||
views_count: int | None
|
||||
cpf: float | None
|
||||
cpm: float | None
|
||||
|
||||
@@ -30,6 +30,7 @@ class PlacementOutput(pydantic.BaseModel):
|
||||
creative_id: uuid.UUID | None = None
|
||||
comment: str | None = None
|
||||
invite_link: str | None
|
||||
invite_link_created_at: datetime.datetime | None = None
|
||||
invite_link_type: InviteLinkType
|
||||
channel: ChannelOutput
|
||||
details: PlacementDetails | None = None
|
||||
|
||||
@@ -72,6 +72,7 @@ async def get_placements_analytics(
|
||||
# Batch fetch subscriptions counts
|
||||
placement_ids = [p.id for p in placements]
|
||||
subscriptions_counts = await self.database.count_subscriptions_by_placement_post_batch(placement_ids)
|
||||
unsubscriptions_counts = await self.database.count_unsubscriptions_by_placement_post_batch(placement_ids)
|
||||
|
||||
results: list[dto.PlacementAnalyticsOutput] = []
|
||||
for placement_post in placements:
|
||||
@@ -92,6 +93,7 @@ async def get_placements_analytics(
|
||||
|
||||
cost = _get_cost(placement_post)
|
||||
subs_count = 0 if hide_subscriptions else subscriptions_counts.get(placement_post.id, 0)
|
||||
unsubs_count = 0 if hide_subscriptions else unsubscriptions_counts.get(placement_post.id, 0)
|
||||
if placement_post.post and placement_post.post.id in views_map:
|
||||
views_count = views_map[placement_post.post.id][0]
|
||||
else:
|
||||
@@ -109,6 +111,7 @@ async def get_placements_analytics(
|
||||
placement_date=_get_placement_date(placement_post),
|
||||
cost=cost,
|
||||
subscriptions_count=subs_count,
|
||||
unsubscriptions_count=unsubs_count,
|
||||
views_count=views_count,
|
||||
cpf=cpf,
|
||||
cpm=_calculate_cpm(cost, views_count),
|
||||
|
||||
@@ -4,6 +4,7 @@ import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from tortoise import timezone
|
||||
|
||||
from src import domain, dto
|
||||
|
||||
@@ -87,6 +88,7 @@ async def build_placement_creative(
|
||||
requires_approval = placement.invite_link_type == domain.InviteLinkType.APPROVAL
|
||||
invite_link = await self.telegram_bot.create_chat_invite_link(project.channel.telegram_id, requires_approval)
|
||||
placement.invite_link = invite_link
|
||||
placement.invite_link_created_at = timezone.now()
|
||||
await self.database.update_placement(placement)
|
||||
|
||||
invite_link = placement.invite_link
|
||||
|
||||
Reference in New Issue
Block a user