norhan12 commited on
Commit
17f00ad
·
verified ·
1 Parent(s): e4d6e01

Update process_interview.py

Browse files
Files changed (1) hide show
  1. process_interview.py +78 -70
process_interview.py CHANGED
@@ -505,153 +505,161 @@ def generate_report(analysis_data: Dict) -> str:
505
 
506
 
507
  # --- NEW, ENHANCED PDF GENERATION FUNCTION ---
 
 
 
 
 
 
 
 
 
 
 
508
  def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text: str):
509
  try:
510
  doc = SimpleDocTemplate(output_path, pagesize=letter,
511
- rightMargin=inch/2, leftMargin=inch/2,
512
- topMargin=inch, bottomMargin=inch/2)
513
 
514
  styles = getSampleStyleSheet()
515
- styles.add(ParagraphStyle(name='Justify', alignment=4)) # TA_JUSTIFY = 4
516
- h1 = ParagraphStyle(name='Heading1', fontSize=18, leading=22, spaceAfter=20, alignment=1, textColor=colors.HexColor('#003366'))
517
- h2 = ParagraphStyle(name='Heading2', fontSize=14, leading=18, spaceBefore=12, spaceAfter=10, textColor=colors.HexColor('#336699'))
518
- body_text = ParagraphStyle(name='BodyText', parent=styles['Normal'], spaceAfter=6)
519
  bullet_style = ParagraphStyle(name='Bullet', parent=body_text, leftIndent=18, bulletIndent=9)
520
-
521
  story = []
522
 
523
- # --- Header and Footer ---
524
  def header_footer(canvas, doc):
525
  canvas.saveState()
526
  # Footer
527
  canvas.setFont('Helvetica', 9)
528
- canvas.drawString(inch, 0.5 * inch, f"Page {doc.page} | EvalBot Analysis")
 
529
  # Header line
530
- canvas.setStrokeColorRGB(0, 0.2, 0.4)
531
  canvas.setLineWidth(1)
532
- canvas.line(doc.leftMargin, doc.height + doc.topMargin - inch/2, doc.width + doc.leftMargin, doc.height + doc.topMargin - inch/2)
533
  canvas.restoreState()
534
 
535
- # --- First Page: Summary ---
536
- story.append(Paragraph("EvalBot Interview Analysis Report", h1))
537
- story.append(Spacer(1, 0.1 * inch))
538
- story.append(Paragraph(f"Analysis Date: {time.strftime('%Y-%m-%d')}", styles['Normal']))
539
- story.append(Spacer(1, 0.3 * inch))
540
 
541
  acceptance_prob = analysis_data.get('acceptance_probability')
542
  if acceptance_prob is not None:
543
  story.append(Paragraph("Candidate Evaluation Summary", h2))
544
- prob_color = colors.green if acceptance_prob >= 70 else (colors.orange if acceptance_prob >= 40 else colors.red)
545
- story.append(Paragraph(f"<font size=14>Estimated Acceptance Probability: <b><font color='{prob_color.hexval()}'>{acceptance_prob:.2f}%</font></b></font>",
546
  ParagraphStyle(name='Prob', fontSize=12, spaceAfter=10)))
547
  if acceptance_prob >= 80:
548
- story.append(Paragraph("This indicates a very strong candidate with high potential.", body_text))
549
  elif acceptance_prob >= 50:
550
- story.append(Paragraph("This candidate shows solid potential with areas for improvement.", body_text))
551
  else:
552
- story.append(Paragraph("This candidate may require significant development or may not be an ideal fit.", body_text))
553
 
554
  story.append(PageBreak())
555
 
556
- # --- Second Page and beyond: Detailed Analysis ---
557
- story.append(Paragraph("1. Detailed Voice Analysis", h2))
 
 
558
  voice_analysis = analysis_data.get('voice_analysis', {})
559
  if voice_analysis and 'error' not in voice_analysis:
560
- # Table for voice metrics
561
  table_data = [
562
  ['Metric', 'Value', 'Interpretation'],
563
- ['Speaking Rate', f"{voice_analysis.get('speaking_rate', 0):.2f} words/sec", 'Average rate'],
564
- ['Filler Words', f"{voice_analysis.get('filler_ratio', 0) * 100:.1f}%", '% of total words'],
565
  ['Anxiety Level', voice_analysis.get('interpretation', {}).get('anxiety_level', 'N/A').upper(), f"Score: {voice_analysis.get('composite_scores', {}).get('anxiety', 0):.3f}"],
566
  ['Confidence Level', voice_analysis.get('interpretation', {}).get('confidence_level', 'N/A').upper(), f"Score: {voice_analysis.get('composite_scores', {}).get('confidence', 0):.3f}"],
567
- ['Fluency', voice_analysis.get('interpretation', {}).get('fluency_level', 'N/A').upper(), 'Overall speech flow']
568
  ]
