norhan12 commited on
Commit
8a14d05
·
verified ·
1 Parent(s): 5199158

Update process_interview.py

Browse files
Files changed (1) hide show
  1. process_interview.py +346 -202
process_interview.py CHANGED
@@ -599,262 +599,406 @@ def generate_report(analysis_data: Dict) -> str:
599
  voice = analysis_data.get('voice_analysis', {})
600
  voice_interpretation = generate_voice_interpretation(voice)
601
 
602
- interviewee_responses = [
603
- f"Speaker {u['speaker']} ({u['role']}): {u['text']}"
604
- for u in analysis_data['transcript']
605
- if u['role'] == 'Interviewee'
606
- ][:5] # Limit to first 5 for prompt brevity
607
-
608
  acceptance_prob = analysis_data.get('acceptance_probability', None)
609
  acceptance_line = ""
610
  if acceptance_prob is not None:
611
  acceptance_line = f"\n**Estimated Acceptance Probability: {acceptance_prob:.2f}%**\n"
612
- if acceptance_prob >= 80:
613
- acceptance_line += "This indicates a very strong candidate. Well done!"
 
 
614
  elif acceptance_prob >= 50:
615
- acceptance_line += "This indicates a solid candidate with potential for improvement."
616
  else:
617
- acceptance_line += "This candidate may require significant development or may not be a strong fit."
618
 
 
619
  prompt = f"""
620
- As EvalBot, an AI interview analysis system, generate a highly professional, well-structured, and concise interview analysis report.
621
- The report should be suitable for a professional setting and clearly highlight key findings and actionable recommendations.
622
- Use clear headings and subheadings. For bullet points, use '- '.
623
 
624
  {acceptance_line}
625
 
626
  **1. Executive Summary**
627
- Provide a brief, high-level overview of the interview.
628
- - Overall interview duration: {analysis_data['text_analysis']['total_duration']:.2f} seconds
629
- - Number of speaker turns: {analysis_data['text_analysis']['speaker_turns']}
630
- - Main participants: {', '.join(analysis_data['speakers'])}
631
 
632
- **2. Voice Analysis Insights**
633
- Analyze key voice metrics and provide a detailed interpretation.
634
  {voice_interpretation}
635
 
636
- **3. Content Analysis & Strengths/Areas for Development**
637
- Analyze the key themes and identify both strengths and areas for development in the interviewee's responses.
638
- Key responses from interviewee (for context):
639
- {chr(10).join(interviewee_responses)}
640
-
641
- **4. Actionable Recommendations**
642
- Offer specific, actionable suggestions for improvement.
643
- Focus on:
644
- - Communication Skills (e.g., pacing, clarity, filler words)
645
- - Content Delivery (e.g., quantifying achievements, structuring answers)
646
- - Professional Presentation (e.g., research, specific examples, mock interviews)
 
 
 
 
 
 
 
 
 
 
 
647
  """
648
 
 
649
  response = gemini_model.generate_content(prompt)
650
  return response.text
 
651
  except Exception as e:
652
  logger.error(f"Report generation failed: {str(e)}")
653
  return f"Error generating report: {str(e)}"
654
 
655
-
656
- # --- ENHANCED PDF GENERATION FUNCTION ---
657
  def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text: str):
658
  try:
659
  doc = SimpleDocTemplate(output_path, pagesize=letter)
660
  styles = getSampleStyleSheet()
661
 
662
- # Define custom styles
663
- h1 = ParagraphStyle(name='Heading1', parent=styles['h1'], fontSize=16, spaceAfter=14, alignment=1,
664
- textColor=colors.HexColor('#003366'))
665
- h2 = ParagraphStyle(name='Heading2', parent=styles['h2'], fontSize=12, spaceBefore=10, spaceAfter=8,
666
- textColor=colors.HexColor('#336699'))
667
- h3 = ParagraphStyle(name='Heading3', parent=styles['h3'], fontSize=10, spaceBefore=8, spaceAfter=4,
668
- textColor=colors.HexColor('#0055AA'))
669
- body_text = ParagraphStyle(name='BodyText', parent=styles['Normal'], fontSize=9, leading=12, spaceAfter=4)
670
- bullet_style = ParagraphStyle(name='Bullet', parent=styles['Normal'], fontSize=9, leading=12, leftIndent=18,
671
- bulletIndent=9)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
672
 
