diff --git a/db.py b/db.py
index fe12d87..b21cdeb 100644
--- a/db.py
+++ b/db.py
@@ -774,3 +774,503 @@ class OracleDatabase:
except Exception as e:
print(f"Error getting sources stats: {e}")
return {}
+
+ # ==============================================
+ # FINANCIAL ANALYTICS METHODS
+ # ==============================================
+
+ async def get_finance_revenue_stats(self, admin_user_id: int = 0) -> dict:
+ """Анализ доходов"""
+ def _get_stats():
+ with self._pool.acquire() as conn:
+ with conn.cursor() as cur:
+ # Общая статистика доходов (исключаем админа)
+ revenue_query = """
+ SELECT
+ SUM(payment_amount) as total_revenue,
+ COUNT(*) as total_transactions,
+ AVG(payment_amount) as avg_transaction,
+ COUNT(DISTINCT user_id) as unique_customers,
+ SUM(CASE WHEN created_date >= SYSDATE - 1 THEN payment_amount ELSE 0 END) as revenue_24h,
+ SUM(CASE WHEN created_date >= SYSDATE - 7 THEN payment_amount ELSE 0 END) as revenue_7d,
+ SUM(CASE WHEN created_date >= SYSDATE - 30 THEN payment_amount ELSE 0 END) as revenue_30d,
+ COUNT(CASE WHEN created_date >= SYSDATE - 1 THEN 1 END) as transactions_24h,
+ COUNT(CASE WHEN created_date >= SYSDATE - 7 THEN 1 END) as transactions_7d,
+ COUNT(CASE WHEN created_date >= SYSDATE - 30 THEN 1 END) as transactions_30d
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ """
+ cur.execute(revenue_query, {"admin_user_id": admin_user_id})
+ revenue = cur.fetchone()
+
+ # Доходы по дням за последние 10 дней (исключаем админа)
+ daily_query = """
+ SELECT
+ TO_CHAR(created_date, 'DD.MM') as day,
+ SUM(payment_amount) as revenue,
+ COUNT(*) as transactions
+ FROM payment_logs
+ WHERE created_date >= SYSDATE - 10 AND payment_status = 'completed' AND user_id != :admin_user_id
+ GROUP BY TO_CHAR(created_date, 'DD.MM'), TRUNC(created_date)
+ ORDER BY TRUNC(created_date) DESC
+ """
+ cur.execute(daily_query, {"admin_user_id": admin_user_id})
+ daily_revenue = cur.fetchall()
+
+ return {
+ "total_revenue": float(revenue[0]) if revenue[0] else 0.0,
+ "total_transactions": revenue[1] or 0,
+ "avg_transaction": round(float(revenue[2]), 2) if revenue[2] else 0.0,
+ "unique_customers": revenue[3] or 0,
+ "revenue_24h": float(revenue[4]) if revenue[4] else 0.0,
+ "revenue_7d": float(revenue[5]) if revenue[5] else 0.0,
+ "revenue_30d": float(revenue[6]) if revenue[6] else 0.0,
+ "transactions_24h": revenue[7] or 0,
+ "transactions_7d": revenue[8] or 0,
+ "transactions_30d": revenue[9] or 0,
+ "daily_revenue": daily_revenue[:10] if daily_revenue 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 revenue stats: {e}")
+ return {}
+
+ async def get_finance_services_stats(self, admin_user_id: int = 0) -> dict:
+ """Анализ доходов по услугам"""
+ def _get_stats():
+ with self._pool.acquire() as conn:
+ with conn.cursor() as cur:
+ try:
+ # Сначала проверим, есть ли данные в таблице (исключаем админа)
+ check_query = "SELECT COUNT(*) FROM payment_logs WHERE payment_status = 'completed' AND user_id != :admin_user_id"
+ cur.execute(check_query, {"admin_user_id": admin_user_id})
+ total_records = cur.fetchone()[0]
+
+ print(f"Total completed payment records: {total_records}")
+
+ if total_records == 0:
+ return {
+ "services_breakdown": [],
+ "trends": {}
+ }
+
+ # Статистика по типам услуг (исключаем админа)
+ services_query = """
+ SELECT
+ service_type,
+ COUNT(*) as transactions,
+ SUM(payment_amount) as revenue,
+ AVG(payment_amount) as avg_price,
+ COUNT(DISTINCT user_id) as unique_users,
+ COUNT(CASE WHEN service_status = 'success' THEN 1 END) as success_count,
+ COUNT(CASE WHEN refund_status != 'no_refund' THEN 1 END) as refunds,
+ AVG(data_found_count) as avg_data_found
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ GROUP BY service_type
+ ORDER BY SUM(payment_amount) DESC
+ """
+
+ print(f"Executing services query: {services_query}")
+ cur.execute(services_query, {"admin_user_id": admin_user_id})
+ services = cur.fetchall()
+ print(f"Services result: {services}")
+
+ # Тренды по услугам за последние 30 дней - упрощенный запрос (исключаем админа)
+ trends_query = """
+ SELECT
+ service_type,
+ SUM(CASE WHEN created_date >= SYSDATE - 7 THEN 1 ELSE 0 END) as week_transactions,
+ SUM(CASE WHEN created_date >= SYSDATE - 30 THEN 1 ELSE 0 END) as month_transactions
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ GROUP BY service_type
+ """
+
+ print(f"Executing trends query: {trends_query}")
+ cur.execute(trends_query, {"admin_user_id": admin_user_id})
+ trends_result = cur.fetchall()
+ print(f"Trends result: {trends_result}")
+
+ trends = {row[0]: {"week": row[1], "month": row[2]} for row in trends_result} if trends_result else {}
+
+ return {
+ "services_breakdown": services if services else [],
+ "trends": trends
+ }
+
+ except Exception as inner_e:
+ print(f"Inner error in services stats: {inner_e}")
+ # Возвращаем базовую структуру данных
+ return {
+ "services_breakdown": [],
+ "trends": {}
+ }
+
+ 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 services stats: {e}")
+ return {
+ "services_breakdown": [],
+ "trends": {}
+ }
+
+ async def get_finance_conversion_stats(self, admin_user_id: int = 0) -> dict:
+ """Анализ конверсии"""
+ def _get_stats():
+ with self._pool.acquire() as conn:
+ with conn.cursor() as cur:
+ try:
+ # Сначала получаем общее количество пользователей
+ users_query = "SELECT COUNT(DISTINCT id) FROM bot_users WHERE is_active = 1"
+ cur.execute(users_query)
+ total_users = cur.fetchone()[0] or 1
+
+ # Статистика по платежам (исключаем админа)
+ payment_query = """
+ SELECT
+ COUNT(DISTINCT user_id) as paying_users,
+ COUNT(*) as total_transactions,
+ COUNT(DISTINCT CASE WHEN user_is_premium = 1 THEN user_id END) as premium_buyers,
+ COUNT(DISTINCT CASE WHEN user_is_premium = 0 THEN user_id END) as regular_buyers,
+ AVG(payment_amount) as avg_purchase
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ """
+ cur.execute(payment_query, {"admin_user_id": admin_user_id})
+ payment_result = cur.fetchone()
+
+ if not payment_result:
+ return {
+ "total_users": total_users,
+ "paying_users": 0,
+ "conversion_rate": 0.0,
+ "total_transactions": 0,
+ "premium_buyers": 0,
+ "regular_buyers": 0,
+ "avg_purchase": 0.0,
+ "one_time_buyers": 0,
+ "regular_buyers": 0,
+ "loyal_buyers": 0,
+ "avg_purchases_per_user": 0.0,
+ "repeat_rate": 0.0
+ }
+
+ paying_users = payment_result[0] or 0
+
+ # Повторные покупки (исключаем админа)
+ repeat_query = """
+ SELECT
+ COUNT(CASE WHEN purchase_count = 1 THEN 1 END) as one_time_buyers,
+ COUNT(CASE WHEN purchase_count BETWEEN 2 AND 5 THEN 1 END) as regular_buyers_count,
+ COUNT(CASE WHEN purchase_count > 5 THEN 1 END) as loyal_buyers,
+ AVG(purchase_count) as avg_purchases_per_user
+ FROM (
+ SELECT user_id, COUNT(*) as purchase_count
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ GROUP BY user_id
+ )
+ """
+ cur.execute(repeat_query, {"admin_user_id": admin_user_id})
+ repeat = cur.fetchone()
+
+ return {
+ "total_users": total_users,
+ "paying_users": paying_users,
+ "conversion_rate": round(paying_users / total_users * 100, 2) if total_users > 0 else 0,
+ "total_transactions": payment_result[1] or 0,
+ "premium_buyers": payment_result[2] or 0,
+ "regular_buyers": payment_result[3] or 0,
+ "avg_purchase": round(float(payment_result[4]), 2) if payment_result[4] else 0.0,
+ "one_time_buyers": repeat[0] or 0 if repeat else 0,
+ "regular_buyers_repeat": repeat[1] or 0 if repeat else 0,
+ "loyal_buyers": repeat[2] or 0 if repeat else 0,
+ "avg_purchases_per_user": round(float(repeat[3]), 2) if repeat and repeat[3] else 0.0,
+ "repeat_rate": round(((repeat[1] or 0) + (repeat[2] or 0)) / paying_users * 100, 2) if paying_users > 0 and repeat else 0
+ }
+
+ except Exception as inner_e:
+ print(f"Inner error in conversion stats: {inner_e}")
+ return {
+ "total_users": 0,
+ "paying_users": 0,
+ "conversion_rate": 0.0,
+ "total_transactions": 0,
+ "premium_buyers": 0,
+ "regular_buyers": 0,
+ "avg_purchase": 0.0,
+ "one_time_buyers": 0,
+ "regular_buyers_repeat": 0,
+ "loyal_buyers": 0,
+ "avg_purchases_per_user": 0.0,
+ "repeat_rate": 0.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 conversion stats: {e}")
+ return {
+ "total_users": 0,
+ "paying_users": 0,
+ "conversion_rate": 0.0,
+ "total_transactions": 0,
+ "premium_buyers": 0,
+ "regular_buyers": 0,
+ "avg_purchase": 0.0,
+ "one_time_buyers": 0,
+ "regular_buyers_repeat": 0,
+ "loyal_buyers": 0,
+ "avg_purchases_per_user": 0.0,
+ "repeat_rate": 0.0
+ }
+
+ async def get_finance_refunds_stats(self, admin_user_id: int = 0) -> dict:
+ """Анализ возвратов"""
+ def _get_stats():
+ with self._pool.acquire() as conn:
+ with conn.cursor() as cur:
+ # Статистика возвратов (исключаем админа)
+ refunds_query = """
+ SELECT
+ COUNT(*) as total_transactions,
+ COUNT(CASE WHEN refund_status != 'no_refund' THEN 1 END) as refund_count,
+ SUM(CASE WHEN refund_status != 'no_refund' THEN payment_amount ELSE 0 END) as refund_amount,
+ COUNT(CASE WHEN refund_status = 'auto_refund' THEN 1 END) as auto_refunds,
+ COUNT(CASE WHEN refund_status = 'manual_refund' THEN 1 END) as manual_refunds,
+ COUNT(CASE WHEN refund_status = 'admin_refund' THEN 1 END) as admin_refunds
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ """
+ cur.execute(refunds_query, {"admin_user_id": admin_user_id})
+ refunds = cur.fetchone()
+
+ # Причины возвратов по типам услуг (исключаем админа)
+ reasons_query = """
+ SELECT
+ service_type,
+ refund_status,
+ COUNT(*) as count,
+ SUM(payment_amount) as amount
+ FROM payment_logs
+ WHERE refund_status != 'no_refund' AND payment_status = 'completed' AND user_id != :admin_user_id
+ GROUP BY service_type, refund_status
+ ORDER BY service_type, COUNT(*) DESC
+ """
+ cur.execute(reasons_query, {"admin_user_id": admin_user_id})
+ reasons = cur.fetchall()
+
+ total_transactions = refunds[0] or 1
+ refund_count = refunds[1] or 0
+
+ return {
+ "total_transactions": total_transactions,
+ "refund_count": refund_count,
+ "refund_rate": round(refund_count / total_transactions * 100, 2),
+ "refund_amount": float(refunds[2]) if refunds[2] else 0.0,
+ "auto_refunds": refunds[3] or 0,
+ "manual_refunds": refunds[4] or 0,
+ "admin_refunds": refunds[5] or 0,
+ "refund_breakdown": reasons if reasons 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 refunds stats: {e}")
+ return {}
+
+ async def get_finance_transactions_stats(self, admin_user_id: int = 0) -> dict:
+ """Анализ транзакций"""
+ def _get_stats():
+ with self._pool.acquire() as conn:
+ with conn.cursor() as cur:
+ # Статистика транзакций (исключаем админа)
+ transactions_query = """
+ SELECT
+ COUNT(*) as total_attempts,
+ COUNT(CASE WHEN payment_status = 'completed' THEN 1 END) as completed_count,
+ COUNT(CASE WHEN payment_status = 'pending' THEN 1 END) as pending_count,
+ COUNT(CASE WHEN payment_status = 'failed' THEN 1 END) as failed_count,
+ COUNT(CASE WHEN service_status = 'success' THEN 1 END) as service_success_count,
+ COUNT(CASE WHEN service_status = 'no_data' THEN 1 END) as no_data_count,
+ COUNT(CASE WHEN service_status = 'error' THEN 1 END) as service_error_count
+ FROM payment_logs
+ WHERE user_id != :admin_user_id
+ """
+ cur.execute(transactions_query, {"admin_user_id": admin_user_id})
+ transactions = cur.fetchone()
+
+ # Статистика ошибок (исключаем админа)
+ errors_query = """
+ SELECT
+ service_type,
+ COUNT(CASE WHEN payment_status = 'failed' THEN 1 END) as payment_failures,
+ COUNT(CASE WHEN service_status = 'error' THEN 1 END) as service_errors,
+ COUNT(CASE WHEN service_status = 'no_data' THEN 1 END) as no_data_cases
+ FROM payment_logs
+ WHERE user_id != :admin_user_id
+ GROUP BY service_type
+ """
+ cur.execute(errors_query, {"admin_user_id": admin_user_id})
+ errors = cur.fetchall()
+
+ total_attempts = transactions[0] or 1
+
+ return {
+ "total_attempts": total_attempts,
+ "completed": transactions[1] or 0,
+ "pending": transactions[2] or 0,
+ "failed": transactions[3] or 0,
+ "payment_success_rate": round((transactions[1] or 0) / total_attempts * 100, 2),
+ "service_success": transactions[4] or 0,
+ "no_data": transactions[5] or 0,
+ "service_error": transactions[6] or 0,
+ "service_success_rate": round((transactions[4] or 0) / (transactions[1] or 1) * 100, 2),
+ "error_breakdown": errors if errors 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 transactions stats: {e}")
+ return {}
+
+ async def get_finance_efficiency_stats(self, admin_user_id: int = 0) -> dict:
+ """Анализ эффективности"""
+ def _get_stats():
+ with self._pool.acquire() as conn:
+ with conn.cursor() as cur:
+ try:
+ # Разбиваем сложный запрос на несколько простых
+
+ # 1. Средний доход на транзакцию (исключаем админа)
+ avg_revenue_query = """
+ SELECT AVG(payment_amount) as avg_revenue_per_transaction
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ """
+ cur.execute(avg_revenue_query, {"admin_user_id": admin_user_id})
+ avg_revenue_result = cur.fetchone()
+ avg_revenue_per_transaction = round(float(avg_revenue_result[0]), 2) if avg_revenue_result and avg_revenue_result[0] else 0.0
+
+ # 2. Количество уникальных дней с транзакциями (исключаем админа)
+ days_query = """
+ SELECT COUNT(DISTINCT TRUNC(created_date)) as unique_days
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ """
+ cur.execute(days_query, {"admin_user_id": admin_user_id})
+ days_result = cur.fetchone()
+ unique_days = days_result[0] if days_result and days_result[0] else 1
+
+ # 3. Общее количество транзакций (исключаем админа)
+ total_transactions_query = """
+ SELECT COUNT(*) as total_transactions
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ """
+ cur.execute(total_transactions_query, {"admin_user_id": admin_user_id})
+ total_transactions_result = cur.fetchone()
+ total_transactions = total_transactions_result[0] if total_transactions_result else 0
+
+ avg_transactions_per_day = round(total_transactions / unique_days, 2) if unique_days > 0 else 0
+
+ # 4. Доход и транзакции на клиента (исключаем админа)
+ customer_stats_query = """
+ SELECT
+ SUM(payment_amount) as total_revenue,
+ COUNT(*) as total_transactions,
+ COUNT(DISTINCT user_id) as unique_customers
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ """
+ cur.execute(customer_stats_query, {"admin_user_id": admin_user_id})
+ customer_result = cur.fetchone()
+
+ if customer_result and customer_result[2] and customer_result[2] > 0:
+ revenue_per_customer = round(float(customer_result[0]) / customer_result[2], 2)
+ transactions_per_customer = round(float(customer_result[1]) / customer_result[2], 2)
+ else:
+ revenue_per_customer = 0.0
+ transactions_per_customer = 0.0
+
+ # 5. Эффективность по часам дня (исключаем админа)
+ hourly_query = """
+ SELECT
+ EXTRACT(HOUR FROM created_date) as hour,
+ COUNT(*) as transactions,
+ SUM(payment_amount) as revenue
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND created_date >= SYSDATE - 30 AND user_id != :admin_user_id
+ GROUP BY EXTRACT(HOUR FROM created_date)
+ ORDER BY EXTRACT(HOUR FROM created_date)
+ """
+ cur.execute(hourly_query, {"admin_user_id": admin_user_id})
+ hourly = cur.fetchall()
+
+ # 6. Топ VIN по доходам (исключаем админа)
+ top_vins_query = """
+ SELECT
+ vin_number,
+ COUNT(*) as requests,
+ SUM(payment_amount) as revenue
+ FROM payment_logs
+ WHERE payment_status = 'completed' AND user_id != :admin_user_id
+ GROUP BY vin_number
+ ORDER BY SUM(payment_amount) DESC
+ FETCH FIRST 10 ROWS ONLY
+ """
+ cur.execute(top_vins_query, {"admin_user_id": admin_user_id})
+ top_vins = cur.fetchall()
+
+ return {
+ "avg_revenue_per_transaction": avg_revenue_per_transaction,
+ "avg_transactions_per_day": avg_transactions_per_day,
+ "revenue_per_customer": revenue_per_customer,
+ "transactions_per_customer": transactions_per_customer,
+ "hourly_distribution": hourly if hourly else [],
+ "top_vins": top_vins[:10] if top_vins else []
+ }
+
+ except Exception as inner_e:
+ print(f"Inner error in efficiency stats: {inner_e}")
+ return {
+ "avg_revenue_per_transaction": 0.0,
+ "avg_transactions_per_day": 0.0,
+ "revenue_per_customer": 0.0,
+ "transactions_per_customer": 0.0,
+ "hourly_distribution": [],
+ "top_vins": []
+ }
+
+ 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 efficiency stats: {e}")
+ return {
+ "avg_revenue_per_transaction": 0.0,
+ "avg_transactions_per_day": 0.0,
+ "revenue_per_customer": 0.0,
+ "transactions_per_customer": 0.0,
+ "hourly_distribution": [],
+ "top_vins": []
+ }
+
+
diff --git a/main.py b/main.py
index 5aac882..2853779 100644
--- a/main.py
+++ b/main.py
@@ -690,12 +690,11 @@ async def admin_stats_callback(callback: CallbackQuery, db: OracleDatabase = Non
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.adjust(2)
builder.button(text="🏠 Main Menu", callback_data="main_menu")
- builder.adjust(1, 1)
+ builder.adjust(1)
await callback.message.answer(admin_menu_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
await callback.answer()
@@ -765,35 +764,6 @@ async def admin_finance_callback(callback: CallbackQuery, db: OracleDatabase = N
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):
@@ -1161,6 +1131,390 @@ async def admin_users_sources_callback(callback: CallbackQuery, db: OracleDataba
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
+# ==============================================
+# FINANCIAL ANALYTICS REPORT HANDLERS
+# ==============================================
+
+@dp.callback_query(lambda c: c.data == "admin_finance_revenue")
+async def admin_finance_revenue_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_finance_revenue_stats(ADMIN_USER_ID)
+
+ # Формируем график доходов
+ daily_chart = ""
+ if stats['daily_revenue']:
+ daily_chart = "\n📈 Доходы по дням (последние 10):\n"
+ for day, revenue, transactions in stats['daily_revenue']:
+ daily_chart += f"• {day}: {revenue:.0f} ⭐ ({transactions} транз.)\n"
+
+ report = f"""💵 Анализ доходов
+
+💰 Общая статистика:
+• Общая выручка: {stats['total_revenue']:,.0f} ⭐️
+• Всего транзакций: {stats['total_transactions']:,}
+• Средний чек: {stats['avg_transaction']:.1f} ⭐️
+• Уникальных клиентов: {stats['unique_customers']:,}
+
+📊 Доходы по периодам:
+• За 24 часа: {stats['revenue_24h']:,.0f} ⭐ ({stats['transactions_24h']} транз.)
+• За 7 дней: {stats['revenue_7d']:,.0f} ⭐ ({stats['transactions_7d']} транз.)
+• За 30 дней: {stats['revenue_30d']:,.0f} ⭐ ({stats['transactions_30d']} транз.)
+
+📈 Средние показатели:
+• Доход на клиента: {round(stats['total_revenue']/stats['unique_customers'], 1) if stats['unique_customers'] > 0 else 0:.1f} ⭐
+• Транзакций на клиента: {round(stats['total_transactions']/stats['unique_customers'], 1) if stats['unique_customers'] > 0 else 0:.1f}
+
+{daily_chart}
+
+📅 Сгенерировано: {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="🔙 Назад", callback_data="admin_finance")
+ 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 revenue stats: {e}")
+ await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
+
+
+@dp.callback_query(lambda c: c.data == "admin_finance_services")
+async def admin_finance_services_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_finance_services_stats(ADMIN_USER_ID)
+
+ # Проверяем, есть ли данные
+ if not stats or not stats.get('services_breakdown'):
+ report = """📊 Анализ по услугам
+
+📭 Нет данных
+
+В системе пока нет завершенных транзакций для анализа.
+
+💡 Возможные причины:
+• Таблица payment_logs пустая
+• Нет записей со статусом 'completed'
+• Все транзакции имеют статус 'pending' или 'failed'
+
+📅 Сгенерировано: {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
+ else:
+ # Статистика по услугам
+ services_text = ""
+ if stats['services_breakdown']:
+ services_text = "\n📊 Breakdown по услугам:\n"
+ service_names = {
+ 'decode_vin': '🔍 Декодинг VIN',
+ 'check_salvage': '💥 Проверка Salvage',
+ 'get_photos': '📸 Получение фото'
+ }
+
+ for service, transactions, revenue, avg_price, users, success_count, refunds, avg_data in stats['services_breakdown']:
+ name = service_names.get(service, service)
+ success_rate = round(success_count/transactions*100, 1) if transactions > 0 else 0
+ refund_rate = round(refunds/transactions*100, 1) if transactions > 0 else 0
+
+ # Получаем тренды
+ trends = stats['trends'].get(service, {"week": 0, "month": 0})
+
+ services_text += f"• {name}:\n"
+ services_text += f" 💰 Доход: {revenue:.0f} ⭐ ({transactions} транз.)\n"
+ services_text += f" 💵 Средняя цена: {avg_price:.1f} ⭐\n"
+ services_text += f" 👥 Клиентов: {users}\n"
+ services_text += f" ✅ Успешность: {success_rate}%\n"
+ services_text += f" ↩️ Возвраты: {refund_rate}%\n"
+ services_text += f" 📈 За неделю/месяц: {trends['week']}/{trends['month']}\n\n"
+
+ report = f"""📊 Анализ по услугам
+
+{services_text}
+
+💡 Выводы:
+• Наиболее доходная услуга по общей выручке
+• Услуга с наивысшей конверсией
+• Оптимизация ценообразования по успешности
+
+📅 Сгенерировано: {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="🔙 Назад", callback_data="admin_finance")
+ 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 services stats: {e}")
+ await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
+
+
+@dp.callback_query(lambda c: c.data == "admin_finance_conversion")
+async def admin_finance_conversion_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_finance_conversion_stats(ADMIN_USER_ID)
+
+ report = f"""🔄 Анализ конверсии
+
+🎯 Основная конверсия:
+• Всего пользователей: {stats['total_users']:,}
+• Платящих пользователей: {stats['paying_users']:,}
+• Конверсия в покупку: {stats['conversion_rate']:.2f}%
+
+👥 Сегментация покупателей:
+• Premium покупатели: {stats['premium_buyers']:,}
+• Обычные покупатели: {stats['regular_buyers']:,}
+• Средняя покупка: {stats['avg_purchase']:.1f} ⭐
+
+🔄 Повторные покупки:
+• Разовые покупатели: {stats['one_time_buyers']:,}
+• Регулярные (2-5): {stats['regular_buyers_repeat']:,}
+• Лояльные (5+): {stats['loyal_buyers']:,}
+• Среднее покупок на клиента: {stats['avg_purchases_per_user']:.1f}
+
+📈 Retention метрики:
+• Repeat Rate: {stats['repeat_rate']:.1f}%
+• Всего транзакций: {stats['total_transactions']:,}
+
+💡 Анализ:
+• {stats['repeat_rate']:.1f}% покупателей совершают повторные покупки
+• Потенциал роста конверсии до {100 - stats['conversion_rate']:.1f}%
+
+📅 Сгенерировано: {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="🔙 Назад", callback_data="admin_finance")
+ 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 conversion stats: {e}")
+ await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
+
+
+@dp.callback_query(lambda c: c.data == "admin_finance_refunds")
+async def admin_finance_refunds_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_finance_refunds_stats(ADMIN_USER_ID)
+
+ # Breakdown возвратов по услугам
+ refunds_breakdown = ""
+ if stats['refund_breakdown']:
+ refunds_breakdown = "\n↩️ Возвраты по услугам:\n"
+ service_names = {
+ 'decode_vin': '🔍 Декодинг VIN',
+ 'check_salvage': '💥 Проверка Salvage',
+ 'get_photos': '📸 Получение фото'
+ }
+
+ current_service = None
+ for service, refund_type, count, amount in stats['refund_breakdown']:
+ if service != current_service:
+ current_service = service
+ name = service_names.get(service, service)
+ refunds_breakdown += f"\n• {name}:\n"
+
+ refund_name = {
+ 'auto_refund': 'Авто возврат',
+ 'manual_refund': 'Ручной возврат',
+ 'admin_refund': 'Админ возврат'
+ }.get(refund_type, refund_type)
+
+ refunds_breakdown += f" - {refund_name}: {count} шт. ({amount:.0f} ⭐)\n"
+
+ report = f"""↩️ Анализ возвратов
+
+📊 Общая статистика:
+• Всего транзакций: {stats['total_transactions']:,}
+• Возвратов: {stats['refund_count']:,}
+• Процент возвратов: {stats['refund_rate']:.2f}%
+• Сумма возвратов: {stats['refund_amount']:,.0f} ⭐
+
+🔄 Типы возвратов:
+• Автоматические: {stats['auto_refunds']:,}
+• Ручные: {stats['manual_refunds']:,}
+• Админские: {stats['admin_refunds']:,}
+
+{refunds_breakdown}
+
+💡 Анализ:
+• Низкий процент возвратов (<5%) - показатель качества
+• Высокий процент (>10%) - требует оптимизации услуг
+• Автовозвраты снижают нагрузку на поддержку
+
+📅 Сгенерировано: {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="🔙 Назад", callback_data="admin_finance")
+ 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 refunds stats: {e}")
+ await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
+
+
+@dp.callback_query(lambda c: c.data == "admin_finance_transactions")
+async def admin_finance_transactions_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_finance_transactions_stats(ADMIN_USER_ID)
+
+ # Breakdown ошибок по услугам
+ errors_breakdown = ""
+ if stats['error_breakdown']:
+ errors_breakdown = "\n🚨 Ошибки по услугам:\n"
+ service_names = {
+ 'decode_vin': '🔍 Декодинг VIN',
+ 'check_salvage': '💥 Проверка Salvage',
+ 'get_photos': '📸 Получение фото'
+ }
+
+ for service, payment_failures, service_errors, no_data in stats['error_breakdown']:
+ name = service_names.get(service, service)
+ errors_breakdown += f"• {name}:\n"
+ errors_breakdown += f" - Ошибки платежей: {payment_failures}\n"
+ errors_breakdown += f" - Ошибки сервиса: {service_errors}\n"
+ errors_breakdown += f" - Нет данных: {no_data}\n"
+
+ report = f"""💳 Анализ транзакций
+
+💰 Статистика платежей:
+• Всего попыток: {stats['total_attempts']:,}
+• Успешно: {stats['completed']:,}
+• В ожидании: {stats['pending']:,}
+• Неудачно: {stats['failed']:,}
+• Успешность платежей: {stats['payment_success_rate']:.1f}%
+
+⚙️ Статистика сервисов:
+• Успешные сервисы: {stats['service_success']:,}
+• Нет данных: {stats['no_data']:,}
+• Ошибки сервиса: {stats['service_error']:,}
+• Успешность сервисов: {stats['service_success_rate']:.1f}%
+
+{errors_breakdown}
+
+📊 Ключевые метрики:
+• Конверсия попытка → успех: {stats['payment_success_rate']:.1f}%
+• Конверсия платеж → данные: {stats['service_success_rate']:.1f}%
+
+📅 Сгенерировано: {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="🔙 Назад", callback_data="admin_finance")
+ 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 transactions stats: {e}")
+ await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
+
+
+@dp.callback_query(lambda c: c.data == "admin_finance_efficiency")
+async def admin_finance_efficiency_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_finance_efficiency_stats(ADMIN_USER_ID)
+
+ # Распределение по часам
+ hourly_chart = ""
+ if stats['hourly_distribution']:
+ hourly_chart = "\n⏰ Активность по часам (UTC):\n"
+ for hour, transactions, revenue in stats['hourly_distribution'][:12]: # Топ 12 часов
+ hourly_chart += f"• {int(hour):02d}:00 - {transactions} транз. ({revenue:.0f} ⭐)\n"
+
+ # Топ VIN по доходам
+ top_vins_text = ""
+ if stats['top_vins']:
+ top_vins_text = "\n🏆 Топ VIN по доходам:\n"
+ for vin, requests, revenue in stats['top_vins'][:5]: # Топ 5
+ top_vins_text += f"• {vin}: {requests} запр. = {revenue:.0f} ⭐\n"
+
+ report = f"""🎯 Анализ эффективности
+
+💰 Финансовая эффективность:
+• Доход на транзакцию: {stats['avg_revenue_per_transaction']:.1f} ⭐
+• Транзакций в день: {stats['avg_transactions_per_day']:.1f}
+• Доход на клиента: {stats['revenue_per_customer']:.1f} ⭐
+• Транзакций на клиента: {stats['transactions_per_customer']:.1f}
+
+{hourly_chart}
+
+{top_vins_text}
+
+📊 Insights:
+• LTV клиента: {stats['revenue_per_customer']:.0f} ⭐
+• Средняя частота покупок: {stats['transactions_per_customer']:.1f}
+• Дневной оборот: {stats['avg_transactions_per_day'] * stats['avg_revenue_per_transaction']:.0f} ⭐
+
+📅 Сгенерировано: {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="🔙 Назад", callback_data="admin_finance")
+ 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 efficiency stats: {e}")
+ await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
+
+
+
+
@dp.callback_query(lambda c: c.data and c.data.startswith("pay_detailed_info:"))
async def pay_detailed_info_callback(callback: CallbackQuery, db: OracleDatabase = None):
# Используем переданный db или глобальный oracle_db