Spaces:
Sleeping
Sleeping
| # 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." |