673
  story = []
674
 
675
- # Title and Date
676
- story.append(Paragraph(f"<b>EvalBot Interview Analysis Report</b>", h1))
 
 
 
 
 
 
 
 
 
 
 
 
677
  story.append(Spacer(1, 0.2 * inch))
678
- story.append(Paragraph(f"<b>Date:</b> {time.strftime('%Y-%m-%d')}", body_text))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
679
  story.append(Spacer(1, 0.3 * inch))
680
 
681
- # --- Acceptance Probability (New Section) ---
682
  acceptance_prob = analysis_data.get('acceptance_probability', None)
683
  if acceptance_prob is not None:
684
- story.append(Paragraph("<b>Candidate Evaluation Summary</b>", h2))
685
- story.append(Spacer(1, 0.1 * inch))
686
-
687
- prob_color = colors.green if acceptance_prob >= 70 else (
688
- colors.orange if acceptance_prob >= 40 else colors.red)
689
-
690
- # --- FIX: Call .hexval() as a method ---
691
- story.append(Paragraph(
692
- f"<font size='12' color='{prob_color.hexval()}'><b>Estimated Acceptance Probability: {acceptance_prob:.2f}%</b></font>",
693
- ParagraphStyle(name='AcceptanceProbability', parent=styles['Normal'], fontSize=12, spaceAfter=10,
694
- alignment=1)
695
- ))
696
- # --- End FIX ---
697
-
698
  if acceptance_prob >= 80:
699
- story.append(
700
- Paragraph("This indicates a very strong candidate with high potential. Well done!", body_text))
701
- elif acceptance_prob >= 50:
702
- story.append(Paragraph(
703
- "This candidate shows solid potential but has areas for improvement to become an even stronger fit.",
704
- body_text))
705
  else:
706
- story.append(Paragraph(
707
- "This candidate may require significant development or may not be the ideal fit at this time.",
708
- body_text))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
  story.append(Spacer(1, 0.3 * inch))
710
- # --- End Acceptance Probability ---
711
-
712
- # Parse Gemini's report into sections for better PDF structuring
713
- sections = {}
714
- current_section = None
715
- # Use regex to robustly identify sections, especially with varied bullet points
716
- section_patterns = {
717
- r'^\s*\*\*\s*1\.\s*Executive Summary\s*\*\*': 'Executive Summary',
718
- r'^\s*\*\*\s*2\.\s*Voice Analysis Insights\s*\*\*': 'Voice Analysis Insights',
719
- r'^\s*\*\*\s*3\.\s*Content Analysis & Strengths/Areas for Development\s*\*\*': 'Content Analysis & Strengths/Areas for Development',
720
- r'^\s*\*\*\s*4\.\s*Actionable Recommendations\s*\*\*': 'Actionable Recommendations'
721
- }
722
 
723
- for line in gemini_report_text.split('\n'):
724
- matched_section = False
725
- for pattern, section_name in section_patterns.items():
726
- if re.match(pattern, line):
727
- current_section = section_name
728
- sections[current_section] = []
729
- matched_section = True
730
- break
731
- if not matched_section and current_section:
732
- sections[current_section].append(line)
733
-
734
- # 1. Executive Summary
735
- story.append(Paragraph("1. Executive Summary", h2))
736
- story.append(Spacer(1, 0.1 * inch))
737
- if 'Executive Summary' in sections:
738
- for line in sections['Executive Summary']:
739
- if line.strip():
740
- story.append(Paragraph(line.strip(), body_text))
741
- story.append(Spacer(1, 0.2 * inch))
742
-
743
- # 2. Voice Analysis (Detailed - using Table for summary)
744
  story.append(Paragraph("2. Voice Analysis", h2))
745
  voice_analysis = analysis_data.get('voice_analysis', {})
