# Report Generator MCP - Downloadable Reports """ š REPORT GENERATOR MCP ======================== Generate professional downloadable reports like ChatGPT's Canvas. Features: - š PDF Reports with charts and data - š Word Documents (DOCX) - š Excel Workbooks with formatted data - š HTML Reports (interactive) - š§ Email-ready summaries - šØ Professional styling Usage: from mcp.report_generator import ReportGenerator generator = ReportGenerator() report_path = generator.generate_pdf(df, title="Sales Report") Author: AI Business Analyst Team Version: 1.0.0 """ import os import io import json import logging from typing import Dict, List, Any, Optional from datetime import datetime from dataclasses import dataclass from enum import Enum import pandas as pd import numpy as np logger = logging.getLogger(__name__) class ReportFormat(Enum): """Supported report formats""" PDF = "pdf" WORD = "docx" EXCEL = "xlsx" HTML = "html" MARKDOWN = "md" JSON = "json" CSV = "csv" @dataclass class ReportSection: """A section in the report""" title: str content: str section_type: str # text, table, chart, summary data: Optional[Any] = None @dataclass class ReportMetadata: """Metadata for the report""" title: str subtitle: Optional[str] = None author: str = "AI Business Analyst" created_at: datetime = None description: str = "" tags: List[str] = None def __post_init__(self): if self.created_at is None: self.created_at = datetime.now() if self.tags is None: self.tags = [] class ReportGenerator: """ š Professional Report Generator Creates downloadable reports in multiple formats: - PDF with charts and tables - Word documents - Excel workbooks - HTML for web viewing """ # Report storage directory REPORTS_DIR = "storage/reports" def __init__(self, user_id: str = "default"): self.user_id = user_id self.reports_dir = f"{self.REPORTS_DIR}/{user_id}" os.makedirs(self.reports_dir, exist_ok=True) # Color scheme self.colors = { 'primary': '#f97316', 'secondary': '#06b6d4', 'success': '#22c55e', 'warning': '#eab308', 'danger': '#ef4444', 'dark': '#1e293b', 'light': '#f8fafc' } def generate( self, df: pd.DataFrame, query: str, title: str = None, format: ReportFormat = ReportFormat.HTML, include_charts: bool = True, include_summary: bool = True, include_recommendations: bool = True ) -> Dict[str, Any]: """ Generate a downloadable report from data. Args: df: DataFrame with data query: User's original query title: Report title format: Output format (pdf, excel, html, etc.) include_charts: Whether to include visualizations include_summary: Whether to include executive summary include_recommendations: Whether to include AI recommendations Returns: Dict with report path and metadata """ if df is None or df.empty: return {"success": False, "error": "No data provided"} # Generate title from query if not provided if not title: title = self._generate_title(query, df) # Create metadata metadata = ReportMetadata( title=title, subtitle=query[:100] if query else None, description=f"Generated from {len(df)} rows, {len(df.columns)} columns" ) # Build report sections sections = self._build_sections(df, query, include_charts, include_summary, include_recommendations) # Generate in requested format generators = { ReportFormat.HTML: self._generate_html, ReportFormat.PDF: self._generate_pdf, ReportFormat.EXCEL: self._generate_excel, ReportFormat.WORD: self._generate_word, ReportFormat.MARKDOWN: self._generate_markdown, ReportFormat.JSON: self._generate_json, ReportFormat.CSV: self._generate_csv } generator = generators.get(format, self._generate_html) result = generator(df, metadata, sections) return result def _generate_title(self, query: str, df: pd.DataFrame) -> str: """Generate report title from query.""" if query: # Extract key terms words = query.split()[:5] return " ".join(w.capitalize() for w in words) + " Analysis" return f"Data Report ({len(df)} records)" def _build_sections( self, df: pd.DataFrame, query: str, include_charts: bool, include_summary: bool, include_recommendations: bool ) -> List[ReportSection]: """Build report sections.""" sections = [] # 1. Executive Summary if include_summary: summary = self._generate_executive_summary(df, query) sections.append(ReportSection( title="Executive Summary", content=summary, section_type="summary" )) # 2. Data Overview overview = self._generate_data_overview(df) sections.append(ReportSection( title="Data Overview", content=overview, section_type="text" )) # 3. Key Statistics stats = self._generate_statistics_section(df) sections.append(ReportSection( title="Key Statistics", content=stats["text"], section_type="table", data=stats["table"] )) # 4. Charts/Visualizations if include_charts: chart_section = self._generate_chart_section(df) sections.append(ReportSection( title="Visualizations", content=chart_section["description"], section_type="chart", data=chart_section["charts"] )) # 5. Detailed Data Table sections.append(ReportSection( title="Data Table", content=f"First {min(100, len(df))} records of the dataset", section_type="table", data=df.head(100).to_dict('records') )) # 6. Recommendations if include_recommendations: recommendations = self._generate_recommendations(df, query) sections.append(ReportSection( title="AI Recommendations", content=recommendations, section_type="text" )) return sections def _generate_executive_summary(self, df: pd.DataFrame, query: str) -> str: """Generate executive summary.""" num_rows = len(df) num_cols = len(df.columns) numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist() summary = f"""This report provides an analysis of {num_rows:,} records across {num_cols} variables. **Key Highlights:** """ # Add highlights for numeric columns for col in numeric_cols[:3]: try: mean_val = df[col].mean() max_val = df[col].max() min_val = df[col].min() summary += f"\n⢠**{col}**: Average {mean_val:,.2f} (Range: {min_val:,.2f} - {max_val:,.2f})" except: pass # Add category insights cat_cols = df.select_dtypes(include=['object']).columns[:2] for col in cat_cols: try: top_val = df[col].value_counts().index[0] top_count = df[col].value_counts().values[0] summary += f"\n⢠**{col}**: Most common value is '{top_val}' ({top_count:,} records)" except: pass return summary def _generate_data_overview(self, df: pd.DataFrame) -> str: """Generate data overview section.""" overview = f"""**Dataset Statistics:** - Total Records: {len(df):,} - Total Columns: {len(df.columns)} - Numeric Columns: {len(df.select_dtypes(include=[np.number]).columns)} - Text Columns: {len(df.select_dtypes(include=['object']).columns)} - Missing Values: {df.isna().sum().sum():,} ({df.isna().sum().sum() / df.size * 100:.1f}%) **Columns:** {', '.join(df.columns[:15])}{'...' if len(df.columns) > 15 else ''} """ return overview def _generate_statistics_section(self, df: pd.DataFrame) -> Dict: """Generate statistics section.""" numeric_cols = df.select_dtypes(include=[np.number]).columns[:10] stats_data = [] for col in numeric_cols: try: stats_data.append({ "Column": col, "Mean": f"{df[col].mean():,.2f}", "Median": f"{df[col].median():,.2f}", "Std Dev": f"{df[col].std():,.2f}", "Min": f"{df[col].min():,.2f}", "Max": f"{df[col].max():,.2f}" }) except: pass return { "text": f"Statistical summary for {len(numeric_cols)} numeric columns:", "table": stats_data } def _generate_chart_section(self, df: pd.DataFrame) -> Dict: """Generate chart configurations for the report.""" charts = [] # Bar chart for categorical data cat_cols = df.select_dtypes(include=['object']).columns[:1] num_cols = df.select_dtypes(include=[np.number]).columns[:1] if len(cat_cols) > 0 and len(num_cols) > 0: cat_col, num_col = cat_cols[0], num_cols[0] grouped = df.groupby(cat_col)[num_col].sum().nlargest(10) charts.append({ "type": "bar", "title": f"{num_col} by {cat_col}", "labels": grouped.index.tolist(), "values": grouped.values.tolist() }) # Line chart for time series (if date column exists) date_cols = [c for c in df.columns if 'date' in c.lower() or 'time' in c.lower()] if date_cols and len(num_cols) > 0: try: chart_data = df.groupby(date_cols[0])[num_cols[0]].mean().head(50) charts.append({ "type": "line", "title": f"{num_cols[0]} Over Time", "labels": chart_data.index.astype(str).tolist(), "values": chart_data.values.tolist() }) except: pass # Pie chart for distribution if len(cat_cols) > 0: dist = df[cat_cols[0]].value_counts().head(8) charts.append({ "type": "pie", "title": f"Distribution of {cat_cols[0]}", "labels": dist.index.tolist(), "values": dist.values.tolist() }) return { "description": f"Generated {len(charts)} visualizations based on data analysis.", "charts": charts } def _generate_recommendations(self, df: pd.DataFrame, query: str) -> str: """Generate AI recommendations.""" recommendations = "**Based on the data analysis, here are our recommendations:**\n\n" # Check for missing data missing_pct = df.isna().sum().sum() / df.size * 100 if missing_pct > 10: recommendations += f"1. **Data Quality**: {missing_pct:.1f}% of values are missing. Consider data cleaning before analysis.\n\n" # Check for outliers in numeric columns numeric_cols = df.select_dtypes(include=[np.number]).columns outlier_cols = [] for col in numeric_cols[:5]: try: q1, q3 = df[col].quantile([0.25, 0.75]) iqr = q3 - q1 outliers = ((df[col] < q1 - 1.5*iqr) | (df[col] > q3 + 1.5*iqr)).sum() if outliers > len(df) * 0.05: outlier_cols.append(col) except: pass if outlier_cols: recommendations += f"2. **Outliers Detected**: Columns {', '.join(outlier_cols)} contain significant outliers. Review for data errors.\n\n" # Check for high cardinality categoricals for col in df.select_dtypes(include=['object']).columns[:5]: unique_ratio = df[col].nunique() / len(df) if unique_ratio > 0.5: recommendations += f"3. **High Cardinality**: Column '{col}' has many unique values ({df[col].nunique():,}). Consider grouping or encoding.\n\n" break # General recommendations recommendations += f"4. **Next Steps**: Consider creating a dashboard for ongoing monitoring of key metrics.\n\n" recommendations += f"5. **Further Analysis**: Explore correlations between numeric variables for deeper insights.\n" return recommendations # ========================================================================== # FORMAT-SPECIFIC GENERATORS # ========================================================================== def _generate_html( self, df: pd.DataFrame, metadata: ReportMetadata, sections: List[ReportSection] ) -> Dict[str, Any]: """Generate HTML report.""" timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"report_{timestamp}.html" filepath = os.path.join(self.reports_dir, filename) html = f"""
{content}
" elif section.section_type == "table" and section.data: if isinstance(section.data, list) and len(section.data) > 0: html += "| {h} | " for h in headers) + "
|---|
| {v} | " for v in row.values()) + "
Showing 50 of {len(section.data)} records
" else: html += f"{section.content}
" elif section.section_type == "chart" and section.data: html += '