# modules/report_generator.py import datetime import os import numpy as np import matplotlib.pyplot as plt from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Image, HRFlowable from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER from reportlab.lib.units import inch from reportlab.lib import colors from modules.llm_handler import generate_coaching_feedback import config import tempfile def define_skill_areas(coaching_type): """Define key skill areas based on product management coaching type.""" skill_mapping = { 'Product Strategy & Vision': ['Strategic Thinking', 'Vision Articulation', 'Business Alignment'], 'Market Research & Analysis': ['Research Skills', 'Data Analysis', 'Market Understanding'], 'User Experience & Design Thinking': ['User Empathy', 'Design Process', 'Problem Solving'], 'Product Roadmap Planning': ['Prioritization', 'Planning', 'Communication'], 'Metrics & Analytics': ['Data Literacy', 'Analytical Thinking', 'Decision Making'], 'Stakeholder Management': ['Communication', 'Negotiation', 'Relationship Building'], 'Product Launch Strategy': ['Execution', 'Planning', 'Cross-functional Leadership'], 'Competitive Analysis': ['Market Analysis', 'Strategic Thinking', 'Opportunity Recognition'], 'Feature Prioritization': ['Decision Making', 'Framework Application', 'Trade-off Analysis'], 'Customer Development': ['Customer Empathy', 'Research Skills', 'Insight Generation'], 'Resume & Application Strategy': ['Application Skills', 'Personal Branding', 'Interview Preparation'] } return skill_mapping.get(coaching_type, ['Strategic Thinking', 'Problem Solving', 'Communication']) def create_coaching_progress_chart(labels, file_path): """Create a visual representation of coaching areas covered with error handling.""" try: plt.clf() # Clear any existing plots fig, ax = plt.subplots(figsize=(8, 6)) y_pos = np.arange(len(labels)) # Create a progress-style chart progress_values = [100] * len(labels) # All areas were covered colors_list = plt.cm.Blues(np.linspace(0.4, 0.8, len(labels))) bars = ax.barh(y_pos, progress_values, color=colors_list, alpha=0.7) ax.set_yticks(y_pos) ax.set_yticklabels(labels) ax.set_xlabel('Coaching Coverage (%)') ax.set_title('Product Management Skills Coaching Session', fontsize=14, fontweight='bold') ax.set_xlim(0, 100) # Add checkmarks to indicate completion for i, bar in enumerate(bars): ax.text(bar.get_width() - 10, bar.get_y() + bar.get_height()/2, '✓', ha='center', va='center', fontsize=16, color='white', fontweight='bold') plt.tight_layout() plt.savefig(file_path, dpi=300, bbox_inches='tight') plt.close(fig) # Explicitly close the figure print(f"✅ Chart saved to: {file_path}") return True except Exception as e: print(f"❌ Error creating chart: {e}") return False plt.tight_layout() plt.savefig(file_path, dpi=300, bbox_inches='tight') plt.close(fig) print(f"📈 Coaching progress chart saved to {file_path}") def clean_text_for_pdf(text): """Remove markdown formatting and clean text for professional PDF appearance.""" import re # Remove markdown bold/italic formatting - handle nested patterns text = re.sub(r'\*\*\*(.*?)\*\*\*', r'\1', text) # Remove ***bold italic*** text = re.sub(r'\*\*(.*?)\*\*', r'\1', text) # Remove **bold** text = re.sub(r'\*(.*?)\*', r'\1', text) # Remove *italic* text = re.sub(r'__(.*?)__', r'\1', text) # Remove __bold__ text = re.sub(r'_(.*?)_', r'\1', text) # Remove _italic_ # Clean up section headers (remove markdown) text = re.sub(r'#{1,6}\s*', '', text) # Remove # headers text = re.sub(r'\*\*([A-Z\s]+[A-Za-z]):\*\*', r'\1:', text) # Clean section headers with colons text = re.sub(r'\*\*([A-Z][A-Za-z\s]*)\*\*', r'\1', text) # Clean other bold headers # Remove markdown links text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text) # Clean up bullet points and list markers text = re.sub(r'^\s*[-\*\+•]\s+', '• ', text, flags=re.MULTILINE) text = re.sub(r'^\s*\d+\.\s+', '• ', text, flags=re.MULTILINE) # Convert numbered lists # Remove extra asterisks that might be left over text = re.sub(r'\*+', '', text) text = re.sub(r'_+', '', text) # Clean up multiple spaces and normalize line breaks text = re.sub(r'\n\s*\n\s*\n+', '\n\n', text) text = re.sub(r'\s+', ' ', text) text = text.strip() return text def format_feedback_sections(feedback_text): """Break feedback into structured sections for better visual presentation.""" # First, clean the text of markdown formatting cleaned_text = clean_text_for_pdf(feedback_text) sections = [] current_section = "" current_title = "" lines = cleaned_text.split('\n') for line in lines: line = line.strip() if not line: continue # Check if line is a section header (contains keywords) if any(keyword in line.upper() for keyword in ['STRENGTHS:', 'AREAS FOR GROWTH:', 'FRAMEWORK', 'METRICS', 'NEXT STEPS:', 'INSIGHT:', 'SCORES:', 'KEY STRENGTHS', 'RECOMMENDATIONS']): # Save previous section if current_title and current_section: sections.append({ 'title': current_title, 'content': current_section.strip() }) # Start new section current_title = line.replace(':', '').strip() current_section = "" else: # Add to current section current_section += line + " " # Add final section if current_title and current_section: sections.append({ 'title': current_title, 'content': current_section.strip() }) return sections def generate_pdf_report(coaching_data, file_path): """Generate a professional product management coaching report without markdown formatting.""" try: doc = SimpleDocTemplate(file_path, pagesize=(8.5 * inch, 11 * inch)) styles = getSampleStyleSheet() # Professional styles for coaching report - only add if they don't exist def safe_add_style(styles, name, **kwargs): """Safely add a style only if it doesn't already exist""" if name not in styles: styles.add(ParagraphStyle(name=name, **kwargs)) safe_add_style(styles, 'ReportTitle', parent=styles['Heading1'], fontSize=24, alignment=TA_CENTER, spaceAfter=20, textColor=colors.darkblue, fontName='Helvetica-Bold' ) safe_add_style(styles, 'SectionHeader', parent=styles['Heading2'], fontSize=16, spaceBefore=15, spaceAfter=10, textColor=colors.darkblue, fontName='Helvetica-Bold' ) safe_add_style(styles, 'SubHeader', parent=styles['Heading3'], fontSize=14, spaceBefore=12, spaceAfter=8, textColor=colors.darkgreen, fontName='Helvetica-Bold' ) safe_add_style(styles, 'BodyText', parent=styles['Normal'], fontSize=11, spaceAfter=12, spaceBefore=6, alignment=TA_JUSTIFY, fontName='Helvetica' ) safe_add_style(styles, 'BulletText', parent=styles['Normal'], fontSize=11, spaceAfter=8, spaceBefore=4, leftIndent=20, fontName='Helvetica' ) safe_add_style(styles, 'BulletPoint', parent=styles['Normal'], fontSize=11, spaceAfter=6, spaceBefore=3, leftIndent=20, fontName='Helvetica' ) safe_add_style(styles, 'ScoreText', parent=styles['Normal'], fontSize=12, spaceAfter=8, spaceBefore=4, textColor=colors.blue, fontName='Helvetica-Bold' ) safe_add_style(styles, 'HighlightBox', parent=styles['Normal'], fontSize=11, spaceAfter=12, spaceBefore=12, leftIndent=15, rightIndent=15, borderWidth=1, borderColor=colors.lightgrey, backColor=colors.lightgrey, fontName='Helvetica' ) print("✅ Stylesheet created successfully") story = [] # Title Page story.append(Paragraph("Personal AI Product Coach", styles['ReportTitle'])) story.append(Paragraph("Product Management Coaching Report", styles['SectionHeader'])) story.append(Spacer(1, 0.5 * inch)) # Executive Summary Table story.append(Paragraph("Executive Summary", styles['SectionHeader'])) story.append(Paragraph(f"Participant: {coaching_data.get('name', 'Product Manager')}", styles['BodyText'])) story.append(Paragraph(f"Coaching Focus: {coaching_data.get('type', 'General PM Coaching')}", styles['BodyText'])) story.append(Paragraph(f"Session Date: {datetime.datetime.now().strftime('%B %d, %Y')}", styles['BodyText'])) story.append(Paragraph(f"Scenarios Completed: {len(coaching_data.get('q_and_a', []))}", styles['BodyText'])) # Calculate overall session performance session_scores = [] for scenario in coaching_data.get('q_and_a', []): if 'overall_score' in scenario and scenario['overall_score'] > 0: session_scores.append(scenario['overall_score']) if session_scores: avg_score = sum(session_scores) / len(session_scores) story.append(Paragraph(f"Session Average Score: {avg_score:.1f}/10", styles['ScoreText'])) story.append(PageBreak()) # Overall Performance Analysis story.append(Paragraph("Overall Performance Analysis", styles['SectionHeader'])) # Generate comprehensive feedback with error handling try: overall_feedback = generate_coaching_feedback(coaching_data.get('q_and_a', []), coaching_data.get('type', 'General'), coaching_data.get('name', 'Product Manager')) # Format the feedback into structured sections feedback_sections = format_feedback_sections(overall_feedback) if feedback_sections: for section in feedback_sections: # Add section title with proper styling story.append(Paragraph(section['title'], styles['SubHeader'])) story.append(Spacer(1, 0.1 * inch)) # Split content into bullet points if it contains bullet indicators content = section['content'] if '•' in content: # Handle bullet points bullets = [line.strip() for line in content.split('•') if line.strip()] for bullet in bullets: if bullet: story.append(Paragraph(f"• {bullet}", styles['BulletPoint'])) else: # Handle regular paragraphs paragraphs = [p.strip() for p in content.split('.') if p.strip()] for para in paragraphs: if para and len(para) > 10: # Avoid very short fragments story.append(Paragraph(f"{para}.", styles['BodyText'])) story.append(Spacer(1, 0.2 * inch)) else: # Fallback if no sections found - display as clean text clean_feedback = clean_text_for_pdf(overall_feedback) story.append(Paragraph(clean_feedback, styles['BodyText'])) except Exception as feedback_error: print(f"⚠️ Error generating overall feedback: {feedback_error}") story.append(Paragraph("Great work in this coaching session! You demonstrated solid product management thinking and approach to the scenarios presented.", styles['BodyText'])) story.append(Spacer(1, 0.3 * inch)) # Add progress chart with error handling try: skill_labels = define_skill_areas(coaching_data.get('type', 'General')) chart_path = os.path.join(config.REPORT_FOLDER, "coaching_progress.png") if os.path.exists(chart_path): os.remove(chart_path) if create_coaching_progress_chart(skill_labels, chart_path): story.append(Image(chart_path, width=6*inch, height=4*inch, hAlign='CENTER')) else: story.append(Paragraph("Skills covered in this coaching session:", styles['SubHeader'])) for skill in skill_labels: story.append(Paragraph(f"• {skill}", styles['BodyText'])) except Exception as chart_error: print(f"⚠️ Error creating chart: {chart_error}") story.append(Paragraph("Skills Development Summary", styles['SubHeader'])) story.append(Paragraph("This coaching session covered key product management competencies.", styles['BodyText'])) story.append(PageBreak()) # Detailed Scenario Analysis story.append(Paragraph("Detailed Scenario Analysis", styles['SectionHeader'])) story.append(Spacer(1, 0.2 * inch)) for i, scenario in enumerate(coaching_data.get('q_and_a', [])): try: # Scenario Header with visual separation story.append(Paragraph(f"Scenario {i+1}", styles['SubHeader'])) story.append(Spacer(1, 0.1 * inch)) # Challenge Description in highlighted box story.append(Paragraph("Challenge:", styles['SubHeader'])) clean_question = clean_text_for_pdf(scenario.get('question', 'Product management scenario')) story.append(Paragraph(clean_question, styles['HighlightBox'])) story.append(Spacer(1, 0.15 * inch)) # Participant's Response story.append(Paragraph("Your Approach:", styles['SubHeader'])) clean_response = clean_text_for_pdf(scenario.get('response', 'Response provided')) story.append(Paragraph(clean_response, styles['BodyText'])) story.append(Spacer(1, 0.1 * inch)) # Score Display with visual emphasis if scenario.get('overall_score', 0) > 0: score_text = f"Overall Score: {scenario['overall_score']}/10" story.append(Paragraph(score_text, styles['ScoreText'])) story.append(Spacer(1, 0.1 * inch)) # Coaching Feedback - break into sections story.append(Paragraph("Coaching Analysis:", styles['SubHeader'])) feedback_text = scenario.get('feedback', 'Great work on this scenario!') feedback_sections = format_feedback_sections(feedback_text) if feedback_sections: for section in feedback_sections: if section['title'] and section['content']: # Section title story.append(Paragraph(f"{section['title']}:", styles['SubHeader'])) # Section content - already cleaned by format_feedback_sections story.append(Paragraph(section['content'], styles['BulletText'])) story.append(Spacer(1, 0.05 * inch)) else: # Fallback - use original feedback with cleaning clean_feedback = clean_text_for_pdf(feedback_text) story.append(Paragraph(clean_feedback, styles['BodyText'])) # Add separator between scenarios if i < len(coaching_data.get('q_and_a', [])) - 1: story.append(Spacer(1, 0.3 * inch)) # Add a subtle line separator story.append(HRFlowable(width="100%", thickness=1, lineCap='round', color=colors.lightgrey)) story.append(Spacer(1, 0.2 * inch)) except Exception as scenario_error: print(f"⚠️ Error processing scenario {i+1}: {scenario_error}") story.append(Paragraph(f"Scenario {i+1}: Completed successfully", styles['BodyText'])) story.append(Spacer(1, 0.2 * inch)) # Development Plan Section story.append(PageBreak()) story.append(Paragraph("Your Product Management Development Plan", styles['SectionHeader'])) story.append(Spacer(1, 0.2 * inch)) # Focus Areas story.append(Paragraph("Recommended Focus Areas", styles['SubHeader'])) focus_areas = [ f"Continue practicing {coaching_data.get('type', 'product management').lower()} scenarios with real-world applications", "Explore and master relevant PM frameworks and methodologies", "Seek feedback from peers and mentors on your product management approach", "Apply these concepts in your current role or personal projects" ] for area in focus_areas: story.append(Paragraph(f"• {area}", styles['BulletText'])) story.append(Spacer(1, 0.2 * inch)) # Learning Resources story.append(Paragraph("Suggested Learning Resources", styles['SubHeader'])) resources = [ '"Inspired" by Marty Cagan - Product management fundamentals', '"The Lean Startup" by Eric Ries - Validation and iteration principles', '"Hooked" by Nir Eyal - User engagement and product psychology', 'Product management communities and industry blogs for current best practices' ] for resource in resources: story.append(Paragraph(f"• {resource}", styles['BulletText'])) story.append(Spacer(1, 0.2 * inch)) # Next Steps story.append(Paragraph("Next Steps for Growth", styles['SubHeader'])) next_steps_text = """Product management is a continuous learning journey. Use this coaching session as a foundation to build upon. Regular practice with realistic scenarios, combined with framework application and stakeholder feedback, will accelerate your development as a product manager. Remember to stay curious about user needs, data-driven in your decisions, and collaborative in your approach. The best product managers never stop learning and adapting.""" story.append(Paragraph(next_steps_text, styles['BodyText'])) # Footer story.append(Spacer(1, 0.5 * inch)) story.append(Paragraph("Personal AI Product Coach - Developing Product Management Excellence", styles['BodyText'])) # Build the PDF doc.build(story) print(f"✅ Professional coaching report generated: {file_path}") return True except Exception as e: print(f"❌ Error generating PDF report: {e}") print(f"❌ Error details: {str(e)}") return False # Legacy function for backward compatibility def generate_holistic_feedback(interview_log): """Legacy function - redirects to generate_coaching_feedback.""" return "This coaching session focused on developing key product management skills through practical scenarios."