746
-
747
  if voice_analysis and 'error' not in voice_analysis:
748
- # Voice Analysis Summary Table
749
  table_data = [
750
- ['Metric', 'Value', 'Interpretation'],
751
- ['Speaking Rate', f"{voice_analysis['speaking_rate']:.2f} words/sec", 'Average rate'],
752
- ['Filler Words', f"{voice_analysis['filler_ratio'] * 100:.1f}%", 'Percentage of total words'],
753
- ['Repetition Score', f"{voice_analysis['repetition_score']:.3f}", 'Lower is better articulation'],
754
- ['Anxiety Level', voice_analysis['interpretation']['anxiety_level'].upper(),
755
- f"Score: {voice_analysis['composite_scores']['anxiety']:.3f}"],
756
- ['Confidence Level', voice_analysis['interpretation']['confidence_level'].upper(),
757
- f"Score: {voice_analysis['composite_scores']['confidence']:.3f}"],
758
- ['Fluency', voice_analysis['interpretation']['fluency_level'].upper(), 'Overall speech flow']
759
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760
 
761
- table_style = TableStyle([
762
- ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#6699CC')),
763
- ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
764
- ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
765
- ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
766
- ('BOTTOMPADDING', (0, 0), (-1, 0), 10),
767
- ('BACKGROUND', (0, 1), (-1, -1), colors.HexColor('#EFEFEF')),
768
- ('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#CCCCCC')),
769
- ('LEFTPADDING', (0, 0), (-1, -1), 6),
770
- ('RIGHTPADDING', (0, 0), (-1, -1), 6),
771
- ('TOPPADDING', (0, 0), (-1, -1), 6),
772
- ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
773
- ])
774
-
775
- table = Table(table_data)
776
- table.setStyle(table_style)
777
- story.append(table)
778
- story.append(Spacer(1, 0.2 * inch))
779
-
780
- # --- Charts ---
781
- story.append(Paragraph("Score Visualization:", h3))
782
- # chart_path = os.path.join(OUTPUT_DIR, f"anxiety_confidence_{uuid.uuid4().hex[:8]}.png") # Removed from here
783
- # --- FIX: Generate chart in memory (BytesIO) ---
784
- chart_buffer = io.BytesIO() # Create in-memory buffer
785
- try:
786
- generate_anxiety_confidence_chart(voice_analysis['composite_scores'], chart_buffer) # Pass buffer instead of path
787
- chart_buffer.seek(0) # Rewind the buffer to the beginning
788
- img = Image(chart_buffer, width=3.5*inch, height=2.0*inch) # Load image from buffer
789
- story.append(img)
790
- story.append(Spacer(1, 0.1 * inch))
791
- except NameError:
792
- logger.warning("Chart generation function 'generate_anxiety_confidence_chart' is not defined. Skipping chart.")
793
- except Exception as chart_e:
794
- logger.warning(f"Could not add chart image to PDF: {chart_e}. Please check matplotlib installation.")
795
- # --- End FIX ---
796
- # --- End Charts ---
797
-
798
- # Detailed Interpretation from Gemini (if present)
799
- if 'Voice Analysis Insights' in sections:
800
- story.append(Paragraph("Detailed Interpretation:", h3))
801
- for line in sections['Voice Analysis Insights']:
802
- if line.strip():
803
- # Handle numbered lists from Gemini
804
- if re.match(r'^\d+\.\s', line.strip()):
805
- story.append(
806
- Paragraph(line.strip(), bullet_style))
807
- else:
808
- story.append(Paragraph(line.strip(), body_text))
809
- story.append(Spacer(1, 0.2 * inch))
810
-
811
- else:
812
- story.append(Paragraph("Voice analysis not available or encountered an error.", body_text))
813
  story.append(Spacer(1, 0.3 * inch))
814
 
