# 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.product import Product 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_shops = self._get_unique_shops_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_shops": unique_shops, "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( Product.marketplace, func.count(Product.id).label("total_products"), func.count(func.distinct(Product.shop_name)).label("unique_shops"), func.count(func.distinct(Product.brand)).label("unique_brands"), ) .filter(Product.marketplace.isnot(None)) .group_by(Product.marketplace) .all() ) stats_list = [ { "marketplace": stat.marketplace, "total_products": stat.total_products, "unique_shops": stat.unique_shops, "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_shops": self._get_unique_shops_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) shops = self._get_shops_by_marketplace(db, marketplace) return { "marketplace": marketplace, "total_products": product_count, "unique_brands": len(brands), "unique_shops": len(shops), "brands": brands, "shops": shops, } 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(Product).count() def _get_unique_brands_count(self, db: Session) -> int: """Get count of unique brands.""" return ( db.query(Product.brand) .filter(Product.brand.isnot(None), Product.brand != "") .distinct() .count() ) def _get_unique_categories_count(self, db: Session) -> int: """Get count of unique categories.""" return ( db.query(Product.google_product_category) .filter( Product.google_product_category.isnot(None), Product.google_product_category != "", ) .distinct() .count() ) def _get_unique_marketplaces_count(self, db: Session) -> int: """Get count of unique marketplaces.""" return ( db.query(Product.marketplace) .filter(Product.marketplace.isnot(None), Product.marketplace != "") .distinct() .count() ) def _get_unique_shops_count(self, db: Session) -> int: """Get count of unique shops.""" return ( db.query(Product.shop_name) .filter(Product.shop_name.isnot(None), Product.shop_name != "") .distinct() .count() ) def _get_products_with_gtin_count(self, db: Session) -> int: """Get count of products with GTIN.""" return ( db.query(Product) .filter(Product.gtin.isnot(None), Product.gtin != "") .count() ) def _get_products_with_images_count(self, db: Session) -> int: """Get count of products with images.""" return ( db.query(Product) .filter(Product.image_link.isnot(None), Product.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(Product.brand) .filter( Product.marketplace == marketplace, Product.brand.isnot(None), Product.brand != "", ) .distinct() .all() ) return [brand[0] for brand in brands] def _get_shops_by_marketplace(self, db: Session, marketplace: str) -> List[str]: """Get unique shops for a specific marketplace.""" shops = ( db.query(Product.shop_name) .filter( Product.marketplace == marketplace, Product.shop_name.isnot(None), Product.shop_name != "", ) .distinct() .all() ) return [shop[0] for shop in shops] def _get_products_by_marketplace_count(self, db: Session, marketplace: str) -> int: """Get product count for a specific marketplace.""" return db.query(Product).filter(Product.marketplace == marketplace).count() # Create service instance following the same pattern as other services stats_service = StatsService()