norhan12 commited on
Commit
ba2c2ae
·
verified ·
1 Parent(s): 2dda39f

Update process_interview.py

Browse files
Files changed (1) hide show
  1. process_interview.py +351 -138
process_interview.py CHANGED
@@ -198,9 +198,9 @@ def transcribe(audio_path: str) -> Dict:
198
  elif result['status'] == 'error':
199
  raise Exception(result['error'])
200
  time.sleep(5)
201
- except Exception as e:
202
- logger.error(f"Transcription failed: {str(e)}")
203
- raise
204
 
205
  def process_utterance(utterance: Dict, full_audio: AudioSegment, wav_file: str) -> Dict:
206
  try:
@@ -279,7 +279,7 @@ def train_role_classifier(utterances: List[Dict]):
279
  sum(1 for token in doc if token.pos_ == 'NOUN')
280
  ])
281
  features.append(feat)
282
- labels.append(0 if i % 2 == 0 else 1) # Simplified for demo
283
  scaler = StandardScaler()
284
  X = scaler.fit_transform(features)
285
  clf = RandomForestClassifier(
@@ -402,7 +402,12 @@ def generate_voice_interpretation(analysis: Dict) -> str:
402
  "- High filler word usage undermines perceived credibility.",
403
  "- Elevated anxiety suggests pressure; training can improve resilience.",
404
  "- Strong confidence supports leadership presence.",
405
- "- Fluent speech enhances engagement in team settings."
 
 
 
 
 
406
  ]
407
  return "\n".join(interpretation_lines)
408
  except Exception as e:
@@ -458,10 +463,18 @@ def generate_report(analysis_data: Dict) -> str:
458
  try:
459
  voice = analysis_data.get('voice_analysis', {})
460
  voice_interpretation = generate_voice_interpretation(voice)
461
- interviewee_responses = [u['text'] for u in analysis_data['transcript'] if u['role'] == 'Interviewee']
462
  if not interviewee_responses:
463
  logger.warning("No interviewee responses found for report generation")
464
- return f"""**1. Executive Summary**
 
 
 
 
 
 
 
 
465
  - Insufficient interviewee content to generate a summary.
466
  - Interview duration suggests limited engagement.
467
 
@@ -488,9 +501,9 @@ def generate_report(analysis_data: Dict) -> str:
488
  acceptance_line += "HR Verdict: Moderate potential, needs additional assessment."
489
  else:
490
  acceptance_line += "HR Verdict: Limited fit, significant improvement required."
491
- transcript_text = "\n".join([f"- {u['speaker']}: {u['text']}" for u in analysis_data['transcript']])
492
  prompt = f"""
493
- You are EvalBot, a senior HR consultant delivering a professional interview analysis report. Use clear headings with '**', bullet points ('-'), complete sentences, and formal language. Avoid redundancy, vague terms, and special characters that could break formatting (e.g., parentheses). Ensure each section is unique, actionable, and contains at least 2-3 bullet points. If content is limited, provide reasonable inferences based on available data.
494
 
495
  **Input Data**
496
  - Suitability Score: {acceptance_prob:.2f}%
@@ -500,11 +513,17 @@ You are EvalBot, a senior HR consultant delivering a professional interview anal
500
  - Voice Analysis:
501
  {voice_interpretation}
502
  - Transcript Sample:
503
- {transcript_text[:1000]}...
504
 
505
  **Report Structure**
506
  {acceptance_line}
507
 
 
 
 
 
 
 
508
  **1. Executive Summary**
509
  - Provide a narrative overview of the candidate’s performance, focusing on key strengths and role fit.
510
  - Highlight communication style and engagement based on voice analysis and transcript.
@@ -529,11 +548,19 @@ You are EvalBot, a senior HR consultant delivering a professional interview anal
529
  """
530
  response = gemini_model.generate_content(prompt)
531
  report_text = re.sub(r'[^\x00-\x7F]+|[()]+', '', response.text)
532
- logger.info(f"Generated Gemini report: {report_text[:500]}...") # Log for debugging
533
  return report_text
534
  except Exception as e:
535
  logger.error(f"Report generation failed: {str(e)}", exc_info=True)
536
- return f"""**1. Executive Summary**
 
 
 
 
 
 
 
 
537
  - Report generation failed due to processing error.
538
 
539
  **2. Communication and Vocal Dynamics**
@@ -550,7 +577,7 @@ You are EvalBot, a senior HR consultant delivering a professional interview anal
550
  - Development: Investigate processing error.
