Fix AI insights feature issues
This commit is contained in:
@@ -501,15 +501,157 @@ class SalesService:
|
||||
error=str(e), product_id=product_id, tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
async def get_inventory_products_by_category(self, category: str, tenant_id: UUID,
|
||||
async def get_inventory_products_by_category(self, category: str, tenant_id: UUID,
|
||||
product_type: Optional[str] = None) -> List[Dict[str, Any]]:
|
||||
"""Get products by category from inventory service"""
|
||||
try:
|
||||
products = await self.inventory_client.get_products_by_category(category, tenant_id, product_type)
|
||||
logger.info("Retrieved inventory products by category", category=category,
|
||||
logger.info("Retrieved inventory products by category", category=category,
|
||||
count=len(products), tenant_id=tenant_id)
|
||||
return products
|
||||
except Exception as e:
|
||||
logger.error("Failed to get inventory products by category",
|
||||
logger.error("Failed to get inventory products by category",
|
||||
error=str(e), category=category, tenant_id=tenant_id)
|
||||
return []
|
||||
return []
|
||||
|
||||
async def analyze_product_demand_patterns(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
inventory_product_id: UUID,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
min_history_days: int = 90
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Analyze demand patterns for a specific product from historical sales data.
|
||||
|
||||
This method provides insights on:
|
||||
- Demand trends (increasing/decreasing)
|
||||
- Volatility (coefficient of variation)
|
||||
- Weekly seasonal patterns
|
||||
- Peak/low demand days
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant identifier
|
||||
inventory_product_id: Product identifier
|
||||
start_date: Start date for analysis (optional)
|
||||
end_date: End date for analysis (optional)
|
||||
min_history_days: Minimum days of history required
|
||||
|
||||
Returns:
|
||||
Analysis results with patterns, trends, and statistics
|
||||
"""
|
||||
try:
|
||||
import pandas as pd
|
||||
|
||||
logger.info(
|
||||
"Analyzing product demand patterns",
|
||||
tenant_id=tenant_id,
|
||||
inventory_product_id=inventory_product_id
|
||||
)
|
||||
|
||||
# Fetch sales data for the product
|
||||
sales_records = await self.get_product_sales(
|
||||
tenant_id=tenant_id,
|
||||
inventory_product_id=inventory_product_id,
|
||||
start_date=start_date,
|
||||
end_date=end_date
|
||||
)
|
||||
|
||||
if not sales_records or len(sales_records) < min_history_days:
|
||||
return {
|
||||
'analyzed_at': datetime.utcnow().isoformat(),
|
||||
'history_days': len(sales_records) if sales_records else 0,
|
||||
'patterns': {},
|
||||
'trend_analysis': {},
|
||||
'seasonal_factors': {},
|
||||
'statistics': {},
|
||||
'error': f'Insufficient historical data (need {min_history_days} days, got {len(sales_records) if sales_records else 0})'
|
||||
}
|
||||
|
||||
# Convert to DataFrame for analysis
|
||||
sales_data = pd.DataFrame([{
|
||||
'date': record.date,
|
||||
'quantity': record.quantity_sold,
|
||||
'revenue': float(record.revenue) if record.revenue else 0
|
||||
} for record in sales_records])
|
||||
|
||||
sales_data['date'] = pd.to_datetime(sales_data['date'])
|
||||
sales_data = sales_data.sort_values('date')
|
||||
|
||||
# Calculate basic statistics
|
||||
mean_demand = sales_data['quantity'].mean()
|
||||
std_demand = sales_data['quantity'].std()
|
||||
cv = (std_demand / mean_demand) if mean_demand > 0 else 0
|
||||
|
||||
# Trend analysis
|
||||
sales_data['days_since_start'] = (sales_data['date'] - sales_data['date'].min()).dt.days
|
||||
trend_correlation = sales_data['days_since_start'].corr(sales_data['quantity'])
|
||||
is_increasing = trend_correlation > 0.2
|
||||
is_decreasing = trend_correlation < -0.2
|
||||
|
||||
# Seasonal pattern detection (day of week)
|
||||
sales_data['day_of_week'] = sales_data['date'].dt.dayofweek
|
||||
weekly_pattern = sales_data.groupby('day_of_week')['quantity'].mean().to_dict()
|
||||
peak_day = max(weekly_pattern, key=weekly_pattern.get)
|
||||
low_day = min(weekly_pattern, key=weekly_pattern.get)
|
||||
peak_ratio = weekly_pattern[peak_day] / weekly_pattern[low_day] if weekly_pattern[low_day] > 0 else 1.0
|
||||
|
||||
logger.info(
|
||||
"Demand pattern analysis complete",
|
||||
tenant_id=tenant_id,
|
||||
inventory_product_id=inventory_product_id,
|
||||
data_points=len(sales_data),
|
||||
trend_direction='increasing' if is_increasing else 'decreasing' if is_decreasing else 'stable'
|
||||
)
|
||||
|
||||
return {
|
||||
'analyzed_at': datetime.utcnow().isoformat(),
|
||||
'history_days': len(sales_data),
|
||||
'date_range': {
|
||||
'start': sales_data['date'].min().isoformat(),
|
||||
'end': sales_data['date'].max().isoformat()
|
||||
},
|
||||
'statistics': {
|
||||
'mean_demand': round(mean_demand, 2),
|
||||
'std_demand': round(std_demand, 2),
|
||||
'coefficient_of_variation': round(cv, 3),
|
||||
'total_quantity': round(sales_data['quantity'].sum(), 2),
|
||||
'total_revenue': round(sales_data['revenue'].sum(), 2),
|
||||
'min_demand': round(sales_data['quantity'].min(), 2),
|
||||
'max_demand': round(sales_data['quantity'].max(), 2)
|
||||
},
|
||||
'trend_analysis': {
|
||||
'correlation': round(trend_correlation, 3),
|
||||
'is_increasing': is_increasing,
|
||||
'is_decreasing': is_decreasing,
|
||||
'direction': 'increasing' if is_increasing else 'decreasing' if is_decreasing else 'stable'
|
||||
},
|
||||
'patterns': {
|
||||
'weekly_pattern': {str(k): round(v, 2) for k, v in weekly_pattern.items()},
|
||||
'peak_day': int(peak_day),
|
||||
'low_day': int(low_day)
|
||||
},
|
||||
'seasonal_factors': {
|
||||
'peak_ratio': round(peak_ratio, 2),
|
||||
'has_strong_pattern': peak_ratio > 1.5
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Error analyzing product demand patterns",
|
||||
tenant_id=tenant_id,
|
||||
inventory_product_id=inventory_product_id,
|
||||
error=str(e),
|
||||
exc_info=True
|
||||
)
|
||||
return {
|
||||
'analyzed_at': datetime.utcnow().isoformat(),
|
||||
'history_days': 0,
|
||||
'patterns': {},
|
||||
'trend_analysis': {},
|
||||
'seasonal_factors': {},
|
||||
'statistics': {},
|
||||
'error': str(e)
|
||||
}
|
||||
Reference in New Issue
Block a user