569
- table = Table(table_data, colWidths=[1.5*inch, 1.5*inch, 3*inch])
570
  table.setStyle(TableStyle([
571
- ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#4682B4')),
572
  ('TEXTCOLOR',(0,0),(-1,0),colors.whitesmoke),
573
- ('ALIGN', (0,0), (-1,-1), 'CENTER'),
574
  ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
575
  ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
576
- ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
 
 
577
  ('BACKGROUND', (0, 1), (-1, -1), colors.HexColor('#F0F8FF')),
578
- ('GRID', (0,0), (-1,-1), 1, colors.black)
579
  ]))
580
  story.append(table)
581
  story.append(Spacer(1, 0.2 * inch))
582
 
583
- # Chart generation
584
  chart_buffer = io.BytesIO()
585
  generate_anxiety_confidence_chart(voice_analysis.get('composite_scores', {}), chart_buffer)
586
  chart_buffer.seek(0)
587
  img = Image(chart_buffer, width=4*inch, height=2.5*inch)
 
588
  story.append(img)
589
  else:
590
  story.append(Paragraph("Voice analysis data not available.", body_text))
591
 
592
  story.append(PageBreak())
593
-
594
- # --- Parse and display Gemini report sections ---
595
  sections = {}
 
 
 
 
 
 
 
 
 
596
  current_section = None
597
- # Simplified patterns to find sections
598
- section_patterns = {
599
- 'Executive Summary': r'executive summary',
600
- 'Voice Analysis Insights': r'voice analysis insights',
601
- 'Content Analysis & Strengths/Areas for Development': r'content analysis|strengths/areas for development',
602
- 'Actionable Recommendations': r'actionable recommendations'
603
- }
604
- # Pre-populate sections to maintain order
605
- for name in section_patterns.keys():
606
- sections[name] = []
607
-
608
- # Parse text into sections based on keywords
609
- for line in gemini_report_text.split('\n'):
610
- line_lower = line.lower()
611
- matched = False
612
- for name, pattern in section_patterns.items():
613
- if re.search(pattern, line_lower):
614
- current_section = name
615
- matched = True
616
  break
617
- if not matched and current_section:
618
- sections[current_section].append(line)
 
619
 
620
- # Display Content Analysis and Recommendations
621
- story.append(Paragraph("2. Content Analysis", h2))
622
  if sections['Content Analysis & Strengths/Areas for Development']:
623
  for line in sections['Content Analysis & Strengths/Areas for Development']:
624
- line = line.strip()
625
- if not line: continue
626
  if line.startswith(('-', '•', '*')):
627
  story.append(Paragraph(line.lstrip('-•* ').strip(), bullet_style))
628
  else:
629
  story.append(Paragraph(line, body_text))
630
  else:
631
- story.append(Paragraph("Content analysis not provided in the report.", body_text))
632
 
633
- story.append(Spacer(1, 0.2*inch))
634
-
635
- story.append(Paragraph("3. Recommendations", h2))
636
  if sections['Actionable Recommendations']:
637
  for line in sections['Actionable Recommendations']:
638
- line = line.strip()
639
- if not line: continue
640
  if line.startswith(('-', '•', '*')):
641
  story.append(Paragraph(line.lstrip('-•* ').strip(), bullet_style))
642
  else:
643
  story.append(Paragraph(line, body_text))
644
  else:
645
- story.append(Paragraph("Recommendations not provided in the report.", body_text))
646
-
647
  doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
648
  return True
649
  except Exception as e:
650
  logger.error(f"Enhanced PDF creation failed: {str(e)}", exc_info=True)
651
  return False
652
 
653
-
654
-
655
  def convert_to_serializable(obj):
656
  if isinstance(obj, np.generic): return obj.item()
657
  if isinstance(obj, dict): return {k: convert_to_serializable(v) for k, v in obj.items()}
 
505
 
506
 
507
  # --- NEW, ENHANCED PDF GENERATION FUNCTION ---