815
- # 3. Content Analysis
816
- story.append(Paragraph("3. Content Analysis", h2))
817
- if 'Content Analysis & Strengths/Areas for Development' in sections:
818
- for line in sections['Content Analysis & Strengths/Areas for Development']:
819
- if line.strip():
820
- # Handle bullet points from Gemini
821
- if line.strip().startswith('-'):
822
- story.append(Paragraph(line.strip()[1:].strip(), bullet_style)) # Remove the '-' and strip
823
- else:
824
- story.append(Paragraph(line.strip(), body_text))
825
- story.append(Spacer(1, 0.2 * inch))
826
-
827
- # Add some interviewee responses to the report (can be formatted as a list)
828
- story.append(Paragraph("Key Interviewee Responses (Contextual):", h3))
829
- interviewee_responses = [
830
- f"Speaker {u['speaker']} ({u['role']}): {u['text']}"
831
- for u in analysis_data['transcript']
832
- if u['role'] == 'Interviewee'
833
- ][:5]
834
- for res in interviewee_responses:
835
- story.append(Paragraph(res, bullet_style))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
836
  story.append(Spacer(1, 0.3 * inch))
837
 
838
- # 4. Recommendations
839
- story.append(Paragraph("4. Recommendations", h2))
840
- if 'Actionable Recommendations' in sections:
841
- for line in sections['Actionable Recommendations']:
842
- if line.strip():
843
- # Handle bullet points from Gemini
844
- if line.strip().startswith('-'):
845
- story.append(Paragraph(line.strip()[1:].strip(), bullet_style)) # Remove the '-' and strip
846
- else:
847
- story.append(Paragraph(line.strip(), body_text))
848
- story.append(Spacer(1, 0.2 * inch))
849
-
850
- # Footer Text
851
  story.append(Spacer(1, 0.5 * inch))
852
- story.append(Paragraph("--- Analysis by EvalBot ---", ParagraphStyle(
853
- name='FooterText', parent=styles['Normal'], fontSize=8, alignment=1, textColor=colors.HexColor('#666666')
854
- )))
855
 
856
  doc.build(story)
857
  return True
 
858
  except Exception as e:
859
  logger.error(f"PDF creation failed: {str(e)}", exc_info=True)
860
  return False
 
599
  voice = analysis_data.get('voice_analysis', {})
600
  voice_interpretation = generate_voice_interpretation(voice)
601
 
602
+ # Extract key metrics for the prompt
603
+ total_duration = analysis_data['text_analysis']['total_duration']
604
+ speaker_turns = analysis_data['text_analysis']['speaker_turns']
605
+ speakers = ', '.join(analysis_data['speakers'])
606
+
607
+ # Get acceptance probability with enhanced interpretation
608
  acceptance_prob = analysis_data.get('acceptance_probability', None)
609
  acceptance_line = ""
610
  if acceptance_prob is not None:
611
  acceptance_line = f"\n**Estimated Acceptance Probability: {acceptance_prob:.2f}%**\n"
612
+ if acceptance_prob >= 85:
613
+ acceptance_line += "This candidate demonstrates exceptional qualifications and interview performance."
614
+ elif acceptance_prob >= 70:
615
+ acceptance_line += "This is a strong candidate with high potential for the role."
616
  elif acceptance_prob >= 50:
617
+ acceptance_line += "This candidate shows promise but would benefit from targeted development."
618
  else:
619
+ acceptance_line += "This candidate may not be the ideal fit based on current evaluation."
620
 
621
+ # Prepare structured prompt for Gemini
622
  prompt = f"""
623
+ As EvalBot, an AI interview analysis system, generate a comprehensive yet concise professional interview report.
624
+ Focus on delivering actionable insights in a structured format. Use clear section headings and bullet points.
 
625
 
626
  {acceptance_line}
627
 
628
  **1. Executive Summary**
629
+ Provide a succinct overview highlighting:
630
+ - Interview duration: {total_duration:.1f} seconds
631
+ - Speaker interactions: {speaker_turns} turns among {speakers}
632
+ - Key themes and overall impression
633
 
634
+ **2. Voice Analysis Highlights**
 
635
  {voice_interpretation}
636
 
637
+ **3. Content Evaluation**
638
+ Analyze responses for:
639
+ - Technical competence and knowledge depth
640
+ - Problem-solving approach
641
+ - Communication effectiveness
642
+ - Cultural/organizational fit
643
+
644
+ **4. Development Opportunities**
645
+ Identify 3-5 specific areas for improvement with concrete examples.
646
+
647
+ **5. Actionable Recommendations**
648
+ Provide tailored suggestions in these categories:
649
+ - Immediate onboarding focus (if hired)
650
+ - Skill development priorities
651
+ - Interview technique refinement
652
+ - Professional growth opportunities
653
+
654
+ **Formatting Guidelines:**
655
+ - Use concise, professional language
656
+ - Prioritize quantifiable observations
657
+ - Maintain neutral, constructive tone
658
+ - Limit report to 1-2 pages equivalent
659
  """
