Spaces:
Running
Running
| """ | |
| Report Agent β Enterprise Business Intelligence Reports | |
| ========================================================= | |
| Generates comprehensive, McKinsey-quality summaries of business performance. | |
| Uses dynamic column detection β works with ANY dataset, not just sales data. | |
| """ | |
| from agents.base.agent_runner import AgentRunner, Insight | |
| from graph.query import revenue_dataframe | |
| import pandas as pd | |
| import logging | |
| from typing import List, Dict, Optional, Tuple | |
| from datetime import datetime, timedelta | |
| logger = logging.getLogger(__name__) | |
| # ============================================================================= | |
| # Dynamic Column Detection Helpers | |
| # ============================================================================= | |
| def _detect_amount_column(df: pd.DataFrame) -> Optional[str]: | |
| """Find the primary numeric 'amount' column (revenue, sales, price, etc.).""" | |
| # Priority order of known column names | |
| candidates = [ | |
| 'amount', 'total_amount', 'revenue', 'sales', 'total', 'price', | |
| 'value', 'total_price', 'net_amount', 'gross_amount', 'total_revenue', | |
| 'invoice_amount', 'order_total', 'sum', 'cost', | |
| ] | |
| for col in candidates: | |
| if col in df.columns: | |
| return col | |
| # Fallback: pick the first numeric column that looks like money | |
| numeric_cols = df.select_dtypes(include='number').columns.tolist() | |
| if numeric_cols: | |
| # Prefer columns with large ranges (likely monetary) | |
| for col in numeric_cols: | |
| if df[col].max() > 1 and df[col].min() >= 0: | |
| return col | |
| return numeric_cols[0] | |
| return None | |
| def _detect_category_column(df: pd.DataFrame, role: str = 'customer') -> Optional[str]: | |
| """Find a categorical column by role (customer, product, region, etc.).""" | |
| role_candidates = { | |
| 'customer': ['customer', 'customer_name', 'client', 'client_name', 'account', 'buyer', 'company', 'user'], | |
| 'product': ['product', 'product_name', 'item', 'item_name', 'sku', 'service', 'category', 'product_category'], | |
| 'region': ['region', 'country', 'city', 'state', 'location', 'territory', 'market', 'area'], | |
| } | |
| candidates = role_candidates.get(role, [role]) | |
| for col in candidates: | |
| if col in df.columns: | |
| return col | |
| return None | |
| def _detect_date_column(df: pd.DataFrame) -> Optional[str]: | |
| """Find the primary date/time column.""" | |
| candidates = ['date', 'created_at', 'order_date', 'timestamp', 'datetime', 'invoice_date', 'transaction_date'] | |
| for col in candidates: | |
| if col in df.columns: | |
| return col | |
| # Fallback: first datetime column | |
| dt_cols = df.select_dtypes(include='datetime').columns.tolist() | |
| if dt_cols: | |
| return dt_cols[0] | |
| return None | |
| def _auto_detect_groupby_col(df: pd.DataFrame) -> Optional[str]: | |
| """Pick the best categorical groupby column automatically.""" | |
| object_cols = df.select_dtypes(include='object').columns.tolist() | |
| if not object_cols: | |
| return None | |
| # Prefer columns with reasonable cardinality (2β500 unique values) | |
| scored = [] | |
| for col in object_cols: | |
| nunique = df[col].nunique() | |
| if 2 <= nunique <= 500: | |
| scored.append((col, nunique)) | |
| if scored: | |
| # Prefer lower cardinality (more groupable) | |
| scored.sort(key=lambda x: x[1]) | |
| return scored[0][0] | |
| return object_cols[0] if object_cols else None | |
| # ============================================================================= | |
| # Report Agent | |
| # ============================================================================= | |
| class ReportAgent(AgentRunner): | |
| """Generates premium enterprise-grade business reports using dynamic column detection.""" | |
| def __init__(self): | |
| super().__init__('ReportAgent') | |
| async def detect_insights(self, workspace_id: str) -> List[Insight]: | |
| """Generate report insight""" | |
| insights = [] | |
| try: | |
| df = revenue_dataframe(workspace_id) | |
| if df is None or df.empty: | |
| self.logger.warning(f"No data for workspace {workspace_id}") | |
| return [] | |
| # Generate comprehensive report | |
| report_data = await self._generate_enterprise_report(df) | |
| if report_data: | |
| insight = Insight( | |
| title="π AI Business Intelligence Report", | |
| body=report_data['summary'], | |
| severity='info', | |
| score=100, # Always send reports | |
| metadata=report_data['metrics'], | |
| chart_payload=report_data.get('chart_data') | |
| ) | |
| insights.append(insight) | |
| self.logger.info(f"ReportAgent generated {len(insights)} insights") | |
| return insights | |
| except Exception as e: | |
| self.logger.error(f"ReportAgent failed: {e}", exc_info=True) | |
| return [] | |
| async def _generate_enterprise_report(self, df: pd.DataFrame) -> Optional[Dict]: | |
| """Generate enterprise-quality report using dynamic column detection.""" | |
| try: | |
| # ============================================ | |
| # DYNAMIC COLUMN DETECTION | |
| # ============================================ | |
| amount_col = _detect_amount_column(df) | |
| customer_col = _detect_category_column(df, 'customer') | |
| product_col = _detect_category_column(df, 'product') | |
| date_col = _detect_date_column(df) | |
| total_rows = len(df) | |
| # ============================================ | |
| # CORE METRICS (with fallbacks) | |
| # ============================================ | |
| if amount_col: | |
| total_revenue = df[amount_col].sum() | |
| avg_order_value = total_revenue / total_rows if total_rows > 0 else 0 | |
| min_value = df[amount_col].min() | |
| max_value = df[amount_col].max() | |
| median_value = df[amount_col].median() | |
| else: | |
| total_revenue = 0 | |
| avg_order_value = 0 | |
| min_value = 0 | |
| max_value = 0 | |
| median_value = 0 | |
| # ============================================ | |
| # CUSTOMER/DIMENSION ANALYSIS | |
| # ============================================ | |
| top_customers = [] | |
| customer_concentration = 0 | |
| unique_customers = 0 | |
| if customer_col and amount_col: | |
| unique_customers = df[customer_col].nunique() | |
| customer_revenue = df.groupby(customer_col)[amount_col].sum().sort_values(ascending=False) | |
| top_5_revenue = customer_revenue.head(5).sum() | |
| customer_concentration = (top_5_revenue / total_revenue * 100) if total_revenue > 0 else 0 | |
| for name, revenue in customer_revenue.head(5).items(): | |
| orders = len(df[df[customer_col] == name]) | |
| pct = (revenue / total_revenue * 100) if total_revenue > 0 else 0 | |
| top_customers.append({ | |
| 'name': str(name), | |
| 'revenue': float(revenue), | |
| 'orders': int(orders), | |
| 'percentage': round(pct, 1) | |
| }) | |
| elif customer_col: | |
| unique_customers = df[customer_col].nunique() | |
| # ============================================ | |
| # PRODUCT/CATEGORY ANALYSIS | |
| # ============================================ | |
| top_products = [] | |
| best_product = None | |
| unique_products = 0 | |
| if product_col and amount_col: | |
| unique_products = df[product_col].nunique() | |
| product_revenue = df.groupby(product_col)[amount_col].sum().sort_values(ascending=False) | |
| best_product = str(product_revenue.index[0]) if len(product_revenue) > 0 else None | |
| for name, revenue in product_revenue.head(5).items(): | |
| units = len(df[df[product_col] == name]) | |
| pct = (revenue / total_revenue * 100) if total_revenue > 0 else 0 | |
| top_products.append({ | |
| 'name': str(name), | |
| 'revenue': float(revenue), | |
| 'units': int(units), | |
| 'percentage': round(pct, 1) | |
| }) | |
| elif product_col: | |
| unique_products = df[product_col].nunique() | |
| # ============================================ | |
| # BUILD REPORT SUMMARY | |
| # ============================================ | |
| currency = "βΉ" | |
| # Dynamic label based on detected column | |
| amount_label = (amount_col or "value").replace("_", " ").title() | |
| customer_label = (customer_col or "entity").replace("_", " ").title() | |
| product_label = (product_col or "category").replace("_", " ").title() | |
| summary = f""" | |
| π **Executive Summary** | |
| Your dataset contains **{total_rows:,}** records""" | |
| if amount_col: | |
| summary += f""" with total {amount_label} of **{currency}{total_revenue:,.2f}**. | |
| --- | |
| π° **Key Performance Indicators** | |
| | Metric | Value | | |
| |--------|-------| | |
| | Total {amount_label} | {currency}{total_revenue:,.2f} | | |
| | Total Records | {total_rows:,} | | |
| | Average {amount_label} | {currency}{avg_order_value:,.2f} | | |
| | Median {amount_label} | {currency}{median_value:,.2f} | | |
| | Min {amount_label} | {currency}{min_value:,.2f} | | |
| | Max {amount_label} | {currency}{max_value:,.2f} |""" | |
| else: | |
| summary += ".\n\n> β οΈ No numeric amount column detected β showing row-count analysis only." | |
| if unique_customers: | |
| summary += f"\n| Unique {customer_label}s | {unique_customers} |" | |
| if unique_products: | |
| summary += f"\n| Unique {product_label}s | {unique_products} |" | |
| summary += "\n\n---\n" | |
| # Customer section | |
| if top_customers: | |
| summary += f"\nπ₯ **Top 5 {customer_label}s**\n\n" | |
| summary += f"| Rank | {customer_label} | {amount_label} | Records | Share |\n" | |
| summary += "|------|----------|---------|--------|-------|\n" | |
| for i, c in enumerate(top_customers, 1): | |
| summary += f"| {i} | {c['name']} | {currency}{c['revenue']:,.2f} | {c['orders']} | {c['percentage']}% |\n" | |
| summary += f"\nβ οΈ **Concentration Risk:** Top 5 = {customer_concentration:.1f}% of total\n\n---\n" | |
| # Product section | |
| if top_products: | |
| summary += f"\nπ¦ **Top 5 {product_label}s**\n\n" | |
| summary += f"| Rank | {product_label} | {amount_label} | Units | Share |\n" | |
| summary += "|------|---------|---------|-------|-------|\n" | |
| for i, p in enumerate(top_products, 1): | |
| summary += f"| {i} | {p['name']} | {currency}{p['revenue']:,.2f} | {p['units']} | {p['percentage']}% |\n" | |
| summary += "\n---\n" | |
| # AI Recommendations | |
| summary += "\nπ‘ **AI Recommendations**\n\n" | |
| if customer_concentration > 50: | |
| summary += f"1. **π΄ High Concentration** - Top 5 {customer_label}s represent {customer_concentration:.0f}% of total. Diversify.\n" | |
| elif customer_concentration > 30: | |
| summary += f"1. **π‘ Moderate Concentration** - Consider expanding to new {customer_label} segments.\n" | |
| elif customer_col: | |
| summary += f"1. **π’ Healthy Diversification** - {amount_label} is well-distributed across {customer_label}s.\n" | |
| if amount_col and avg_order_value < 1000: | |
| summary += f"2. **Increase Average** - Current {currency}{avg_order_value:,.2f} β consider bundling or upsells.\n" | |
| elif amount_col: | |
| summary += f"2. **Strong Average** - {currency}{avg_order_value:,.2f} indicates healthy transaction size.\n" | |
| if best_product: | |
| summary += f"3. **Focus on {best_product}** - Top performer. Consider expanding this line.\n" | |
| summary += "\n---\n\n*Report generated by AI Business Analyst Enterprise*" | |
| return { | |
| 'summary': summary, | |
| 'metrics': { | |
| 'total_revenue': float(total_revenue), | |
| 'total_records': total_rows, | |
| 'avg_order_value': float(avg_order_value), | |
| 'unique_customers': unique_customers, | |
| 'unique_products': unique_products, | |
| 'customer_concentration': float(customer_concentration), | |
| 'top_customers': top_customers, | |
| 'top_products': top_products, | |
| 'detected_columns': { | |
| 'amount': amount_col, | |
| 'customer': customer_col, | |
| 'product': product_col, | |
| 'date': date_col, | |
| } | |
| }, | |
| 'chart_data': None | |
| } | |
| except Exception as e: | |
| self.logger.error(f"Failed to generate report: {e}", exc_info=True) | |
| return None | |
| # ============================================================================= | |
| # Scheduler-callable functions | |
| # ============================================================================= | |
| async def generate_daily_report(workspace_id: str) -> Dict: | |
| """Generate daily business report""" | |
| agent = ReportAgent() | |
| insights = await agent.detect_insights(workspace_id) | |
| if insights: | |
| return { | |
| 'success': True, | |
| 'title': insights[0].title, | |
| 'body': insights[0].body, | |
| 'metrics': insights[0].metadata | |
| } | |
| return {'success': False, 'error': 'No data available'} | |
| async def generate_weekly_report(workspace_id: str) -> Dict: | |
| """Generate weekly business report with extended analysis""" | |
| agent = ReportAgent() | |
| insights = await agent.detect_insights(workspace_id) | |
| if insights: | |
| weekly_header = "π **Weekly Business Review**\n\n" | |
| weekly_header += f"Report Period: {(datetime.now() - timedelta(days=7)).strftime('%b %d')} - {datetime.now().strftime('%b %d, %Y')}\n\n" | |
| return { | |
| 'success': True, | |
| 'title': "π Weekly Business Intelligence Report", | |
| 'body': weekly_header + insights[0].body, | |
| 'metrics': insights[0].metadata | |
| } | |
| return {'success': False, 'error': 'No data available'} | |