feat: split subscription stats by access type

This commit is contained in:
3252a8
2026-05-31 00:08:31 +03:00
parent e7f93a5f47
commit 93353db511
9 changed files with 243 additions and 33 deletions
+7 -1
View File
@@ -71,11 +71,17 @@ async def show_statistics_handler(
f"📊 {_('admin_user_stats_total_label')}: <b>{user_stats['total_users']}</b>"
)
# Removed: Active today moved to panel stats
stats_text_parts.append(
f"📡 {_('admin_user_stats_active_subscription_label')}: <b>{user_stats['active_subscriptions']}</b>" # noqa: E501
)
stats_text_parts.append(
f"💳 {_('admin_user_stats_paid_subs_label')}: <b>{user_stats['paid_subscriptions']}</b>"
)
stats_text_parts.append(
f"🆓 {_('admin_user_stats_trial_label')}: <b>{user_stats['trial_users']}</b>"
f"🧪 {_('admin_user_stats_trial_label')}: <b>{user_stats['trial_users']}</b>"
)
stats_text_parts.append(
f"🎁 {_('admin_user_stats_free_subscription_label')}: <b>{user_stats['free_subscription_users']}</b>" # noqa: E501
)
stats_text_parts.append(
f"😴 {_('admin_user_stats_inactive_label')}: <b>{user_stats['inactive_users']}</b>"
+3 -1
View File
@@ -166,8 +166,10 @@ async def create_user_stats_result(
"inline_user_stats_message",
total=user_stats["total_users"],
active_today=user_stats["active_today"],
active=user_stats["active_subscriptions"],
paid=user_stats["paid_subscriptions"],
trial=user_stats["trial_users"],
free=user_stats["free_subscription_users"],
inactive=user_stats["inactive_users"],
banned=user_stats["banned_users"],
referral=user_stats["referral_users"],
@@ -179,7 +181,7 @@ async def create_user_stats_result(
description=_(
"inline_user_stats_description",
total=user_stats["total_users"],
active=user_stats["paid_subscriptions"],
active=user_stats["active_subscriptions"],
),
input_message_content=InputTextMessageContent(
message_text=stats_text, parse_mode="HTML"
+67 -14
View File
@@ -666,17 +666,33 @@ async def get_enhanced_user_statistics(session: AsyncSession) -> Dict[str, Any]:
active_today = int(user_counts[2] or 0)
referral_users = int(user_counts[3] or 0)
subscription_counts_stmt = (
provider_value = func.lower(func.coalesce(Subscription.provider, ""))
panel_status_value = func.upper(func.coalesce(Subscription.status_from_panel, ""))
trial_subscription_condition = or_(
provider_value == "trial",
panel_status_value == "TRIAL",
)
paid_subscription_condition = and_(
provider_value != "",
provider_value != "trial",
panel_status_value != "TRIAL",
)
free_subscription_condition = and_(
provider_value == "",
panel_status_value != "TRIAL",
)
active_subscription_flags_sq = (
select(
func.count(
func.distinct(
case((Subscription.provider.is_not(None), Subscription.user_id), else_=None)
)
Subscription.user_id.label("user_id"),
func.max(case((paid_subscription_condition, 1), else_=0)).label(
"has_paid_subscription"
),
func.count(
func.distinct(
case((Subscription.provider.is_(None), Subscription.user_id), else_=None)
)
func.max(case((trial_subscription_condition, 1), else_=0)).label(
"has_trial_subscription"
),
func.max(case((free_subscription_condition, 1), else_=0)).label(
"has_free_subscription"
),
)
.join(User, Subscription.user_id == User.user_id)
@@ -686,27 +702,64 @@ async def get_enhanced_user_statistics(session: AsyncSession) -> Dict[str, Any]:
Subscription.end_date > now,
)
)
.group_by(Subscription.user_id)
.subquery()
)
subscription_counts_stmt = select(
func.count(active_subscription_flags_sq.c.user_id),
func.coalesce(func.sum(active_subscription_flags_sq.c.has_paid_subscription), 0),
func.coalesce(
func.sum(
case(
(
active_subscription_flags_sq.c.has_paid_subscription == 0,
active_subscription_flags_sq.c.has_trial_subscription,
),
else_=0,
)
),
0,
),
func.coalesce(
func.sum(
case(
(
and_(
active_subscription_flags_sq.c.has_paid_subscription == 0,
active_subscription_flags_sq.c.has_trial_subscription == 0,
),
active_subscription_flags_sq.c.has_free_subscription,
),
else_=0,
)
),
0,
),
)
subscription_counts = (await session.execute(subscription_counts_stmt)).one()
paid_subs_users = int(subscription_counts[0] or 0)
trial_users = int(subscription_counts[1] or 0)
active_subscription_users = int(subscription_counts[0] or 0)
paid_subs_users = int(subscription_counts[1] or 0)
trial_users = int(subscription_counts[2] or 0)
free_subscription_users = int(subscription_counts[3] or 0)
# Inactive users (no active subscription)
inactive_users = total_users - paid_subs_users - trial_users - banned_users
inactive_users = total_users - active_subscription_users
return {
"total_users": total_users,
"banned_users": banned_users,
"active_today": active_today,
"active_subscriptions": active_subscription_users,
"paid_subscriptions": paid_subs_users,
"trial_users": trial_users,
"free_subscription_users": free_subscription_users,
"inactive_users": max(0, inactive_users),
"referral_users": referral_users,
}
async def get_user_ids_with_active_subscription(session: AsyncSession) -> List[int]:
"""Return non-banned user IDs who have an active subscription (paid or trial)."""
"""Return non-banned user IDs who have any active subscription."""
from datetime import datetime, timezone
now = datetime.now(timezone.utc)