Files
gatehouse-api/gatehouse_app/services/superadmin_analytics_service.py
T

178 lines
5.5 KiB
Python

"""Analytics service for platform-wide statistics."""
import logging
from datetime import datetime, timedelta, timezone
from gatehouse_app.models.organization.organization import Organization
from gatehouse_app.models.user.user import User
from gatehouse_app.models.user.session import Session
from gatehouse_app.models.superadmin_audit_log import SuperadminAuditLog
logger = logging.getLogger(__name__)
class SuperadminAnalyticsService:
"""Service for platform-wide analytics and statistics."""
@staticmethod
def get_dashboard_stats() -> dict:
"""Get dashboard statistics for the overview page.
Returns:
Dashboard stats including org count, user count, etc.
"""
now = datetime.now(timezone.utc)
thirty_days_ago = now - timedelta(days=30)
# Total organizations
total_orgs = Organization.query.filter(Organization.deleted_at.is_(None)).count()
active_orgs = Organization.query.filter(
Organization.deleted_at.is_(None),
Organization.is_active == True, # noqa: E712
).count()
# Total users
total_users = User.query.filter(User.deleted_at.is_(None)).count()
# Active sessions
active_sessions = Session.query.filter(
Session.deleted_at.is_(None),
Session.status == "active",
).count()
# New signups in last 30 days
new_users_30d = User.query.filter(
User.deleted_at.is_(None),
User.created_at >= thirty_days_ago,
).count()
# New organizations in last 30 days
new_orgs_30d = Organization.query.filter(
Organization.deleted_at.is_(None),
Organization.created_at >= thirty_days_ago,
).count()
# Suspended organizations
suspended_orgs = Organization.query.filter(
Organization.deleted_at.is_(None),
Organization.is_active == False, # noqa: E712
).count()
return {
"total_organizations": total_orgs,
"active_organizations": active_orgs,
"suspended_organizations": suspended_orgs,
"total_users": total_users,
"active_sessions": active_sessions,
"new_users_30d": new_users_30d,
"new_orgs_30d": new_orgs_30d,
"generated_at": now.isoformat() + "Z",
}
@staticmethod
def get_signup_trends(days: int = 30) -> dict:
"""Get signup trends over time.
Args:
days: Number of days to analyze
Returns:
Daily signup data
"""
now = datetime.now(timezone.utc)
start_date = now - timedelta(days=days)
# Get all users created in period
users = User.query.filter(
User.deleted_at.is_(None),
User.created_at >= start_date,
).all()
# Group by day
daily_signups = {}
for i in range(days):
date = (start_date + timedelta(days=i)).strftime("%Y-%m-%d")
daily_signups[date] = 0
for user in users:
date = user.created_at.strftime("%Y-%m-%d")
if date in daily_signups:
daily_signups[date] += 1
# Convert to list
history = [
{"date": date, "value": count}
for date, count in sorted(daily_signups.items())
]
return {
"period_start": start_date.isoformat() + "Z",
"period_end": now.isoformat() + "Z",
"total": len(users),
"history": history,
}
@staticmethod
def get_org_distribution() -> dict:
"""Get distribution of organizations by size.
Returns:
Organization size distribution
"""
orgs = Organization.query.filter(Organization.deleted_at.is_(None)).all()
distribution = {
"solo": 0, # 1 user
"small": 0, # 2-10 users
"medium": 0, # 11-50 users
"large": 0, # 51-200 users
"enterprise": 0, # 200+ users
}
for org in orgs:
count = org.get_member_count()
if count == 1:
distribution["solo"] += 1
elif count <= 10:
distribution["small"] += 1
elif count <= 50:
distribution["medium"] += 1
elif count <= 200:
distribution["large"] += 1
else:
distribution["enterprise"] += 1
return {
"distribution": distribution,
"total_orgs": len(orgs),
}
@staticmethod
def get_recent_activity(limit: int = 20) -> list:
"""Get recent superadmin actions.
Args:
limit: Maximum number of actions to return
Returns:
List of recent audit log entries
"""
logs = SuperadminAuditLog.query.filter(
SuperadminAuditLog.deleted_at.is_(None),
).order_by(
SuperadminAuditLog.created_at.desc()
).limit(limit).all()
return [
{
"id": log.id,
"superadmin_id": log.superadmin_id,
"action": log.action,
"resource_type": log.resource_type,
"resource_id": log.resource_id,
"extra_data": log.extra_data,
"ip_address": log.ip_address,
"user_agent": log.user_agent,
"created_at": log.created_at.isoformat() + "Z" if log.created_at else None,
}
for log in logs
]