norhan12 commited on
Commit
34523b7
·
verified ·
1 Parent(s): afeb72f

Update process_interview.py

Browse files
Files changed (1) hide show
  1. process_interview.py +153 -177
process_interview.py CHANGED
@@ -600,8 +600,6 @@ def calculate_acceptance_probability(analysis_data: Dict) -> float:
600
  return float(f"{acceptance_probability * 100:.2f}") # Return as percentage
601
 
602
 
603
-
604
-
605
  def generate_report(analysis_data: Dict) -> str:
606
  try:
607
  voice = analysis_data.get('voice_analysis', {})
@@ -628,20 +626,15 @@ def generate_report(analysis_data: Dict) -> str:
628
 
629
  prompt = f"""
630
  You are EvalBot, a highly experienced senior HR analyst generating a comprehensive interview evaluation report based on both objective metrics and full interviewee responses.
631
-
632
  Your task:
633
-
634
  - Analyze deeply based on actual responses provided below. Avoid generic analysis.
635
  - Use only insights that can be inferred from the answers or provided metrics.
636
  - Maintain professional, HR-standard language with clear structure and bullet points.
637
  - Avoid redundancy or overly generic feedback.
638
  - The responses are real interviewee answers, treat them as high-priority source.
639
-
640
  {acceptance_line}
641
-
642
  ### Interviewee Full Responses:
643
  {full_responses_text}
644
-
645
  ### Metrics Summary:
646
  - Duration: {analysis_data['text_analysis']['total_duration']:.2f} seconds
647
  - Speaker Turns: {analysis_data['text_analysis']['speaker_turns']}
@@ -651,42 +644,37 @@ Your task:
651
  - Anxiety Level: {voice.get('interpretation', {}).get('anxiety_level', 'N/A')}
652
  - Fluency Level: {voice.get('interpretation', {}).get('fluency_level', 'N/A')}
653
  - Voice Interpretation Summary: {voice_interpretation}
654
-
655
  ### Report Sections to Generate:
656
-
657
- **1. Executive Summary**
 
 
 
658
  - 3 bullets summarizing performance, key strengths, and hiring recommendation.
659
  - Mention relevant metrics when applicable.
660
-
661
- **2. Communication and Vocal Dynamics**
662
  - Analyze delivery: speaking rate, filler words, confidence, anxiety, fluency.
663
  - Provide 3-4 insightful bullets.
664
  - Give 1 actionable improvement recommendation for workplace communication.
665
-
666
- **3. Competency and Content**
667
  - Identify 5-8 strengths (use HR competencies: leadership, teamwork, problem-solving, etc.).
668
  - For each: provide short explanation + concrete example inferred from responses.
669
  - Identify 5-10 weaknesses or development areas.
670
- - For each weakness: provide actionable, practical feedback.
671
-
672
- **4. Role Fit and Potential**
673
  - Analyze role fit, cultural fit, growth potential in 3 bullets.
674
  - Use examples from responses whenever possible.
675
-
676
- **5. Recommendations**
677
  - Provide 5 actionable recommendations categorized into:
678
  - Communication Skills
679
  - Content Delivery
680
  - Professional Presentation
681
  - Each recommendation should include a short improvement strategy/example.
682
-
683
  **Next Steps for Hiring Managers**
684
  - Provide 5 clear next steps: next round, training, assessment, mentorship, role fit review.
685
-
686
  Ensure each section is clearly titled exactly as requested above.
687
- Avoid repetition between sections.
688
  Use professional HR tone.
689
-
690
  Begin the full analysis now.
691
  """
692
 
@@ -697,7 +685,9 @@ Begin the full analysis now.
697
  logger.error(f"Report generation failed: {str(e)}")
698
  return f"Error generating report: {str(e)}"
699
 
700
- def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text: str) -> bool:
 
 
701
  try:
702
  doc = SimpleDocTemplate(
703
  output_path,
@@ -708,7 +698,7 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
708
  bottomMargin=1*inch
709
  )
710
  styles = getSampleStyleSheet()
711
-
712
  # Custom styles
713
  cover_title = ParagraphStyle(name='CoverTitle', fontSize=24, leading=28, spaceAfter=20, alignment=1, textColor=colors.HexColor('#003087'), fontName='Helvetica-Bold')
714
  h1 = ParagraphStyle(name='Heading1', fontSize=16, leading=20, spaceAfter=14, alignment=1, textColor=colors.HexColor('#003087'), fontName='Helvetica-Bold')
