from dataclasses import dataclass from datetime import datetime, timezone from sqlalchemy import func, select, text from sqlalchemy.ext.asyncio import AsyncSession from app.models.payment import Payment, PaymentStatus from app.models.tariff import Tariff from app.models.user import User from app.repositories.payment import PaymentRepository from app.repositories.tariff import TariffRepository from app.repositories.user import UserRepository from app.services.billing import BillingService @dataclass class SystemStats: total_users: int = 0 active_users: int = 0 expired_subscriptions: int = 0 revenue_month: float = 0.0 revenue_year: float = 0.0 avg_subscription_price: float = 0.0 class StatsService: def __init__(self, session: AsyncSession): self.session = session async def get_stats(self) -> SystemStats: total_users = await self._count_users() active_users = await self._count_active_users() expired_subscriptions = await self._count_expired() revenue_month = await self._sum_revenue_since(self._start_of_month()) revenue_year = await self._sum_revenue_since(self._start_of_year()) avg_subscription_price = await self._avg_tariff_price() return SystemStats( total_users=total_users, active_users=active_users, expired_subscriptions=expired_subscriptions, revenue_month=round(revenue_month, 2), revenue_year=round(revenue_year, 2), avg_subscription_price=round(avg_subscription_price, 2), ) async def _count_users(self) -> int: stmt = select(func.count(User.id)) return (await self.session.execute(stmt)).scalar_one() async def _count_active_users(self) -> int: stmt = select(func.count(User.id)).where(User.is_active.is_(True)) return (await self.session.execute(stmt)).scalar_one() async def _count_expired(self) -> int: user_repo = UserRepository(self.session) tariff_repo = TariffRepository(self.session) payment_repo = PaymentRepository(self.session) billing = BillingService(payment_repo, tariff_repo, user_repo) expired_ids = await billing.expire_subscriptions() return len(set(expired_ids)) async def _sum_revenue_since(self, since: datetime) -> float: stmt = ( select(func.coalesce(func.sum(Payment.amount), 0)) .where(Payment.status == PaymentStatus.CONFIRMED) .where(Payment.paid_at >= since) ) return float((await self.session.execute(stmt)).scalar_one()) async def _avg_tariff_price(self) -> float: stmt = select(func.coalesce(func.avg(Tariff.price), 0)) return float((await self.session.execute(stmt)).scalar_one()) @staticmethod def _start_of_month() -> datetime: now = datetime.now(timezone.utc) return now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) @staticmethod def _start_of_year() -> datetime: now = datetime.now(timezone.utc) return now.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)