diff --git a/db.py b/db.py
index 6fb74ff..fe12d87 100644
--- a/db.py
+++ b/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 {}
diff --git a/main.py b/main.py
index af0d2b5..5aac882 100644
--- a/main.py
+++ b/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🌍 Топ языков:\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}: {count:,} ({percentage}%)\n"
+
+ # Источники регистрации
+ sources_text = ""
+ if stats['registration_sources']:
+ sources_text = "\n📱 Источники регистрации:\n"
+ for source, count in stats['registration_sources']:
+ safe_source = str(source).replace('&', '&').replace('<', '<').replace('>', '>')
+ sources_text += f"• {safe_source}: {count:,}\n"
+
+ report = f"""🌍 География пользователей
+
+📊 Языковая статистика:
+• Всего языков: {stats['total_languages']}
+
+{languages_text}
+
+{sources_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="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:"))