# app/services/stats_service.py """ Statistics service for generating system analytics and metrics. This module provides classes and functions for: - Comprehensive system statistics - Marketplace-specific analytics - Performance metrics and data insights - Cached statistics for performance """ import logging from typing import Any, Dict, List from sqlalchemy import func from sqlalchemy.orm import Session from app.exceptions import ValidationException from models.database.marketplace_product import MarketplaceProduct from models.database.stock import Stock logger = logging.getLogger(__name__) class StatsService: """Service class for statistics operations following the application's service pattern.""" def get_comprehensive_stats(self, db: Session) -> Dict[str, Any]: """ Get comprehensive statistics with marketplace data. Args: db: Database session Returns: Dictionary containing all statistics data Raises: ValidationException: If statistics generation fails """ try: # Use more efficient queries with proper indexes total_products = self._get_product_count(db) unique_brands = self._get_unique_brands_count(db) unique_categories = self._get_unique_categories_count(db) unique_marketplaces = self._get_unique_marketplaces_count(db) unique_vendors = self._get_unique_vendors_count(db) # Stock statistics stock_stats = self._get_stock_statistics(db) stats_data = { "total_products": total_products, "unique_brands": unique_brands, "unique_categories": unique_categories, "unique_marketplaces": unique_marketplaces, "unique_vendors": unique_vendors, "total_stock_entries": stock_stats["total_stock_entries"], "total_inventory_quantity": stock_stats["total_inventory_quantity"], } logger.info( f"Generated comprehensive stats: {total_products} products, {unique_marketplaces} marketplaces" ) return stats_data except Exception as e: logger.error(f"Error getting comprehensive stats: {str(e)}") raise ValidationException("Failed to retrieve system statistics") def get_marketplace_breakdown_stats(self, db: Session) -> List[Dict[str, Any]]: """ Get statistics broken down by marketplace. Args: db: Database session Returns: List of dictionaries containing marketplace statistics Raises: ValidationException: If marketplace statistics generation fails """ try: # Query to get stats per marketplace marketplace_stats = ( db.query( MarketplaceProduct.marketplace, func.count(MarketplaceProduct.id).label("total_products"), func.count(func.distinct(MarketplaceProduct.vendor_name)).label("unique_vendors"), func.count(func.distinct(MarketplaceProduct.brand)).label("unique_brands"), ) .filter(MarketplaceProduct.marketplace.isnot(None)) .group_by(MarketplaceProduct.marketplace) .all() ) stats_list = [ { "marketplace": stat.marketplace, "total_products": stat.total_products, "unique_vendors": stat.unique_vendors, "unique_brands": stat.unique_brands, } for stat in marketplace_stats ] logger.info( f"Generated marketplace breakdown stats for {len(stats_list)} marketplaces" ) return stats_list except Exception as e: logger.error(f"Error getting marketplace breakdown stats: {str(e)}") raise ValidationException("Failed to retrieve marketplace statistics") def get_product_statistics(self, db: Session) -> Dict[str, Any]: """ Get detailed product statistics. Args: db: Database session Returns: Dictionary containing product statistics """ try: stats = { "total_products": self._get_product_count(db), "unique_brands": self._get_unique_brands_count(db), "unique_categories": self._get_unique_categories_count(db), "unique_marketplaces": self._get_unique_marketplaces_count(db), "unique_vendors": self._get_unique_vendors_count(db), "products_with_gtin": self._get_products_with_gtin_count(db), "products_with_images": self._get_products_with_images_count(db), } return stats except Exception as e: logger.error(f"Error getting product statistics: {str(e)}") raise ValidationException("Failed to retrieve product statistics") def get_stock_statistics(self, db: Session) -> Dict[str, Any]: """ Get stock-related statistics. Args: db: Database session Returns: Dictionary containing stock statistics """ try: return self._get_stock_statistics(db) except Exception as e: logger.error(f"Error getting stock statistics: {str(e)}") raise ValidationException("Failed to retrieve stock statistics") def get_marketplace_details(self, db: Session, marketplace: str) -> Dict[str, Any]: """ Get detailed statistics for a specific marketplace. Args: db: Database session marketplace: Marketplace name Returns: Dictionary containing marketplace details """ try: if not marketplace or not marketplace.strip(): raise ValidationException("Marketplace name is required") product_count = self._get_products_by_marketplace_count(db, marketplace) brands = self._get_brands_by_marketplace(db, marketplace) vendors =self._get_vendors_by_marketplace(db, marketplace) return { "marketplace": marketplace, "total_products": product_count, "unique_brands": len(brands), "unique_vendors": len(vendors), "brands": brands, "vendors": vendors, } except ValidationException: raise # Re-raise custom exceptions except Exception as e: logger.error(f"Error getting marketplace details for {marketplace}: {str(e)}") raise ValidationException("Failed to retrieve marketplace details") # Private helper methods def _get_product_count(self, db: Session) -> int: """Get total product count.""" return db.query(MarketplaceProduct).count() def _get_unique_brands_count(self, db: Session) -> int: """Get count of unique brands.""" return ( db.query(MarketplaceProduct.brand) .filter(MarketplaceProduct.brand.isnot(None), MarketplaceProduct.brand != "") .distinct() .count() ) def _get_unique_categories_count(self, db: Session) -> int: """Get count of unique categories.""" return ( db.query(MarketplaceProduct.google_product_category) .filter( MarketplaceProduct.google_product_category.isnot(None), MarketplaceProduct.google_product_category != "", ) .distinct() .count() ) def _get_unique_marketplaces_count(self, db: Session) -> int: """Get count of unique marketplaces.""" return ( db.query(MarketplaceProduct.marketplace) .filter(MarketplaceProduct.marketplace.isnot(None), MarketplaceProduct.marketplace != "") .distinct() .count() ) def _get_unique_vendors_count(self, db: Session) -> int: """Get count of unique vendors.""" return ( db.query(MarketplaceProduct.vendor_name) .filter(MarketplaceProduct.vendor_name.isnot(None), MarketplaceProduct.vendor_name != "") .distinct() .count() ) def _get_products_with_gtin_count(self, db: Session) -> int: """Get count of products with GTIN.""" return ( db.query(MarketplaceProduct) .filter(MarketplaceProduct.gtin.isnot(None), MarketplaceProduct.gtin != "") .count() ) def _get_products_with_images_count(self, db: Session) -> int: """Get count of products with images.""" return ( db.query(MarketplaceProduct) .filter(MarketplaceProduct.image_link.isnot(None), MarketplaceProduct.image_link != "") .count() ) def _get_stock_statistics(self, db: Session) -> Dict[str, int]: """Get stock-related statistics.""" total_stock_entries = db.query(Stock).count() total_inventory = db.query(func.sum(Stock.quantity)).scalar() or 0 return { "total_stock_entries": total_stock_entries, "total_inventory_quantity": total_inventory, } def _get_brands_by_marketplace(self, db: Session, marketplace: str) -> List[str]: """Get unique brands for a specific marketplace.""" brands = ( db.query(MarketplaceProduct.brand) .filter( MarketplaceProduct.marketplace == marketplace, MarketplaceProduct.brand.isnot(None), MarketplaceProduct.brand != "", ) .distinct() .all() ) return [brand[0] for brand in brands] def _get_vendors_by_marketplace(self, db: Session, marketplace: str) -> List[str]: """Get unique vendors for a specific marketplace.""" vendors =( db.query(MarketplaceProduct.vendor_name) .filter( MarketplaceProduct.marketplace == marketplace, MarketplaceProduct.vendor_name.isnot(None), MarketplaceProduct.vendor_name != "", ) .distinct() .all() ) return [vendor [0] for vendor in vendors] def _get_products_by_marketplace_count(self, db: Session, marketplace: str) -> int: """Get product count for a specific marketplace.""" return db.query(MarketplaceProduct).filter(MarketplaceProduct.marketplace == marketplace).count() # Create service instance following the same pattern as other services stats_service = StatsService()