@@ -718,7 +708,7 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
718
  bullet_style = ParagraphStyle(name='Bullet', parent=body_text, leftIndent=18, bulletIndent=8, fontName='Helvetica', bulletFontName='Helvetica', bulletFontSize=9)
719
  table_header = ParagraphStyle(name='TableHeader', fontSize=9, leading=11, textColor=colors.white, fontName='Helvetica-Bold')
720
  table_body = ParagraphStyle(name='TableBody', fontSize=9, leading=11, fontName='Helvetica')
721
-
722
  story = []
723
 
724
  def header_footer(canvas, doc):
@@ -748,16 +738,73 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
748
  story.append(Paragraph("Confidential", ParagraphStyle(name='Confidential', fontSize=10, alignment=1, textColor=colors.HexColor('#D32F2F'), fontName='Helvetica-Bold')))
749
  story.append(PageBreak())
750
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
  # Table of Contents
752
  story.append(Paragraph("Table of Contents", h1))
753
  toc_data = [
754
  [Paragraph("Section", table_header), Paragraph("Page", table_header)],
755
  [Paragraph("1. Interview Evaluation Summary", table_body), Paragraph("3", table_body)],
756
- [Paragraph("2. Communication & Vocal Dynamics", table_body), Paragraph("4", table_body)],
757
- [Paragraph("3. Executive Summary", table_body), Paragraph("4", table_body)],
758
- [Paragraph("4. Competency & Evaluation", table_body), Paragraph("5", table_body)],
759
- [Paragraph("5. Role Fit & Potential", table_body), Paragraph("5", table_body)],
760
- [Paragraph("6. Recommendations", table_body), Paragraph("6", table_body)],
 
761
  ]
762
  toc_table = Table(toc_data, colWidths=[4*inch, 2*inch])
763
  toc_table.setStyle(TableStyle([
@@ -768,13 +815,13 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
768
  ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
769
  ('FONTSIZE', (0,0), (-1,-1), 9),
770
  ('BOTTOMPADDING', (0,0), (-1,-1), 6),
771
- ('TOPPADING', (0,0), (-1,-1), 6),
772
  ('GRID', (0,0), (-1,-1), 0.5, colors.HexColor('#DDE4EE')),
773
  ]))
774
  story.append(toc_table)
775
  story.append(PageBreak())
776
 
777
- # Title Page
778
  story.append(Paragraph("Interview Evaluation Summary", h1))
779
  story.append(Spacer(1, 0.3*inch))
780
  acceptance_prob = analysis_data.get('acceptance_probability', 50.0)
@@ -794,13 +841,17 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
794
  else:
795
  story.append(Paragraph("<b>HR Verdict:</b> Limited fit, significant improvement required.", body_text))
796
  story.append(Spacer(1, 0.2*inch))
797
-
 
 
798
  roles = sorted(set(u.get('role', 'Unknown') for u in analysis_data.get('transcript', [])))
799
  table_data = [
800
  [Paragraph('Metric', table_header), Paragraph('Value', table_header)],
801
- [Paragraph('Interview Duration', table_body), Paragraph(f"{analysis_data['text_analysis'].get('total_duration', 0):.2f} seconds", table_body)],
802
- [Paragraph('Speaker Turns', table_body), Paragraph(f"{analysis_data['text_analysis'].get('speaker_turns', 0)}", table_body)],
803
- [Paragraph('Roles', table_body), Paragraph(', '.join(roles), table_body)],
 
 
804
  ]
805
  table = Table(table_data, colWidths=[2.3*inch, 3.7*inch])
