| |
| 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() |
| fig, ax = plt.subplots(figsize=(8, 6)) |
| y_pos = np.arange(len(labels)) |
| |
| |
| progress_values = [100] * len(labels) |
| 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) |
| |
| |
| 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) |
| 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 |
| |
| |
| text = re.sub(r'\*\*\*(.*?)\*\*\*', r'\1', text) |
| text = re.sub(r'\*\*(.*?)\*\*', r'\1', text) |
| text = re.sub(r'\*(.*?)\*', r'\1', text) |
| text = re.sub(r'__(.*?)__', r'\1', text) |
| text = re.sub(r'_(.*?)_', r'\1', text) |
| |
| |
| text = re.sub(r'#{1,6}\s*', '', text) |
| text = re.sub(r'\*\*([A-Z\s]+[A-Za-z]):\*\*', r'\1:', text) |
| text = re.sub(r'\*\*([A-Z][A-Za-z\s]*)\*\*', r'\1', text) |
| |
| |
| text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text) |
| |
| |
| text = re.sub(r'^\s*[-\*\+β’]\s+', 'β’ ', text, flags=re.MULTILINE) |
| text = re.sub(r'^\s*\d+\.\s+', '', text, flags=re.MULTILINE) |
| |
| |
| text = re.sub(r'\*+', '', text) |
| text = re.sub(r'_+', '', text) |
| |
| |
| text = re.sub(r'\n\s*\n\s*\n+', '\n\n', text) |
| text = re.sub(r'[ \t]+', ' ', text) |
| |
| |
| text = re.sub(r':\s*-', ':', text) |
| text = re.sub(r'\s+:', ':', text) |
| |
| return text.strip() |
|
|
| def format_text_into_paragraphs(text, max_length=500): |
| """Split long text into readable paragraphs for better PDF formatting.""" |
| if not text or len(text) < max_length: |
| return [text] if text else [] |
| |
| |
| paragraphs = text.split('\n\n') |
| formatted_paragraphs = [] |
| |
| for paragraph in paragraphs: |
| paragraph = paragraph.strip() |
| if not paragraph: |
| continue |
| |
| |
| if len(paragraph) > max_length: |
| sentences = paragraph.split('. ') |
| current_para = "" |
| |
| for sentence in sentences: |
| sentence = sentence.strip() |
| if not sentence: |
| continue |
| |
| |
| if not sentence.endswith('.') and not sentence.endswith('!') and not sentence.endswith('?'): |
| sentence += '.' |
| |
| |
| if current_para and len(current_para + ' ' + sentence) > max_length: |
| formatted_paragraphs.append(current_para.strip()) |
| current_para = sentence |
| else: |
| current_para = current_para + ' ' + sentence if current_para else sentence |
| |
| |
| if current_para: |
| formatted_paragraphs.append(current_para.strip()) |
| else: |
| formatted_paragraphs.append(paragraph) |
| |
| return [p for p in formatted_paragraphs if p.strip()] |
|
|
| def format_feedback_sections(feedback_text): |
| """Break feedback into structured sections for better visual presentation.""" |
| |
| 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 |
| |
| |
| section_patterns = [ |
| 'EVALUATION SCORES', 'STRENGTHS', 'AREAS FOR GROWTH', 'FRAMEWORK RECOMMENDATIONS', |
| 'KEY METRICS', 'ACTIONABLE NEXT STEPS', 'EXPERT INSIGHT', 'FEEDBACK', |
| 'KEY STRENGTHS', 'RECOMMENDATIONS', 'IMPROVEMENT AREAS', 'NEXT STEPS' |
| ] |
| |
| is_header = False |
| for pattern in section_patterns: |
| if pattern in line.upper() and (':' in line or line.upper().endswith(pattern)): |
| is_header = True |
| break |
| |
| if is_header: |
| |
| if current_title and current_section.strip(): |
| |
| formatted_content = format_section_content(current_section.strip()) |
| sections.append({ |
| 'title': current_title, |
| 'content': formatted_content |
| }) |
| |
| |
| current_title = line.replace(':', '').strip() |
| current_section = "" |
| else: |
| |
| current_section += line + "\n" |
| |
| |
| if current_title and current_section.strip(): |
| formatted_content = format_section_content(current_section.strip()) |
| sections.append({ |
| 'title': current_title, |
| 'content': formatted_content |
| }) |
| |
| return sections |
|
|
| def format_section_content(content): |
| """Format section content for better readability in PDF.""" |
| if not content: |
| return "" |
| |
| |
| lines = content.split('\n') |
| |
| |
| if any(line.strip().startswith('β’') for line in lines): |
| formatted_items = [] |
| for line in lines: |
| line = line.strip() |
| if line and not line.startswith('β’'): |
| |
| line = f"β’ {line}" |
| if line: |
| formatted_items.append(line) |
| return '\n'.join(formatted_items) |
| |
| |
| if any(char in content for char in ['/', '10', 'Score', 'Rating']): |
| |
| return content |
| |
| |
| paragraphs = format_text_into_paragraphs(content) |
| return '\n\n'.join(paragraphs) |
|
|
| 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() |
| |
| |
| 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 = [] |
| |
| |
| 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)) |
| |
| |
| 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'])) |
| |
| |
| 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()) |
|
|
| |
| story.append(Paragraph("Overall Performance Analysis", styles['SectionHeader'])) |
| |
| |
| try: |
| overall_feedback = generate_coaching_feedback(coaching_data.get('q_and_a', []), |
| coaching_data.get('type', 'General'), |
| coaching_data.get('name', 'Product Manager')) |
| |
| |
| feedback_sections = format_feedback_sections(overall_feedback) |
| |
| if feedback_sections: |
| for section in feedback_sections: |
| if section['title'] and section['content']: |
| |
| story.append(Paragraph(section['title'], styles['SubHeader'])) |
| story.append(Spacer(1, 0.05 * inch)) |
| |
| |
| content = section['content'] |
| if 'β’' in content: |
| |
| bullet_lines = content.split('\n') |
| for line in bullet_lines: |
| line = line.strip() |
| if line and line.startswith('β’'): |
| story.append(Paragraph(line, styles['BulletPoint'])) |
| elif line: |
| story.append(Paragraph(f"β’ {line}", styles['BulletPoint'])) |
| elif any(char in content for char in ['/', '10', 'Score', 'Rating']): |
| |
| story.append(Paragraph(content, styles['ScoreText'])) |
| else: |
| |
| paragraphs = format_text_into_paragraphs(content, 500) |
| for para in paragraphs: |
| if para.strip(): |
| story.append(Paragraph(para, styles['BodyText'])) |
| story.append(Spacer(1, 0.05 * inch)) |
| |
| story.append(Spacer(1, 0.15 * inch)) |
| else: |
| |
| paragraphs = format_text_into_paragraphs(overall_feedback, 500) |
| for para in paragraphs: |
| if para.strip(): |
| story.append(Paragraph(para, styles['BodyText'])) |
| story.append(Spacer(1, 0.1 * inch)) |
| |
| 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)) |
|
|
| |
| 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()) |
|
|
| |
| 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: |
| |
| story.append(Paragraph(f"Scenario {i+1}", styles['SubHeader'])) |
| story.append(Spacer(1, 0.1 * inch)) |
| |
| |
| story.append(Paragraph("Challenge:", styles['SubHeader'])) |
| question_text = scenario.get('question', 'Product management scenario') |
| clean_question = clean_text_for_pdf(question_text) |
| |
| |
| question_paragraphs = format_text_into_paragraphs(clean_question, 350) |
| for para in question_paragraphs: |
| if para.strip(): |
| story.append(Paragraph(para, styles['HighlightBox'])) |
| |
| story.append(Spacer(1, 0.15 * inch)) |
| |
| |
| story.append(Paragraph("Your Approach:", styles['SubHeader'])) |
| response_text = scenario.get('response', 'Response provided') |
| clean_response = clean_text_for_pdf(response_text) |
| |
| |
| response_paragraphs = format_text_into_paragraphs(clean_response, 400) |
| for para in response_paragraphs: |
| if para.strip(): |
| story.append(Paragraph(para, styles['BodyText'])) |
| story.append(Spacer(1, 0.05 * inch)) |
| |
| story.append(Spacer(1, 0.1 * inch)) |
| |
| |
| 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)) |
| |
| |
| story.append(Paragraph("Coaching Analysis:", styles['SubHeader'])) |
| feedback_text = scenario.get('feedback', 'Great work on this scenario!') |
| |
| |
| cleaned_feedback = clean_text_for_pdf(feedback_text) |
| feedback_sections = format_feedback_sections(cleaned_feedback) |
| |
| if feedback_sections: |
| for section in feedback_sections: |
| if section['title'] and section['content']: |
| |
| story.append(Paragraph(section['title'], styles['SubHeader'])) |
| story.append(Spacer(1, 0.05 * inch)) |
| |
| |
| content = section['content'] |
| if 'β’' in content: |
| |
| bullet_lines = content.split('\n') |
| for line in bullet_lines: |
| line = line.strip() |
| if line and line.startswith('β’'): |
| story.append(Paragraph(line, styles['BulletPoint'])) |
| elif line: |
| story.append(Paragraph(f"β’ {line}", styles['BulletPoint'])) |
| elif any(char in content for char in ['/', '10', 'Score', 'Rating']): |
| |
| story.append(Paragraph(content, styles['ScoreText'])) |
| else: |
| |
| paragraphs = format_text_into_paragraphs(content, 400) |
| for para in paragraphs: |
| if para.strip(): |
| story.append(Paragraph(para, styles['BodyText'])) |
| |
| story.append(Spacer(1, 0.1 * inch)) |
| else: |
| |
| paragraphs = format_text_into_paragraphs(cleaned_feedback, 400) |
| for para in paragraphs: |
| if para.strip(): |
| story.append(Paragraph(para, styles['BodyText'])) |
| story.append(Spacer(1, 0.05 * inch)) |
| |
| |
| if i < len(coaching_data.get('q_and_a', [])) - 1: |
| story.append(Spacer(1, 0.3 * inch)) |
| |
| 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)) |
| |
| |
| story.append(PageBreak()) |
| story.append(Paragraph("Your Product Management Development Plan", styles['SectionHeader'])) |
| story.append(Spacer(1, 0.2 * inch)) |
| |
| |
| 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)) |
| |
| |
| 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)) |
| |
| |
| 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'])) |
| |
| |
| story.append(Spacer(1, 0.5 * inch)) |
| story.append(Paragraph("Personal AI Product Coach - Developing Product Management Excellence", |
| styles['BodyText'])) |
| |
| |
| 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 |
|
|
| |
| 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." |