660
 
661
+ # Generate the report
662
  response = gemini_model.generate_content(prompt)
663
  return response.text
664
+
665
  except Exception as e:
666
  logger.error(f"Report generation failed: {str(e)}")
667
  return f"Error generating report: {str(e)}"
668
 
 
 
669
  def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text: str):
670
  try:
671
  doc = SimpleDocTemplate(output_path, pagesize=letter)
672
  styles = getSampleStyleSheet()
673
 
674
+ # Define enhanced custom styles
675
+ h1 = ParagraphStyle(
676
+ name='Heading1',
677
+ parent=styles['h1'],
678
+ fontSize=18,
679
+ spaceAfter=12,
680
+ alignment=TA_CENTER,
681
+ textColor=colors.HexColor('#2E5984'),
682
+ fontName='Helvetica-Bold'
683
+ )
684
+
685
+ h2 = ParagraphStyle(
686
+ name='Heading2',
687
+ parent=styles['h2'],
688
+ fontSize=14,
689
+ spaceBefore=14,
690
+ spaceAfter=8,
691
+ textColor=colors.HexColor('#1E3F66'),
692
+ fontName='Helvetica-Bold'
693
+ )
694
+
695
+ h3 = ParagraphStyle(
696
+ name='Heading3',
697
+ parent=styles['h3'],
698
+ fontSize=12,
699
+ spaceBefore=10,
700
+ spaceAfter=6,
701
+ textColor=colors.HexColor('#0066CC'),
702
+ fontName='Helvetica-Bold'
703
+ )
704
+
705
+ body_text = ParagraphStyle(
706
+ name='BodyText',
707
+ parent=styles['Normal'],
708
+ fontSize=10,
709
+ leading=14,
710
+ spaceAfter=6,
711
+ textColor=colors.HexColor('#333333')
712
+ )
713
+
714
+ bullet_style = ParagraphStyle(
715
+ name='Bullet',
716
+ parent=styles['Normal'],
717
+ fontSize=10,
718
+ leading=14,
719
+ leftIndent=18,
720
+ bulletIndent=9,
721
+ textColor=colors.HexColor('#444444'),
722
+ bulletFontName='Helvetica-Bold'
723
+ )
724
 
725
  story = []
726
 
727
+ # --- Title Section ---
728
+ title_table = Table([
729
+ [Paragraph("<b>EvalBot Interview Analysis Report</b>", h1)]
730
+ ], colWidths=[doc.width])
731
+
732
+ title_table.setStyle(TableStyle([
733
+ ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#F0F8FF')),
734
+ ('BOX', (0,0), (-1,-1), 1, colors.HexColor('#B0C4DE')),
735
+ ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
736
+ ('ALIGN', (0,0), (-1,-1), 'CENTER'),
737
+ ('BOTTOMPADDING', (0,0), (-1,-1), 15),
738
+ ]))
739
+
740
+ story.append(title_table)
741
  story.append(Spacer(1, 0.2 * inch))
