Добавлены методы для получения аналитики пользователей в классе OracleDatabase, включая общую статистику, рост пользователей, анализ Premium пользователей, географию и активность. Обновлены обработчики колбеков в main.py для отображения соответствующих отчетов в админ-панели. Эти изменения улучшают функциональность аналитики и предоставляют администраторам более полное представление о пользователях.

This commit is contained in:
Vlad 2025-06-07 11:22:15 +03:00
parent fa5ed9c5c8
commit 9d51031d2d
2 changed files with 777 additions and 18 deletions

302
db.py
View File

@ -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
View File

@ -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('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
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('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
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:"))