551
  - Next Steps: Retry analysis with corrected audio."""
552
 
553
- def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text: str) -> bool:
554
  try:
555
  doc = SimpleDocTemplate(output_path, pagesize=letter,
556
  rightMargin=0.75*inch, leftMargin=0.75*inch,
@@ -561,82 +588,119 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
561
  h3 = ParagraphStyle(name='Heading3', fontSize=9, leading=11, spaceBefore=6, spaceAfter=4, textColor=colors.HexColor('#3F7CFF'), fontName='Helvetica')
562
  body_text = ParagraphStyle(name='BodyText', fontSize=8, leading=10, spaceAfter=4, fontName='Helvetica', textColor=colors.HexColor('#333333'))
563
  bullet_style = ParagraphStyle(name='Bullet', parent=body_text, leftIndent=16, bulletIndent=6, fontName='Helvetica', bulletFontName='Helvetica', bulletFontSize=8)
564
-
565
  story = []
566
 
567
  def header_footer(canvas, doc):
568
  canvas.saveState()
569
  canvas.setFont('Helvetica', 7)
570
  canvas.setFillColor(colors.HexColor('#666666'))
571
- canvas.drawString(doc.leftMargin, 0.5*inch, f"Page {doc.page} | EvalBot HR Interview Report | Confidential")
572
  canvas.setStrokeColor(colors.HexColor('#0050BC'))
573
  canvas.setLineWidth(0.5)
574
  canvas.line(doc.leftMargin, doc.height + 0.9*inch, doc.width + doc.leftMargin, doc.height + 0.9*inch)
575
  canvas.setFont('Helvetica-Bold', 8)
576
- canvas.drawString(doc.leftMargin, doc.height + 0.95*inch, "Candidate Interview Analysis")
577
  canvas.drawRightString(doc.width + doc.leftMargin, doc.height + 0.95*inch, time.strftime('%B %d, %Y'))
578
  canvas.restoreState()
579
 
580
  # Title Page
581
- story.append(Paragraph("Candidate Interview Analysis", h1))
582
  story.append(Paragraph(f"Generated: {time.strftime('%B %d, %Y')}", ParagraphStyle(name='Date', alignment=1, fontSize=8, textColor=colors.HexColor('#666666'), fontName='Helvetica')))
583
  story.append(Spacer(1, 0.3*inch))
584
- acceptance_prob = analysis_data.get('acceptance_probability', 50.0)
585
- story.append(Paragraph("Hiring Suitability Snapshot", h2))
586
- prob_color = colors.HexColor('#2E7D32') if acceptance_prob >= 80 else (colors.HexColor('#F57C00') if acceptance_prob >= 60 else colors.HexColor('#D32F2F'))
587
- story.append(Paragraph(f"Suitability Score: <font size=14 color='{prob_color.hexval()}'><b>{acceptance_prob:.2f}%</b></font>",
588
- ParagraphStyle(name='Prob', fontSize=10, spaceAfter=8, alignment=1, fontName='Helvetica-Bold')))
589
- if acceptance_prob >= 80:
590
- story.append(Paragraph("<b>HR Verdict:</b> Outstanding candidate, recommended for immediate advancement.", body_text))
591
- elif acceptance_prob >= 60:
592
- story.append(Paragraph("<b>HR Verdict:</b> Strong candidate, suitable for further evaluation.", body_text))
593
- elif acceptance_prob >= 40:
594
- story.append(Paragraph("<b>HR Verdict:</b> Moderate potential, needs additional assessment.", body_text))
595
- else:
596
- story.append(Paragraph("<b>HR Verdict:</b> Limited fit, significant improvement required.", body_text))
597
  story.append(Spacer(1, 0.2*inch))
598
- participants = sorted([p for p in set(u['speaker'] for u in analysis_data['transcript']) if p != 'Unknown'])
599
- participants_str = ', '.join(participants)
600
- table_data = [
601
- ['Metric', 'Value'],
602
- ['Interview Duration', f"{analysis_data['text_analysis']['total_duration']:.2f} seconds"],
603
- ['Speaker Turns', f"{analysis_data['text_analysis']['speaker_turns']}"],
604
- ['Participants', participants_str],
605
- ]
606
- table = Table(table_data, colWidths=[2.0*inch, 4.0*inch])
607
- table.setStyle(TableStyle([
608
- ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#0050BC')),
609
- ('TEXTCOLOR', (0,0), (-1,0), colors.white),
610
- ('ALIGN', (0,0), (-1,-1), 'LEFT'),
611
- ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
612
- ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
613
- ('FONTSIZE', (0,0), (-1,-1), 8),
614
- ('BOTTOMPADDING', (0,0), (-1,0), 6),
615
- ('TOPPADDING', (0,0), (-1,0), 6),
616
- ('BACKGROUND', (0,1), (-1,-1), colors.HexColor('#F5F6FA')),
617
- ('GRID', (0,0), (-1,-1), 0.4, colors.HexColor('#DDE4EB')),
618
- ('LEFTPADDING', (1,3), (1,3), 10),
619
- ('WORDWRAP', (1,3), (1,3), 'CJK'),
620
- ]))
621
- story.append(table)
622
- story.append(Spacer(1, 0.3*inch))
623
- story.append(Paragraph("Prepared by: EvalBot - AI-Powered HR Analysis", body_text))
624
  story.append(PageBreak())
625
 
626
- # Detailed Analysis
627
- story.append(Paragraph("Detailed Candidate Evaluation", h1))
628
-
629
- # Communication and Vocal Dynamics
630
- story.append(Paragraph("1. Communication & Vocal Dynamics", h2))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
  voice_analysis = analysis_data.get('voice_analysis', {})
632
  if voice_analysis and 'error' not in voice_analysis:
633
  table_data = [
634
- ['Metric', 'Value', 'HR Insight'],
635
- ['Speaking Rate', f"{voice_analysis.get('speaking_rate', 0):.2f} words/sec", 'Benchmark: 2.0-3.0 wps; impacts clarity'],
636
- ['Filler Words', f"{voice_analysis.get('filler_ratio', 0) * 100:.1f}%", 'High usage reduces credibility'],
637
- ['Anxiety', voice_analysis.get('interpretation', {}).get('anxiety_level', 'N/A'), f"Score: {voice_analysis.get('composite_scores', {}).get('anxiety', 0):.3f}"],
638
- ['Confidence', voice_analysis.get('interpretation', {}).get('confidence_level', 'N/A'), f"Score: {voice_analysis.get('composite_scores', {}).get('confidence', 0):.3f}"],
639
- ['Fluency', voice_analysis.get('interpretation', {}).get('fluency_level', 'N/A'), 'Drives engagement'],
640
  ]
641
  table = Table(table_data, colWidths=[1.5*inch, 1.3*inch, 3.2*inch])
642
  table.setStyle(TableStyle([
@@ -653,6 +717,150 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
653
  ]))
654
  story.append(table)
655
  story.append(Spacer(1, 0.15*inch))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
  chart_buffer = io.BytesIO()
657
  generate_anxiety_confidence_chart(voice_analysis.get('composite_scores', {}), chart_buffer)
658
  chart_buffer.seek(0)
@@ -665,6 +873,7 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
665
 
666
  # Parse Gemini Report
667
  sections = {
 
668
  "Executive Summary": [],
669
  "Communication": [],
670
  "Competency": {"Strengths": [], "Growth Areas": []},
@@ -678,7 +887,7 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
678
  line = line.strip()
679
  if not line:
680
  continue
681
- logger.debug(f"Parsing line: {line}") # Debug parsing
682
  if line.startswith('**') and line.endswith('**'):
683
  section_title = line.strip('**').strip()
684
  if section_title.startswith(('1.', '2.', '3.', '4.', '5.')):
@@ -686,7 +895,7 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
686
  if 'Executive Summary' in section_title:
687
  current_section = 'Executive Summary'
688
  current_subsection = None
689
- elif 'Communication' in section_title:
690
  current_section = 'Communication'
691
  current_subsection = None
692
  elif 'Competency' in section_title:
@@ -695,7 +904,7 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
695
  elif 'Role Fit' in section_title:
696
  current_section = 'Role Fit'
697
  current_subsection = None
698
- elif 'Recommendations' in section_title:
699
  current_section = 'Recommendations'
700
  current_subsection = None
701
  logger.debug(f"Set section: {current_section}")
@@ -704,22 +913,19 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
704
  if not clean_line:
705
  continue
706
  clean_line = re.sub(r'[^\w\s.,;:-]', '', clean_line)
707
- logger.debug(f"Processing bullet: {clean_line}, section: {current_section}, subsection: {current_subsection}")
708
  if current_section in ['Competency', 'Recommendations']:
709
- # For dictionary sections, append to subsection
710
  if current_subsection is None:
711
- # Set default subsection if unset
712
  if current_section == 'Competency':
713
  current_subsection = 'Strengths'
714
  elif current_section == 'Recommendations':
715
  current_subsection = 'Development'
716
- logger.debug(f"Default subsection set to: {current_subsection}")
717
  if current_subsection:
718
  sections[current_section][current_subsection].append(clean_line)
719
  else:
720
  logger.warning(f"Skipping line due to unset subsection: {clean_line}")
721
  else:
722
- # For list sections, append directly
723
  sections[current_section].append(clean_line)
724
  elif current_section and line:
725
  clean_line = re.sub(r'[^\w\s.,;:-]', '', line)
@@ -728,72 +934,71 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
728
  if current_subsection:
729
  sections[current_section][current_subsection].append(clean_line)
730
  else:
731
- # Default subsection
732
  current_subsection = 'Strengths' if current_section == 'Competency' else 'Development'
733
  sections[current_section][current_subsection].append(clean_line)
734
- logger.debug(f"Default subsection for non-bullet set to: {current_subsection}")
735
  else:
736
  sections[current_section].append(clean_line)
737
 
738
- # Executive Summary
739
- story.append(Paragraph("2. Executive Summary", h2))
740
- if sections['Executive Summary']:
741
- for line in sections['Executive Summary']:
742
- story.append(Paragraph(line, bullet_style))
743
- else:
744
- story.append(Paragraph("Candidate showed moderate engagement; further assessment needed.", bullet_style))
745
- story.append(Paragraph(f"Interview lasted {analysis_data['text_analysis']['total_duration']:.2f} seconds with {analysis_data['text_analysis']['speaker_turns']} turns.", bullet_style))
746
- story.append(Spacer(1, 0.15*inch))
747
 
748
- # Competency and Content
749
- story.append(Paragraph("3. Competency & Content", h2))
750
- story.append(Paragraph("Strengths", h3))
751
- if sections['Competency']['Strengths']:
752
- for line in sections['Competency']['Strengths']:
753
- story.append(Paragraph(line, bullet_style))
754
- else:
755
- story.append(Paragraph("Strengths not fully assessed; candidate demonstrated consistent communication.", bullet_style))
756
- story.append(Spacer(1, 0.1*inch))
757
- story.append(Paragraph("Growth Areas", h3))
758
- if sections['Competency']['Growth Areas']:
759
- for line in sections['Competency']['Growth Areas']:
760
- story.append(Paragraph(line, bullet_style))
761
- else:
762
- story.append(Paragraph("Consider enhancing specificity in responses to highlight expertise.", bullet_style))
763
- story.append(Spacer(1, 0.15*inch))
764
 
765
- # Role Fit
766
- story.append(Paragraph("4. Role Fit & Potential", h2))
767
- if sections['Role Fit']:
768
- for line in sections['Role Fit']:
769
- story.append(Paragraph(line, bullet_style))
770
- else:
771
- story.append(Paragraph("Potential for role fit exists; further evaluation needed to confirm alignment.", bullet_style))
772
- story.append(Spacer(1, 0.15*inch))
773
-
774
- # Recommendations
775
- story.append(Paragraph("5. Recommendations", h2))
776
- story.append(Paragraph("Development Priorities", h3))
777
- if sections['Recommendations']['Development']:
778
- for line in sections['Recommendations']['Development']:
779
- story.append(Paragraph(line, bullet_style))
780
- else:
781
- story.append(Paragraph("Enroll in communication training to reduce filler words.", bullet_style))
782
- story.append(Spacer(1, 0.1*inch))
783
- story.append(Paragraph("Next Steps for Hiring Managers", h3))
784
- if sections['Recommendations']['Next Steps']:
785
- for line in sections['Recommendations']['Next Steps']:
786
- story.append(Paragraph(line, bullet_style))
787
- else:
788
- story.append(Paragraph("Schedule a technical assessment to evaluate role-specific skills.", bullet_style))
789
- story.append(Spacer(1, 0.15*inch))
790
- story.append(Paragraph("This report provides actionable insights to support hiring and candidate development.", body_text))
791
 
792
  doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
793
- logger.info(f"PDF report successfully generated at {output_path}")
794
  return True
795
  except Exception as e:
796
- logger.error(f"PDF generation failed: {str(e)}\nFull Gemini report text:\n{gemini_report_text}", exc_info=True)
797
  return False
798
 
799
  def convert_to_serializable(obj):
@@ -851,21 +1056,28 @@ def process_interview(audio_url: str) -> Dict:
851
  analysis_data['acceptance_probability'] = calculate_acceptance_probability(analysis_data)
852
  gemini_report_text = generate_report(analysis_data)
853
  base_name = str(uuid.uuid4())
854
- pdf_path = os.path.join(OUTPUT_DIR, f"{base_name}_report.pdf")
 
855
  json_path = os.path.join(OUTPUT_DIR, f"{base_name}_analysis.json")
856
- pdf_success = create_pdf_report(analysis_data, pdf_path, gemini_report_text)
 
857
  with open(json_path, 'w') as f:
858
  serializable_data = convert_to_serializable(analysis_data)
859
  json.dump(serializable_data, f, indent=2)
860
- if not pdf_success:
861
- logger.warning(f"PDF report failed to generate for {audio_url}")
862
  return {
863
- 'pdf_path': None,
 
864
  'json_path': json_path,
865
- 'error': 'PDF generation failed'
866
  }
867
  logger.info(f"Processing completed for {audio_url}")
868
- return {'pdf_path': pdf_path, 'json_path': json_path}
 
 
 
 
869
  except Exception as e:
870
  logger.error(f"Processing failed for {audio_url}: {str(e)}", exc_info=True)
871
  base_name = str(uuid.uuid4())
@@ -873,7 +1085,8 @@ def process_interview(audio_url: str) -> Dict:
873
  with open(json_path, 'w') as f:
874
  json.dump({'error': str(e)}, f, indent=2)
875
  return {
876
- 'pdf_path': None,
 
877
  'json_path': json_path,
878
  'error': str(e)
879
  }
 
198
  elif result['status'] == 'error':
199
  raise Exception(result['error'])
200
  time.sleep(5)
201
+ except Exception as e:
202
+ logger.error(f"Transcription failed: {str(e)}")
203
+ raise
204
 
205
  def process_utterance(utterance: Dict, full_audio: AudioSegment, wav_file: str) -> Dict:
206
  try:
 
279
  sum(1 for token in doc if token.pos_ == 'NOUN')
280
  ])
281
  features.append(feat)
282
+ labels.append(0 if i % 2 == 0 else 1)
283
  scaler = StandardScaler()
284
  X = scaler.fit_transform(features)
285
  clf = RandomForestClassifier(
 
402
  "- High filler word usage undermines perceived credibility.",
403
  "- Elevated anxiety suggests pressure; training can improve resilience.",
404
  "- Strong confidence supports leadership presence.",
405
+ "- Fluent speech enhances engagement in team settings.",
406
+ "",
407
+ "Candidate Tips:",
408
+ "- Practice pacing to maintain a steady speaking rate (2.0-3.0 words/sec).",
409
+ "- Reduce filler words (e.g., 'um', 'like') through mock interviews.",
410
+ "- Use breathing exercises to lower anxiety and stabilize pitch."
411
  ]
412
  return "\n".join(interpretation_lines)
413
  except Exception as e:
 
463
  try:
464
  voice = analysis_data.get('voice_analysis', {})
465
  voice_interpretation = generate_voice_interpretation(voice)
466
+ interviewee_responses = [u['text'] for u in analysis_data['transcript'] if u['role'] == 'Interviewee'][:5]
467
  if not interviewee_responses:
468
  logger.warning("No interviewee responses found for report generation")
469
+ return f"""**Suitability Score: 50.00%**
470
+ HR Verdict: Insufficient data for evaluation.
471
+
472
+ **User Feedback**
473
+ - Insufficient content to provide feedback.
474
+ - Practice answering common interview questions to improve engagement.
475
+
476
+ **HR Evaluation**
477
+ **1. Executive Summary**
478
  - Insufficient interviewee content to generate a summary.