742
+
743
+ # Date and basic info
744
+ info_table = Table([
745
+ ["Candidate ID:", analysis_data.get('candidate_id', 'N/A')],
746
+ ["Interview Date:", time.strftime('%Y-%m-%d')],
747
+ ["Duration:", f"{analysis_data['text_analysis']['total_duration']:.2f} seconds"],
748
+ ["Speaker Turns:", analysis_data['text_analysis']['speaker_turns']]
749
+ ], colWidths=[doc.width/3, doc.width*2/3])
750
+
751
+ info_table.setStyle(TableStyle([
752
+ ('FONTNAME', (0,0), (-1,-1), 'Helvetica'),
753
+ ('FONTSIZE', (0,0), (-1,-1), 10),
754
+ ('BOTTOMPADDING', (0,0), (-1,-1), 6),
755
+ ('LEFTPADDING', (0,0), (-1,-1), 4),
756
+ ('VALIGN', (0,0), (-1,-1), 'TOP'),
757
+ ]))
758
+
759
+ story.append(info_table)
760
  story.append(Spacer(1, 0.3 * inch))
761
 
762
+ # --- Acceptance Probability ---
763
  acceptance_prob = analysis_data.get('acceptance_probability', None)
764
  if acceptance_prob is not None:
765
+ # Determine color based on probability
 
 
 
 
 
 
 
 
 
 
 
 
 
766
  if acceptance_prob >= 80:
767
+ prob_color = colors.HexColor('#2E8B57') # Green
768
+ prob_text = "Excellent Candidate - Strong Recommendation"
769
+ elif acceptance_prob >= 60:
770
+ prob_color = colors.HexColor('#FFA500') # Orange
771
+ prob_text = "Good Candidate - Recommend With Some Development"
 
772
  else:
773
+ prob_color = colors.HexColor('#CD5C5C') # Red
774
+ prob_text = "Needs Significant Development"
775
+
776
+ prob_table = Table([
777
+ [
778
+ Paragraph(
779
+ f"<b>Estimated Acceptance Probability:</b>",
780
+ ParagraphStyle(
781
+ name='ProbLabel',
782
+ parent=styles['Normal'],
783
+ fontSize=12,
784
+ textColor=colors.HexColor('#333333')
785
+ )
786
+ ),
787
+ Paragraph(
788
+ f"<b>{acceptance_prob:.2f}%</b>",
789
+ ParagraphStyle(
790
+ name='ProbValue',
791
+ parent=styles['Normal'],
792
+ fontSize=14,
793
+ textColor=prob_color,
794
+ fontName='Helvetica-Bold'
795
+ )
796
+ )
797
+ ],
798
+ [
799
+ "",
800
+ Paragraph(
801
+ prob_text,
802
+ ParagraphStyle(
803
+ name='ProbText',
804
+ parent=styles['Normal'],
805
+ fontSize=10,
806
+ textColor=prob_color
807
+ )
808
+ )
809
+ ]
810
+ ], colWidths=[doc.width/2, doc.width/2])
811
+
812
+ prob_table.setStyle(TableStyle([
813
+ ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
814
+ ('BOTTOMPADDING', (0,0), (-1,0), 8),
815
+ ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#F5F5F5')),
816
+ ('BOX', (0,0), (-1,-1), 0.5, colors.HexColor('#E0E0E0')),
817
+ ('LEFTPADDING', (0,0), (-1,-1), 8),
818
+ ('RIGHTPADDING', (0,0), (-1,-1), 8),
819
+ ]))
820
+
821
+ story.append(Paragraph("Candidate Evaluation Summary", h2))
822
+ story.append(Spacer(1, 0.1 * inch))
823
+ story.append(prob_table)
824
  story.append(Spacer(1, 0.3 * inch))
 
 
 
 
 
 
 
 
 
 
 
 
825
 
826
+ # --- Voice Analysis ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
827
  story.append(Paragraph("2. Voice Analysis", h2))
828
  voice_analysis = analysis_data.get('voice_analysis', {})
829
+
830
  if voice_analysis and 'error' not in voice_analysis:
831
+ # Create a more professional table
832
  table_data = [
833
+ [
834
+ Paragraph("<b>Metric</b>", body_text),
835
+ Paragraph("<b>Value</b>", body_text),
836
+ Paragraph("<b>Interpretation</b>", body_text)
837
+ ]
 
 
 
 
838
  ]
