Добавлены методы для получения аналитики пользователей в классе 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:
|
except Exception as e:
|
||||||
print(f"Error saving payment log for user {user.id}: {e}")
|
print(f"Error saving payment log for user {user.id}: {e}")
|
||||||
return False
|
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")
|
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:
|
try:
|
||||||
# Получаем общую статистику
|
stats = await database.get_users_general_stats()
|
||||||
stats = await database.get_users_summary()
|
|
||||||
|
|
||||||
# Формируем отчет
|
report = f"""📊 **Общая статистика пользователей**
|
||||||
report = f"""📊 **Bot Users Statistics**
|
|
||||||
|
|
||||||
👥 **Users Overview:**
|
👥 **Пользователи:**
|
||||||
• Total users: {stats.get('total_users', 0)}
|
• Всего пользователей: **{stats['total_users']:,}**
|
||||||
• Premium users: {stats.get('premium_users', 0)}
|
• Активных: **{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)} ⭐️
|
• Платящих пользователей: **{stats['paying_users']:,}**
|
||||||
• Total transactions: {stats.get('total_transactions', 0)}
|
• Конверсия в покупку: **{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)}
|
• За 24 часа: **{stats['active_24h']:,}**
|
||||||
• Active last week: {stats.get('active_last_week', 0)}
|
• За 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 = 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.message.answer(report, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error generating admin stats via callback: {e}")
|
logging.error(f"Error generating general user stats: {e}")
|
||||||
await callback.answer("❌ Error generating statistics. Please try again later.", show_alert=True)
|
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:"))
|
@dp.callback_query(lambda c: c.data and c.data.startswith("pay_detailed_info:"))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user