806
  table.setStyle(TableStyle([
@@ -822,10 +873,34 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
822
 
823
  # Detailed Analysis
824
  story.append(Paragraph("Detailed Candidate Evaluation", h1))
825
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
826
  # Communication and Vocal Dynamics
827
- story.append(Paragraph("2. Communication & Vocal Dynamics", h2))
828
- voice_analysis = analysis_data.get('voice_analysis', {})
829
  if voice_analysis and 'error' not in voice_analysis:
830
  table_data = [
831
  [Paragraph('Metric', table_header), Paragraph('Value', table_header), Paragraph('HR Insight', table_header)],
@@ -850,113 +925,37 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
850
  ]))
851
  story.append(table)
852
  story.append(Spacer(1, 0.2*inch))
853
- chart_buffer = io.BytesIO()
854
- generate_anxiety_confidence_chart(voice_analysis.get('composite_scores', {}), chart_buffer)
855
- chart_buffer.seek(0)
856
- img = Image(chart_buffer, width=4.5*inch, height=3*inch)
857
- img.hAlign = 'CENTER'
858
- story.append(img)
 
 
 
 
859
  else:
860
- story.append(Paragraph(f"Vocal analysis unavailable: {voice_analysis.get('error', 'No data available')}", body_text))
 
 
 
861
  story.append(Spacer(1, 0.2*inch))
862
 
863
- # Parse Gemini Report
864
- sections = {
865
- "Executive Summary": [],
866
- "Communication": [],
867
- "Competency": {"Strengths": [], "Weaknesses": []},
868
- "Role Fit": [],
869
- "Recommendations": {"Development": [], "Next Steps": []},
870
- }
871
- current_section = None
872
- current_subsection = None
873
- lines = gemini_report_text.split('\n')
874
- for line in lines:
875
- line = line.strip()
876
- if not line: continue
877
- heading_match = re.match(r'^\**(\d+\.\s+)?([^\*]+)\**$', line)
878
- if heading_match:
879
- section_title = heading_match.group(2).strip()
880
- if 'Executive Summary' in section_title:
881
- current_section = 'Executive Summary'
882
- current_subsection = None
883
- elif 'Communication' in section_title:
884
- current_section = 'Communication'
885
- current_subsection = None
886
- elif 'Competency' in section_title:
887
- current_section = 'Competency'
888
- current_subsection = None
889
- elif 'Role Fit' in section_title:
890
- current_section = 'Role Fit'
891
- current_subsection = None
892
- elif 'Recommendations' in section_title:
893
- current_section = 'Recommendations'
894
- current_subsection = None
895
- elif re.match(r'^[-*•]\s+', line) and current_section:
896
- clean_line = re.sub(r'^[-*•]\s+', '', line).strip()
897
- if not clean_line: continue
898
- clean_line = re.sub(r'[()\[\]{}]', '', clean_line)
899
- if current_section == 'Competency':
900
- if any(k in clean_line.lower() for k in ['leader', 'problem', 'commun', 'adapt', 'strength', 'effective', 'skill', 'team', 'project']):
901
- current_subsection = 'Strengths'
902
- elif any(k in clean_line.lower() for k in ['improv', 'grow', 'weak', 'depth', 'challenge', 'gap']):
903
- current_subsection = 'Weaknesses'
904
- if current_subsection:
905
- sections[current_section][current_subsection].append(clean_line)
906
- elif current_section == 'Recommendations':
907
- if any(k in clean_line.lower() for k in ['commun', 'tech', 'depth', 'pres', 'improve', 'enhance', 'clarity', 'structur', 'tone', 'deliver']):
908
- current_subsection = 'Development'
909
- elif any(k in clean_line.lower() for k in ['adv', 'train', 'assess', 'next', 'mentor', 'round']):
910
- current_subsection = 'Next Steps'
911
- if current_subsection:
912
- sections[current_section][current_subsection].append(clean_line)
913
- else:
914
- sections[current_section].append(clean_line)
915
-
916
- # Key Highlights
917
- story.append(Paragraph("3. Key Highlights", h2))
918
- summary_data = [
919
- [Paragraph("Category", table_header), Paragraph("Detail", table_header)],
920
- [Paragraph("Top Strength", table_body), Paragraph(sections['Competency']['Strengths'][0] if sections['Competency']['Strengths'] else "Demonstrated potential in leadership or teamwork.", table_body)],
921
- [Paragraph("Key Weakness", table_body), Paragraph(sections['Competency']['Weaknesses'][0] if sections['Competency']['Weaknesses'] else "Needs improvement in response structure or technical skills.", table_body)],
922
- [Paragraph("Top Recommendation", table_body), Paragraph(sections['Recommendations']['Development'][0] if sections['Recommendations']['Development'] else "Practice structured responses using the STAR method.", table_body)],
923
- ]
924
- summary_table = Table(summary_data, colWidths=[2*inch, 4*inch])
925
- summary_table.setStyle(TableStyle([
926
- ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#0050BC')),
927
- ('TEXTCOLOR', (0,0), (-1,0), colors.white),
928
- ('ALIGN', (0,0), (-1,-1), 'LEFT'),
929
- ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
930
- ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
931
- ('FONTSIZE', (0,0), (-1,-1), 9),
932
- ('BOTTOMPADDING', (0,0), (-1,-1), 6),
933
- ('TOPPADDING', (0,0), (-1,-1), 6),
934
- ('BACKGROUND', (0,1), (-1,-1), colors.HexColor('#E8F0FE')),
935
- ('GRID', (0,0), (-1,-1), 0.5, colors.HexColor('#DDE4EE')),
936
- ]))
937
- story.append(summary_table)
938
- story.append(Spacer(1, 0.3*inch))
939
-
940
  # Executive Summary
941
- story.append(Paragraph("4. Executive Summary", h2))
942
- if sections['Executive Summary']:
943
- for line in sections['Executive Summary']:
944
  story.append(Paragraph(line, bullet_style))
945
  else:
946
- summary_lines = [
947
- f"High suitability score of {acceptance_prob:.2f}% indicates strong potential.",
948
- f"Interview duration: {analysis_data['text_analysis']['total_duration']:.2f} seconds, {analysis_data['text_analysis']['speaker_turns']} speaker turns.",
949
- "Strengths in leadership and teamwork; recommended for further evaluation."
950
- ]
951
- for line in summary_lines:
952
- story.append(Paragraph(line, bullet_style))
953
  story.append(Spacer(1, 0.2*inch))
954
 
955
  # Competency and Content
956
- story.append(Paragraph("5. Competency & Evaluation", h2))
957
  story.append(Paragraph("Strengths", h3))
958
- if sections['Competency']['Strengths']:
959
- strength_table = Table([[Paragraph(line, bullet_style)] for line in sections['Competency']['Strengths']], colWidths=[6*inch])
960
  strength_table.setStyle(TableStyle([
961
  ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#E6FFE6')),
962
  ('VALIGN', (0,0), (-1,-1), 'TOP'),
@@ -964,11 +963,11 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
964
  ]))
965
  story.append(strength_table)
966
  else:
967
- story.append(Paragraph("No specific strengths identified; candidate shows general potential in teamwork or initiative.", body_text))
968
  story.append(Spacer(1, 0.1*inch))
969
  story.append(Paragraph("Weaknesses", h3))
970
- if sections['Competency']['Weaknesses']:
971
- weakness_table = Table([[Paragraph(line, bullet_style)] for line in sections['Competency']['Weaknesses']], colWidths=[6*inch])
972
  weakness_table.setStyle(TableStyle([
973
  ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#FFF0F0')),
974
  ('VALIGN', (0,0), (-1,-1), 'TOP'),
@@ -979,26 +978,20 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
979
  story.append(Paragraph("No specific weaknesses identified; focus on enhancing existing strengths.", body_text))
980
  story.append(Spacer(1, 0.2*inch))
981
 
982
- # Role Fit
983
- story.append(Paragraph("6. Role Fit & Potential", h2))
984
- if sections['Role Fit']:
985
- for line in sections['Role Fit']:
986
  story.append(Paragraph(line, bullet_style))
987
  else:
988
- fit_lines = [
989
- f"Suitability score of {acceptance_prob:.2f}% suggests alignment with role requirements.",
990
- "Strengths in collaboration indicate fit for team-oriented environments.",
991
- "Further assessment needed to confirm long-term cultural fit."
992
- ]
993
- for line in fit_lines:
994
- story.append(Paragraph(line, bullet_style))
995
  story.append(Spacer(1, 0.2*inch))
996
 
997
  # Recommendations
998
- story.append(Paragraph("7. Recommendations", h2))
999
  story.append(Paragraph("Development Priorities", h3))
1000
- if sections['Recommendations']['Development']:
1001
- dev_table = Table([[Paragraph(line, bullet_style)] for line in sections['Recommendations']['Development']], colWidths=[6*inch])
1002
  dev_table.setStyle(TableStyle([
1003
  ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#E8F0FE')),
1004
  ('VALIGN', (0,0), (-1,-1), 'TOP'),
@@ -1006,34 +999,17 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
1006
  ]))
1007
  story.append(dev_table)
1008
  else:
1009
- dev_lines = [
1010
- "Improve communication clarity by practicing the STAR method for structured responses.",
1011
- "Enhance content delivery by quantifying achievements (e.g., 'Led a team to achieve 20% growth').",
1012
- "Boost professional presentation through public speaking workshops.",
1013
- "Reduce filler words via recorded practice sessions."
1014
- ]
1015
- dev_table = Table([[Paragraph(line, bullet_style)] for line in dev_lines], colWidths=[6*inch])
1016
- dev_table.setStyle(TableStyle([
1017
- ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#E8F0FE')),
1018
- ('VALIGN', (0,0), (-1,-1), 'TOP'),
1019
- ('LEFTPADDING', (0,0), (-1,-1), 6),
1020
- ]))
1021
- story.append(dev_table)
1022
  story.append(Spacer(1, 0.1*inch))
1023
- story.append(Paragraph("Next Steps", h3))
1024
- if sections['Recommendations']['Next Steps']:
1025
- for line in sections['Recommendations']['Next Steps']:
1026
  story.append(Paragraph(line, bullet_style))
1027
  else:
1028
- next_steps = [
1029
- f"Advance to next round given {acceptance_prob:.2f}% suitability score.",
1030
- "Provide training to address technical or communication gaps.",
1031
- "Conduct a behavioral assessment to confirm role alignment."
1032
- ]
1033
- for line in next_steps:
1034
- story.append(Paragraph(line, bullet_style))
1035
  story.append(Spacer(1, 0.2*inch))
1036
 
 
1037
  doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
1038
  logger.info(f"PDF report successfully generated at {output_path}")
1039
  return True
 
600
  return float(f"{acceptance_probability * 100:.2f}") # Return as percentage
601
 
602
 
 
 
603
  def generate_report(analysis_data: Dict) -> str:
604
  try:
605
  voice = analysis_data.get('voice_analysis', {})
 
626
 
627
  prompt = f"""
628
  You are EvalBot, a highly experienced senior HR analyst generating a comprehensive interview evaluation report based on both objective metrics and full interviewee responses.
 
629
  Your task:
 
630
  - Analyze deeply based on actual responses provided below. Avoid generic analysis.
631
  - Use only insights that can be inferred from the answers or provided metrics.
632
  - Maintain professional, HR-standard language with clear structure and bullet points.
633
  - Avoid redundancy or overly generic feedback.
634
  - The responses are real interviewee answers, treat them as high-priority source.
 
635
  {acceptance_line}
 
636
  ### Interviewee Full Responses:
637
  {full_responses_text}
 
638
  ### Metrics Summary:
639
  - Duration: {analysis_data['text_analysis']['total_duration']:.2f} seconds
640
  - Speaker Turns: {analysis_data['text_analysis']['speaker_turns']}
 
644
  - Anxiety Level: {voice.get('interpretation', {}).get('anxiety_level', 'N/A')}
645
  - Fluency Level: {voice.get('interpretation', {}).get('fluency_level', 'N/A')}
646
  - Voice Interpretation Summary: {voice_interpretation}
 
647
  ### Report Sections to Generate:
648
+ **1. Key Highlights**
649
+ - Provide 3 bullets: top strength, most critical weakness, and most impactful recommendation.
650
+ - Each bullet must be concise, specific, and derived from responses or metrics.
651
+ - Example: "Top Strength: Demonstrated leadership by leading a project to 20% efficiency gain (Response: 'I led a team to...')."
652
+ **2. Executive Summary**
653
  - 3 bullets summarizing performance, key strengths, and hiring recommendation.
654
  - Mention relevant metrics when applicable.
655
+ **3. Communication and Vocal Dynamics**
 
656
  - Analyze delivery: speaking rate, filler words, confidence, anxiety, fluency.
657
  - Provide 3-4 insightful bullets.
658
  - Give 1 actionable improvement recommendation for workplace communication.
659
+ **4. Competency and Content**
 
660
  - Identify 5-8 strengths (use HR competencies: leadership, teamwork, problem-solving, etc.).
661
  - For each: provide short explanation + concrete example inferred from responses.
662
  - Identify 5-10 weaknesses or development areas.
663
+ - For each weakness: provide actionable, practical feedback furlongfeedback.
664
+ **5. Role Fit and Potential**
 
665
  - Analyze role fit, cultural fit, growth potential in 3 bullets.
666
  - Use examples from responses whenever possible.
667
+ **6. Recommendations**
 
668
  - Provide 5 actionable recommendations categorized into:
669
  - Communication Skills
670
  - Content Delivery
671
  - Professional Presentation
672
  - Each recommendation should include a short improvement strategy/example.
 
673
  **Next Steps for Hiring Managers**
674
  - Provide 5 clear next steps: next round, training, assessment, mentorship, role fit review.
 
675
  Ensure each section is clearly titled exactly as requested above.
676
+ Avoid repetition between sections, especially between Key Highlights and Executive Summary.
677
  Use professional HR tone.
 
678
  Begin the full analysis now.
679
  """
680
 
 
685
  logger.error(f"Report generation failed: {str(e)}")
686
  return f"Error generating report: {str(e)}"
687
 
688
+
689
+
690
+ def create_pdf_report(analysis_data: dict, output_path: str, gemini_report_text: str) -> bool:
691
  try:
692
  doc = SimpleDocTemplate(
693
  output_path,
 
698
  bottomMargin=1*inch
699
  )
700
  styles = getSampleStyleSheet()
701
+
702
  # Custom styles
703
  cover_title = ParagraphStyle(name='CoverTitle', fontSize=24, leading=28, spaceAfter=20, alignment=1, textColor=colors.HexColor('#003087'), fontName='Helvetica-Bold')
704
  h1 = ParagraphStyle(name='Heading1', fontSize=16, leading=20, spaceAfter=14, alignment=1, textColor=colors.HexColor('#003087'), fontName='Helvetica-Bold')
 
708
  bullet_style = ParagraphStyle(name='Bullet', parent=body_text, leftIndent=18, bulletIndent=8, fontName='Helvetica', bulletFontName='Helvetica', bulletFontSize=9)
709
  table_header = ParagraphStyle(name='TableHeader', fontSize=9, leading=11, textColor=colors.white, fontName='Helvetica-Bold')
710
  table_body = ParagraphStyle(name='TableBody', fontSize=9, leading=11, fontName='Helvetica')
711
+
712
  story = []
713
 
714
  def header_footer(canvas, doc):
 
738
  story.append(Paragraph("Confidential", ParagraphStyle(name='Confidential', fontSize=10, alignment=1, textColor=colors.HexColor('#D32F2F'), fontName='Helvetica-Bold')))
739
  story.append(PageBreak())
740
 
741
+ # Parse gemini_report_text with robust section matching
742
+ sections = {
743
+ "Key Highlights": [],
744
+ "Executive Summary": [],
745
+ "Communication and Vocal Dynamics": [],
746
+ "Competency and Content": {"Strengths": [], "Weaknesses": []},
747
+ "Role Fit and Potential": [],
748
+ "Recommendations": {"Development": [], "Next Steps": []},
749
+ }
750
+ current_section = None
751
+ current_subsection = None
752
+ lines = gemini_report_text.split('\n')
753
+ for line in lines:
754
+ line = line.strip()
755
+ if not line:
756
+ continue
757
+ # Match section headers exactly
758
+ section_headers = [
759
+ "Key Highlights",
760
+ "Executive Summary",
761
+ "Communication and Vocal Dynamics",
762
+ "Competency and Content",
763
+ "Role Fit and Potential",
764
+ "Recommendations"
765
+ ]
766
+ if line in section_headers:
767
+ current_section = line
768
+ current_subsection = None
769
+ continue
770
+ # Handle subsections for Competency and Content
771
+ if current_section == "Competency and Content":
772
+ if line.lower().startswith("strengths"):
773
+ current_subsection = "Strengths"
774
+ continue
775
+ elif line.lower().startswith("weaknesses"):
776
+ current_subsection = "Weaknesses"
777
+ continue
778
+ # Handle subsections for Recommendations
779
+ if current_section == "Recommendations":
780
+ if line.lower().startswith("communication skills") or line.lower().startswith("content delivery") or line.lower().startswith("professional presentation"):
781
+ current_subsection = "Development"
782
+ continue
783
+ elif line.lower().startswith("next steps for hiring managers"):
784
+ current_subsection = "Next Steps"
785
+ continue
786
+ # Add bullet points to the appropriate section
787
+ if re.match(r'^[-*•]\s+', line) and current_section:
788
+ clean_line = re.sub(r'^[-*•]\s+', '', line).strip()
789
+ if not clean_line:
790
+ continue
791
+ clean_line = re.sub(r'[()\[\]{}]', '', clean_line)
792
+ if current_section in ["Competency and Content", "Recommendations"] and current_subsection:
793
+ sections[current_section][current_subsection].append(clean_line)
794
+ else:
795
+ sections[current_section].append(clean_line)
796
+
797
  # Table of Contents
798
  story.append(Paragraph("Table of Contents", h1))
799
  toc_data = [
800
  [Paragraph("Section", table_header), Paragraph("Page", table_header)],
801
  [Paragraph("1. Interview Evaluation Summary", table_body), Paragraph("3", table_body)],
802
+ [Paragraph("2. Key Highlights", table_body), Paragraph("4", table_body)],
803
+ [Paragraph("3. Communication and Vocal Dynamics", table_body), Paragraph("4", table_body)],
804
+ [Paragraph("4. Executive Summary", table_body), Paragraph("5", table_body)],
805
+ [Paragraph("5. Competency and Content", table_body), Paragraph("5", table_body)],
806
+ [Paragraph("6. Role Fit and Potential", table_body), Paragraph("6", table_body)],
807
+ [Paragraph("7. Recommendations", table_body), Paragraph("6", table_body)],
808
  ]
809
  toc_table = Table(toc_data, colWidths=[4*inch, 2*inch])
810
  toc_table.setStyle(TableStyle([
 
815
  ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
816
  ('FONTSIZE', (0,0), (-1,-1), 9),
817
  ('BOTTOMPADDING', (0,0), (-1,-1), 6),
818
+ ('TOPPADDING', (0,0), (-1,-1), 6),
819
  ('GRID', (0,0), (-1,-1), 0.5, colors.HexColor('#DDE4EE')),
820
  ]))
821
  story.append(toc_table)
822
  story.append(PageBreak())
823
 
824
+ # Interview Evaluation Summary
825
  story.append(Paragraph("Interview Evaluation Summary", h1))
826
  story.append(Spacer(1, 0.3*inch))
827
  acceptance_prob = analysis_data.get('acceptance_probability', 50.0)
 
841
  else:
842
  story.append(Paragraph("<b>HR Verdict:</b> Limited fit, significant improvement required.", body_text))
843
  story.append(Spacer(1, 0.2*inch))
844
+
845
+ # Metrics Table
846
+ voice_analysis = analysis_data.get('voice_analysis', {})
847
  roles = sorted(set(u.get('role', 'Unknown') for u in analysis_data.get('transcript', [])))
848
  table_data = [
849
  [Paragraph('Metric', table_header), Paragraph('Value', table_header)],
850
+ [Paragraph('Interview Duration', table_body), Paragraph(f"{analysis_data.get('text_analysis', {}).get('total_duration', 0):.2f} seconds", table_body)],
851
+ [Paragraph('Speaker Turns', table_body), Paragraph(f"{analysis_data.get('text_analysis', {}).get('speaker_turns', 0)}", table_body)],
852
+ [Paragraph('Roles', table_body), Paragraph(', '.join(roles) if roles else 'N/A', table_body)],
853
+ [Paragraph('Speaking Rate', table_body), Paragraph(f"{voice_analysis.get('speaking_rate', 'N/A')} words/sec", table_body)],
854
+ [Paragraph('Filler Words', table_body), Paragraph(f"{voice_analysis.get('filler_ratio', 0) * 100:.1f}%", table_body)],
855
  ]
856
  table = Table(table_data, colWidths=[2.3*inch, 3.7*inch])
857
  table.setStyle(TableStyle([
 
873
 
874
  # Detailed Analysis
875
  story.append(Paragraph("Detailed Candidate Evaluation", h1))
876
+
877
+ # Key Highlights
878
+ story.append(Paragraph("Key Highlights", h2))
879
+ if sections["Key Highlights"]:
880
+ highlight_table = Table([[Paragraph(line, bullet_style)] for line in sections["Key Highlights"]], colWidths=[6*inch])
881
+ highlight_table.setStyle(TableStyle([
882
+ ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#E8F0FE')),
883
+ ('VALIGN', (0,0), (-1,-1), 'TOP'),
884
+ ('LEFTPADDING', (0,0), (-1,-1), 6),
885
+ ]))
886
+ story.append(highlight_table)
887
+ else:
888
+ fallback_highlights = [
889
+ f"Top Strength: Demonstrated potential based on {acceptance_prob:.2f}% suitability score.",
890
+ "Critical Weakness: Further clarity needed in responses (review transcript for details).",
891
+ "Key Recommendation: Practice structured responses to enhance communication."
892
+ ]
893
+ highlight_table = Table([[Paragraph(line, bullet_style)] for line in fallback_highlights], colWidths=[6*inch])
894
+ highlight_table.setStyle(TableStyle([
895
+ ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#E8F0FE')),
896
+ ('VALIGN', (0,0), (-1,-1), 'TOP'),
897
+ ('LEFTPADDING', (0,0), (-1,-1), 6),
898
+ ]))
899
+ story.append(highlight_table)
900
+ story.append(Spacer(1, 0.2*inch))
901
+
902
  # Communication and Vocal Dynamics
903
+ story.append(Paragraph("Communication and Vocal Dynamics", h2))
 
904
  if voice_analysis and 'error' not in voice_analysis:
905
  table_data = [
906
  [Paragraph('Metric', table_header), Paragraph('Value', table_header), Paragraph('HR Insight', table_header)],
 
925
  ]))
926
  story.append(table)
927
  story.append(Spacer(1, 0.2*inch))
928
+ try:
929
+ chart_buffer = io.BytesIO()
930
+ generate_anxiety_confidence_chart(voice_analysis.get('composite_scores', {}), chart_buffer)
931
+ chart_buffer.seek(0)
932
+ img = Image(chart_buffer, width=4.5*inch, height=3*inch)
933
+ img.hAlign = 'CENTER'
934
+ story.append(img)
935
+ except Exception as e:
936
+ logger.warning(f"Failed to generate chart: {str(e)}")
937
+ story.append(Paragraph("Chart unavailable due to missing or invalid data.", body_text))
938
  else:
939
+ error_msg = voice_analysis.get('error', 'No voice analysis data available')
940
+ story.append(Paragraph(f"Vocal analysis unavailable: {error_msg}", body_text))
941
+ for line in sections["Communication and Vocal Dynamics"]:
942
+ story.append(Paragraph(line, bullet_style))
943
  story.append(Spacer(1, 0.2*inch))
944
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
945
  # Executive Summary
946
+ story.append(Paragraph("Executive Summary", h2))
947
+ if sections["Executive Summary"]:
948
+ for line in sections["Executive Summary"]:
949
  story.append(Paragraph(line, bullet_style))
950
  else:
951
+ story.append(Paragraph("No executive summary provided; refer to metrics for candidate performance.", body_text))
 
 
 
 
 
 
952
  story.append(Spacer(1, 0.2*inch))
953
 
954
  # Competency and Content
955
+ story.append(Paragraph("Competency and Content", h2))
956
  story.append(Paragraph("Strengths", h3))
957
+ if sections["Competency and Content"]["Strengths"]:
958
+ strength_table = Table([[Paragraph(line, bullet_style)] for line in sections["Competency and Content"]["Strengths"]], colWidths=[6*inch])
959
  strength_table.setStyle(TableStyle([
960
  ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#E6FFE6')),
961
  ('VALIGN', (0,0), (-1,-1), 'TOP'),
 
963
  ]))
964
  story.append(strength_table)
965
  else:
966
+ story.append(Paragraph("No specific strengths identified; candidate shows general potential.", body_text))
967
  story.append(Spacer(1, 0.1*inch))
968
  story.append(Paragraph("Weaknesses", h3))
969
+ if sections["Competency and Content"]["Weaknesses"]:
970
+ weakness_table = Table([[Paragraph(line, bullet_style)] for line in sections["Competency and Content"]["Weaknesses"]], colWidths=[6*inch])
971
  weakness_table.setStyle(TableStyle([
972
  ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#FFF0F0')),
973
  ('VALIGN', (0,0), (-1,-1), 'TOP'),
 
978
  story.append(Paragraph("No specific weaknesses identified; focus on enhancing existing strengths.", body_text))
979
  story.append(Spacer(1, 0.2*inch))
980
 
981
+ # Role Fit and Potential
982
+ story.append(Paragraph("Role Fit and Potential", h2))
983
+ if sections["Role Fit and Potential"]:
984
+ for line in sections["Role Fit and Potential"]:
985
  story.append(Paragraph(line, bullet_style))
986
  else:
987
+ story.append(Paragraph("No role fit analysis provided; assess based on metrics and responses.", body_text))
 
 
 
 
 
 
988
  story.append(Spacer(1, 0.2*inch))
989
 
990
  # Recommendations
991
+ story.append(Paragraph("Recommendations", h2))
992
  story.append(Paragraph("Development Priorities", h3))
993
+ if sections["Recommendations"]["Development"]:
994
+ dev_table = Table([[Paragraph(line, bullet_style)] for line in sections["Recommendations"]["Development"]], colWidths=[6*inch])
995
  dev_table.setStyle(TableStyle([
996
  ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#E8F0FE')),
997
  ('VALIGN', (0,0), (-1,-1), 'TOP'),
 
999
  ]))
1000
  story.append(dev_table)
1001
  else:
1002
+ story.append(Paragraph("No development recommendations provided; focus on general skill enhancement.", body_text))
 
 
 
 
 
 
 
 
 
 
 
 
1003
  story.append(Spacer(1, 0.1*inch))
1004
+ story.append(Paragraph("Next Steps for Hiring Managers", h3))
1005
+ if sections["Recommendations"]["Next Steps"]:
1006
+ for line in sections["Recommendations"]["Next Steps"]:
1007
  story.append(Paragraph(line, bullet_style))
1008
  else:
1009
+ story.append(Paragraph("No next steps provided; consider further evaluation based on suitability score.", body_text))
 
 
 
 
 
 
1010
  story.append(Spacer(1, 0.2*inch))
1011
 
1012
+ # Build PDF
1013
  doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
1014
  logger.info(f"PDF report successfully generated at {output_path}")
1015
  return True