Добавлены новые классы аналитики: FinanceAnalytics и BusinessAnalytics в модуль базы данных. Эти изменения расширяют функциональность аналитики, предоставляя более полное представление о финансовых и бизнес-показателях.
This commit is contained in:
parent
a4d8862a19
commit
bf23b80ad9
3
.cursorignore
Normal file
3
.cursorignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
run.cmd
|
||||||
|
env.example
|
||||||
|
salvagebot*.dockerimg
|
||||||
@ -9,6 +9,8 @@ from .payment_tracking import PaymentTracker
|
|||||||
|
|
||||||
# Импорты из аналитики
|
# Импорты из аналитики
|
||||||
from .analytics.users_stats import UserAnalytics
|
from .analytics.users_stats import UserAnalytics
|
||||||
|
from .analytics.finance_stats import FinanceAnalytics
|
||||||
|
from .analytics.business_stats import BusinessAnalytics
|
||||||
|
|
||||||
# Главный класс базы данных - агрегатор всех модулей
|
# Главный класс базы данных - агрегатор всех модулей
|
||||||
class DatabaseManager(
|
class DatabaseManager(
|
||||||
@ -16,7 +18,9 @@ class DatabaseManager(
|
|||||||
VinQueries,
|
VinQueries,
|
||||||
UserManager,
|
UserManager,
|
||||||
PaymentTracker,
|
PaymentTracker,
|
||||||
UserAnalytics
|
UserAnalytics,
|
||||||
|
FinanceAnalytics,
|
||||||
|
BusinessAnalytics
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Главный класс для работы с базой данных
|
Главный класс для работы с базой данных
|
||||||
@ -31,5 +35,7 @@ __all__ = [
|
|||||||
'UserManager',
|
'UserManager',
|
||||||
'PaymentTracker',
|
'PaymentTracker',
|
||||||
'UserAnalytics',
|
'UserAnalytics',
|
||||||
|
'FinanceAnalytics',
|
||||||
|
'BusinessAnalytics',
|
||||||
'DatabaseManager'
|
'DatabaseManager'
|
||||||
]
|
]
|
||||||
504
database/analytics/business_stats.py
Normal file
504
database/analytics/business_stats.py
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
"""
|
||||||
|
Бизнес аналитика
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from ..base import OracleDatabase
|
||||||
|
|
||||||
|
|
||||||
|
class BusinessAnalytics(OracleDatabase):
|
||||||
|
"""Класс для бизнес аналитики"""
|
||||||
|
|
||||||
|
async def get_biz_trends_stats(self) -> Dict:
|
||||||
|
"""Анализ трендов бизнеса"""
|
||||||
|
def _get_stats():
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Тренд роста пользователей
|
||||||
|
user_growth_query = """
|
||||||
|
SELECT
|
||||||
|
TO_CHAR(created_at, 'YYYY-MM') as month,
|
||||||
|
COUNT(*) as new_users,
|
||||||
|
COUNT(CASE WHEN is_premium = 1 THEN 1 END) as premium_users
|
||||||
|
FROM bot_users
|
||||||
|
WHERE created_at >= ADD_MONTHS(SYSDATE, -12)
|
||||||
|
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
|
||||||
|
ORDER BY TO_CHAR(created_at, 'YYYY-MM')
|
||||||
|
"""
|
||||||
|
cur.execute(user_growth_query)
|
||||||
|
user_growth = cur.fetchall()
|
||||||
|
|
||||||
|
# Тренд выручки
|
||||||
|
revenue_trend_query = """
|
||||||
|
SELECT
|
||||||
|
TO_CHAR(created_date, 'YYYY-MM') as month,
|
||||||
|
COUNT(*) as transactions,
|
||||||
|
SUM(payment_amount) as revenue,
|
||||||
|
COUNT(DISTINCT user_id) as paying_users
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed'
|
||||||
|
AND created_date >= ADD_MONTHS(SYSDATE, -12)
|
||||||
|
GROUP BY TO_CHAR(created_date, 'YYYY-MM')
|
||||||
|
ORDER BY TO_CHAR(created_date, 'YYYY-MM')
|
||||||
|
"""
|
||||||
|
cur.execute(revenue_trend_query)
|
||||||
|
revenue_trend = cur.fetchall()
|
||||||
|
|
||||||
|
# Тренд использования услуг
|
||||||
|
services_trend_query = """
|
||||||
|
SELECT
|
||||||
|
service_type,
|
||||||
|
TO_CHAR(created_date, 'YYYY-MM') as month,
|
||||||
|
COUNT(*) as usage_count
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed'
|
||||||
|
AND created_date >= ADD_MONTHS(SYSDATE, -6)
|
||||||
|
GROUP BY service_type, TO_CHAR(created_date, 'YYYY-MM')
|
||||||
|
ORDER BY TO_CHAR(created_date, 'YYYY-MM'), service_type
|
||||||
|
"""
|
||||||
|
cur.execute(services_trend_query)
|
||||||
|
services_trend = cur.fetchall()
|
||||||
|
|
||||||
|
# Тренд конверсии
|
||||||
|
conversion_trend_query = """
|
||||||
|
SELECT
|
||||||
|
TO_CHAR(bu.created_at, 'YYYY-MM') as month,
|
||||||
|
COUNT(DISTINCT bu.id) as total_users,
|
||||||
|
COUNT(DISTINCT pl.user_id) as converted_users
|
||||||
|
FROM bot_users bu
|
||||||
|
LEFT JOIN payment_logs pl ON bu.id = pl.user_id AND pl.payment_status = 'completed'
|
||||||
|
WHERE bu.created_at >= ADD_MONTHS(SYSDATE, -12)
|
||||||
|
GROUP BY TO_CHAR(bu.created_at, 'YYYY-MM')
|
||||||
|
ORDER BY TO_CHAR(bu.created_at, 'YYYY-MM')
|
||||||
|
"""
|
||||||
|
cur.execute(conversion_trend_query)
|
||||||
|
conversion_trend = cur.fetchall()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'user_growth_trend': user_growth or [],
|
||||||
|
'revenue_trend': revenue_trend or [],
|
||||||
|
'services_trend': services_trend or [],
|
||||||
|
'conversion_trend': conversion_trend or []
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"SQL Error in get_biz_trends_stats:")
|
||||||
|
logging.error(f"Error: {e}")
|
||||||
|
return {
|
||||||
|
'user_growth_trend': [],
|
||||||
|
'revenue_trend': [],
|
||||||
|
'services_trend': [],
|
||||||
|
'conversion_trend': []
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
|
||||||
|
async def get_biz_forecasts_stats(self) -> Dict:
|
||||||
|
"""Прогнозы развития бизнеса"""
|
||||||
|
def _get_stats():
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Прогноз на основе последних 3 месяцев
|
||||||
|
recent_growth_query = """
|
||||||
|
SELECT
|
||||||
|
TO_CHAR(created_at, 'YYYY-MM') as month,
|
||||||
|
COUNT(*) as new_users,
|
||||||
|
SUM(total_payments) as revenue
|
||||||
|
FROM bot_users
|
||||||
|
WHERE created_at >= ADD_MONTHS(SYSDATE, -3)
|
||||||
|
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
|
||||||
|
ORDER BY TO_CHAR(created_at, 'YYYY-MM') DESC
|
||||||
|
"""
|
||||||
|
cur.execute(recent_growth_query)
|
||||||
|
recent_growth = cur.fetchall()
|
||||||
|
|
||||||
|
# Сезонность по дням недели
|
||||||
|
seasonality_query = """
|
||||||
|
SELECT
|
||||||
|
TO_CHAR(created_date, 'D') as day_of_week,
|
||||||
|
COUNT(*) as transactions,
|
||||||
|
AVG(payment_amount) as avg_amount
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed'
|
||||||
|
AND created_date >= SYSDATE - 30
|
||||||
|
GROUP BY TO_CHAR(created_date, 'D')
|
||||||
|
ORDER BY TO_CHAR(created_date, 'D')
|
||||||
|
"""
|
||||||
|
cur.execute(seasonality_query)
|
||||||
|
seasonality = cur.fetchall()
|
||||||
|
|
||||||
|
# Потенциал роста по регионам
|
||||||
|
regional_potential_query = """
|
||||||
|
SELECT
|
||||||
|
language_code,
|
||||||
|
COUNT(*) as user_count,
|
||||||
|
COUNT(CASE WHEN total_payments > 0 THEN 1 END) as paying_users,
|
||||||
|
AVG(total_payments) as avg_revenue
|
||||||
|
FROM bot_users
|
||||||
|
WHERE is_active = 1
|
||||||
|
GROUP BY language_code
|
||||||
|
ORDER BY COUNT(*) DESC
|
||||||
|
"""
|
||||||
|
cur.execute(regional_potential_query)
|
||||||
|
regional_potential = cur.fetchall()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'recent_growth': recent_growth or [],
|
||||||
|
'seasonality': seasonality or [],
|
||||||
|
'regional_potential': regional_potential or []
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"SQL Error in get_biz_forecasts_stats:")
|
||||||
|
logging.error(f"Error: {e}")
|
||||||
|
return {
|
||||||
|
'recent_growth': [],
|
||||||
|
'seasonality': [],
|
||||||
|
'regional_potential': []
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
|
||||||
|
async def get_biz_regions_stats(self) -> Dict:
|
||||||
|
"""Анализ регионов роста"""
|
||||||
|
def _get_stats():
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Топ регионы по росту
|
||||||
|
regions_growth_query = """
|
||||||
|
SELECT
|
||||||
|
language_code,
|
||||||
|
COUNT(CASE WHEN created_at >= SYSDATE - 30 THEN 1 END) as new_month,
|
||||||
|
COUNT(CASE WHEN created_at >= SYSDATE - 60 AND created_at < SYSDATE - 30 THEN 1 END) as prev_month,
|
||||||
|
COUNT(*) as total_users,
|
||||||
|
AVG(total_payments) as avg_revenue,
|
||||||
|
COUNT(CASE WHEN total_payments > 0 THEN 1 END) as paying_users
|
||||||
|
FROM bot_users
|
||||||
|
GROUP BY language_code
|
||||||
|
HAVING COUNT(*) >= 5
|
||||||
|
ORDER BY COUNT(CASE WHEN created_at >= SYSDATE - 30 THEN 1 END) DESC
|
||||||
|
"""
|
||||||
|
cur.execute(regions_growth_query)
|
||||||
|
regions_growth = cur.fetchall()
|
||||||
|
|
||||||
|
# Конверсия по регионам
|
||||||
|
regional_conversion_query = """
|
||||||
|
SELECT
|
||||||
|
bu.language_code,
|
||||||
|
COUNT(DISTINCT bu.id) as total_users,
|
||||||
|
COUNT(DISTINCT pl.user_id) as paying_users,
|
||||||
|
COUNT(pl.log_id) as total_transactions,
|
||||||
|
SUM(pl.payment_amount) as total_revenue
|
||||||
|
FROM bot_users bu
|
||||||
|
LEFT JOIN payment_logs pl ON bu.id = pl.user_id AND pl.payment_status = 'completed'
|
||||||
|
WHERE bu.is_active = 1
|
||||||
|
GROUP BY bu.language_code
|
||||||
|
HAVING COUNT(DISTINCT bu.id) >= 3
|
||||||
|
ORDER BY COUNT(DISTINCT pl.user_id) DESC
|
||||||
|
"""
|
||||||
|
cur.execute(regional_conversion_query)
|
||||||
|
regional_conversion = cur.fetchall()
|
||||||
|
|
||||||
|
# Premium распределение по регионам
|
||||||
|
premium_distribution_query = """
|
||||||
|
SELECT
|
||||||
|
language_code,
|
||||||
|
COUNT(*) as total_users,
|
||||||
|
COUNT(CASE WHEN is_premium = 1 THEN 1 END) as premium_users,
|
||||||
|
AVG(CASE WHEN is_premium = 1 THEN total_payments END) as premium_avg_revenue,
|
||||||
|
AVG(CASE WHEN is_premium = 0 THEN total_payments END) as regular_avg_revenue
|
||||||
|
FROM bot_users
|
||||||
|
WHERE is_active = 1
|
||||||
|
GROUP BY language_code
|
||||||
|
HAVING COUNT(*) >= 5
|
||||||
|
ORDER BY COUNT(CASE WHEN is_premium = 1 THEN 1 END) DESC
|
||||||
|
"""
|
||||||
|
cur.execute(premium_distribution_query)
|
||||||
|
premium_distribution = cur.fetchall()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'regions_growth': regions_growth or [],
|
||||||
|
'regional_conversion': regional_conversion or [],
|
||||||
|
'premium_distribution': premium_distribution or []
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"SQL Error in get_biz_regions_stats:")
|
||||||
|
logging.error(f"Error: {e}")
|
||||||
|
return {
|
||||||
|
'regions_growth': [],
|
||||||
|
'regional_conversion': [],
|
||||||
|
'premium_distribution': []
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
|
||||||
|
async def get_biz_monetization_stats(self) -> Dict:
|
||||||
|
"""Анализ монетизации"""
|
||||||
|
def _get_stats():
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Воронка монетизации
|
||||||
|
monetization_funnel_query = """
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_users,
|
||||||
|
COUNT(CASE WHEN interaction_count > 1 THEN 1 END) as engaged_users,
|
||||||
|
COUNT(CASE WHEN successful_payments_count > 0 THEN 1 END) as paying_users,
|
||||||
|
COUNT(CASE WHEN successful_payments_count > 1 THEN 1 END) as repeat_buyers,
|
||||||
|
COUNT(CASE WHEN total_payments > 50 THEN 1 END) as high_value_users
|
||||||
|
FROM bot_users
|
||||||
|
WHERE is_active = 1
|
||||||
|
"""
|
||||||
|
cur.execute(monetization_funnel_query)
|
||||||
|
funnel_result = cur.fetchone()
|
||||||
|
|
||||||
|
# Анализ LTV (пожизненная ценность)
|
||||||
|
ltv_analysis_query = """
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN total_payments = 0 THEN 'No Payment'
|
||||||
|
WHEN total_payments <= 10 THEN 'Low Value (≤10)'
|
||||||
|
WHEN total_payments <= 50 THEN 'Medium Value (11-50)'
|
||||||
|
WHEN total_payments <= 100 THEN 'High Value (51-100)'
|
||||||
|
ELSE 'Premium Value (>100)'
|
||||||
|
END as user_segment,
|
||||||
|
COUNT(*) as user_count,
|
||||||
|
AVG(total_payments) as avg_ltv,
|
||||||
|
SUM(total_payments) as total_revenue,
|
||||||
|
AVG(successful_payments_count) as avg_transactions
|
||||||
|
FROM bot_users
|
||||||
|
WHERE is_active = 1
|
||||||
|
GROUP BY CASE
|
||||||
|
WHEN total_payments = 0 THEN 'No Payment'
|
||||||
|
WHEN total_payments <= 10 THEN 'Low Value (≤10)'
|
||||||
|
WHEN total_payments <= 50 THEN 'Medium Value (11-50)'
|
||||||
|
WHEN total_payments <= 100 THEN 'High Value (51-100)'
|
||||||
|
ELSE 'Premium Value (>100)'
|
||||||
|
END
|
||||||
|
ORDER BY AVG(total_payments) DESC
|
||||||
|
"""
|
||||||
|
cur.execute(ltv_analysis_query)
|
||||||
|
ltv_analysis = cur.fetchall()
|
||||||
|
|
||||||
|
# Анализ прибыльности услуг
|
||||||
|
service_profitability_query = """
|
||||||
|
SELECT
|
||||||
|
service_type,
|
||||||
|
COUNT(*) as transaction_count,
|
||||||
|
SUM(payment_amount) as total_revenue,
|
||||||
|
AVG(payment_amount) as avg_price,
|
||||||
|
COUNT(DISTINCT user_id) as unique_users,
|
||||||
|
COUNT(CASE WHEN service_status = 'success' THEN 1 END) as successful_transactions
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed'
|
||||||
|
AND created_date >= SYSDATE - 90
|
||||||
|
GROUP BY service_type
|
||||||
|
ORDER BY SUM(payment_amount) DESC
|
||||||
|
"""
|
||||||
|
cur.execute(service_profitability_query)
|
||||||
|
service_profitability = cur.fetchall()
|
||||||
|
|
||||||
|
# Время до первой покупки
|
||||||
|
time_to_purchase_query = """
|
||||||
|
SELECT
|
||||||
|
ROUND(AVG(pl.created_date - bu.created_at), 1) as avg_days_to_purchase,
|
||||||
|
COUNT(*) as first_purchases
|
||||||
|
FROM bot_users bu
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT user_id, MIN(created_date) as first_purchase_date
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed'
|
||||||
|
GROUP BY user_id
|
||||||
|
) first_pl ON bu.id = first_pl.user_id
|
||||||
|
INNER JOIN payment_logs pl ON bu.id = pl.user_id AND pl.created_date = first_pl.first_purchase_date
|
||||||
|
WHERE bu.created_at >= SYSDATE - 365
|
||||||
|
"""
|
||||||
|
cur.execute(time_to_purchase_query)
|
||||||
|
time_to_purchase = cur.fetchone()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'monetization_funnel': funnel_result,
|
||||||
|
'ltv_analysis': ltv_analysis or [],
|
||||||
|
'service_profitability': service_profitability or [],
|
||||||
|
'time_to_purchase': time_to_purchase
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"SQL Error in get_biz_monetization_stats:")
|
||||||
|
logging.error(f"Error: {e}")
|
||||||
|
return {
|
||||||
|
'monetization_funnel': None,
|
||||||
|
'ltv_analysis': [],
|
||||||
|
'service_profitability': [],
|
||||||
|
'time_to_purchase': None
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
|
||||||
|
async def get_biz_optimization_stats(self) -> Dict:
|
||||||
|
"""Анализ возможностей оптимизации"""
|
||||||
|
def _get_stats():
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Анализ оттока пользователей
|
||||||
|
churn_analysis_query = """
|
||||||
|
SELECT
|
||||||
|
user_status,
|
||||||
|
COUNT(*) as user_count,
|
||||||
|
AVG(total_payments) as avg_revenue,
|
||||||
|
COUNT(CASE WHEN total_payments > 0 THEN 1 END) as paying_users
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN last_interaction_date >= SYSDATE - 7 THEN 'Active (0-7 days)'
|
||||||
|
WHEN last_interaction_date >= SYSDATE - 30 THEN 'Recent (8-30 days)'
|
||||||
|
WHEN last_interaction_date >= SYSDATE - 90 THEN 'Dormant (31-90 days)'
|
||||||
|
ELSE 'Churned (>90 days)'
|
||||||
|
END as user_status,
|
||||||
|
CASE
|
||||||
|
WHEN last_interaction_date >= SYSDATE - 7 THEN 1
|
||||||
|
WHEN last_interaction_date >= SYSDATE - 30 THEN 2
|
||||||
|
WHEN last_interaction_date >= SYSDATE - 90 THEN 3
|
||||||
|
ELSE 4
|
||||||
|
END as sort_order,
|
||||||
|
total_payments
|
||||||
|
FROM bot_users
|
||||||
|
WHERE is_active = 1 AND last_interaction_date IS NOT NULL
|
||||||
|
) t
|
||||||
|
GROUP BY user_status, sort_order
|
||||||
|
ORDER BY sort_order
|
||||||
|
"""
|
||||||
|
cur.execute(churn_analysis_query)
|
||||||
|
churn_analysis = cur.fetchall()
|
||||||
|
|
||||||
|
# Неэффективные запросы
|
||||||
|
inefficient_requests_query = """
|
||||||
|
SELECT
|
||||||
|
service_type,
|
||||||
|
COUNT(*) as total_requests,
|
||||||
|
COUNT(CASE WHEN service_status = 'no_data' THEN 1 END) as no_data_requests,
|
||||||
|
COUNT(CASE WHEN service_status = 'error' THEN 1 END) as error_requests,
|
||||||
|
COUNT(CASE WHEN refund_status <> 'no_refund' THEN 1 END) as refunded_requests
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed'
|
||||||
|
AND created_date >= SYSDATE - 30
|
||||||
|
GROUP BY service_type
|
||||||
|
ORDER BY service_type
|
||||||
|
"""
|
||||||
|
cur.execute(inefficient_requests_query)
|
||||||
|
inefficient_requests = cur.fetchall()
|
||||||
|
|
||||||
|
# Пользователи с высоким потенциалом
|
||||||
|
high_potential_query = """
|
||||||
|
SELECT
|
||||||
|
COUNT(CASE WHEN interaction_count >= 5 AND successful_payments_count = 0 THEN 1 END) as engaged_non_buyers,
|
||||||
|
COUNT(CASE WHEN successful_payments_count = 1 AND total_payments >= 5 THEN 1 END) as potential_repeat_buyers,
|
||||||
|
COUNT(CASE WHEN is_premium = 1 AND total_payments < 20 THEN 1 END) as underperforming_premium,
|
||||||
|
COUNT(CASE WHEN total_payments >= 10 AND last_interaction_date < SYSDATE - 30 THEN 1 END) as valuable_dormant
|
||||||
|
FROM bot_users
|
||||||
|
WHERE is_active = 1
|
||||||
|
"""
|
||||||
|
cur.execute(high_potential_query)
|
||||||
|
high_potential = cur.fetchone()
|
||||||
|
|
||||||
|
# Анализ ценообразования
|
||||||
|
pricing_analysis_query = """
|
||||||
|
SELECT
|
||||||
|
service_type,
|
||||||
|
payment_amount as price,
|
||||||
|
COUNT(*) as purchase_count,
|
||||||
|
COUNT(CASE WHEN service_status = 'success' THEN 1 END) as successful_count,
|
||||||
|
COUNT(CASE WHEN refund_status <> 'no_refund' THEN 1 END) as refund_count
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed'
|
||||||
|
AND created_date >= SYSDATE - 60
|
||||||
|
GROUP BY service_type, payment_amount
|
||||||
|
ORDER BY service_type, payment_amount
|
||||||
|
"""
|
||||||
|
cur.execute(pricing_analysis_query)
|
||||||
|
pricing_analysis = cur.fetchall()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'churn_analysis': churn_analysis or [],
|
||||||
|
'inefficient_requests': inefficient_requests or [],
|
||||||
|
'high_potential': high_potential,
|
||||||
|
'pricing_analysis': pricing_analysis or []
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"SQL Error in get_biz_optimization_stats:")
|
||||||
|
logging.error(f"Error: {e}")
|
||||||
|
return {
|
||||||
|
'churn_analysis': [],
|
||||||
|
'inefficient_requests': [],
|
||||||
|
'high_potential': None,
|
||||||
|
'pricing_analysis': []
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
|
||||||
|
async def get_biz_recommendations_stats(self) -> Dict:
|
||||||
|
"""Автоматические рекомендации для бизнеса"""
|
||||||
|
def _get_stats():
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Ключевые метрики для анализа
|
||||||
|
key_metrics_query = """
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_users,
|
||||||
|
COUNT(CASE WHEN successful_payments_count > 0 THEN 1 END) as paying_users,
|
||||||
|
AVG(total_payments) as avg_ltv,
|
||||||
|
COUNT(CASE WHEN last_interaction_date < SYSDATE - 30 THEN 1 END) as dormant_users,
|
||||||
|
COUNT(CASE WHEN interaction_count >= 5 AND successful_payments_count = 0 THEN 1 END) as engaged_non_buyers,
|
||||||
|
COUNT(CASE WHEN successful_payments_count = 1 THEN 1 END) as one_time_buyers
|
||||||
|
FROM bot_users
|
||||||
|
WHERE is_active = 1
|
||||||
|
"""
|
||||||
|
cur.execute(key_metrics_query)
|
||||||
|
key_metrics = cur.fetchone()
|
||||||
|
|
||||||
|
# Метрики успешности услуг
|
||||||
|
service_success_query = """
|
||||||
|
SELECT
|
||||||
|
service_type,
|
||||||
|
COUNT(*) as total_requests,
|
||||||
|
COUNT(CASE WHEN service_status = 'success' THEN 1 END) as successful_requests,
|
||||||
|
COUNT(CASE WHEN refund_status <> 'no_refund' THEN 1 END) as refunds,
|
||||||
|
AVG(payment_amount) as avg_price
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed'
|
||||||
|
AND created_date >= SYSDATE - 30
|
||||||
|
GROUP BY service_type
|
||||||
|
"""
|
||||||
|
cur.execute(service_success_query)
|
||||||
|
service_success = cur.fetchall()
|
||||||
|
|
||||||
|
# Рост пользователей за последние периоды
|
||||||
|
growth_metrics_query = """
|
||||||
|
SELECT
|
||||||
|
COUNT(CASE WHEN created_at >= SYSDATE - 7 THEN 1 END) as users_last_week,
|
||||||
|
COUNT(CASE WHEN created_at >= SYSDATE - 14 AND created_at < SYSDATE - 7 THEN 1 END) as users_prev_week,
|
||||||
|
COUNT(CASE WHEN created_at >= SYSDATE - 30 THEN 1 END) as users_last_month,
|
||||||
|
COUNT(CASE WHEN created_at >= SYSDATE - 60 AND created_at < SYSDATE - 30 THEN 1 END) as users_prev_month
|
||||||
|
FROM bot_users
|
||||||
|
"""
|
||||||
|
cur.execute(growth_metrics_query)
|
||||||
|
growth_metrics = cur.fetchone()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'key_metrics': key_metrics,
|
||||||
|
'service_success': service_success or [],
|
||||||
|
'growth_metrics': growth_metrics
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"SQL Error in get_biz_recommendations_stats:")
|
||||||
|
logging.error(f"Error: {e}")
|
||||||
|
return {
|
||||||
|
'key_metrics': None,
|
||||||
|
'service_success': [],
|
||||||
|
'growth_metrics': None
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
497
database/analytics/finance_stats.py
Normal file
497
database/analytics/finance_stats.py
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
"""
|
||||||
|
Финансовая аналитика
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from ..base import OracleDatabase
|
||||||
|
|
||||||
|
|
||||||
|
class FinanceAnalytics(OracleDatabase):
|
||||||
|
"""Класс для финансовой аналитики"""
|
||||||
|
|
||||||
|
async def get_finance_revenue_stats(self, admin_user_id: int = 0) -> Dict:
|
||||||
|
"""Анализ доходов"""
|
||||||
|
def _get_stats():
|
||||||
|
try:
|
||||||
|
with self._get_connection() 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 []
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"SQL Error in get_finance_revenue_stats:")
|
||||||
|
logging.error(f"Error: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error getting revenue stats: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def get_finance_services_stats(self, admin_user_id: int = 0) -> Dict:
|
||||||
|
"""Анализ доходов по услугам"""
|
||||||
|
def _get_stats():
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Сначала проверим, есть ли данные в таблице (исключаем админа)
|
||||||
|
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]
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
cur.execute(services_query, {"admin_user_id": admin_user_id})
|
||||||
|
services = cur.fetchall()
|
||||||
|
|
||||||
|
# Тренды по услугам за последние 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
|
||||||
|
"""
|
||||||
|
cur.execute(trends_query, {"admin_user_id": admin_user_id})
|
||||||
|
trends_result = cur.fetchall()
|
||||||
|
|
||||||
|
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 e:
|
||||||
|
logging.error(f"SQL Error in get_finance_services_stats:")
|
||||||
|
logging.error(f"Error: {e}")
|
||||||
|
return {
|
||||||
|
"services_breakdown": [],
|
||||||
|
"trends": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(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():
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Сначала получаем общее количество пользователей
|
||||||
|
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_repeat": 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 e:
|
||||||
|
logging.error(f"SQL Error in get_finance_conversion_stats:")
|
||||||
|
logging.error(f"Error: {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:
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(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():
|
||||||
|
try:
|
||||||
|
with self._get_connection() 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 []
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"SQL Error in get_finance_refunds_stats:")
|
||||||
|
logging.error(f"Error: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error getting refunds stats: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def get_finance_transactions_stats(self, admin_user_id: int = 0) -> Dict:
|
||||||
|
"""Анализ транзакций"""
|
||||||
|
def _get_stats():
|
||||||
|
try:
|
||||||
|
with self._get_connection() 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 []
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"SQL Error in get_finance_transactions_stats:")
|
||||||
|
logging.error(f"Error: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error getting transactions stats: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def get_finance_efficiency_stats(self, admin_user_id: int = 0) -> Dict:
|
||||||
|
"""Анализ эффективности"""
|
||||||
|
def _get_stats():
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# 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 e:
|
||||||
|
logging.error(f"SQL Error in get_finance_efficiency_stats:")
|
||||||
|
logging.error(f"Error: {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:
|
||||||
|
return await self._execute_query(_get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(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": []
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user