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