327 lines
17 KiB
Python

"""
Аналитика пользователей
"""
import logging
from typing import Dict, List
from ..base import OracleDatabase
class UserAnalytics(OracleDatabase):
"""Класс для аналитики пользователей"""
async def get_users_general_stats(self) -> Dict:
"""Общая статистика пользователей"""
def _get_stats():
try:
with self._get_connection() as conn:
with conn.cursor() as cur:
query = """
SELECT
COUNT(*) as total_users,
COUNT(CASE WHEN is_premium = 1 THEN 1 END) as premium_users,
COUNT(CASE WHEN is_active = 1 THEN 1 END) as active_users,
COUNT(CASE WHEN is_blocked = 1 THEN 1 END) as blocked_users,
COUNT(CASE WHEN successful_payments_count > 0 THEN 1 END) as paying_users,
SUM(total_payments) as total_revenue,
SUM(successful_payments_count) as total_transactions,
SUM(interaction_count) as total_interactions,
AVG(interaction_count) as avg_interactions_per_user,
COUNT(CASE WHEN last_interaction_date >= SYSDATE - 1 THEN 1 END) as active_24h,
COUNT(CASE WHEN last_interaction_date >= SYSDATE - 7 THEN 1 END) as active_7d,
COUNT(CASE WHEN last_interaction_date >= SYSDATE - 30 THEN 1 END) as active_30d
FROM bot_users
"""
cur.execute(query)
result = cur.fetchone()
return {
"total_users": result[0] or 0,
"premium_users": result[1] or 0,
"active_users": result[2] or 0,
"blocked_users": result[3] or 0,
"paying_users": result[4] or 0,
"total_revenue": float(result[5]) if result[5] else 0.0,
"total_transactions": result[6] or 0,
"total_interactions": result[7] or 0,
"avg_interactions_per_user": round(float(result[8]), 2) if result[8] else 0.0,
"active_24h": result[9] or 0,
"active_7d": result[10] or 0,
"active_30d": result[11] or 0
}
except Exception as e:
logging.error(f"SQL Error in get_users_general_stats:")
logging.error(f"Error: {e}")
raise
try:
return await self._execute_query(_get_stats)
except Exception as e:
logging.error(f"Error getting general user stats: {e}")
return {}
async def get_users_growth_stats(self) -> Dict:
"""Статистика роста пользователей"""
def _get_stats():
try:
with self._get_connection() as conn:
with conn.cursor() as cur:
query = """
SELECT
COUNT(CASE WHEN created_at >= SYSDATE - 1 THEN 1 END) as new_today,
COUNT(CASE WHEN created_at >= SYSDATE - 7 THEN 1 END) as new_week,
COUNT(CASE WHEN created_at >= SYSDATE - 30 THEN 1 END) as new_month,
COUNT(CASE WHEN created_at >= SYSDATE - 365 THEN 1 END) as new_year,
TO_CHAR(MIN(created_at), 'DD.MM.YYYY') as first_user_date,
ROUND((SYSDATE - MIN(created_at))) as days_since_start
FROM bot_users
"""
cur.execute(query)
result = cur.fetchone()
# Получаем рост по дням за последние 30 дней
daily_query = """
SELECT
TO_CHAR(created_at, 'DD.MM') as day,
COUNT(*) as count
FROM bot_users
WHERE created_at >= SYSDATE - 30
GROUP BY TO_CHAR(created_at, 'DD.MM'), TRUNC(created_at)
ORDER BY TRUNC(created_at) DESC
"""
cur.execute(daily_query)
daily_growth = cur.fetchall()
return {
"new_today": result[0] or 0,
"new_week": result[1] or 0,
"new_month": result[2] or 0,
"new_year": result[3] or 0,
"first_user_date": result[4] or "N/A",
"days_since_start": result[5] or 0,
"daily_growth": daily_growth[:10] if daily_growth else []
}
except Exception as e:
logging.error(f"SQL Error in get_users_growth_stats:")
logging.error(f"Error: {e}")
raise
try:
return await self._execute_query(_get_stats)
except Exception as e:
logging.error(f"Error getting growth stats: {e}")
return {}
async def get_users_premium_stats(self) -> Dict:
"""Анализ Premium пользователей"""
def _get_stats():
try:
with self._get_connection() as conn:
with conn.cursor() as cur:
query = """
SELECT
COUNT(CASE WHEN is_premium = 1 THEN 1 END) as premium_users,
COUNT(CASE WHEN is_premium = 0 THEN 1 END) as regular_users,
AVG(CASE WHEN is_premium = 1 THEN total_payments END) as premium_avg_payment,
AVG(CASE WHEN is_premium = 0 THEN total_payments END) as regular_avg_payment,
AVG(CASE WHEN is_premium = 1 THEN interaction_count END) as premium_avg_interactions,
AVG(CASE WHEN is_premium = 0 THEN interaction_count END) as regular_avg_interactions,
COUNT(CASE WHEN is_premium = 1 AND successful_payments_count > 0 THEN 1 END) as premium_paying,
COUNT(CASE WHEN is_premium = 0 AND successful_payments_count > 0 THEN 1 END) as regular_paying
FROM bot_users
WHERE is_active = 1
"""
cur.execute(query)
result = cur.fetchone()
premium_total = result[0] or 0
regular_total = result[1] or 0
return {
"premium_users": premium_total,
"regular_users": regular_total,
"premium_percentage": round((premium_total / (premium_total + regular_total) * 100), 2) if (premium_total + regular_total) > 0 else 0,
"premium_avg_payment": round(float(result[2]), 2) if result[2] else 0.0,
"regular_avg_payment": round(float(result[3]), 2) if result[3] else 0.0,
"premium_avg_interactions": round(float(result[4]), 2) if result[4] else 0.0,
"regular_avg_interactions": round(float(result[5]), 2) if result[5] else 0.0,
"premium_paying": result[6] or 0,
"regular_paying": result[7] or 0,
"premium_conversion": round((result[6] / premium_total * 100), 2) if premium_total > 0 else 0,
"regular_conversion": round((result[7] / regular_total * 100), 2) if regular_total > 0 else 0
}
except Exception as e:
logging.error(f"SQL Error in get_users_premium_stats:")
logging.error(f"Error: {e}")
raise
try:
return await self._execute_query(_get_stats)
except Exception as e:
logging.error(f"Error getting premium stats: {e}")
return {}
async def get_users_geography_stats(self) -> Dict:
"""География пользователей"""
def _get_stats():
try:
with self._get_connection() as conn:
with conn.cursor() as cur:
# Топ языков
lang_query = """
SELECT
COALESCE(language_code, 'unknown') as language,
COUNT(*) as count,
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM bot_users), 2) as percentage
FROM bot_users
GROUP BY language_code
ORDER BY COUNT(*) DESC
"""
cur.execute(lang_query)
languages = cur.fetchall()
# Статистика источников регистрации
source_query = """
SELECT
registration_source,
COUNT(*) as count
FROM bot_users
GROUP BY registration_source
ORDER BY COUNT(*) DESC
"""
cur.execute(source_query)
sources = cur.fetchall()
return {
"top_languages": languages[:10] if languages else [],
"total_languages": len(languages) if languages else 0,
"registration_sources": sources if sources else []
}
except Exception as e:
logging.error(f"SQL Error in get_users_geography_stats:")
logging.error(f"Error: {e}")
raise
try:
return await self._execute_query(_get_stats)
except Exception as e:
logging.error(f"Error getting geography stats: {e}")
return {}
async def get_users_activity_stats(self) -> Dict:
"""Анализ активности пользователей"""
def _get_stats():
try:
with self._get_connection() as conn:
with conn.cursor() as cur:
# Активность за разные периоды
activity_query = """
SELECT
COUNT(CASE WHEN last_interaction_date >= SYSDATE - 1 THEN 1 END) as active_1d,
COUNT(CASE WHEN last_interaction_date >= SYSDATE - 3 THEN 1 END) as active_3d,
COUNT(CASE WHEN last_interaction_date >= SYSDATE - 7 THEN 1 END) as active_7d,
COUNT(CASE WHEN last_interaction_date >= SYSDATE - 14 THEN 1 END) as active_14d,
COUNT(CASE WHEN last_interaction_date >= SYSDATE - 30 THEN 1 END) as active_30d,
COUNT(CASE WHEN last_interaction_date < SYSDATE - 30 THEN 1 END) as inactive_30d
FROM bot_users
WHERE is_active = 1
"""
cur.execute(activity_query)
activity = cur.fetchone()
# Распределение по количеству взаимодействий
interaction_query = """
SELECT
CASE
WHEN interaction_count = 1 THEN 'Новички (1)'
WHEN interaction_count BETWEEN 2 AND 5 THEN 'Начинающие (2-5)'
WHEN interaction_count BETWEEN 6 AND 20 THEN 'Активные (6-20)'
WHEN interaction_count BETWEEN 21 AND 100 THEN 'Постоянные (21-100)'
ELSE 'Суперактивные (100+)'
END as category,
COUNT(*) as count
FROM bot_users
WHERE is_active = 1
GROUP BY
CASE
WHEN interaction_count = 1 THEN 'Новички (1)'
WHEN interaction_count BETWEEN 2 AND 5 THEN 'Начинающие (2-5)'
WHEN interaction_count BETWEEN 6 AND 20 THEN 'Активные (6-20)'
WHEN interaction_count BETWEEN 21 AND 100 THEN 'Постоянные (21-100)'
ELSE 'Суперактивные (100+)'
END
ORDER BY COUNT(*) DESC
"""
cur.execute(interaction_query)
interactions = cur.fetchall()
return {
"active_1d": activity[0] or 0,
"active_3d": activity[1] or 0,
"active_7d": activity[2] or 0,
"active_14d": activity[3] or 0,
"active_30d": activity[4] or 0,
"inactive_30d": activity[5] or 0,
"interaction_distribution": interactions if interactions else []
}
except Exception as e:
logging.error(f"SQL Error in get_users_activity_stats:")
logging.error(f"Error: {e}")
raise
try:
return await self._execute_query(_get_stats)
except Exception as e:
logging.error(f"Error getting activity stats: {e}")
return {}
async def get_users_sources_stats(self) -> Dict:
"""Анализ источников пользователей"""
def _get_stats():
try:
with self._get_connection() as conn:
with conn.cursor() as cur:
# Статистика по источникам
sources_query = """
SELECT
registration_source,
COUNT(*) as total_users,
COUNT(CASE WHEN successful_payments_count > 0 THEN 1 END) as paying_users,
AVG(total_payments) as avg_revenue,
AVG(interaction_count) as avg_interactions,
COUNT(CASE WHEN created_at >= SYSDATE - 30 THEN 1 END) as new_last_30d
FROM bot_users
GROUP BY registration_source
ORDER BY COUNT(*) DESC
"""
cur.execute(sources_query)
sources = cur.fetchall()
# Топ реферальные источники (если есть)
referral_query = """
SELECT
registration_source,
COUNT(*) as count
FROM bot_users
WHERE registration_source NOT IN ('bot', 'direct', 'unknown')
GROUP BY registration_source
ORDER BY COUNT(*) DESC
FETCH FIRST 10 ROWS ONLY
"""
cur.execute(referral_query)
referrals = cur.fetchall()
return {
"source_breakdown": sources if sources else [],
"top_referrals": referrals if referrals else []
}
except Exception as e:
logging.error(f"SQL Error in get_users_sources_stats:")
logging.error(f"Error: {e}")
raise
try:
return await self._execute_query(_get_stats)
except Exception as e:
logging.error(f"Error getting sources stats: {e}")
return {}