Spaces:
Sleeping
Sleeping
File size: 20,640 Bytes
db70c95 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | # 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." |