Spaces:
Running
Running
| """ | |
| Dashboard visualization components for Wildberries Analytics | |
| Creates interactive charts and visualizations using Plotly | |
| """ | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| from plotly.subplots import make_subplots | |
| import pandas as pd | |
| import numpy as np | |
| from datetime import datetime, timedelta | |
| from typing import Dict, List, Any, Optional | |
| import logging | |
| from wildberries_client import validate_wb_data, aggregate_wb_data | |
| logger = logging.getLogger(__name__) | |
| def create_sales_dashboard(sales_data: pd.DataFrame, period: str = "week", daily_revenue_data: pd.DataFrame = None) -> go.Figure: | |
| """ | |
| Create comprehensive sales dashboard with multiple visualizations | |
| Args: | |
| sales_data: DataFrame with sales data | |
| period: Analysis period ("week" or "month") | |
| daily_revenue_data: Pre-calculated daily revenue data (optional) | |
| Returns: | |
| Plotly figure with sales dashboard | |
| """ | |
| if sales_data.empty: | |
| return create_empty_chart("No sales data available") | |
| # Create subplot layout with better spacing | |
| fig = make_subplots( | |
| rows=3, cols=1, | |
| subplot_titles=[ | |
| "π Daily Revenue Trend", | |
| "π Top Products by Revenue", | |
| "π Sales by Category" | |
| ], | |
| specs=[[{"secondary_y": True}], | |
| [{"type": "bar"}], | |
| [{"type": "pie"}]], | |
| vertical_spacing=0.15, | |
| row_heights=[0.4, 0.35, 0.25] | |
| ) | |
| try: | |
| # 1. Daily Revenue Trend (Top Left) - Use pre-calculated data if available | |
| if daily_revenue_data is not None and not daily_revenue_data.empty: | |
| # Use the pre-calculated daily revenue data to ensure consistency | |
| daily_revenue = daily_revenue_data.copy() | |
| elif 'sale_date' in sales_data.columns and 'total_price' in sales_data.columns: | |
| # Fallback to calculating if not provided | |
| daily_revenue = sales_data.groupby(sales_data['sale_date'].dt.date).agg({ | |
| 'total_price': 'sum', | |
| 'quantity': 'sum' | |
| }).reset_index() | |
| else: | |
| daily_revenue = pd.DataFrame() | |
| if not daily_revenue.empty: | |
| # Revenue line | |
| fig.add_trace( | |
| go.Scatter( | |
| x=daily_revenue['sale_date'], | |
| y=daily_revenue['total_price'], | |
| mode='lines+markers', | |
| name='Revenue (β½)', | |
| line=dict(color='#2E86AB', width=3), | |
| marker=dict(size=8), | |
| hovertemplate='<b>%{x}</b><br>Revenue: β½%{y:,.0f}<extra></extra>' | |
| ), | |
| row=1, col=1 | |
| ) | |
| # Quantity bars on secondary y-axis | |
| fig.add_trace( | |
| go.Bar( | |
| x=daily_revenue['sale_date'], | |
| y=daily_revenue['quantity'], | |
| name='Quantity', | |
| marker_color='rgba(46, 134, 171, 0.3)', | |
| yaxis='y2', | |
| hovertemplate='<b>%{x}</b><br>Quantity: %{y}<extra></extra>' | |
| ), | |
| row=1, col=1, secondary_y=True | |
| ) | |
| # 2. Top Products by Revenue (Second Row) | |
| if 'product_name' in sales_data.columns and 'total_price' in sales_data.columns: | |
| top_products = sales_data.groupby('product_name')['total_price'].sum().nlargest(8).reset_index() | |
| fig.add_trace( | |
| go.Bar( | |
| x=top_products['total_price'], | |
| y=top_products['product_name'], | |
| orientation='h', | |
| name='Top Products', | |
| marker_color='#A23B72', | |
| hovertemplate='<b>%{y}</b><br>Revenue: β½%{x:,.0f}<extra></extra>' | |
| ), | |
| row=2, col=1 | |
| ) | |
| # 3. Sales by Category (Third Row) | |
| if 'category' in sales_data.columns and 'total_price' in sales_data.columns: | |
| category_sales = sales_data.groupby('category')['total_price'].sum().reset_index() | |
| fig.add_trace( | |
| go.Pie( | |
| labels=category_sales['category'], | |
| values=category_sales['total_price'], | |
| name='Categories', | |
| hovertemplate='<b>%{label}</b><br>Revenue: β½%{value:,.0f}<br>Percent: %{percent}<extra></extra>', | |
| marker_colors=px.colors.qualitative.Set3 | |
| ), | |
| row=3, col=1 | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error creating sales dashboard: {str(e)}") | |
| return create_empty_chart(f"Error creating dashboard: {str(e)}") | |
| # Update layout | |
| fig.update_layout( | |
| title=f"π Sales Analytics Dashboard - Last {period.title()}", | |
| title_x=0.5, | |
| showlegend=False, | |
| height=1100, | |
| font=dict(size=12), | |
| template="plotly_white", | |
| margin=dict(t=120, b=80, l=80, r=80) | |
| ) | |
| # Update axes labels | |
| fig.update_xaxes(title_text="Date", row=1, col=1) | |
| fig.update_yaxes(title_text="Revenue (β½)", row=1, col=1) | |
| fig.update_yaxes(title_text="Quantity", secondary_y=True, row=1, col=1) | |
| fig.update_xaxes(title_text="Revenue (β½)", row=2, col=1) | |
| fig.update_yaxes(title_text="Products", row=2, col=1) | |
| return fig | |
| def create_inventory_dashboard(forecast_data: pd.DataFrame) -> go.Figure: | |
| """ | |
| Create inventory risk analysis dashboard | |
| Args: | |
| forecast_data: DataFrame with forecast results | |
| Returns: | |
| Plotly figure with inventory dashboard | |
| """ | |
| if forecast_data.empty: | |
| return create_empty_chart("No inventory data available") | |
| # Create subplot layout | |
| fig = make_subplots( | |
| rows=3, cols=1, | |
| subplot_titles=[ | |
| "π¨ Risk Level Distribution", | |
| "β° Days Until Stockout", | |
| "π¦ Current Stock Levels" | |
| ], | |
| specs=[[{"type": "pie"}], | |
| [{"type": "bar"}], | |
| [{"type": "bar"}]], | |
| vertical_spacing=0.15, | |
| row_heights=[0.35, 0.35, 0.30] | |
| ) | |
| try: | |
| # 1. Risk Level Distribution (Top Left) | |
| if 'risk_level' in forecast_data.columns: | |
| risk_counts = forecast_data['risk_level'].value_counts() | |
| colors = { | |
| 'π΄ Critical': '#FF4444', | |
| 'π‘ Warning': '#FFAA00', | |
| 'π’ Safe': '#44AA44', | |
| 'β Error': '#888888' | |
| } | |
| fig.add_trace( | |
| go.Pie( | |
| labels=risk_counts.index, | |
| values=risk_counts.values, | |
| name='Risk Levels', | |
| marker_colors=[colors.get(label, '#CCCCCC') for label in risk_counts.index], | |
| hovertemplate='<b>%{label}</b><br>Count: %{value}<br>Percent: %{percent}<extra></extra>' | |
| ), | |
| row=1, col=1 | |
| ) | |
| # 2. Days Until Stockout (Top Right) | |
| if 'days_until_stockout' in forecast_data.columns and 'product_name' in forecast_data.columns: | |
| # Sort by days until stockout and take top 15 for readability | |
| sorted_data = forecast_data.nsmallest(15, 'days_until_stockout') | |
| # Color bars based on risk level | |
| bar_colors = [] | |
| for days in sorted_data['days_until_stockout']: | |
| if days < 7: | |
| bar_colors.append('#FF4444') | |
| elif days < 14: | |
| bar_colors.append('#FFAA00') | |
| else: | |
| bar_colors.append('#44AA44') | |
| fig.add_trace( | |
| go.Bar( | |
| x=sorted_data['days_until_stockout'], | |
| y=sorted_data['product_name'], | |
| orientation='h', | |
| name='Days Until Stockout', | |
| marker_color=bar_colors, | |
| hovertemplate='<b>%{y}</b><br>Days: %{x:.1f}<extra></extra>' | |
| ), | |
| row=2, col=1 | |
| ) | |
| # 3. Current Stock Levels (Third Row) | |
| if 'current_stock' in forecast_data.columns and 'product_name' in forecast_data.columns: | |
| # Take top 10 products by stock level for better visibility | |
| stock_data = forecast_data.nlargest(10, 'current_stock') | |
| fig.add_trace( | |
| go.Bar( | |
| x=stock_data['product_name'], | |
| y=stock_data['current_stock'], | |
| name='Current Stock', | |
| marker_color='#2E86AB', | |
| hovertemplate='<b>%{x}</b><br>Stock: %{y}<extra></extra>' | |
| ), | |
| row=3, col=1 | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error creating inventory dashboard: {str(e)}") | |
| return create_empty_chart(f"Error creating dashboard: {str(e)}") | |
| # Update layout | |
| fig.update_layout( | |
| title="π¦ Inventory Risk Analysis Dashboard", | |
| title_x=0.5, | |
| showlegend=False, | |
| height=1100, | |
| font=dict(size=12), | |
| template="plotly_white", | |
| margin=dict(t=120, b=80, l=80, r=80) | |
| ) | |
| # Update axes | |
| fig.update_xaxes(title_text="Days", row=2, col=1) | |
| fig.update_yaxes(title_text="Products", row=2, col=1) | |
| fig.update_xaxes(title_text="Products", row=3, col=1, tickangle=45) | |
| fig.update_yaxes(title_text="Stock Quantity", row=3, col=1) | |
| return fig | |
| def create_trend_chart(data: pd.DataFrame, | |
| date_col: str = 'sale_date', | |
| value_col: str = 'total_price', | |
| title: str = "Trend Analysis") -> go.Figure: | |
| """ | |
| Create a trend analysis chart | |
| Args: | |
| data: DataFrame with time series data | |
| date_col: Name of date column | |
| value_col: Name of value column | |
| title: Chart title | |
| Returns: | |
| Plotly figure with trend chart | |
| """ | |
| if data.empty: | |
| return create_empty_chart("No data available for trend analysis") | |
| try: | |
| # Group by date | |
| if date_col in data.columns and value_col in data.columns: | |
| daily_data = data.groupby(data[date_col].dt.date)[value_col].sum().reset_index() | |
| fig = go.Figure() | |
| # Add main trend line | |
| fig.add_trace( | |
| go.Scatter( | |
| x=daily_data[date_col], | |
| y=daily_data[value_col], | |
| mode='lines+markers', | |
| name='Daily Values', | |
| line=dict(color='#2E86AB', width=3), | |
| marker=dict(size=8), | |
| hovertemplate='<b>%{x}</b><br>Value: %{y:,.0f}<extra></extra>' | |
| ) | |
| ) | |
| # Add moving average if enough data points | |
| if len(daily_data) >= 7: | |
| daily_data['ma7'] = daily_data[value_col].rolling(window=7, center=True).mean() | |
| fig.add_trace( | |
| go.Scatter( | |
| x=daily_data[date_col], | |
| y=daily_data['ma7'], | |
| mode='lines', | |
| name='7-day Moving Average', | |
| line=dict(color='#A23B72', width=2, dash='dash'), | |
| hovertemplate='<b>%{x}</b><br>7-day MA: %{y:,.0f}<extra></extra>' | |
| ) | |
| ) | |
| fig.update_layout( | |
| title=title, | |
| title_x=0.5, | |
| xaxis_title="Date", | |
| yaxis_title="Value", | |
| template="plotly_white", | |
| height=400 | |
| ) | |
| return fig | |
| else: | |
| return create_empty_chart(f"Required columns not found: {date_col}, {value_col}") | |
| except Exception as e: | |
| logger.error(f"Error creating trend chart: {str(e)}") | |
| return create_empty_chart(f"Error creating chart: {str(e)}") | |
| def create_comparison_chart(data: pd.DataFrame, | |
| category_col: str = 'category', | |
| value_col: str = 'total_price', | |
| title: str = "Category Comparison") -> go.Figure: | |
| """ | |
| Create a comparison chart for categories | |
| Args: | |
| data: DataFrame with categorical data | |
| category_col: Name of category column | |
| value_col: Name of value column | |
| title: Chart title | |
| Returns: | |
| Plotly figure with comparison chart | |
| """ | |
| if data.empty: | |
| return create_empty_chart("No data available for comparison") | |
| try: | |
| if category_col in data.columns and value_col in data.columns: | |
| category_data = data.groupby(category_col)[value_col].sum().sort_values(ascending=True) | |
| fig = go.Figure() | |
| fig.add_trace( | |
| go.Bar( | |
| x=category_data.values, | |
| y=category_data.index, | |
| orientation='h', | |
| marker_color=px.colors.qualitative.Set3, | |
| hovertemplate='<b>%{y}</b><br>Value: %{x:,.0f}<extra></extra>' | |
| ) | |
| ) | |
| fig.update_layout( | |
| title=title, | |
| title_x=0.5, | |
| xaxis_title="Value", | |
| yaxis_title="Category", | |
| template="plotly_white", | |
| height=400 | |
| ) | |
| return fig | |
| else: | |
| return create_empty_chart(f"Required columns not found: {category_col}, {value_col}") | |
| except Exception as e: | |
| logger.error(f"Error creating comparison chart: {str(e)}") | |
| return create_empty_chart(f"Error creating chart: {str(e)}") | |
| def create_empty_chart(message: str = "No data available") -> go.Figure: | |
| """ | |
| Create an empty chart with a message | |
| Args: | |
| message: Message to display | |
| Returns: | |
| Plotly figure with message | |
| """ | |
| fig = go.Figure() | |
| fig.add_annotation( | |
| x=0.5, | |
| y=0.5, | |
| xref="paper", | |
| yref="paper", | |
| text=message, | |
| showarrow=False, | |
| font=dict(size=16, color="gray"), | |
| xanchor="center", | |
| yanchor="middle" | |
| ) | |
| fig.update_layout( | |
| template="plotly_white", | |
| height=400, | |
| xaxis=dict(showgrid=False, showticklabels=False, zeroline=False), | |
| yaxis=dict(showgrid=False, showticklabels=False, zeroline=False) | |
| ) | |
| return fig | |
| def create_kpi_cards(data: pd.DataFrame) -> Dict[str, Any]: | |
| """ | |
| Calculate KPIs for dashboard cards | |
| Args: | |
| data: DataFrame with sales/inventory data | |
| Returns: | |
| Dictionary with KPI values | |
| """ | |
| if data.empty: | |
| return { | |
| "total_revenue": 0, | |
| "total_orders": 0, | |
| "avg_order_value": 0, | |
| "top_product": "N/A" | |
| } | |
| kpis = {} | |
| try: | |
| # Revenue metrics | |
| if 'total_price' in data.columns: | |
| kpis['total_revenue'] = data['total_price'].sum() | |
| kpis['avg_order_value'] = data['total_price'].mean() | |
| else: | |
| kpis['total_revenue'] = 0 | |
| kpis['avg_order_value'] = 0 | |
| # Order count (net orders: sales minus returns) | |
| if 'is_return' in data.columns: | |
| sales_count = (~data['is_return']).sum() | |
| returns_count = data['is_return'].sum() | |
| kpis['total_orders'] = sales_count - returns_count | |
| kpis['sales_count'] = sales_count | |
| kpis['returns_count'] = returns_count | |
| else: | |
| kpis['total_orders'] = len(data) | |
| # Top product | |
| if 'product_name' in data.columns and 'total_price' in data.columns: | |
| top_product = data.groupby('product_name')['total_price'].sum().idxmax() | |
| kpis['top_product'] = top_product | |
| else: | |
| kpis['top_product'] = "N/A" | |
| # Growth calculations (if date column available) | |
| if 'sale_date' in data.columns: | |
| # Calculate week-over-week growth | |
| current_week = data[data['sale_date'] >= (datetime.now() - timedelta(days=7))] | |
| previous_week = data[ | |
| (data['sale_date'] >= (datetime.now() - timedelta(days=14))) & | |
| (data['sale_date'] < (datetime.now() - timedelta(days=7))) | |
| ] | |
| current_revenue = current_week['total_price'].sum() if 'total_price' in data.columns else 0 | |
| previous_revenue = previous_week['total_price'].sum() if 'total_price' in data.columns else 0 | |
| if previous_revenue > 0: | |
| growth_rate = ((current_revenue - previous_revenue) / previous_revenue) * 100 | |
| kpis['revenue_growth'] = round(growth_rate, 1) | |
| else: | |
| kpis['revenue_growth'] = 0 | |
| except Exception as e: | |
| logger.error(f"Error calculating KPIs: {str(e)}") | |
| kpis = { | |
| "total_revenue": 0, | |
| "total_orders": 0, | |
| "avg_order_value": 0, | |
| "top_product": "Error calculating", | |
| "revenue_growth": 0 | |
| } | |
| return kpis | |
| def format_metric(value: float, metric_type: str = "currency") -> str: | |
| """ | |
| Format metrics for display | |
| Args: | |
| value: Numeric value to format | |
| metric_type: Type of metric ("currency", "number", "percent") | |
| Returns: | |
| Formatted string | |
| """ | |
| if pd.isna(value) or value is None: | |
| return "N/A" | |
| try: | |
| if metric_type == "currency": | |
| return f"β½{value:,.0f}" | |
| elif metric_type == "percent": | |
| return f"{value:+.1f}%" | |
| elif metric_type == "number": | |
| return f"{value:,.0f}" | |
| else: | |
| return str(value) | |
| except: | |
| return "N/A" | |
| def create_wb_kpi_cards(data: pd.DataFrame) -> Dict[str, Any]: | |
| """Enhanced KPIs specifically for Wildberries data""" | |
| kpis = create_kpi_cards(data) # Use existing function | |
| try: | |
| # Add Wildberries-specific metrics | |
| if 'sales_commission' in data.columns: | |
| total_commission = data['sales_commission'].sum() | |
| kpis['total_commission'] = total_commission | |
| if 'total_price' in data.columns and data['total_price'].sum() > 0: | |
| commission_rate = (total_commission / data['total_price'].sum() * 100) | |
| kpis['avg_commission_rate'] = commission_rate | |
| else: | |
| kpis['avg_commission_rate'] = 0 | |
| if 'amount_for_pay' in data.columns: | |
| kpis['total_payout'] = data['amount_for_pay'].sum() | |
| # Calculate net profit (amount_for_pay is what seller receives) | |
| if 'total_price' in data.columns: | |
| gross_revenue = data['total_price'].sum() | |
| net_revenue = data['amount_for_pay'].sum() | |
| kpis['platform_fees'] = gross_revenue - net_revenue | |
| kpis['net_margin_percent'] = (net_revenue / gross_revenue * 100) if gross_revenue > 0 else 0 | |
| if 'office_name' in data.columns: | |
| office_sales = data.groupby('office_name')['total_price'].sum() | |
| if not office_sales.empty: | |
| kpis['top_office'] = office_sales.idxmax() | |
| kpis['top_office_revenue'] = office_sales.max() | |
| if 'delivery_cost' in data.columns: | |
| kpis['total_delivery_cost'] = data['delivery_cost'].sum() | |
| # Calculate sales velocity (average daily sales) | |
| if 'sale_date' in data.columns and not data.empty: | |
| date_range = (data['sale_date'].max() - data['sale_date'].min()).days | |
| if date_range > 0: | |
| kpis['daily_sales_velocity'] = len(data) / date_range | |
| else: | |
| kpis['daily_sales_velocity'] = len(data) | |
| except Exception as e: | |
| logger.error(f"Error calculating Wildberries KPIs: {str(e)}") | |
| return kpis | |
| def create_commission_analysis_chart(sales_data: pd.DataFrame) -> go.Figure: | |
| """Create commission analysis visualization for Wildberries data""" | |
| if sales_data.empty or 'sales_commission' not in sales_data.columns: | |
| return create_empty_chart("No commission data available") | |
| fig = make_subplots( | |
| rows=2, cols=2, | |
| subplot_titles=[ | |
| "Commission by Product", | |
| "Commission Rate Trend", | |
| "Revenue vs Commission", | |
| "Top Commission Products" | |
| ], | |
| specs=[[{"type": "bar"}, {"type": "scatter"}], | |
| [{"type": "scatter"}, {"type": "bar"}]], | |
| vertical_spacing=0.1, | |
| horizontal_spacing=0.1 | |
| ) | |
| try: | |
| # 1. Commission by Product (Top 10) | |
| if 'product_name' in sales_data.columns: | |
| product_commission = sales_data.groupby('product_name')['sales_commission'].sum().nlargest(10) | |
| fig.add_trace( | |
| go.Bar( | |
| x=product_commission.index, | |
| y=product_commission.values, | |
| name='Commission', | |
| marker_color='#FF6B6B', | |
| hovertemplate='<b>%{x}</b><br>Commission: β½%{y:,.0f}<extra></extra>' | |
| ), | |
| row=1, col=1 | |
| ) | |
| # 2. Daily commission trend | |
| if 'sale_date' in sales_data.columns: | |
| daily_commission = sales_data.groupby(sales_data['sale_date'].dt.date)['sales_commission'].sum() | |
| fig.add_trace( | |
| go.Scatter( | |
| x=daily_commission.index, | |
| y=daily_commission.values, | |
| mode='lines+markers', | |
| name='Daily Commission', | |
| line=dict(color='#4ECDC4', width=2), | |
| hovertemplate='<b>%{x}</b><br>Commission: β½%{y:,.0f}<extra></extra>' | |
| ), | |
| row=1, col=2 | |
| ) | |
| # 3. Revenue vs Commission scatter | |
| if 'total_price' in sales_data.columns and 'product_name' in sales_data.columns: | |
| product_data = sales_data.groupby('product_name').agg({ | |
| 'total_price': 'sum', | |
| 'sales_commission': 'sum' | |
| }).reset_index() | |
| fig.add_trace( | |
| go.Scatter( | |
| x=product_data['total_price'], | |
| y=product_data['sales_commission'], | |
| mode='markers', | |
| name='Revenue vs Commission', | |
| marker=dict( | |
| size=8, | |
| color='#45B7D1', | |
| opacity=0.7 | |
| ), | |
| text=product_data['product_name'], | |
| hovertemplate='<b>%{text}</b><br>Revenue: β½%{x:,.0f}<br>Commission: β½%{y:,.0f}<extra></extra>' | |
| ), | |
| row=2, col=1 | |
| ) | |
| # 4. Commission rate by product | |
| if 'product_name' in sales_data.columns and 'total_price' in sales_data.columns: | |
| product_stats = sales_data.groupby('product_name').agg({ | |
| 'total_price': 'sum', | |
| 'sales_commission': 'sum' | |
| }) | |
| product_stats['commission_rate'] = (product_stats['sales_commission'] / product_stats['total_price'] * 100) | |
| top_rates = product_stats['commission_rate'].nlargest(10) | |
| fig.add_trace( | |
| go.Bar( | |
| x=top_rates.index, | |
| y=top_rates.values, | |
| name='Commission Rate (%)', | |
| marker_color='#96CEB4', | |
| hovertemplate='<b>%{x}</b><br>Commission Rate: %{y:.1f}%<extra></extra>' | |
| ), | |
| row=2, col=2 | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error creating commission analysis: {str(e)}") | |
| return create_empty_chart(f"Error creating commission analysis: {str(e)}") | |
| # Update layout | |
| fig.update_layout( | |
| title="π° Commission Analysis Dashboard", | |
| title_x=0.5, | |
| showlegend=False, | |
| height=800, | |
| font=dict(size=10), | |
| template="plotly_white" | |
| ) | |
| # Update axes | |
| fig.update_xaxes(title_text="Products", row=1, col=1) | |
| fig.update_yaxes(title_text="Commission (β½)", row=1, col=1) | |
| fig.update_xaxes(title_text="Date", row=1, col=2) | |
| fig.update_yaxes(title_text="Commission (β½)", row=1, col=2) | |
| fig.update_xaxes(title_text="Revenue (β½)", row=2, col=1) | |
| fig.update_yaxes(title_text="Commission (β½)", row=2, col=1) | |
| fig.update_xaxes(title_text="Products", row=2, col=2) | |
| fig.update_yaxes(title_text="Commission Rate (%)", row=2, col=2) | |
| return fig | |
| def validate_and_process_wb_data(weekly_data: pd.DataFrame, monthly_data: pd.DataFrame) -> Dict[str, Any]: | |
| """Validate and process Wildberries data with enhanced error handling""" | |
| # Run validation | |
| validation_results = validate_wb_data(weekly_data, monthly_data) | |
| # Process data for better performance if large datasets | |
| processed_data = { | |
| "weekly": weekly_data, | |
| "monthly": monthly_data, | |
| "validation": validation_results | |
| } | |
| # Aggregate data if datasets are large (>1000 records) | |
| if len(weekly_data) > 1000: | |
| processed_data["weekly_aggregated"] = aggregate_wb_data(weekly_data, 'daily') | |
| logger.info(f"Aggregated weekly data from {len(weekly_data)} to {len(processed_data['weekly_aggregated'])} records") | |
| if len(monthly_data) > 1000: | |
| processed_data["monthly_aggregated"] = aggregate_wb_data(monthly_data, 'daily') | |
| logger.info(f"Aggregated monthly data from {len(monthly_data)} to {len(processed_data['monthly_aggregated'])} records") | |
| return processed_data |