Files
orion/app/services/stats_service.py

299 lines
11 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.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()