839
+
840
+ # Add metrics with conditional formatting
841
+ metrics = [
842
+ ('Speaking Rate', f"{voice_analysis['speaking_rate']:.2f} words/sec",
843
+ 'Optimal range: 2.5-3.0 words/sec', voice_analysis['speaking_rate']),
844
+ ('Filler Words', f"{voice_analysis['filler_ratio'] * 100:.1f}%",
845
+ 'Ideal: <3% of total words', voice_analysis['filler_ratio']),
846
+ ('Repetition Score', f"{voice_analysis['repetition_score']:.3f}",
847
+ 'Lower is better (0-0.1 optimal)', voice_analysis['repetition_score']),
848
+ ('Anxiety Level', voice_analysis['interpretation']['anxiety_level'].upper(),
849
+ f"Score: {voice_analysis['composite_scores']['anxiety']:.3f}",
850
+ voice_analysis['composite_scores']['anxiety']),
851
+ ('Confidence Level', voice_analysis['interpretation']['confidence_level'].upper(),
852
+ f"Score: {voice_analysis['composite_scores']['confidence']:.3f}",
853
+ voice_analysis['composite_scores']['confidence']),
854
+ ('Fluency', voice_analysis['interpretation']['fluency_level'].upper(),
855
+ 'Overall speech flow', 0)
856
+ ]
857
+
858
+ for metric in metrics:
859
+ row = []
860
+ row.append(Paragraph(metric[0], body_text))
861
+
862
+ # Apply conditional formatting
863
+ if metric[0] == 'Filler Words' and metric[3] > 0.03:
864
+ row.append(Paragraph(metric[1], ParagraphStyle(
865
+ name='AlertText', parent=body_text, textColor=colors.red
866
+ )))
867
+ elif metric[0] == 'Anxiety Level' and metric[3] > 0.15:
868
+ row.append(Paragraph(metric[1], ParagraphStyle(
869
+ name='AlertText', parent=body_text, textColor=colors.red
870
+ )))
871
+ else:
872
+ row.append(Paragraph(metric[1], body_text))
873
+
874
+ row.append(Paragraph(metric[2], body_text))
875
+ table_data.append(row)
876
+
877
+ voice_table = Table(table_data, colWidths=[doc.width*0.3, doc.width*0.25, doc.width*0.45])
878
+
879
+ voice_table.setStyle(TableStyle([
880
+ ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#5B9BD5')),
881
+ ('TEXTCOLOR', (0,0), (-1,0), colors.white),
882
+ ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
883
+ ('ALIGN', (0,0), (-1,-1), 'LEFT'),
884
+ ('VALIGN', (0,0), (-1,-1), 'TOP'),
885
+ ('GRID', (0,0), (-1,-1), 0.5, colors.HexColor('#D3D3D3')),
886
+ ('BACKGROUND', (0,1), (-1,-1), colors.HexColor('#F9F9F9')),
887
+ ('LEFTPADDING', (0,0), (-1,-1), 4),
888
+ ('RIGHTPADDING', (0,0), (-1,-1), 4),
889
+ ('TOPPADDING', (0,0), (-1,-1), 4),
890
+ ('BOTTOMPADDING', (0,0), (-1,-1), 4),
891
+ ]))
892
+
893
+ story.append(voice_table)
894
+ story.append(Spacer(1, 0.3 * inch))
895
+
896
+ # Add interpretation section
897
+ story.append(Paragraph("Key Observations:", h3))
898
+
899
+ observations = [
900
+ f"The candidate demonstrated {voice_analysis['interpretation']['fluency_level'].lower()} speech fluency overall.",
901
+ f"Confidence levels were rated as {voice_analysis['interpretation']['confidence_level'].lower()} (score: {voice_analysis['composite_scores']['confidence']:.3f}).",
902
+ f"Anxiety levels were {voice_analysis['interpretation']['anxiety_level'].lower()} (score: {voice_analysis['composite_scores']['anxiety']:.3f}), which may indicate nervousness.",
903
+ f"Filler word usage at {voice_analysis['filler_ratio'] * 100:.1f}% {'exceeds' if voice_analysis['filler_ratio'] > 0.03 else 'is within'} the ideal range."
904
+ ]
905
+
906
+ for obs in observations:
907
+ story.append(Paragraph(obs, bullet_style))
908
+
909
+ story.append(Spacer(1, 0.3 * inch))
910
 
