# app/modules/tenancy/services/tenancy_metrics.py """ Metrics provider for the tenancy module. Provides metrics for: - Store counts and status - User counts and activation - Team members (store users) - Custom domains """ import logging from typing import TYPE_CHECKING from sqlalchemy import func from sqlalchemy.orm import Session from app.modules.contracts.metrics import ( MetricsContext, MetricValue, ) if TYPE_CHECKING: pass logger = logging.getLogger(__name__) class TenancyMetricsProvider: """ Metrics provider for tenancy module. Provides store, user, and organizational metrics. """ @property def metrics_category(self) -> str: return "tenancy" def get_store_metrics( self, db: Session, store_id: int, context: MetricsContext | None = None, ) -> list[MetricValue]: """ Get metrics for a specific store. For stores, this provides: - Team member count - Custom domains count """ from app.modules.tenancy.models import StoreDomain, StoreUser try: # Team members count team_count = ( db.query(StoreUser) .filter(StoreUser.store_id == store_id, StoreUser.is_active == True) .count() ) # Custom domains count domains_count = ( db.query(StoreDomain) .filter(StoreDomain.store_id == store_id) .count() ) # Verified domains count verified_domains_count = ( db.query(StoreDomain) .filter( StoreDomain.store_id == store_id, StoreDomain.is_verified == True, ) .count() ) return [ MetricValue( key="tenancy.team_members", value=team_count, label="Team Members", category="tenancy", icon="users", description="Active team members with access to this store", ), MetricValue( key="tenancy.domains", value=domains_count, label="Custom Domains", category="tenancy", icon="globe", description="Custom domains configured for this store", ), MetricValue( key="tenancy.verified_domains", value=verified_domains_count, label="Verified Domains", category="tenancy", icon="check-circle", description="Custom domains that have been verified", ), ] except Exception as e: logger.warning(f"Failed to get tenancy store metrics: {e}") return [] def get_platform_metrics( self, db: Session, platform_id: int, context: MetricsContext | None = None, ) -> list[MetricValue]: """ Get metrics aggregated for a platform. For platforms, this provides: - Total stores - Active stores - Verified stores - Total users - Active users """ from app.modules.tenancy.models import ( AdminPlatform, Merchant, Store, StorePlatform, StoreUser, User, ) try: # Store metrics - using StorePlatform junction table # Get store IDs that are on this platform platform_store_ids = ( db.query(StorePlatform.store_id) .filter(StorePlatform.platform_id == platform_id) .subquery() ) total_stores = ( db.query(Store) .filter(Store.id.in_(platform_store_ids)) .count() ) # Active stores on this platform (store active AND membership active) active_store_ids = ( db.query(StorePlatform.store_id) .filter( StorePlatform.platform_id == platform_id, StorePlatform.is_active == True, ) .subquery() ) active_stores = ( db.query(Store) .filter( Store.id.in_(active_store_ids), Store.is_active == True, ) .count() ) verified_stores = ( db.query(Store) .filter( Store.id.in_(platform_store_ids), Store.is_verified == True, ) .count() ) pending_stores = ( db.query(Store) .filter( Store.id.in_(active_store_ids), Store.is_active == True, Store.is_verified == False, ) .count() ) inactive_stores = total_stores - active_stores # User metrics - using AdminPlatform junction table # Get user IDs that have access to this platform platform_user_ids = ( db.query(AdminPlatform.user_id) .filter( AdminPlatform.platform_id == platform_id, AdminPlatform.is_active == True, ) .subquery() ) total_users = ( db.query(User) .filter(User.id.in_(platform_user_ids)) .count() ) active_users = ( db.query(User) .filter( User.id.in_(platform_user_ids), User.is_active == True, ) .count() ) admin_users = ( db.query(User) .filter( User.id.in_(platform_user_ids), User.role.in_(["super_admin", "platform_admin"]), ) .count() ) inactive_users = total_users - active_users # Merchant user metrics # Owners: distinct users who own a merchant merchant_owners = ( db.query(func.count(func.distinct(Merchant.owner_user_id))).scalar() or 0 ) # Team members: distinct StoreUser users who are NOT merchant owners team_members = ( db.query(func.count(func.distinct(StoreUser.user_id))) .filter( ~StoreUser.user_id.in_(db.query(Merchant.owner_user_id)), ) .scalar() or 0 ) # Total: union of both sets (deduplicated) owner_ids = db.query(Merchant.owner_user_id).distinct() team_ids = db.query(StoreUser.user_id).distinct() merchant_users_total = ( db.query(func.count(func.distinct(User.id))) .filter(User.id.in_(owner_ids.union(team_ids))) .scalar() or 0 ) merchant_users_active = ( db.query(func.count(func.distinct(User.id))) .filter( User.id.in_(owner_ids.union(team_ids)), User.is_active == True, ) .scalar() or 0 ) # Calculate rates verification_rate = ( (verified_stores / total_stores * 100) if total_stores > 0 else 0 ) user_activation_rate = ( (active_users / total_users * 100) if total_users > 0 else 0 ) return [ # Store metrics MetricValue( key="tenancy.total_stores", value=total_stores, label="Total Stores", category="tenancy", icon="store", description="Total number of stores on this platform", ), MetricValue( key="tenancy.active_stores", value=active_stores, label="Active Stores", category="tenancy", icon="check-circle", description="Stores that are currently active", ), MetricValue( key="tenancy.verified_stores", value=verified_stores, label="Verified Stores", category="tenancy", icon="badge-check", description="Stores that have been verified", ), MetricValue( key="tenancy.pending_stores", value=pending_stores, label="Pending Stores", category="tenancy", icon="clock", description="Active stores pending verification", ), MetricValue( key="tenancy.inactive_stores", value=inactive_stores, label="Inactive Stores", category="tenancy", icon="pause-circle", description="Stores that are not currently active", ), MetricValue( key="tenancy.store_verification_rate", value=round(verification_rate, 1), label="Verification Rate", category="tenancy", icon="percent", unit="%", description="Percentage of stores that are verified", ), # User metrics MetricValue( key="tenancy.total_users", value=total_users, label="Total Users", category="tenancy", icon="users", description="Total number of users on this platform", ), MetricValue( key="tenancy.active_users", value=active_users, label="Active Users", category="tenancy", icon="user-check", description="Users that are currently active", ), MetricValue( key="tenancy.admin_users", value=admin_users, label="Admin Users", category="tenancy", icon="shield", description="Users with admin role", ), MetricValue( key="tenancy.inactive_users", value=inactive_users, label="Inactive Users", category="tenancy", icon="user-x", description="Users that are not currently active", ), MetricValue( key="tenancy.user_activation_rate", value=round(user_activation_rate, 1), label="User Activation Rate", category="tenancy", icon="percent", unit="%", description="Percentage of users that are active", ), # Merchant user metrics MetricValue( key="tenancy.merchant_users_total", value=merchant_users_total, label="Total Merchant Users", category="tenancy", icon="users", description="Total merchant-related users (owners and team members)", ), MetricValue( key="tenancy.merchant_users_active", value=merchant_users_active, label="Active Merchant Users", category="tenancy", icon="user-check", description="Active merchant users", ), MetricValue( key="tenancy.merchant_owners", value=merchant_owners, label="Merchant Owners", category="tenancy", icon="office-building", description="Distinct merchant owners", ), MetricValue( key="tenancy.merchant_team_members", value=team_members, label="Team Members", category="tenancy", icon="user-group", description="Distinct store team members", ), ] except Exception as e: logger.warning(f"Failed to get tenancy platform metrics: {e}") return [] # Singleton instance tenancy_metrics_provider = TenancyMetricsProvider() __all__ = ["TenancyMetricsProvider", "tenancy_metrics_provider"]