508
+ # --- Make sure these imports are at the top of your process_interview.py file ---
509
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, Image
510
+ from reportlab.lib.pagesizes import letter
511
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
512
+ from reportlab.lib.units import inch
513
+ from reportlab.lib import colors
514
+ import time
515
+ import re
516
+ import io
517
+
518
+ # --- New, Enhanced PDF Generation Function ---
519
  def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text: str):
520
  try:
521
  doc = SimpleDocTemplate(output_path, pagesize=letter,
522
+ rightMargin=0.75*inch, leftMargin=0.75*inch,
523
+ topMargin=1*inch, bottomMargin=1*inch)
524
 
525
  styles = getSampleStyleSheet()
526
+ h1 = ParagraphStyle(name='Heading1', fontSize=20, leading=24, spaceAfter=18, alignment=1, textColor=colors.HexColor('#00205B'))
527
+ h2 = ParagraphStyle(name='Heading2', fontSize=14, leading=18, spaceBefore=12, spaceAfter=6, textColor=colors.HexColor('#003366'))
528
+ body_text = ParagraphStyle(name='BodyText', parent=styles['Normal'], fontSize=10, leading=14, spaceAfter=6)
 
529
  bullet_style = ParagraphStyle(name='Bullet', parent=body_text, leftIndent=18, bulletIndent=9)
530
+
531
  story = []
532
 
533
+ # --- Header and Footer Logic ---
534
  def header_footer(canvas, doc):
535
  canvas.saveState()
536
  # Footer
537
  canvas.setFont('Helvetica', 9)
538
+ canvas.setFillColor(colors.grey)
539
+ canvas.drawString(doc.leftMargin, 0.5 * inch, f"Page {doc.page} | EvalBot Confidential Report")
540
  # Header line
541
+ canvas.setStrokeColor(colors.HexColor('#003366'))
542
  canvas.setLineWidth(1)
543
+ canvas.line(doc.leftMargin, doc.height + 0.75*inch, doc.width + doc.leftMargin, doc.height + 0.75*inch)
544
  canvas.restoreState()
545
 
546
+ # --- First Page: Title and Summary ---
547
+ story.append(Paragraph("Interview Performance Analysis", h1))
548
+ story.append(Paragraph(f"Analysis Date: {time.strftime('%Y-%m-%d')}", ParagraphStyle(name='Date', alignment=1, fontSize=9, textColor=colors.grey)))
549
+ story.append(Spacer(1, 0.4 * inch))
 
550
 
551
  acceptance_prob = analysis_data.get('acceptance_probability')
552
  if acceptance_prob is not None:
553
  story.append(Paragraph("Candidate Evaluation Summary", h2))
554
+ prob_color = colors.green if acceptance_prob >= 70 else (colors.darkorange if acceptance_prob >= 40 else colors.red)
555
+ story.append(Paragraph(f"Estimated Acceptance Probability: <font size=14 color='{prob_color.hexval()}'><b>{acceptance_prob:.2f}%</b></font>",
556
  ParagraphStyle(name='Prob', fontSize=12, spaceAfter=10)))
557
  if acceptance_prob >= 80:
558
+ story.append(Paragraph("<b>Overall Assessment:</b> This indicates a very strong candidate with high potential. Recommended for the next round.", body_text))
559
  elif acceptance_prob >= 50:
560
+ story.append(Paragraph("<b>Overall Assessment:</b> This candidate shows solid potential but has key areas for improvement.", body_text))
561
  else:
562
+ story.append(Paragraph("<b>Overall Assessment:</b> This candidate may require significant development or may not be the ideal fit at this time.", body_text))
563
 
564
  story.append(PageBreak())
565
 
566
+ # --- Second Page: Detailed Analysis ---
567
+ story.append(Paragraph("Detailed Analysis", h1))
568
+
569
+ story.append(Paragraph("1. Voice & Speech Metrics", h2))
570
  voice_analysis = analysis_data.get('voice_analysis', {})
571
  if voice_analysis and 'error' not in voice_analysis:
572
+ # --- This is the corrected table ---
573
  table_data = [
574
  ['Metric', 'Value', 'Interpretation'],
575
+ ['Speaking Rate', f"{voice_analysis.get('speaking_rate', 0):.2f} words/sec", 'Indicator of pace and confidence.'],
576
+ ['Filler Words Ratio', f"{voice_analysis.get('filler_ratio', 0) * 100:.1f}%", 'Measures use of "um", "uh", etc.'],
577
  ['Anxiety Level', voice_analysis.get('interpretation', {}).get('anxiety_level', 'N/A').upper(), f"Score: {voice_analysis.get('composite_scores', {}).get('anxiety', 0):.3f}"],
578
  ['Confidence Level', voice_analysis.get('interpretation', {}).get('confidence_level', 'N/A').upper(), f"Score: {voice_analysis.get('composite_scores', {}).get('confidence', 0):.3f}"],
579
+ ['Fluency Level', voice_analysis.get('interpretation', {}).get('fluency_level', 'N/A').upper(), 'Overall speech flow and coherence.']
580
  ]