479
  - Interview duration suggests limited engagement.
480
 
 
501
  acceptance_line += "HR Verdict: Moderate potential, needs additional assessment."
502
  else:
503
  acceptance_line += "HR Verdict: Limited fit, significant improvement required."
504
+ transcript_text = "\n".join([f"- {u['speaker']}: {u['text']}" for u in analysis_data['transcript']][:10])
505
  prompt = f"""
506
+ You are EvalBot, a senior HR consultant delivering a dual-purpose interview analysis report. Generate two sections: one for the candidate (**User Feedback**) with actionable self-improvement tips, and one for HR (**HR Evaluation**) with professional analysis. Use clear headings with '**', bullet points ('-'), complete sentences, and formal language for HR, friendly language for User Feedback. Avoid redundancy, vague terms, and special characters that could break formatting. Ensure each section is unique, actionable, and contains at least 2-3 bullet points.
507
 
508
  **Input Data**
509
  - Suitability Score: {acceptance_prob:.2f}%
 
513
  - Voice Analysis:
514
  {voice_interpretation}
515
  - Transcript Sample:
516
+ {transcript_text}
517
 
518
  **Report Structure**
519
  {acceptance_line}
520
 
521
+ **User Feedback**
522
+ - Provide friendly, actionable tips for the candidate to improve communication, confidence, and content.
523
+ - Focus on practical steps (e.g., practice pacing, reduce fillers).
524
+ - Keep tone motivational and concise.
525
+
526
+ **HR Evaluation**
527
  **1. Executive Summary**
528
  - Provide a narrative overview of the candidate’s performance, focusing on key strengths and role fit.
529
  - Highlight communication style and engagement based on voice analysis and transcript.
 
548
  """
