327 lines
17 KiB
Python
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 {} |