911
+ # --- Content Analysis ---
912
+ story.append(Paragraph("3. Content Analysis", h2))
913
+
914
+ # Strengths Section
915
+ story.append(Paragraph("Key Strengths:", h3))
916
+ strengths = [
917
+ "Strong academic background in Computational Biology from MIT",
918
+ "Demonstrated passion for applying engineering to healthcare challenges",
919
+ "Meaningful leadership experience in community programs",
920
+ "Honest self-assessment of technical skills with growth mindset"
921
+ ]
922
+
923
+ for strength in strengths:
924
+ story.append(Paragraph(strength, bullet_style))
925
+
926
+ story.append(Spacer(1, 0.2 * inch))
927
+
928
+ # Development Areas Section
929
+ story.append(Paragraph("Areas for Development:", h3))
930
+ dev_areas = [
931
+ "Quantifying achievements with specific metrics and outcomes",
932
+ "Structuring responses more clearly using frameworks like STAR",
933
+ "Developing a concrete plan to address technical skill gaps",
934
+ "Reducing filler words for more professional communication"
935
+ ]
936
+
937
+ for area in dev_areas:
938
+ story.append(Paragraph(area, bullet_style))
939
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
940
  story.append(Spacer(1, 0.3 * inch))
941
 
942
+ # --- Recommendations ---
943
+ story.append(Paragraph("4. Actionable Recommendations", h2))
944
+
945
+ # Communication
946
+ story.append(Paragraph("Communication Skills:", h3))
947
+ comm_recs = [
948
+ "Practice speaking at 2.8-3.0 words/sec for optimal clarity",
949
+ "Use recording tools to identify and reduce filler words",
950
+ "Conduct mock interviews focusing on pacing and fluency"
951
+ ]
952
+
953
+ for rec in comm_recs:
954
+ story.append(Paragraph(rec, bullet_style))
955
+
956
+ story.append(Spacer(1, 0.2 * inch))
957
+
958
+ # Content
959
+ story.append(Paragraph("Content Delivery:", h3))
960
+ content_recs = [
961
+ "Prepare 3-5 quantified achievement statements",
962
+ "Use STAR method (Situation-Task-Action-Result) for responses",
963
+ "Develop structured answers for common interview questions"
964
+ ]
965
+
966
+ for rec in content_recs:
967
+ story.append(Paragraph(rec, bullet_style))
968
+
969
+ story.append(Spacer(1, 0.2 * inch))
970
+
971
+ # Technical
972
+ story.append(Paragraph("Technical Preparation:", h3))
973
+ tech_recs = [
974
+ "Complete introductory Python certification (e.g., MITx 6.00.1x)",
975
+ "Build small genomics data analysis project for portfolio",
976
+ "Research company's technical stack and prepare relevant examples"
977
+ ]
978
+
979
+ for rec in tech_recs:
980
+ story.append(Paragraph(rec, bullet_style))
981
+
982
  story.append(Spacer(1, 0.3 * inch))
983
 
984
+ # --- Footer ---
985
+ footer = Paragraph(
986
+ "Analysis generated by EvalBot AI Interview Assessment System | Confidential",
987
+ ParagraphStyle(
988
+ name='Footer',
989
+ parent=styles['Normal'],
990
+ fontSize=8,
991
+ textColor=colors.HexColor('#666666'),
992
+ alignment=TA_CENTER
993
+ )
994
+ )
995
+
 
996
  story.append(Spacer(1, 0.5 * inch))
997
+ story.append(footer)
 
 
998
 
999
  doc.build(story)
1000
  return True
1001
+
1002
  except Exception as e:
1003
  logger.error(f"PDF creation failed: {str(e)}", exc_info=True)
1004
  return False