549
  response = gemini_model.generate_content(prompt)
550
  report_text = re.sub(r'[^\x00-\x7F]+|[()]+', '', response.text)
551
+ logger.info(f"Generated Gemini report: {report_text[:500]}...")
552
  return report_text
553
  except Exception as e:
554
  logger.error(f"Report generation failed: {str(e)}", exc_info=True)
555
+ return f"""**Suitability Score: 50.00%**
556
+ HR Verdict: Report generation failed.
557
+
558
+ **User Feedback**
559
+ - Unable to provide feedback due to processing error.
560
+ - Practice answering questions clearly to improve future interviews.
561
+
562
+ **HR Evaluation**
563
+ **1. Executive Summary**
564
  - Report generation failed due to processing error.
565
 
566
  **2. Communication and Vocal Dynamics**
 
577
  - Development: Investigate processing error.
578
  - Next Steps: Retry analysis with corrected audio."""
579
 
580
+ def create_user_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text: str) -> bool:
581
  try:
582
  doc = SimpleDocTemplate(output_path, pagesize=letter,
583
  rightMargin=0.75*inch, leftMargin=0.75*inch,
 
588
  h3 = ParagraphStyle(name='Heading3', fontSize=9, leading=11, spaceBefore=6, spaceAfter=4, textColor=colors.HexColor('#3F7CFF'), fontName='Helvetica')
589
  body_text = ParagraphStyle(name='BodyText', fontSize=8, leading=10, spaceAfter=4, fontName='Helvetica', textColor=colors.HexColor('#333333'))
590
  bullet_style = ParagraphStyle(name='Bullet', parent=body_text, leftIndent=16, bulletIndent=6, fontName='Helvetica', bulletFontName='Helvetica', bulletFontSize=8)
591
+
592
  story = []
593
 
594
  def header_footer(canvas, doc):
595
  canvas.saveState()
596
  canvas.setFont('Helvetica', 7)
597
  canvas.setFillColor(colors.HexColor('#666666'))
598
+ canvas.drawString(doc.leftMargin, 0.5*inch, f"Page {doc.page} | EvalBot Personal Feedback Report")
599
  canvas.setStrokeColor(colors.HexColor('#0050BC'))
600
  canvas.setLineWidth(0.5)
601
  canvas.line(doc.leftMargin, doc.height + 0.9*inch, doc.width + doc.leftMargin, doc.height + 0.9*inch)
602
  canvas.setFont('Helvetica-Bold', 8)
603
+ canvas.drawString(doc.leftMargin, doc.height + 0.95*inch, "Personal Interview Feedback")
604
  canvas.drawRightString(doc.width + doc.leftMargin, doc.height + 0.95*inch, time.strftime('%B %d, %Y'))
605
  canvas.restoreState()
606
 
607
  # Title Page
608
+ story.append(Paragraph("Your Interview Feedback Report", h1))
609
  story.append(Paragraph(f"Generated: {time.strftime('%B %d, %Y')}", ParagraphStyle(name='Date', alignment=1, fontSize=8, textColor=colors.HexColor('#666666'), fontName='Helvetica')))
610
  story.append(Spacer(1, 0.3*inch))
611
+ story.append(Paragraph("This report provides personalized tips to help you shine in future interviews.", body_text))
 
 
 
 
 
 
 
 
 
 
 
 
612
  story.append(Spacer(1, 0.2*inch))
613
+ story.append(Paragraph("Prepared by: EvalBot - AI-Powered Interview Coach", body_text))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
  story.append(PageBreak())
615
 
616
+ # Parse Gemini Report
617
+ sections = {
618
+ "User Feedback": [],
619
+ "Executive Summary": [],
620
+ "Communication": [],
621
+ "Competency": {"Strengths": [], "Growth Areas": []},
622
+ "Recommendations": {"Development": [], "Next Steps": []},
623
+ "Role Fit": [],
624
+ }
625
+ current_section = None
626
+ current_subsection = None
627
+ lines = gemini_report_text.split('\n')
628
+ for line in lines:
629
+ line = line.strip()
630
+ if not line:
631
+ continue
632
+ logger.debug(f"Parsing line: {line}")
633
+ if line.startswith('**') and line.endswith('**'):
634
+ section_title = line.strip('**').strip()
635
+ if section_title.startswith(('1.', '2.', '3.', '4.', '5.')):
636
+ section_title = section_title[2:].strip()
637
+ if 'User Feedback' in section_title:
638
+ current_section = 'User Feedback'
639
+ current_subsection = None
640
+ elif 'Executive Summary' in section_title:
641
+ current_section = 'Executive Summary'
642
+ current_subsection = None
643
+ elif 'Communication' in section_title:
644
+ current_section = 'Communication'
645
+ current_subsection = None
646
+ elif 'Competency' in section_title:
647
+ current_section = 'Competency'
648
+ current_subsection = None
649
+ elif 'Role Fit' in section_title:
650
+ current_section = 'Role Fit'
651
+ current_subsection = None
652
+ elif 'Recommendations' in section_title:
653
+ current_section = 'Recommendations'
654
+ current_subsection = None
655
+ logger.debug(f"Set section: {current_section}")
656
+ elif line.startswith('-') and current_section:
657
+ clean_line = line.lstrip('-').strip()
658
+ if not clean_line:
659
+ continue
660
+ clean_line = re.sub(r'[^\w\s.,;:-]', '', clean_line)
661
+ logger.debug(f"Processing bullet: {clean_line}, section: {current_section}, subsection: {current_subsection}")
662
+ if current_section in ['Competency', 'Recommendations']:
663
+ if current_subsection is None:
664
+ if current_section == 'Competency':
665
+ current_subsection = 'Strengths'
666
+ elif current_section == 'Recommendations':
667
+ current_subsection = 'Development'
668
+ logger.debug(f"Default subsection set to: {current_subsection}")
669
+ if current_subsection:
670
+ sections[current_section][current_subsection].append(clean_line)
671
+ else:
672
+ logger.warning(f"Skipping line due to unset subsection: {clean_line}")
673
+ else:
674
+ sections[current_section].append(clean_line)
675
+ elif current_section and line:
676
+ clean_line = re.sub(r'[^\w\s.,;:-]', '', line)
677
+ logger.debug(f"Processing non-bullet: {clean_line}, section: {current_section}, subsection: {current_subsection}")
678
+ if current_section in ['Competency', 'Recommendations']:
679
+ if current_subsection:
680
+ sections[current_section][current_subsection].append(clean_line)
681
+ else:
682
+ current_subsection = 'Strengths' if current_section == 'Competency' else 'Development'
683
+ sections[current_section][current_subsection].append(clean_line)
684
+ logger.debug(f"Default subsection for non-bullet set to: {current_subsection}")
685
+ else:
686
+ sections[current_section].append(clean_line)
687
+
688
+ # Introduction
689
+ story.append(Paragraph("How to Use This Report", h2))
690
+ story.append(Paragraph("This report is designed to help you improve your interview skills. Review the feedback below and try the suggested tips to boost your confidence and clarity.", body_text))
691
+ story.append(Spacer(1, 0.15*inch))
692
+
693
+ # Your Communication Style
694
+ story.append(Paragraph("Your Communication Style", h2))
695
  voice_analysis = analysis_data.get('voice_analysis', {})
696
  if voice_analysis and 'error' not in voice_analysis:
697
  table_data = [
698
+ ['Metric', 'Value', 'What It Means'],
699
+ ['Speaking Rate', f"{voice_analysis.get('speaking_rate', 0):.2f} words/sec", 'How fast you speak'],
700
+ ['Filler Words', f"{voice_analysis.get('filler_ratio', 0) * 100:.1f}%", 'Words like "um" or "like"'],
701
+ ['Anxiety', voice_analysis.get('interpretation', {}).get('anxiety_level', 'N/A'), 'Your stress level'],
702
+ ['Confidence', voice_analysis.get('interpretation', {}).get('confidence_level', 'N/A'), 'Your vocal strength'],
703
+ ['Fluency', voice_analysis.get('interpretation', {}).get('fluency_level', 'N/A'), 'How smoothly you speak'],
704
  ]
705
  table = Table(table_data, colWidths=[1.5*inch, 1.3*inch, 3.2*inch])
706
  table.setStyle(TableStyle([
 
717
  ]))
718
  story.append(table)
719
  story.append(Spacer(1, 0.15*inch))
720
+ story.append(Paragraph("Tips to Improve:", h3))
721
+ for line in sections['Communication'][-3:]: # Use candidate tips from voice_interpretation
722
+ story.append(Paragraph(line, bullet_style))
723
+ else:
724
+ story.append(Paragraph(f"Voice analysis unavailable: {voice_analysis.get('error', 'Unknown error')}", body_text))
725
+ story.append(Spacer(1, 0.15*inch))
726
+
727
+ # Your Responses
728
+ story.append(Paragraph("Your Responses", h2))
729
+ if sections['Competency']['Strengths'] or sections['Competency']['Growth Areas']:
730
+ story.append(Paragraph("Strengths", h3))
731
+ for line in sections['Competency']['Strengths'][:3]:
732
+ story.append(Paragraph(line, bullet_style))
733
+ story.append(Spacer(1, 0.1*inch))
734
+ story.append(Paragraph("Areas to Work On", h3))
735
+ for line in sections['Competency']['Growth Areas'][:3]:
736
+ story.append(Paragraph(line, bullet_style))
737
+ else:
738
+ story.append(Paragraph("You showed effort in responding; try to provide more specific examples.", bullet_style))
739
+ story.append(Paragraph("Practice structuring answers using the STAR method (Situation, Task, Action, Result).", bullet_style))
740
+ story.append(Spacer(1, 0.15*inch))
741
+
742
+ # Action Plan
743
+ story.append(Paragraph("Your Action Plan", h2))
744
+ if sections['User Feedback']:
745
+ for line in sections['User Feedback']:
746
+ story.append(Paragraph(line, bullet_style))
747
+ else:
748
+ story.append(Paragraph("Practice mock interviews to build confidence.", bullet_style))
749
+ story.append(Paragraph("Record yourself to identify and reduce filler words.", bullet_style))
750
+ story.append(Paragraph("Join a public speaking group to improve fluency.", bullet_style))
751
+ story.append(Spacer(1, 0.15*inch))
752
+ story.append(Paragraph("Keep practicing, and you'll see improvement in your next interview!", body_text))
753
+
754
+ doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
755
+ logger.info(f"User PDF report successfully generated at {output_path}")
756
+ return True
757
+ except Exception as e:
758
+ logger.error(f"User PDF generation failed: {str(e)}\nFull Gemini report text:\n{gemini_report_text}", exc_info=True)
759
+ return False
760
+
761
+ def create_company_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text: str) -> bool:
762
+ try:
763
+ doc = SimpleDocTemplate(output_path, pagesize=letter,
764
+ rightMargin=0.75*inch, leftMargin=0.75*inch,
765
+ topMargin=1*inch, bottomMargin=1*inch)
766
+ styles = getSampleStyleSheet()
767
+ h1 = ParagraphStyle(name='Heading1', fontSize=18, leading=22, spaceAfter=16, alignment=1, textColor=colors.HexColor('#003087'), fontName='Helvetica-Bold')
768
+ h2 = ParagraphStyle(name='Heading2', fontSize=13, leading=15, spaceBefore=10, spaceAfter=6, textColor=colors.HexColor('#0050BC'), fontName='Helvetica-Bold')
769
+ h3 = ParagraphStyle(name='Heading3', fontSize=9, leading=11, spaceBefore=6, spaceAfter=4, textColor=colors.HexColor('#3F7CFF'), fontName='Helvetica')
770
+ body_text = ParagraphStyle(name='BodyText', fontSize=8, leading=10, spaceAfter=4, fontName='Helvetica', textColor=colors.HexColor('#333333'))
771
+ bullet_style = ParagraphStyle(name='Bullet', parent=body_text, leftIndent=16, bulletIndent=6, fontName='Helvetica', bulletFontName='Helvetica', bulletFontSize=8)
772
+
773
+ story = []
774
+
775
+ def header_footer(canvas, doc):
776
+ canvas.saveState()
777
+ canvas.setFont('Helvetica', 7)
778
+ canvas.setFillColor(colors.HexColor('#666666'))
779
+ canvas.drawString(doc.leftMargin, 0.5*inch, f"Page {doc.page} | EvalBot HR Interview Report | Confidential")
780
+ canvas.setStrokeColor(colors.HexColor('#0050BC'))
781
+ canvas.setLineWidth(0.5)
782
+ canvas.line(doc.leftMargin, doc.height + 0.9*inch, doc.width + doc.leftMargin, doc.height + 0.9*inch)
783
+ canvas.setFont('Helvetica-Bold', 8)
784
+ canvas.drawString(doc.leftMargin, doc.height + 0.95*inch, "Candidate Interview Analysis")
785
+ canvas.drawRightString(doc.width + doc.leftMargin, doc.height + 0.95*inch, time.strftime('%B %d, %Y'))
786
+ canvas.restoreState()
787
+
788
+ # Title Page
789
+ story.append(Paragraph("Candidate Interview Analysis", h1))
790
+ story.append(Paragraph(f"Generated {time.strftime('%B %d, %Y')}", ParagraphStyle(name='Date', alignment=1, fontSize=8, textColor=colors.HexColor('#666666'), fontName='Helvetica')))
791
+ story.append(Spacer(1, 0.3*inch))
792
+ acceptance_prob = analysis_data.get('acceptance_probability', 50.0)
793
+ story.append(Paragraph("Hiring Suitability Snapshot", h2))
794
+ prob_color = colors.HexColor('#2E7D32') if acceptance_prob >= 80 else colors.HexColor('#F57C00') if acceptance_prob >= 60 else colors.HexColor('#D32F2F')
795
+ story.append(Paragraph(f"Suitability Score: <font size=14 color='{prob_color.hexval()}'><b>{acceptance_prob:.2f}%</b></font>",
796
+ ParagraphStyle(name='Prob', fontSize=10, spaceAfter=8, alignment=1, fontName='Helvetica-Bold')))
797
+ if acceptance_prob >= 80:
798
+ story.append(Paragraph("<b>HR Verdict:</b> Outstanding candidate, recommended for immediate advancement.", body_text))
799
+ elif acceptance_prob >= 60:
800
+ story.append(Paragraph("<b>HR Verdict:</b> Strong candidate, suitable for further evaluation.", body_text))
801
+ elif acceptance_prob >= 40:
802
+ story.append(Paragraph("<b>HR Verdict:</b> Moderate potential, needs additional assessment.", body_text))
803
+ else:
804
+ story.append(Paragraph("<b>HR Verdict:</b> Limited fit, significant improvement required.", body_text))
805
+ story.append(Spacer(1, 0.2*inch))
806
+ participants = sorted([p for u in set(u['speaker'] for u in analysis_data['transcript'] if u['speaker'] != 'Unknown'])
807
+ participants_str = ', '.join(participants)
808
+ table_data = [
809
+ ['Metric', 'Value'],
810
+ ['Interview Duration', f"{analysis_data['text_analysis']['total_duration']:.2f} seconds"],
811
+ ['Speaker Turns', f"{analysis_data['text_analysis']['speaker_turns']}"],
812
+ ['Participants', participants_str],
813
+ ]
814
+ table = Table(table_data, colWidths=[2.0*inch, 4.0*inch])
815
+ table.setStyle(TableStyle([
816
+ ('Background', (0,0), (-1,0), colors.HexColor('#0050BC')),
817
+ ('TEXTCOLOR', (0,0), (-1,0), colors.white),
818
+ ('ALIGN', (0,0), (-1,-1), 'LEFT'),
819
+ ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
820
+ ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
821
+ ('FONTSIZE', (0,0), (-1,-1), 8),
822
+ ('BOTTOMPADING', (0,0), (-1,0), 6),
823
+ ('TOPPADING', (0,0), (-1,0), 6),
824
+ ('Background', (0,1), (-1,-1), colors.HexColor('#F5F6FA')),
825
+ ('GRID', (0,0), (-1,-1), 0.4, colors.HexColor('#DDE4EB')),
826
+ ('LEFTPAD', (1,3), (1,3), 10),
827
+ ('WORDWRAP', (1,3), (1,3), 'CJK'),
828
+ ]))
829
+ story.append(table)
830
+ story.append(Spacer(1, 0.3*inch))
831
+ story.append(Paragraph("Prepared by EvalBot - AI-Powered HR Analysis", body_text))
832
+ story.append(PageBreak())
833
+
834
+ # Detailed Analysis
835
+ story.append(Paragraph("Detailed Candidate Evaluation", h1))
836
+
837
+ # Communication and Vocal Dynamics
838
+ story.append(Paragraph("1. Communication & Vocal Dynamics", h2))
839
+ voice_analysis = analysis_data.get('voice_analysis', {})
840
+ if voice_analysis and 'error' not in voice_analysis:
841
+ table_data = [
842
+ ['Metric', 'Value', 'HR Insight'],
843
+ ['Speaking Rate', f"{voice_analysis.get('speaking_rate', 0):.2f} words/sec", 'Benchmark: 2.0-3.0 wps; impacts clarity'],
844
+ ['Filler Words', f"{voice_analysis.get('filler_ratio', 0) * 100:.1f}%", 'High usage reduces credibility'],
845
+ ['Anxiety', voice_analysis.get('interpretation', {}).get('anxiety_level', 'N/A'), f"Score: {voice_analysis.get('composite_scores', {}).get('anxiety', 0):.3f}"],
846
+ ['Confidence', voice_analysis.get('('interpretation', {}).get('confidence_level', 'N/A'), f"Score: {voice_analysis.get('('composite_scores', {}).get('confidence', 0):.3f}"],
847
+ ['Fluency', voice_analysis.get('interpretation', {}).get('fluency_level', 'N/A'), 'Drives engagement'],
848
+ ]
849
+ table = Table(table_data, colWidths=[1.5*inch, 1.3*inch, 3.2*inch])
850
+ table.setStyle(TableStyle([
851
+ ('Background', (0,0), (-1,0), colors.HexColor('#0050BC')),
852
+ ('TEXTCOLOR', (0,0), (-1,0), colors.white),
853
+ ('ALIGN', (0,0), (-1,-1), 'LEFT'),
854
+ ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
855
+ ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
856
+ ('FONTSIZE', (0,0), (-1,-1), 8),
857
+ ('BOTTOMPADING', (0,0), (-1,0), 6),
858
+ ('TOPPADING', (0,0), (-1,0), 6),
859
+ ('Background', (0,1), (-1,-1), colors.HexColor('#F5F6FA')),
860
+ ('GRID', (0,0), (-1,-1), 0.4, colors.HexColor('#DDE4EB')),
861
+ ]))
862
+ story.append(table)
863
+ story.append(Spacer(1, 0.15*inch))
864
  chart_buffer = io.BytesIO()
865
  generate_anxiety_confidence_chart(voice_analysis.get('composite_scores', {}), chart_buffer)
866
  chart_buffer.seek(0)
 
873
 
874
  # Parse Gemini Report
875
  sections = {
876
+ "User Feedback": [],
877
  "Executive Summary": [],
878
  "Communication": [],
879
  "Competency": {"Strengths": [], "Growth Areas": []},
 
887
  line = line.strip()
888
  if not line:
889
  continue
890
+ logger.debug(f"Parsing line: {line}")
891
  if line.startswith('**') and line.endswith('**'):
892
  section_title = line.strip('**').strip()
893
  if section_title.startswith(('1.', '2.', '3.', '4.', '5.')):
 
895
  if 'Executive Summary' in section_title:
896
  current_section = 'Executive Summary'
897
  current_subsection = None
898
+ elif section_title == 'Communication and Vocal Dynamics':
899
  current_section = 'Communication'
900
  current_subsection = None
901
  elif 'Competency' in section_title:
 
904
  elif 'Role Fit' in section_title:
905
  current_section = 'Role Fit'
906
  current_subsection = None
907
+ elif section_title == 'Recommendations':
908
  current_section = 'Recommendations'
909
  current_subsection = None
910
  logger.debug(f"Set section: {current_section}")
 
913
  if not clean_line:
914
  continue
915
  clean_line = re.sub(r'[^\w\s.,;:-]', '', clean_line)
916
+ logger.debug(f"Processing bullet: {clean_line}, section: {current_section}, {subsection: current_subsection}")
917
  if current_section in ['Competency', 'Recommendations']:
 
918
  if current_subsection is None:
 
919
  if current_section == 'Competency':
920
  current_subsection = 'Strengths'
921
  elif current_section == 'Recommendations':
922
  current_subsection = 'Development'
923
+ logger.debug(f"Default subsetion set to: {current_subsection}")
924
  if current_subsection:
925
  sections[current_section][current_subsection].append(clean_line)
926
  else:
927
  logger.warning(f"Skipping line due to unset subsection: {clean_line}")
928
  else:
 
929
  sections[current_section].append(clean_line)
930
  elif current_section and line:
931
  clean_line = re.sub(r'[^\w\s.,;:-]', '', line)
 
934
  if current_subsection:
935
  sections[current_section][current_subsection].append(clean_line)
936
  else:
 
937
  current_subsection = 'Strengths' if current_section == 'Competency' else 'Development'
938
  sections[current_section][current_subsection].append(clean_line)
939
+ logger.debug(f"Default subsetion for non-bullet set to: {current_subsection}")
940
  else:
941
  sections[current_section].append(clean_line)
942
 
943
+ # Executive Summary
944
+ story.append(Paragraph("2. Executive Summary", h2))
945
+ if sections['Executive Summary']:
946
+ for line in sections['Executive Summary']:
947
+ story.append(Paragraph(line, bullet_style))
948
+ else:
949
+ story.append(Paragraph("Candidate showed moderate engagement; further assessment needed.", bullet_style))
950
+ story.append(Paragraph(f"Interview lasted {analysis_data['text_analysis']['total_duration']:.2f} seconds with {analysis_data['text_analysis']['speaker_turns']} turns.", bullet_style))
951
+ story.append(Spacer(1, 0.15*inch))
952
 
953
+ # Competency and Content
954
+ story.append(Paragraph("3. Competency & Content", h2))
955
+ story.append(Paragraph("Strengths", h3))
956
+ if sections['Competency']['Strengths']:
957
+ for line in sections['Competency']['Strengths']:
958
+ story.append(Paragraph(line, bullet_style))
959
+ else:
960
+ story.append(Paragraph("Strengths not fully assessed; candidate demonstrated consistent communication.", bullet_style))
961
+ story.append(Spacer(1, 0.1*inch))
962
+ story.append(Paragraph("Growth Areas", h3))
963
+ if sections['Competency']['Growth Areas']:
964
+ for line in sections['Competency']['Growth Areas']:
965
+ story.append(Paragraph(line, bullet_style))
966
+ else:
967
+ story.append(Paragraph("Consider enhancing specificity in responses to highlight expertise.", bullet_style))
968
+ story.append(Spacer(1, 0.15*inch))
969
 
970
+ # Role Fit
971
+ story.append(Paragraph("4. Role Fit & Potential", h2))
972
+ if sections['Role Fit']:
973
+ for line in sections['Role Fit']:
974
+ story.append(Paragraph(line, bullet_style))
975
+ else:
976
+ story.append(Paragraph("Potential for role fit exists; further evaluation needed to confirm alignment.", bullet_style))
977
+ story.append(Spacer(1, 0.15*inch))
978
+
979
+ # Recommendations
980
+ story.append(Paragraph("5. Recommendations", h2))
981
+ story.append(Paragraph("Development Priorities", h3))
982
+ if sections['Recommendations']['Development']:
983
+ for line in sections['Recommendations']['Development']:
984
+ story.append(Paragraph(line, bullet_style))
985
+ else:
986
+ story.append(Paragraph("Enrollment in communication training to reduce filler words.", bullet_style))
987
+ story.append(Spacer(1, 0.1*inch))
988
+ story.append(Paragraph("Next Steps for Hiring Managers", h3))
989
+ if sections['Recommendations']['Next Steps']:
990
+ for line in sections['Recommendations']['Next Steps']:
991
+ story.append(Paragraph(line, bullet_style))
992
+ else:
993
+ story.append(Paragraph("Schedule a technical assessment to evaluate role-specific skills.", bullet_style))
994
+ story.append(Spacer(1, 0.15*inch))
995
+ story.append(Paragraph("This report provides actionable insights to support hiring decisions.", body_text))
996
 
997
  doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
998
+ logger.info(f"Company PDF report successfully generated at {output_path}")
999
  return True
1000
  except Exception as e:
1001
+ logger.error(f"Company PDF generation failed: {str(e)}\nFull Gemini report text:\n{gemini_report_text}", exc_info=True)
1002
  return False
1003
 
1004
  def convert_to_serializable(obj):
 
1056
  analysis_data['acceptance_probability'] = calculate_acceptance_probability(analysis_data)
1057
  gemini_report_text = generate_report(analysis_data)
1058
  base_name = str(uuid.uuid4())
1059
+ user_pdf_path = os.path.join(OUTPUT_DIR, f"{base_name}_user_report.pdf")
1060
+ company_pdf_path = os.path.join(OUTPUT_DIR, f"{base_name}_company_report.pdf")
1061
  json_path = os.path.join(OUTPUT_DIR, f"{base_name}_analysis.json")
1062
+ user_pdf_success = create_user_pdf_report(analysis_data, user_pdf_path, gemini_report_text)
1063
+ company_pdf_success = create_company_pdf_report(analysis_data, company_pdf_path, gemini_report_text)
1064
  with open(json_path, 'w') as f:
1065
  serializable_data = convert_to_serializable(analysis_data)
1066
  json.dump(serializable_data, f, indent=2)
1067
+ if not (user_pdf_success and company_pdf_success):
1068
+ logger.warning(f"One or both PDF reports failed to generate for {audio_url}")
1069
  return {
1070
+ 'user_pdf_path': user_pdf_path if user_pdf_success else None,
1071
+ 'company_pdf_path': company_pdf_path if company_pdf_success else None,
1072
  'json_path': json_path,
1073
+ 'error': 'One or both PDF generations failed'
1074
  }
1075
  logger.info(f"Processing completed for {audio_url}")
1076
+ return {
1077
+ 'user_pdf_path': user_pdf_path,
1078
+ 'company_pdf_path': company_pdf_path,
1079
+ 'json_path': json_path
1080
+ }
1081
  except Exception as e:
1082
  logger.error(f"Processing failed for {audio_url}: {str(e)}", exc_info=True)
1083
  base_name = str(uuid.uuid4())
 
1085
  with open(json_path, 'w') as f:
1086
  json.dump({'error': str(e)}, f, indent=2)
1087
  return {
1088
+ 'user_pdf_path': None,
1089
+ 'company_pdf_path': None,
1090
  'json_path': json_path,
1091
  'error': str(e)
1092
  }