# app/modules/customers/services/customer_metrics.py """ Metrics provider for the customers module. Provides metrics for: - Customer counts - New customers - Customer addresses """ import logging from datetime import datetime, timedelta 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 CustomerMetricsProvider: """ Metrics provider for customers module. Provides customer-related metrics for store and platform dashboards. """ @property def metrics_category(self) -> str: return "customers" def get_store_metrics( self, db: Session, store_id: int, context: MetricsContext | None = None, ) -> list[MetricValue]: """ Get customer metrics for a specific store. Provides: - Total customers - New customers (in period) - Customers with addresses """ from app.modules.customers.models import Customer, CustomerAddress try: # Total customers total_customers = ( db.query(Customer).filter(Customer.store_id == store_id).count() ) # New customers (default to last 30 days) date_from = context.date_from if context else None if date_from is None: date_from = datetime.utcnow() - timedelta(days=30) new_customers_query = db.query(Customer).filter( Customer.store_id == store_id, Customer.created_at >= date_from, ) if context and context.date_to: new_customers_query = new_customers_query.filter( Customer.created_at <= context.date_to ) new_customers = new_customers_query.count() # Customers with addresses customers_with_addresses = ( db.query(func.count(func.distinct(CustomerAddress.customer_id))) .join(Customer, Customer.id == CustomerAddress.customer_id) .filter(Customer.store_id == store_id) .scalar() or 0 ) return [ MetricValue( key="customers.total", value=total_customers, label="Total Customers", category="customers", icon="users", description="Total number of customers", ), MetricValue( key="customers.new", value=new_customers, label="New Customers", category="customers", icon="user-plus", description="Customers acquired in the period", ), MetricValue( key="customers.with_addresses", value=customers_with_addresses, label="With Addresses", category="customers", icon="map-pin", description="Customers who have saved addresses", ), ] except Exception as e: logger.warning(f"Failed to get customer store metrics: {e}") return [] def get_platform_metrics( self, db: Session, platform_id: int, context: MetricsContext | None = None, ) -> list[MetricValue]: """ Get customer metrics aggregated for a platform. For platforms, aggregates customer data across all stores. """ from app.modules.customers.models import Customer from app.modules.tenancy.models import StorePlatform try: # Get all store IDs for this platform using StorePlatform junction table store_ids = ( db.query(StorePlatform.store_id) .filter( StorePlatform.platform_id == platform_id, StorePlatform.is_active == True, ) .subquery() ) # Total customers across all stores total_customers = ( db.query(Customer).filter(Customer.store_id.in_(store_ids)).count() ) # Unique customers (by email across platform) unique_customer_emails = ( db.query(func.count(func.distinct(Customer.email))) .filter(Customer.store_id.in_(store_ids)) .scalar() or 0 ) # New customers (default to last 30 days) date_from = context.date_from if context else None if date_from is None: date_from = datetime.utcnow() - timedelta(days=30) new_customers_query = db.query(Customer).filter( Customer.store_id.in_(store_ids), Customer.created_at >= date_from, ) if context and context.date_to: new_customers_query = new_customers_query.filter( Customer.created_at <= context.date_to ) new_customers = new_customers_query.count() return [ MetricValue( key="customers.total", value=total_customers, label="Total Customers", category="customers", icon="users", description="Total customer records across all stores", ), MetricValue( key="customers.unique_emails", value=unique_customer_emails, label="Unique Customers", category="customers", icon="user", description="Unique customer emails across platform", ), MetricValue( key="customers.new", value=new_customers, label="New Customers", category="customers", icon="user-plus", description="Customers acquired in the period", ), ] except Exception as e: logger.warning(f"Failed to get customer platform metrics: {e}") return [] def get_merchant_metrics( self, db: Session, merchant_id: int, context: MetricsContext | None = None, ) -> list[MetricValue]: """ Get customer metrics scoped to a merchant. Aggregates customer counts across all stores owned by the merchant. """ from app.modules.customers.models import Customer from app.modules.tenancy.models import Store try: merchant_store_ids = ( db.query(Store.id) .filter(Store.merchant_id == merchant_id) .subquery() ) total_customers = ( db.query(Customer) .filter(Customer.store_id.in_(merchant_store_ids)) .count() ) return [ MetricValue( key="customers.total", value=total_customers, label="Total Customers", category="customers", icon="users", description="Total customers across all merchant stores", ), ] except Exception as e: logger.warning(f"Failed to get customer merchant metrics: {e}") return [] # Singleton instance customer_metrics_provider = CustomerMetricsProvider() __all__ = ["CustomerMetricsProvider", "customer_metrics_provider"]