Files
orion/app/services/stats_service.py

299 lines
10 KiB
Python

# 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()