Spaces:
Sleeping
Sleeping
| from pathlib import Path | |
| import gradio as gr | |
| from anthropic import Anthropic | |
| from pptx import Presentation | |
| from pptx.enum.shapes import MSO_SHAPE | |
| from pptx.util import Inches, Pt | |
| from pptx.dml.color import RGBColor | |
| from pptx.enum.text import PP_ALIGN, MSO_ANCHOR | |
| from pptx.chart.data import CategoryChartData | |
| from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION | |
| import os | |
| import json | |
| from typing import Dict, List, Any, Optional | |
| from datetime import datetime | |
| class ReportGenerator: | |
| def __init__(self): | |
| if 'ANTHROPIC_API_KEY' not in os.environ: | |
| raise EnvironmentError("ANTHROPIC_API_KEY not found in environment variables") | |
| self.client = Anthropic(api_key=os.environ['ANTHROPIC_API_KEY']) | |
| self.colors = { | |
| 'primary': RGBColor(0, 76, 153), | |
| 'secondary': RGBColor(64, 224, 208), | |
| 'success': RGBColor(39, 174, 96), | |
| 'warning': RGBColor(241, 196, 15), | |
| 'danger': RGBColor(231, 76, 60), | |
| 'text': RGBColor(44, 62, 80) | |
| } | |
| def validate_metrics(self, metrics: List[Dict]) -> None: | |
| if not metrics: | |
| raise ValueError("No metrics data provided") | |
| required_fields = ['metric', 'target', 'actual', 'achievement'] | |
| for metric in metrics: | |
| missing = [field for field in required_fields if field not in metric] | |
| if missing: | |
| raise ValueError(f"Missing required metric fields: {', '.join(missing)}") | |
| def validate_file(self, file_path: str) -> None: | |
| if not os.path.exists(file_path): | |
| raise FileNotFoundError(f"File not found: {file_path}") | |
| if os.path.getsize(file_path) > 5 * 1024 * 1024: | |
| raise ValueError("File size exceeds 5MB limit") | |
| try: | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| f.read() | |
| except UnicodeDecodeError: | |
| raise ValueError("File must be a valid text file with UTF-8 encoding") | |
| def validate_insights(self, insights: Dict) -> None: | |
| required_keys = [ | |
| 'executive_summary', 'objectives', 'metrics_analysis', | |
| 'insights', 'recommendations', 'next_month' | |
| ] | |
| missing_keys = [key for key in required_keys if key not in insights] | |
| if missing_keys: | |
| raise ValueError(f"Missing required sections: {', '.join(missing_keys)}") | |
| def get_insights(self, file_path: str) -> Dict[str, Any]: | |
| self.validate_file(file_path) | |
| with open(file_path, 'r', encoding='utf-8') as file: | |
| content = file.read() | |
| print(f"\n=== PROCESSING FILE: {file_path} ===") | |
| prompt = f""" | |
| You are a sales performance analyst. Analyze this sales tracking data and provide your analysis in JSON format with the following exact structure: | |
| {{ | |
| "executive_summary": {{ | |
| "overview": "string", // 2-3 sentence overview of month's performance | |
| "overall_performance_score": "number" // 0-100 based on overall metrics achievement | |
| }}, | |
| "objectives": {{ | |
| "total_objectives": "number", | |
| "objectives_achieved": "number", | |
| "objectives_list": [ | |
| {{ | |
| "objective": "string", | |
| "achievement": "number", | |
| "status": "string" // "Achieved", "Partial", or "Not Achieved" | |
| }} | |
| ] | |
| }}, | |
| "metrics_analysis": {{ | |
| "key_metrics": [ | |
| {{ | |
| "metric": "string", | |
| "target": "number", | |
| "actual": "number", | |
| "achievement": "number" | |
| }} | |
| ], | |
| "conversion_rates": {{ | |
| "connection_rate": {{ | |
| "target": "number", | |
| "actual": "number" | |
| }}, | |
| "conversion_rate": {{ | |
| "target": "number", | |
| "actual": "number" | |
| }} | |
| }}, | |
| "pipeline_value": {{ | |
| "target": "number", | |
| "actual": "number", | |
| "gap": "number" | |
| }} | |
| }}, | |
| "insights": {{ | |
| "key_achievements": ["string"], | |
| "areas_of_concern": ["string"], | |
| "learnings": ["string"] | |
| }}, | |
| "recommendations": {{ | |
| "immediate_actions": ["string"], | |
| "long_term_suggestions": ["string"] | |
| }}, | |
| "next_month": {{ | |
| "suggested_objectives": [ | |
| {{ | |
| "objective": "string", | |
| "target": "string", | |
| "rationale": "string" | |
| }} | |
| ], | |
| "focus_areas": ["string"] | |
| }} | |
| }} | |
| Extract exact numbers from the data where available. For missing data, use the context to make reasonable estimates. Ensure all numeric values match the data exactly where present. | |
| Data to analyze: | |
| {content} | |
| """ | |
| try: | |
| message = self.client.messages.create( | |
| model="claude-3-sonnet-20240229", | |
| max_tokens=1500, | |
| temperature=0, | |
| messages=[{"role": "user", "content": prompt}] | |
| ) | |
| insights = json.loads(message.content[0].text) | |
| self.validate_insights(insights) | |
| return insights | |
| except json.JSONDecodeError as e: | |
| print(f"Error parsing Claude's response: {e}") | |
| raise ValueError("Failed to parse Claude's response as JSON") | |
| except Exception as e: | |
| print(f"Error in Claude API call: {e}") | |
| raise | |
| def create_presentation(self, insights: Dict[str, Any], output_path: Optional[str] = None) -> str: | |
| if output_path is None: | |
| output_path = f"sales_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pptx" | |
| prs = Presentation() | |
| prs.slide_width = Inches(13.333) | |
| prs.slide_height = Inches(7.5) | |
| self._add_title_slide(prs, insights) | |
| self._add_objectives_slide(prs, insights) | |
| self._add_analysis_slide(prs, insights) | |
| self._add_forward_slide(prs, insights) | |
| prs.save(output_path) | |
| return output_path | |
| def _add_title_slide(self, prs: Presentation, insights: Dict[str, Any]) -> None: | |
| """Create executive overview slide with enhanced formatting""" | |
| slide = prs.slides.add_slide(prs.slide_layouts[0]) | |
| # Title with better positioning and formatting | |
| title = slide.shapes.title | |
| title.top = Inches(0.3) | |
| title.left = Inches(0.5) | |
| title.width = Inches(12) | |
| title.text = "Monthly Sales Performance Review" | |
| title.text_frame.paragraphs[0].font.size = Pt(44) | |
| title.text_frame.paragraphs[0].font.color.rgb = self.colors['primary'] | |
| title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER | |
| # Date subtitle | |
| subtitle_left = Inches(0.5) | |
| subtitle_top = Inches(1.2) | |
| subtitle_width = Inches(12) | |
| subtitle_height = Inches(0.5) | |
| subtitle = slide.shapes.add_textbox(subtitle_left, subtitle_top, subtitle_width, subtitle_height) | |
| tf = subtitle.text_frame | |
| p = tf.add_paragraph() | |
| p.text = datetime.now().strftime("%B %Y") | |
| p.font.size = Pt(16) | |
| p.alignment = PP_ALIGN.CENTER | |
| p.font.color.rgb = self.colors['secondary'] | |
| # Performance Score in large circle | |
| score = insights['executive_summary']['overall_performance_score'] | |
| score_left = Inches(0.8) | |
| score_top = Inches(2.0) | |
| score_width = Inches(2.5) | |
| score_height = Inches(2.5) | |
| score_box = slide.shapes.add_shape( | |
| MSO_SHAPE.OVAL, | |
| score_left, score_top, | |
| score_width, score_height | |
| ) | |
| score_box.fill.solid() | |
| score_box.fill.fore_color.rgb = self.colors['primary'] | |
| score_text = score_box.text_frame | |
| score_text.vertical_anchor = MSO_ANCHOR.MIDDLE | |
| p = score_text.add_paragraph() | |
| p.text = f"{score}%" | |
| p.font.size = Pt(36) | |
| p.font.bold = True | |
| p.font.color.rgb = RGBColor(255, 255, 255) | |
| p.alignment = PP_ALIGN.CENTER | |
| p = score_text.add_paragraph() | |
| p.text = "Performance" | |
| p.font.size = Pt(14) | |
| p.font.color.rgb = RGBColor(255, 255, 255) | |
| p.alignment = PP_ALIGN.CENTER | |
| # Executive Summary Box | |
| summary_left = Inches(3.8) | |
| summary_top = Inches(2.0) | |
| summary_width = Inches(8.7) | |
| summary_height = Inches(1.0) | |
| summary_box = slide.shapes.add_textbox(summary_left, summary_top, summary_width, summary_height) | |
| tf = summary_box.text_frame | |
| p = tf.add_paragraph() | |
| p.text = "Executive Summary" | |
| p.font.bold = True | |
| p.font.size = Pt(18) | |
| p.font.color.rgb = self.colors['primary'] | |
| p = tf.add_paragraph() | |
| p.text = insights['executive_summary']['overview'] | |
| p.font.size = Pt(12) | |
| p.space_before = Pt(6) | |
| # KPI Dashboard Table | |
| self._add_kpi_dashboard(slide, insights['metrics_analysis']['key_metrics']) | |
| def _add_kpi_dashboard(self, slide: Any, metrics: List[Dict]) -> None: | |
| """Add enhanced KPI dashboard table""" | |
| left = Inches(0.5) | |
| top = Inches(4.0) | |
| width = Inches(12) | |
| height = Inches(3) | |
| # Create table with fixed column widths | |
| table = slide.shapes.add_table( | |
| rows=len(metrics) + 1, # +1 for header | |
| cols=4, | |
| left=left, | |
| top=top, | |
| width=width, | |
| height=height | |
| ).table | |
| # Set column widths | |
| table.columns[0].width = Inches(4) # Metric name | |
| table.columns[1].width = Inches(2.5) # Target | |
| table.columns[2].width = Inches(2.5) # Actual | |
| table.columns[3].width = Inches(3) # Achievement | |
| # Headers | |
| headers = ['Key Metrics', 'Target', 'Actual', 'Achievement'] | |
| for i, header in enumerate(headers): | |
| cell = table.cell(0, i) | |
| cell.fill.solid() | |
| cell.fill.fore_color.rgb = self.colors['primary'] | |
| paragraph = cell.text_frame.paragraphs[0] | |
| paragraph.text = header | |
| paragraph.font.size = Pt(12) | |
| paragraph.font.bold = True | |
| paragraph.font.color.rgb = RGBColor(255, 255, 255) | |
| paragraph.alignment = PP_ALIGN.CENTER | |
| # Data rows with conditional formatting | |
| for i, metric in enumerate(metrics, start=1): | |
| # Metric name | |
| cell = table.cell(i, 0) | |
| cell.text = metric['metric'] | |
| cell.text_frame.paragraphs[0].font.size = Pt(11) | |
| # Target value (right-aligned) | |
| cell = table.cell(i, 1) | |
| cell.text = str(metric['target']) | |
| p = cell.text_frame.paragraphs[0] | |
| p.alignment = PP_ALIGN.CENTER | |
| p.font.size = Pt(11) | |
| # Actual value (right-aligned) | |
| cell = table.cell(i, 2) | |
| cell.text = str(metric['actual']) | |
| p = cell.text_frame.paragraphs[0] | |
| p.alignment = PP_ALIGN.CENTER | |
| p.font.size = Pt(11) | |
| # Achievement with conditional color | |
| achievement = metric['achievement'] | |
| cell = table.cell(i, 3) | |
| cell.text = f"{achievement}%" | |
| p = cell.text_frame.paragraphs[0] | |
| p.alignment = PP_ALIGN.CENTER | |
| p.font.size = Pt(11) | |
| p.font.bold = True | |
| # Color coding based on achievement | |
| if achievement >= 90: | |
| p.font.color.rgb = self.colors['success'] | |
| elif achievement >= 75: | |
| p.font.color.rgb = self.colors['warning'] | |
| else: | |
| p.font.color.rgb = self.colors['danger'] | |
| # Add subtle fill to alternate rows | |
| if i % 2 == 0: | |
| for j in range(4): | |
| cell = table.cell(i, j) | |
| cell.fill.solid() | |
| cell.fill.fore_color.rgb = RGBColor(245, 245, 245) | |
| def _add_objectives_slide(self, prs: Presentation, insights: Dict[str, Any]) -> None: | |
| """Create objectives and metrics slide with enhanced layout""" | |
| slide = prs.slides.add_slide(prs.slide_layouts[1]) | |
| # Title | |
| title = slide.shapes.title | |
| title.text = "Objectives & Key Metrics" | |
| title.top = Inches(0.3) | |
| title.left = Inches(0.5) | |
| title.width = Inches(12) | |
| title.text_frame.paragraphs[0].font.size = Pt(40) | |
| title.text_frame.paragraphs[0].font.color.rgb = self.colors['primary'] | |
| title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER | |
| # Left side: Objectives | |
| objectives_left = Inches(0.5) | |
| objectives_top = Inches(1.3) | |
| objectives_width = Inches(5.8) | |
| objectives_height = Inches(5.7) | |
| self._add_objectives_section( | |
| slide, | |
| insights['objectives'], | |
| objectives_left, | |
| objectives_top, | |
| objectives_width, | |
| objectives_height | |
| ) | |
| # Right side: Metrics Chart | |
| chart_left = Inches(6.8) | |
| chart_top = Inches(1.3) | |
| chart_width = Inches(6) | |
| chart_height = Inches(5.7) | |
| self._add_metrics_chart( | |
| slide, | |
| insights['metrics_analysis'], | |
| chart_left, | |
| chart_top, | |
| chart_width, | |
| chart_height | |
| ) | |
| def _add_objectives_section(self, slide: Any, objectives: Dict, left: Inches, top: Inches, width: Inches, | |
| height: Inches) -> None: | |
| """Add enhanced objectives section with status indicators""" | |
| # Create main title shape instead of textbox | |
| title_shape = slide.shapes.add_textbox(left, top, width, Inches(0.5)) | |
| title_frame = title_shape.text_frame | |
| title_frame.word_wrap = True | |
| # Section header with completion ratio | |
| p = title_frame.paragraphs[0] # Use existing paragraph | |
| p.text = f"Monthly Objectives Progress ({objectives['objectives_achieved']}/{objectives['total_objectives']})" | |
| p.font.size = Pt(18) | |
| p.font.bold = True | |
| p.font.color.rgb = self.colors['primary'] | |
| current_top = top + Inches(0.7) # Adjusted spacing after title | |
| # Add each objective with enhanced status display | |
| for obj in objectives['objectives_list']: | |
| # Create container shape for each objective | |
| obj_container = slide.shapes.add_shape( | |
| MSO_SHAPE.RECTANGLE, | |
| left, | |
| current_top, | |
| width, | |
| Inches(0.8) | |
| ) | |
| obj_container.fill.solid() | |
| obj_container.fill.fore_color.rgb = RGBColor(248, 249, 250) | |
| obj_container.line.color.rgb = RGBColor(230, 230, 230) | |
| # Add status indicator box | |
| status_color = ( | |
| self.colors['success'] if obj['status'] == "Achieved" | |
| else self.colors['warning'] if obj['status'] == "Partial" | |
| else self.colors['danger'] | |
| ) | |
| status_box = slide.shapes.add_shape( | |
| MSO_SHAPE.RECTANGLE, | |
| left + Inches(0.2), | |
| current_top + Inches(0.2), | |
| Inches(0.15), | |
| Inches(0.15) | |
| ) | |
| status_box.fill.solid() | |
| status_box.fill.fore_color.rgb = status_color | |
| # Add text frame for objective content | |
| obj_text = slide.shapes.add_textbox( | |
| left + Inches(0.5), # Moved right to accommodate status box | |
| current_top + Inches(0.1), | |
| width - Inches(0.6), | |
| Inches(0.6) | |
| ) | |
| tf = obj_text.text_frame | |
| tf.word_wrap = True | |
| # Objective title | |
| p = tf.paragraphs[0] | |
| p.text = obj['objective'] | |
| p.font.size = Pt(14) | |
| p.font.bold = True | |
| # Status text | |
| p = tf.add_paragraph() | |
| p.text = f"Status: {obj['status']} ({obj['achievement']}%)" | |
| p.font.size = Pt(12) | |
| p.font.color.rgb = status_color | |
| # Increment current_top for next objective | |
| current_top += Inches(1) # Reduced spacing between objectives | |
| def _add_metrics_chart(self, slide: Any, metrics_analysis: Dict, left: Inches, top: Inches, width: Inches, | |
| height: Inches) -> None: | |
| """Add enhanced metrics comparison chart""" | |
| metrics = metrics_analysis['key_metrics'] | |
| chart_data = CategoryChartData() | |
| chart_data.categories = [m['metric'] for m in metrics] | |
| chart_data.add_series('Target', [m['target'] for m in metrics]) | |
| chart_data.add_series('Actual', [m['actual'] for m in metrics]) | |
| chart = slide.shapes.add_chart( | |
| XL_CHART_TYPE.COLUMN_CLUSTERED, | |
| left, top, width, height, | |
| chart_data | |
| ).chart | |
| # Enhanced chart formatting | |
| chart.has_legend = True | |
| chart.has_title = True | |
| chart.chart_title.text_frame.text = "Target vs Actual Performance" | |
| chart.chart_title.text_frame.paragraphs[0].font.size = Pt(16) | |
| chart.chart_title.text_frame.paragraphs[0].font.bold = True | |
| # Format plot area | |
| plot = chart.plots[0] | |
| plot.gap_width = 50 | |
| plot.overlap = -25 | |
| # Format axes | |
| category_axis = chart.category_axis | |
| category_axis.tick_labels.font.size = Pt(9) | |
| category_axis.tick_labels.font.color.rgb = self.colors['text'] | |
| value_axis = chart.value_axis | |
| value_axis.tick_labels.font.size = Pt(9) | |
| value_axis.tick_labels.font.color.rgb = self.colors['text'] | |
| # Format legend | |
| chart.legend.position = XL_LEGEND_POSITION.BOTTOM | |
| chart.legend.include_in_layout = False | |
| chart.legend.font.size = Pt(10) | |
| def _add_analysis_slide(self, prs: Presentation, insights: Dict[str, Any]) -> None: | |
| """Create analysis and insights slide with enhanced formatting""" | |
| slide = prs.slides.add_slide(prs.slide_layouts[1]) | |
| # Title | |
| title = slide.shapes.title | |
| title.text = "Analysis & Insights" | |
| title.top = Inches(0.3) | |
| title.left = Inches(0.5) | |
| title.width = Inches(12) | |
| title.text_frame.paragraphs[0].font.size = Pt(40) | |
| title.text_frame.paragraphs[0].font.color.rgb = self.colors['primary'] | |
| title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER | |
| # Two columns for achievements and concerns | |
| achievements_box = slide.shapes.add_shape( | |
| MSO_SHAPE.RECTANGLE, | |
| Inches(0.5), | |
| Inches(1.3), | |
| Inches(5.8), | |
| Inches(2.5) | |
| ) | |
| achievements_box.fill.solid() | |
| achievements_box.fill.fore_color.rgb = RGBColor(248, 249, 250) | |
| achievements_box.line.color.rgb = self.colors['success'] | |
| # Add achievements text | |
| achievements_text = slide.shapes.add_textbox( | |
| Inches(0.7), # Slightly indented from box | |
| Inches(1.4), | |
| Inches(5.4), | |
| Inches(2.3) | |
| ) | |
| tf = achievements_text.text_frame | |
| tf.word_wrap = True | |
| # Add header | |
| p = tf.paragraphs[0] | |
| p.text = "Key Achievements" | |
| p.font.size = Pt(18) | |
| p.font.bold = True | |
| p.font.color.rgb = self.colors['success'] | |
| p.space_after = Pt(12) | |
| # Add achievements | |
| for achievement in insights['insights']['key_achievements']: | |
| p = tf.add_paragraph() | |
| p.text = f"• {achievement}" | |
| p.font.size = Pt(14) | |
| p.space_before = Pt(6) | |
| p.space_after = Pt(6) | |
| # Concerns box | |
| concerns_box = slide.shapes.add_shape( | |
| MSO_SHAPE.RECTANGLE, | |
| Inches(6.8), | |
| Inches(1.3), | |
| Inches(5.8), | |
| Inches(2.5) | |
| ) | |
| concerns_box.fill.solid() | |
| concerns_box.fill.fore_color.rgb = RGBColor(248, 249, 250) | |
| concerns_box.line.color.rgb = self.colors['danger'] | |
| # Add concerns text | |
| concerns_text = slide.shapes.add_textbox( | |
| Inches(7.0), # Slightly indented from box | |
| Inches(1.4), | |
| Inches(5.4), | |
| Inches(2.3) | |
| ) | |
| tf = concerns_text.text_frame | |
| tf.word_wrap = True | |
| # Add header | |
| p = tf.paragraphs[0] | |
| p.text = "Areas of Concern" | |
| p.font.size = Pt(18) | |
| p.font.bold = True | |
| p.font.color.rgb = self.colors['danger'] | |
| p.space_after = Pt(12) | |
| # Add concerns | |
| for concern in insights['insights']['areas_of_concern']: | |
| p = tf.add_paragraph() | |
| p.text = f"• {concern}" | |
| p.font.size = Pt(14) | |
| p.space_before = Pt(6) | |
| p.space_after = Pt(6) | |
| # Key learnings section | |
| learnings_box = slide.shapes.add_shape( | |
| MSO_SHAPE.RECTANGLE, | |
| Inches(0.5), | |
| Inches(4.1), | |
| Inches(12.1), | |
| Inches(3) | |
| ) | |
| learnings_box.fill.solid() | |
| learnings_box.fill.fore_color.rgb = RGBColor(248, 249, 250) | |
| learnings_box.line.color.rgb = self.colors['secondary'] | |
| def _add_insight_section(self, slide: Any, title: str, items: List[str], left: Inches, top: Inches, width: Inches, | |
| color: RGBColor) -> None: | |
| """Add formatted insight section""" | |
| box = slide.shapes.add_textbox(left, top, width, Inches(0.5)) | |
| tf = box.text_frame | |
| # Title | |
| p = tf.add_paragraph() | |
| p.text = title | |
| p.font.bold = True | |
| p.font.size = Pt(16) | |
| p.font.color.rgb = color | |
| p.space_after = Pt(10) | |
| # Content items | |
| content_box = slide.shapes.add_textbox(left, top + Inches(0.6), width, Inches(2)) | |
| tf = content_box.text_frame | |
| for item in items: | |
| p = tf.add_paragraph() | |
| p.text = f"• {item}" | |
| p.font.size = Pt(12) | |
| p.space_after = Pt(6) | |
| def _add_forward_slide(self, prs: Presentation, insights: Dict[str, Any]) -> None: | |
| """Create forward-looking slide with enhanced formatting""" | |
| slide = prs.slides.add_slide(prs.slide_layouts[1]) | |
| # Title | |
| title = slide.shapes.title | |
| title.text = "Action Plan & Next Steps" | |
| title.top = Inches(0.3) | |
| title.left = Inches(0.5) | |
| title.width = Inches(12) | |
| title.text_frame.paragraphs[0].font.size = Pt(40) | |
| title.text_frame.paragraphs[0].font.color.rgb = self.colors['primary'] | |
| title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER | |
| # Actions sections | |
| self._add_actions_section( | |
| slide, | |
| "Immediate Actions", | |
| insights['recommendations']['immediate_actions'], | |
| Inches(0.5), | |
| Inches(1.3), | |
| Inches(5.8), | |
| self.colors['warning'] | |
| ) | |
| self._add_actions_section( | |
| slide, | |
| "Strategic Initiatives", | |
| insights['recommendations']['long_term_suggestions'], | |
| Inches(6.8), | |
| Inches(1.3), | |
| Inches(5.8), | |
| self.colors['primary'] | |
| ) | |
| # Next month objectives | |
| self._add_next_month_section( | |
| slide, | |
| insights['next_month'], | |
| Inches(0.5), | |
| Inches(4.1), | |
| Inches(12.1) | |
| ) | |
| def _add_actions_section(self, slide: Any, title: str, items: List[str], left: Inches, top: Inches, width: Inches, | |
| color: RGBColor) -> None: | |
| """Add formatted actions section""" | |
| # Background box | |
| box = slide.shapes.add_shape( | |
| MSO_SHAPE.RECTANGLE, # Changed from add_rectangle | |
| left, top, width, Inches(2.5) | |
| ) | |
| box.fill.solid() | |
| box.fill.fore_color.rgb = RGBColor(248, 249, 250) | |
| box.line.color.rgb = color | |
| # Content | |
| text_box = slide.shapes.add_textbox(left + Inches(0.2), top + Inches(0.1), width - Inches(0.4), Inches(2.3)) | |
| tf = text_box.text_frame | |
| p = tf.add_paragraph() | |
| p.text = title | |
| p.font.bold = True | |
| p.font.size = Pt(16) | |
| p.font.color.rgb = color | |
| p.space_after = Pt(10) | |
| for item in items: | |
| p = tf.add_paragraph() | |
| p.text = f"• {item}" | |
| p.font.size = Pt(12) | |
| p.space_after = Pt(6) | |
| def _add_next_month_section(self, slide: Any, next_month: Dict, left: Inches, top: Inches, width: Inches) -> None: | |
| """Add enhanced next month objectives section""" | |
| # Background box | |
| box = slide.shapes.add_shape( | |
| MSO_SHAPE.RECTANGLE, | |
| left, top, width, Inches(3) | |
| ) | |
| box.fill.solid() | |
| box.fill.fore_color.rgb = RGBColor(248, 249, 250) | |
| box.line.color.rgb = self.colors['secondary'] | |
| # Main content box | |
| text_box = slide.shapes.add_textbox( | |
| left + Inches(0.2), | |
| top + Inches(0.1), | |
| width - Inches(0.4), | |
| Inches(2.8) | |
| ) | |
| tf = text_box.text_frame | |
| tf.word_wrap = True | |
| # Title | |
| p = tf.add_paragraph() | |
| p.text = "Objectives for Next Month" | |
| p.font.bold = True | |
| p.font.size = Pt(16) | |
| p.font.color.rgb = self.colors['secondary'] | |
| p.space_after = Pt(12) | |
| # Add objectives with proper spacing | |
| for obj in next_month['suggested_objectives']: | |
| # Objective title | |
| p = tf.add_paragraph() | |
| p.text = f"• {obj['objective']}" | |
| p.font.bold = True | |
| p.font.size = Pt(14) | |
| p.space_after = Pt(6) | |
| # Target | |
| p = tf.add_paragraph() | |
| p.text = f" Target: {obj['target']}" # Indented with spaces | |
| p.font.size = Pt(12) | |
| p.space_before = Pt(3) | |
| p.space_after = Pt(3) | |
| p.level = 1 # Indentation level | |
| # Rationale | |
| p = tf.add_paragraph() | |
| p.text = f" Rationale: {obj['rationale']}" # Indented with spaces | |
| p.font.size = Pt(12) | |
| p.space_before = Pt(3) | |
| p.space_after = Pt(12) | |
| p.level = 1 # Indentation level | |
| # Add focus areas if present | |
| if 'focus_areas' in next_month and next_month['focus_areas']: | |
| p = tf.add_paragraph() | |
| p.text = "Key Focus Areas:" | |
| p.font.bold = True | |
| p.font.size = Pt(14) | |
| p.space_before = Pt(12) | |
| p.space_after = Pt(6) | |
| for area in next_month['focus_areas']: | |
| p = tf.add_paragraph() | |
| p.text = f"• {area}" | |
| p.font.size = Pt(12) | |
| p.space_after = Pt(4) | |
| def process_file(file: gr.File) -> str: | |
| if not file.name.endswith('.txt'): | |
| raise gr.Error("Please upload a text file (.txt)") | |
| try: | |
| generator = ReportGenerator() | |
| insights = generator.get_insights(file.name) | |
| return generator.create_presentation(insights) | |
| except Exception as e: | |
| raise gr.Error(f"Error processing file: {str(e)}") | |
| # Gradio interface | |
| interface = gr.Interface( | |
| fn=process_file, | |
| inputs=gr.File(label="Upload Sales Tracking Data (Text File)", file_types=[".txt"]), | |
| outputs=gr.File(label="Download Enhanced Report"), | |
| title="Sales Performance Report Generator", | |
| description="Upload your sales tracking data to generate a detailed performance analysis report." | |
| ) | |
| if __name__ == "__main__": | |
| interface.launch() | |