581
+ table = Table(table_data, colWidths=[1.6*inch, 1.2*inch, 3.7*inch])
582
  table.setStyle(TableStyle([
583
+ ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#003366')),
584
  ('TEXTCOLOR',(0,0),(-1,0),colors.whitesmoke),
585
+ ('ALIGN', (0,0), (-1,-1), 'LEFT'),
586
  ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
587
  ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
588
+ ('FONTSIZE', (0, 0), (-1, -1), 9),
589
+ ('BOTTOMPADDING', (0, 0), (-1, 0), 10),
590
+ ('TOPPADDING', (0, 0), (-1, 0), 10),
591
  ('BACKGROUND', (0, 1), (-1, -1), colors.HexColor('#F0F8FF')),
592
+ ('GRID', (0,0), (-1,-1), 1, colors.lightgrey)
593
  ]))
594
  story.append(table)
595
  story.append(Spacer(1, 0.2 * inch))
596
 
 
597
  chart_buffer = io.BytesIO()
598
  generate_anxiety_confidence_chart(voice_analysis.get('composite_scores', {}), chart_buffer)
599
  chart_buffer.seek(0)
600
  img = Image(chart_buffer, width=4*inch, height=2.5*inch)
601
+ img.hAlign = 'CENTER'
602
  story.append(img)
603
  else:
604
  story.append(Paragraph("Voice analysis data not available.", body_text))
605
 
606
  story.append(PageBreak())
607
+
608
+ # --- Gemini Report Parsing and Display ---
609
  sections = {}
610
+ # Pre-populate to maintain order
611
+ section_titles = ["Executive Summary", "Voice Analysis Insights", "Content Analysis & Strengths/Areas for Development", "Actionable Recommendations"]
612
+ for title in section_titles:
613
+ sections[title] = []
614
+
615
+ # Use a more robust way to capture content under each heading
616
+ # This regex captures the heading line itself to exclude it from the content
617
+ report_parts = re.split(r'(\s*\*\*\s*\d\.\s*.*?\s*\*\*)', gemini_report_text)
618
+
619
  current_section = None
620
+ for part in report_parts:
621
+ if not part.strip(): continue
622
+
623
+ is_heading = False
624
+ for title in section_titles:
625
+ # Check if the part is a heading
626
+ if title.lower() in part.lower():
627
+ current_section = title
628
+ is_heading = True
 
 
 
 
 
 
 
 
 
 
629
  break
630
+
631
+ if not is_heading and current_section:
632
+ sections[current_section].append(part.strip())
633
 
634
+ # Display Content and Recommendations
635
+ story.append(Paragraph("2. Content Analysis (from Gemini)", h2))
636
  if sections['Content Analysis & Strengths/Areas for Development']:
637
  for line in sections['Content Analysis & Strengths/Areas for Development']:
 
 
638
  if line.startswith(('-', '•', '*')):
639
  story.append(Paragraph(line.lstrip('-•* ').strip(), bullet_style))
640
  else:
641
  story.append(Paragraph(line, body_text))
642
  else:
643
+ story.append(Paragraph("Content analysis not provided.", body_text))
644
 
645
+ story.append(Spacer(1, 0.3*inch))
646
+
647
+ story.append(Paragraph("3. Actionable Recommendations (from Gemini)", h2))
648
  if sections['Actionable Recommendations']:
649
  for line in sections['Actionable Recommendations']:
 
 
650
  if line.startswith(('-', '•', '*')):
651
  story.append(Paragraph(line.lstrip('-•* ').strip(), bullet_style))
652
  else:
653
  story.append(Paragraph(line, body_text))
654
  else:
655
+ story.append(Paragraph("Recommendations not provided.", body_text))
656
+
657
  doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
658
  return True
659
  except Exception as e:
660
  logger.error(f"Enhanced PDF creation failed: {str(e)}", exc_info=True)
661
  return False
662
 
 
 
663
  def convert_to_serializable(obj):
664
  if isinstance(obj, np.generic): return obj.item()
665
  if isinstance(obj, dict): return {k: convert_to_serializable(v) for k, v in obj.items()}