Добавлены методы для получения аналитики пользователей в классе OracleDatabase, включая общую статистику, рост пользователей, анализ Premium пользователей, географию и активность. Обновлены обработчики колбеков в main.py для отображения соответствующих отчетов в админ-панели. Эти изменения улучшают функциональность аналитики и предоставляют администраторам более полное представление о пользователях.
This commit is contained in:
parent
fa5ed9c5c8
commit
9d51031d2d
302
db.py
302
db.py
@ -472,3 +472,305 @@ class OracleDatabase:
|
||||
except Exception as e:
|
||||
print(f"Error saving payment log for user {user.id}: {e}")
|
||||
return False
|
||||
|
||||
# ==============================================
|
||||
# USER ANALYTICS METHODS
|
||||
# ==============================================
|
||||
|
||||
async def get_users_general_stats(self) -> dict:
|
||||
"""Общая статистика пользователей"""
|
||||
def _get_stats():
|
||||
with self._pool.acquire() 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
|
||||
}
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, _get_stats)
|
||||
except Exception as e:
|
||||
print(f"Error getting general user stats: {e}")
|
||||
return {}
|
||||
|
||||
async def get_users_growth_stats(self) -> dict:
|
||||
"""Статистика роста пользователей"""
|
||||
def _get_stats():
|
||||
with self._pool.acquire() 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 []
|
||||
}
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, _get_stats)
|
||||
except Exception as e:
|
||||
print(f"Error getting growth stats: {e}")
|
||||
return {}
|
||||
|
||||
async def get_users_premium_stats(self) -> dict:
|
||||
"""Анализ Premium пользователей"""
|
||||
def _get_stats():
|
||||
with self._pool.acquire() 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
|
||||
}
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, _get_stats)
|
||||
except Exception as e:
|
||||
print(f"Error getting premium stats: {e}")
|
||||
return {}
|
||||
|
||||
async def get_users_geography_stats(self) -> dict:
|
||||
"""География пользователей"""
|
||||
def _get_stats():
|
||||
with self._pool.acquire() 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 []
|
||||
}
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, _get_stats)
|
||||
except Exception as e:
|
||||
print(f"Error getting geography stats: {e}")
|
||||
return {}
|
||||
|
||||
async def get_users_activity_stats(self) -> dict:
|
||||
"""Анализ активности пользователей"""
|
||||
def _get_stats():
|
||||
with self._pool.acquire() 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 []
|
||||
}
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, _get_stats)
|
||||
except Exception as e:
|
||||
print(f"Error getting activity stats: {e}")
|
||||
return {}
|
||||
|
||||
async def get_users_sources_stats(self) -> dict:
|
||||
"""Анализ источников пользователей"""
|
||||
def _get_stats():
|
||||
with self._pool.acquire() 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 []
|
||||
}
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, _get_stats)
|
||||
except Exception as e:
|
||||
print(f"Error getting sources stats: {e}")
|
||||
return {}
|
||||
|
||||
493
main.py
493
main.py
@ -671,37 +671,494 @@ async def admin_stats_callback(callback: CallbackQuery, db: OracleDatabase = Non
|
||||
# Сохраняем данные пользователя при нажатии кнопки админки
|
||||
await database.save_user(callback.from_user, "admin_stats_button")
|
||||
|
||||
# Формируем меню админ панели с категориями отчетов
|
||||
admin_menu_text = """🔧 **Admin Dashboard**
|
||||
|
||||
Выберите категорию отчетов для анализа:
|
||||
|
||||
📊 **Доступные отчеты:**
|
||||
• Пользовательская аналитика
|
||||
• Финансовая аналитика
|
||||
• Техническая аналитика
|
||||
• Операционные отчеты
|
||||
• Бизнес-аналитика
|
||||
|
||||
💡 Выберите интересующую категорию ниже:"""
|
||||
|
||||
# Создаем кнопки для категорий отчетов
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="👥 Пользователи", callback_data="admin_users")
|
||||
builder.button(text="💰 Финансы", callback_data="admin_finance")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🔍 Техническая", callback_data="admin_technical")
|
||||
builder.button(text="⚡ Операционная", callback_data="admin_operations")
|
||||
builder.adjust(2)
|
||||
builder.button(text="📈 Бизнес-аналитика", callback_data="admin_business")
|
||||
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||
builder.adjust(1, 1)
|
||||
|
||||
await callback.message.answer(admin_menu_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
|
||||
# ==============================================
|
||||
# ADMIN REPORTS CALLBACKS
|
||||
# ==============================================
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_users")
|
||||
async def admin_users_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""Пользовательская аналитика"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
menu_text = """👥 **Пользовательская аналитика**
|
||||
|
||||
Выберите тип отчета:"""
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="📊 Общая статистика", callback_data="admin_users_general")
|
||||
builder.button(text="📈 Рост пользователей", callback_data="admin_users_growth")
|
||||
builder.adjust(2)
|
||||
builder.button(text="👑 Premium анализ", callback_data="admin_users_premium")
|
||||
builder.button(text="🌍 География", callback_data="admin_users_geo")
|
||||
builder.adjust(2)
|
||||
builder.button(text="⚡ Активность", callback_data="admin_users_activity")
|
||||
builder.button(text="📋 Источники", callback_data="admin_users_sources")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🔙 Назад", callback_data="admin_stats")
|
||||
builder.adjust(1)
|
||||
|
||||
await callback.message.answer(menu_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_finance")
|
||||
async def admin_finance_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""Финансовая аналитика"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
menu_text = """💰 **Финансовая аналитика**
|
||||
|
||||
Выберите тип отчета:"""
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="💵 Доходы", callback_data="admin_finance_revenue")
|
||||
builder.button(text="📊 По услугам", callback_data="admin_finance_services")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🔄 Конверсия", callback_data="admin_finance_conversion")
|
||||
builder.button(text="↩️ Возвраты", callback_data="admin_finance_refunds")
|
||||
builder.adjust(2)
|
||||
builder.button(text="💳 Транзакции", callback_data="admin_finance_transactions")
|
||||
builder.button(text="🎯 Эффективность", callback_data="admin_finance_efficiency")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🔙 Назад", callback_data="admin_stats")
|
||||
builder.adjust(1)
|
||||
|
||||
await callback.message.answer(menu_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_technical")
|
||||
async def admin_technical_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""Техническая аналитика"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
menu_text = """🔍 **Техническая аналитика**
|
||||
|
||||
Выберите тип отчета:"""
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🚗 Популярные авто", callback_data="admin_tech_popular")
|
||||
builder.button(text="📅 Тренды годов", callback_data="admin_tech_years")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🗺️ География ДТП", callback_data="admin_tech_geography")
|
||||
builder.button(text="💥 Типы повреждений", callback_data="admin_tech_damages")
|
||||
builder.adjust(2)
|
||||
builder.button(text="📊 Качество данных", callback_data="admin_tech_quality")
|
||||
builder.button(text="📸 Статистика фото", callback_data="admin_tech_photos")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🔙 Назад", callback_data="admin_stats")
|
||||
builder.adjust(1)
|
||||
|
||||
await callback.message.answer(menu_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_operations")
|
||||
async def admin_operations_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""Операционные отчеты"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
menu_text = """⚡ **Операционные отчеты**
|
||||
|
||||
Выберите тип отчета:"""
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="⏱️ Производительность", callback_data="admin_ops_performance")
|
||||
builder.button(text="🚨 Ошибки системы", callback_data="admin_ops_errors")
|
||||
builder.adjust(2)
|
||||
builder.button(text="📈 Нагрузка", callback_data="admin_ops_load")
|
||||
builder.button(text="🏪 Аукционы", callback_data="admin_ops_auctions")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🔍 Проблемные VIN", callback_data="admin_ops_problem_vins")
|
||||
builder.button(text="👀 Мониторинг", callback_data="admin_ops_monitoring")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🔙 Назад", callback_data="admin_stats")
|
||||
builder.adjust(1)
|
||||
|
||||
await callback.message.answer(menu_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_business")
|
||||
async def admin_business_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""Бизнес-аналитика"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
menu_text = """📈 **Бизнес-аналитика**
|
||||
|
||||
Выберите тип отчета:"""
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="📊 Тренды", callback_data="admin_biz_trends")
|
||||
builder.button(text="🔮 Прогнозы", callback_data="admin_biz_forecasts")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🌍 Регионы роста", callback_data="admin_biz_regions")
|
||||
builder.button(text="💎 Монетизация", callback_data="admin_biz_monetization")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🎯 Оптимизация", callback_data="admin_biz_optimization")
|
||||
builder.button(text="💡 Рекомендации", callback_data="admin_biz_recommendations")
|
||||
builder.adjust(2)
|
||||
builder.button(text="🔙 Назад", callback_data="admin_stats")
|
||||
builder.adjust(1)
|
||||
|
||||
await callback.message.answer(menu_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
|
||||
# ==============================================
|
||||
# USER ANALYTICS REPORT HANDLERS
|
||||
# ==============================================
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_users_general")
|
||||
async def admin_users_general_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""Общая статистика пользователей"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
try:
|
||||
# Получаем общую статистику
|
||||
stats = await database.get_users_summary()
|
||||
stats = await database.get_users_general_stats()
|
||||
|
||||
# Формируем отчет
|
||||
report = f"""📊 **Bot Users Statistics**
|
||||
report = f"""📊 **Общая статистика пользователей**
|
||||
|
||||
👥 **Users Overview:**
|
||||
• Total users: {stats.get('total_users', 0)}
|
||||
• Premium users: {stats.get('premium_users', 0)}
|
||||
👥 **Пользователи:**
|
||||
• Всего пользователей: **{stats['total_users']:,}**
|
||||
• Активных: **{stats['active_users']:,}** ({round(stats['active_users']/stats['total_users']*100, 1) if stats['total_users'] > 0 else 0}%)
|
||||
• Premium: **{stats['premium_users']:,}** ({round(stats['premium_users']/stats['total_users']*100, 1) if stats['total_users'] > 0 else 0}%)
|
||||
• Заблокированных: **{stats['blocked_users']:,}**
|
||||
|
||||
💰 **Revenue:**
|
||||
• Total revenue: {stats.get('total_revenue', 0)} ⭐️
|
||||
• Total transactions: {stats.get('total_transactions', 0)}
|
||||
💰 **Монетизация:**
|
||||
• Платящих пользователей: **{stats['paying_users']:,}**
|
||||
• Конверсия в покупку: **{round(stats['paying_users']/stats['total_users']*100, 2) if stats['total_users'] > 0 else 0}%**
|
||||
• Общая выручка: **{stats['total_revenue']:,.0f}** ⭐️
|
||||
• Всего транзакций: **{stats['total_transactions']:,}**
|
||||
|
||||
📈 **Activity:**
|
||||
• Active last 24h: {stats.get('active_last_24h', 0)}
|
||||
• Active last week: {stats.get('active_last_week', 0)}
|
||||
⚡ **Активность:**
|
||||
• За 24 часа: **{stats['active_24h']:,}**
|
||||
• За 7 дней: **{stats['active_7d']:,}**
|
||||
• За 30 дней: **{stats['active_30d']:,}**
|
||||
• Всего взаимодействий: **{stats['total_interactions']:,}**
|
||||
• Среднее на пользователя: **{stats['avg_interactions_per_user']:.1f}**
|
||||
|
||||
📅 **Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"""
|
||||
📅 **Сгенерировано:** {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||
|
||||
# Создаем кнопку возврата в главное меню
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🏠 Back to Main Menu", callback_data="main_menu")
|
||||
builder.button(text="🔙 Назад", callback_data="admin_users")
|
||||
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||
builder.adjust(2)
|
||||
|
||||
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error generating admin stats via callback: {e}")
|
||||
await callback.answer("❌ Error generating statistics. Please try again later.", show_alert=True)
|
||||
logging.error(f"Error generating general user stats: {e}")
|
||||
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_users_growth")
|
||||
async def admin_users_growth_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""Статистика роста пользователей"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
try:
|
||||
stats = await database.get_users_growth_stats()
|
||||
|
||||
# Формируем график роста за последние дни
|
||||
daily_chart = ""
|
||||
if stats['daily_growth']:
|
||||
daily_chart = "\n📈 **Рост по дням (последние 10):**\n"
|
||||
for day, count in stats['daily_growth']:
|
||||
daily_chart += f"• {day}: **{count}** новых\n"
|
||||
|
||||
report = f"""📈 **Рост пользователей**
|
||||
|
||||
🆕 **Новые пользователи:**
|
||||
• Сегодня: **{stats['new_today']:,}**
|
||||
• За неделю: **{stats['new_week']:,}**
|
||||
• За месяц: **{stats['new_month']:,}**
|
||||
• За год: **{stats['new_year']:,}**
|
||||
|
||||
📊 **Общая статистика:**
|
||||
• Первый пользователь: **{stats['first_user_date']}**
|
||||
• Дней с запуска: **{stats['days_since_start']:,}**
|
||||
• Средний рост в день: **{round(stats['new_year']/365, 1) if stats['days_since_start'] > 0 else 0}**
|
||||
|
||||
{daily_chart}
|
||||
|
||||
📅 **Сгенерировано:** {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🔙 Назад", callback_data="admin_users")
|
||||
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||
builder.adjust(2)
|
||||
|
||||
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error generating growth stats: {e}")
|
||||
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_users_premium")
|
||||
async def admin_users_premium_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""Анализ Premium пользователей"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
try:
|
||||
stats = await database.get_users_premium_stats()
|
||||
|
||||
report = f"""👑 **Premium анализ**
|
||||
|
||||
📊 **Распределение пользователей:**
|
||||
• Premium: **{stats['premium_users']:,}** ({stats['premium_percentage']:.1f}%)
|
||||
• Обычные: **{stats['regular_users']:,}** ({100-stats['premium_percentage']:.1f}%)
|
||||
|
||||
💰 **Средние платежи:**
|
||||
• Premium пользователи: **{stats['premium_avg_payment']:.1f}** ⭐️
|
||||
• Обычные пользователи: **{stats['regular_avg_payment']:.1f}** ⭐️
|
||||
• Разница: **{stats['premium_avg_payment'] - stats['regular_avg_payment']:.1f}** ⭐️
|
||||
|
||||
⚡ **Активность:**
|
||||
• Premium - взаимодействий: **{stats['premium_avg_interactions']:.1f}**
|
||||
• Обычные - взаимодействий: **{stats['regular_avg_interactions']:.1f}**
|
||||
|
||||
🎯 **Конверсия в покупки:**
|
||||
• Premium пользователи: **{stats['premium_conversion']:.1f}%** ({stats['premium_paying']}/{stats['premium_users']})
|
||||
• Обычные пользователи: **{stats['regular_conversion']:.1f}%** ({stats['regular_paying']}/{stats['regular_users']})
|
||||
|
||||
💡 **Вывод:** Premium пользователи платят в **{round(stats['premium_avg_payment']/stats['regular_avg_payment'], 1) if stats['regular_avg_payment'] > 0 else 0}х** раз больше и в **{round(stats['premium_conversion']/stats['regular_conversion'], 1) if stats['regular_conversion'] > 0 else 0}х** раз чаще покупают услуги.
|
||||
|
||||
📅 **Сгенерировано:** {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🔙 Назад", callback_data="admin_users")
|
||||
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||
builder.adjust(2)
|
||||
|
||||
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error generating premium stats: {e}")
|
||||
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_users_geo")
|
||||
async def admin_users_geo_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""География пользователей"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
try:
|
||||
stats = await database.get_users_geography_stats()
|
||||
|
||||
# Топ языков с экранированием специальных символов
|
||||
languages_text = ""
|
||||
if stats['top_languages']:
|
||||
languages_text = "\n🌍 <b>Топ языков:</b>\n"
|
||||
for lang, count, percentage in stats['top_languages']:
|
||||
# Экранируем потенциально проблемные символы
|
||||
safe_lang = str(lang).replace('&', '&').replace('<', '<').replace('>', '>')
|
||||
flag = {"ru": "🇷🇺", "en": "🇺🇸", "uk": "🇺🇦", "de": "🇩🇪", "fr": "🇫🇷", "es": "🇪🇸", "it": "🇮🇹"}.get(lang, "🌐")
|
||||
languages_text += f"• {flag} {safe_lang}: <b>{count:,}</b> ({percentage}%)\n"
|
||||
|
||||
# Источники регистрации
|
||||
sources_text = ""
|
||||
if stats['registration_sources']:
|
||||
sources_text = "\n📱 <b>Источники регистрации:</b>\n"
|
||||
for source, count in stats['registration_sources']:
|
||||
safe_source = str(source).replace('&', '&').replace('<', '<').replace('>', '>')
|
||||
sources_text += f"• {safe_source}: <b>{count:,}</b>\n"
|
||||
|
||||
report = f"""🌍 <b>География пользователей</b>
|
||||
|
||||
📊 <b>Языковая статистика:</b>
|
||||
• Всего языков: <b>{stats['total_languages']}</b>
|
||||
|
||||
{languages_text}
|
||||
|
||||
{sources_text}
|
||||
|
||||
📅 <b>Сгенерировано:</b> {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🔙 Назад", callback_data="admin_users")
|
||||
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||
builder.adjust(2)
|
||||
|
||||
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||
await callback.answer()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error generating geography stats: {e}")
|
||||
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_users_activity")
|
||||
async def admin_users_activity_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""Анализ активности пользователей"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
try:
|
||||
stats = await database.get_users_activity_stats()
|
||||
|
||||
# Распределение по активности
|
||||
activity_text = ""
|
||||
if stats['interaction_distribution']:
|
||||
activity_text = "\n👥 **Распределение по активности:**\n"
|
||||
for category, count in stats['interaction_distribution']:
|
||||
activity_text += f"• {category}: **{count:,}**\n"
|
||||
|
||||
report = f"""⚡ **Анализ активности**
|
||||
|
||||
📊 **Активность по периодам:**
|
||||
• За 1 день: **{stats['active_1d']:,}**
|
||||
• За 3 дня: **{stats['active_3d']:,}**
|
||||
• За 7 дней: **{stats['active_7d']:,}**
|
||||
• За 14 дней: **{stats['active_14d']:,}**
|
||||
• За 30 дней: **{stats['active_30d']:,}**
|
||||
• Неактивны 30+ дней: **{stats['inactive_30d']:,}**
|
||||
|
||||
📈 **Retention Rate:**
|
||||
• 1 день: **{round(stats['active_1d']/stats['active_30d']*100, 1) if stats['active_30d'] > 0 else 0}%**
|
||||
• 7 дней: **{round(stats['active_7d']/stats['active_30d']*100, 1) if stats['active_30d'] > 0 else 0}%**
|
||||
• 14 дней: **{round(stats['active_14d']/stats['active_30d']*100, 1) if stats['active_30d'] > 0 else 0}%**
|
||||
|
||||
{activity_text}
|
||||
|
||||
📅 **Сгенерировано:** {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🔙 Назад", callback_data="admin_users")
|
||||
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||
builder.adjust(2)
|
||||
|
||||
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error generating activity stats: {e}")
|
||||
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query(lambda c: c.data == "admin_users_sources")
|
||||
async def admin_users_sources_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||
"""Анализ источников пользователей"""
|
||||
database = db or oracle_db
|
||||
|
||||
if callback.from_user.id != ADMIN_USER_ID:
|
||||
await callback.answer("❌ Access denied.", show_alert=True)
|
||||
return
|
||||
|
||||
try:
|
||||
stats = await database.get_users_sources_stats()
|
||||
|
||||
# Основные источники
|
||||
sources_text = ""
|
||||
if stats['source_breakdown']:
|
||||
sources_text = "\n📊 **Источники пользователей:**\n"
|
||||
for source, total, paying, revenue, interactions, new_30d in stats['source_breakdown']:
|
||||
conversion = round(paying/total*100, 1) if total > 0 else 0
|
||||
avg_revenue = round(revenue, 1) if revenue else 0
|
||||
avg_int = round(interactions, 1) if interactions else 0
|
||||
sources_text += f"• **{source}**: {total:,} чел. (конв: {conversion}%, ср.доход: {avg_revenue}⭐, активность: {avg_int}, новых за 30д: {new_30d})\n"
|
||||
|
||||
# Реферальные источники
|
||||
referrals_text = ""
|
||||
if stats['top_referrals']:
|
||||
referrals_text = "\n🔗 **Топ реферальные источники:**\n"
|
||||
for source, count in stats['top_referrals']:
|
||||
referrals_text += f"• {source}: **{count:,}**\n"
|
||||
|
||||
report = f"""📋 **Источники пользователей**
|
||||
|
||||
{sources_text}
|
||||
|
||||
{referrals_text}
|
||||
|
||||
💡 **Анализ:** Наиболее эффективными являются источники с высокой конверсией в покупки и средним доходом на пользователя.
|
||||
|
||||
📅 **Сгенерировано:** {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🔙 Назад", callback_data="admin_users")
|
||||
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||
builder.adjust(2)
|
||||
|
||||
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||
await callback.answer()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error generating sources stats: {e}")
|
||||
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query(lambda c: c.data and c.data.startswith("pay_detailed_info:"))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user