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

Update process_interview.py

Browse files
Files changed (1) hide show
  1. process_interview.py +118 -57
process_interview.py CHANGED
@@ -35,7 +35,15 @@ import spacy
35
  import google.generativeai as genai
36
  import joblib
37
  from concurrent.futures import ThreadPoolExecutor
38
-
 
 
 
 
 
 
 
 
39
  # Setup logging
40
  logging.basicConfig(level=logging.INFO)
41
  logger = logging.getLogger(__name__)
@@ -495,69 +503,75 @@ def generate_report(analysis_data: Dict) -> str:
495
  return f"Error generating report: {str(e)}"
496
 
497
 
 
 
498
  def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text: str):
499
  try:
500
- doc = SimpleDocTemplate(output_path, pagesize=letter)
 
 
 
501
  styles = getSampleStyleSheet()
502
- h1 = ParagraphStyle(name='Heading1', parent=styles['h1'], fontSize=16, spaceAfter=14, alignment=1, textColor=colors.HexColor('#003366'))
503
- h2 = ParagraphStyle(name='Heading2', parent=styles['h2'], fontSize=12, spaceBefore=10, spaceAfter=8, textColor=colors.HexColor('#336699'))
504
- h3 = ParagraphStyle(name='Heading3', parent=styles['h3'], fontSize=10, spaceBefore=8, spaceAfter=4, textColor=colors.HexColor('#0055AA'))
505
- body_text = ParagraphStyle(name='BodyText', parent=styles['Normal'], fontSize=9, leading=12, spaceAfter=4)
506
  bullet_style = ParagraphStyle(name='Bullet', parent=body_text, leftIndent=18, bulletIndent=9)
 
507
  story = []
508
- story.append(Paragraph(f"<b>EvalBot Interview Analysis Report</b>", h1))
509
- story.append(Spacer(1, 0.2 * inch))
510
- story.append(Paragraph(f"<b>Date:</b> {time.strftime('%Y-%m-%d')}", body_text))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  story.append(Spacer(1, 0.3 * inch))
 
512
  acceptance_prob = analysis_data.get('acceptance_probability')
513
  if acceptance_prob is not None:
514
- story.append(Paragraph("<b>Candidate Evaluation Summary</b>", h2))
515
- story.append(Spacer(1, 0.1 * inch))
516
  prob_color = colors.green if acceptance_prob >= 70 else (colors.orange if acceptance_prob >= 40 else colors.red)
517
- story.append(Paragraph(f"<font size='12' color='{prob_color.hexval()}'><b>Estimated Acceptance Probability: {acceptance_prob:.2f}%</b></font>", ParagraphStyle(name='AcceptanceProbability', parent=styles['Normal'], fontSize=12, spaceAfter=10, alignment=1)))
518
- if acceptance_prob >= 80: story.append(Paragraph("This indicates a very strong candidate with high potential. Well done!", body_text))
519
- elif acceptance_prob >= 50: story.append(Paragraph("This candidate shows solid potential but has areas for improvement.", body_text))
520
- else: story.append(Paragraph("This candidate may require significant development or may not be an ideal fit.", body_text))
521
- story.append(Spacer(1, 0.3 * inch))
522
-
523
- sections = {}
524
- current_section = None
525
- section_patterns = {
526
- r'^\s*\*\*\s*1\.\s*Executive Summary\s*\*\*': 'Executive Summary',
527
- r'^\s*\*\*\s*2\.\s*Voice Analysis Insights\s*\*\*': 'Voice Analysis Insights',
528
- r'^\s*\*\*\s*3\.\s*Content Analysis & Strengths/Areas for Development\s*\*\*': 'Content Analysis',
529
- r'^\s*\*\*\s*4\.\s*Actionable Recommendations\s*\*\*': 'Recommendations'
530
- }
531
- for line in gemini_report_text.split('\n'):
532
- matched_section = False
533
- for pattern, section_name in section_patterns.items():
534
- if re.match(pattern, line):
535
- current_section = section_name
536
- sections[current_section] = []
537
- matched_section = True
538
- break
539
- if not matched_section and current_section:
540
- sections[current_section].append(line)
541
-
542
- story.append(PageBreak()) # Start detailed report on a new page
543
 
544
- story.append(Paragraph("<b>1. Detailed Voice Analysis</b>", h2))
 
 
 
545
  voice_analysis = analysis_data.get('voice_analysis', {})
546
  if voice_analysis and 'error' not in voice_analysis:
 
547
  table_data = [
548
  ['Metric', 'Value', 'Interpretation'],
549
- ['Speaking Rate', f"{voice_analysis['speaking_rate']:.2f} words/sec", 'Average rate'],
550
- ['Filler Words', f"{voice_analysis['filler_ratio'] * 100:.1f}%", '% of total words'],
551
- ['Repetition Score', f"{voice_analysis['repetition_score']:.3f}", 'Lower is better'],
552
- ['Anxiety Level', voice_analysis['interpretation']['anxiety_level'].upper(), f"Score: {voice_analysis['composite_scores']['anxiety']:.3f}"],
553
- ['Confidence Level', voice_analysis['interpretation']['confidence_level'].upper(), f"Score: {voice_analysis['composite_scores']['confidence']:.3f}"],
554
- ['Fluency', voice_analysis['interpretation']['fluency_level'].upper(), 'Overall speech flow']
555
  ]
556
  table = Table(table_data, colWidths=[1.5*inch, 1.5*inch, 3*inch])
557
  table.setStyle(TableStyle([
558
  ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#4682B4')),
559
  ('TEXTCOLOR',(0,0),(-1,0),colors.whitesmoke),
560
  ('ALIGN', (0,0), (-1,-1), 'CENTER'),
 
561
  ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
562
  ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
563
  ('BACKGROUND', (0, 1), (-1, -1), colors.HexColor('#F0F8FF')),
@@ -566,31 +580,78 @@ def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text:
566
  story.append(table)
567
  story.append(Spacer(1, 0.2 * inch))
568
 
 
569
  chart_buffer = io.BytesIO()
570
- generate_anxiety_confidence_chart(voice_analysis['composite_scores'], chart_buffer)
571
  chart_buffer.seek(0)
572
  img = Image(chart_buffer, width=4*inch, height=2.5*inch)
573
  story.append(img)
574
  else:
575
- story.append(Paragraph("Voice analysis not available.", body_text))
576
 
577
  story.append(PageBreak())
578
-
579
- for section_title, key in [("2. Content Analysis", "Content Analysis"), ("3. Recommendations", "Recommendations")]:
580
- story.append(Paragraph(f"<b>{section_title}</b>", h2))
581
- if key in sections:
582
- for line in sections[key]:
583
- if line.strip():
584
- story.append(Paragraph(line.strip().lstrip('-').strip(), bullet_style if line.strip().startswith('-') else body_text))
585
- story.append(Spacer(1, 0.2*inch))
586
-
587
- doc.build(story)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  return True
589
  except Exception as e:
590
- logger.error(f"PDF creation failed: {str(e)}", exc_info=True)
591
  return False
592
 
593
 
 
594
  def convert_to_serializable(obj):
595
  if isinstance(obj, np.generic): return obj.item()
596
  if isinstance(obj, dict): return {k: convert_to_serializable(v) for k, v in obj.items()}
 
35
  import google.generativeai as genai
36
  import joblib
37
  from concurrent.futures import ThreadPoolExecutor
38
+ # --- Imports to ensure are present at the top of process_interview.py ---
39
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, Image
40
+ from reportlab.lib.pagesizes import letter
41
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
42
+ from reportlab.lib.units import inch
43
+ from reportlab.lib import colors
44
+ import time
45
+ import re
46
+ import io
47
  # Setup logging
48
  logging.basicConfig(level=logging.INFO)
49
  logger = logging.getLogger(__name__)
 
503
  return f"Error generating report: {str(e)}"
504
 
505
 
506
+
507
+ # --- NEW, ENHANCED PDF GENERATION FUNCTION ---
508
  def create_pdf_report(analysis_data: Dict, output_path: str, gemini_report_text: str):
509
  try:
510
+ doc = SimpleDocTemplate(output_path, pagesize=letter,
511
+ rightMargin=inch/2, leftMargin=inch/2,
512
+ topMargin=inch, bottomMargin=inch/2)
513
+
514
  styles = getSampleStyleSheet()
515
+ styles.add(ParagraphStyle(name='Justify', alignment=4)) # TA_JUSTIFY = 4
516
+ h1 = ParagraphStyle(name='Heading1', fontSize=18, leading=22, spaceAfter=20, alignment=1, textColor=colors.HexColor('#003366'))
517
+ h2 = ParagraphStyle(name='Heading2', fontSize=14, leading=18, spaceBefore=12, spaceAfter=10, textColor=colors.HexColor('#336699'))
518
+ body_text = ParagraphStyle(name='BodyText', parent=styles['Normal'], spaceAfter=6)
519
  bullet_style = ParagraphStyle(name='Bullet', parent=body_text, leftIndent=18, bulletIndent=9)
520
+
521
  story = []
522
+
523
+ # --- Header and Footer ---
524
+ def header_footer(canvas, doc):
525
+ canvas.saveState()
526
+ # Footer
527
+ canvas.setFont('Helvetica', 9)
528
+ canvas.drawString(inch, 0.5 * inch, f"Page {doc.page} | EvalBot Analysis")
529
+ # Header line
530
+ canvas.setStrokeColorRGB(0, 0.2, 0.4)
531
+ canvas.setLineWidth(1)
532
+ canvas.line(doc.leftMargin, doc.height + doc.topMargin - inch/2, doc.width + doc.leftMargin, doc.height + doc.topMargin - inch/2)
533
+ canvas.restoreState()
534
+
535
+ # --- First Page: Summary ---
536
+ story.append(Paragraph("EvalBot Interview Analysis Report", h1))
537
+ story.append(Spacer(1, 0.1 * inch))
538
+ story.append(Paragraph(f"Analysis Date: {time.strftime('%Y-%m-%d')}", styles['Normal']))
539
  story.append(Spacer(1, 0.3 * inch))
540
+
541
  acceptance_prob = analysis_data.get('acceptance_probability')
542
  if acceptance_prob is not None:
543
+ story.append(Paragraph("Candidate Evaluation Summary", h2))
 
544
  prob_color = colors.green if acceptance_prob >= 70 else (colors.orange if acceptance_prob >= 40 else colors.red)
545
+ story.append(Paragraph(f"<font size=14>Estimated Acceptance Probability: <b><font color='{prob_color.hexval()}'>{acceptance_prob:.2f}%</font></b></font>",
546
+ ParagraphStyle(name='Prob', fontSize=12, spaceAfter=10)))
547
+ if acceptance_prob >= 80:
548
+ story.append(Paragraph("This indicates a very strong candidate with high potential.", body_text))
549
+ elif acceptance_prob >= 50:
550
+ story.append(Paragraph("This candidate shows solid potential with areas for improvement.", body_text))
551
+ else:
552
+ story.append(Paragraph("This candidate may require significant development or may not be an ideal fit.", body_text))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
 
554
+ story.append(PageBreak())
555
+
556
+ # --- Second Page and beyond: Detailed Analysis ---
557
+ story.append(Paragraph("1. Detailed Voice Analysis", h2))
558
  voice_analysis = analysis_data.get('voice_analysis', {})
559
  if voice_analysis and 'error' not in voice_analysis:
560
+ # Table for voice metrics
561
  table_data = [
562
  ['Metric', 'Value', 'Interpretation'],
563
+ ['Speaking Rate', f"{voice_analysis.get('speaking_rate', 0):.2f} words/sec", 'Average rate'],
564
+ ['Filler Words', f"{voice_analysis.get('filler_ratio', 0) * 100:.1f}%", '% of total words'],
565
+ ['Anxiety Level', voice_analysis.get('interpretation', {}).get('anxiety_level', 'N/A').upper(), f"Score: {voice_analysis.get('composite_scores', {}).get('anxiety', 0):.3f}"],
566
+ ['Confidence Level', voice_analysis.get('interpretation', {}).get('confidence_level', 'N/A').upper(), f"Score: {voice_analysis.get('composite_scores', {}).get('confidence', 0):.3f}"],
567
+ ['Fluency', voice_analysis.get('interpretation', {}).get('fluency_level', 'N/A').upper(), 'Overall speech flow']
 
568
  ]
569
  table = Table(table_data, colWidths=[1.5*inch, 1.5*inch, 3*inch])
570
  table.setStyle(TableStyle([
571
  ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#4682B4')),
572
  ('TEXTCOLOR',(0,0),(-1,0),colors.whitesmoke),
573
  ('ALIGN', (0,0), (-1,-1), 'CENTER'),
574
+ ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
575
  ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
576
  ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
577
  ('BACKGROUND', (0, 1), (-1, -1), colors.HexColor('#F0F8FF')),
 
580
  story.append(table)
581
  story.append(Spacer(1, 0.2 * inch))
582
 
583
+ # Chart generation
584
  chart_buffer = io.BytesIO()
585
+ generate_anxiety_confidence_chart(voice_analysis.get('composite_scores', {}), chart_buffer)
586
  chart_buffer.seek(0)
587
  img = Image(chart_buffer, width=4*inch, height=2.5*inch)
588
  story.append(img)
589
  else:
590
+ story.append(Paragraph("Voice analysis data not available.", body_text))
591
 
592
  story.append(PageBreak())
593
+
594
+ # --- Parse and display Gemini report sections ---
595
+ sections = {}
596
+ current_section = None
597
+ # Simplified patterns to find sections
598
+ section_patterns = {
599
+ 'Executive Summary': r'executive summary',
600
+ 'Voice Analysis Insights': r'voice analysis insights',
601
+ 'Content Analysis & Strengths/Areas for Development': r'content analysis|strengths/areas for development',
602
+ 'Actionable Recommendations': r'actionable recommendations'
603
+ }
604
+ # Pre-populate sections to maintain order
605
+ for name in section_patterns.keys():
606
+ sections[name] = []
607
+
608
+ # Parse text into sections based on keywords
609
+ for line in gemini_report_text.split('\n'):
610
+ line_lower = line.lower()
611
+ matched = False
612
+ for name, pattern in section_patterns.items():
613
+ if re.search(pattern, line_lower):
614
+ current_section = name
615
+ matched = True
616
+ break
617
+ if not matched and current_section:
618
+ sections[current_section].append(line)
619
+
620
+ # Display Content Analysis and Recommendations
621
+ story.append(Paragraph("2. Content Analysis", h2))
622
+ if sections['Content Analysis & Strengths/Areas for Development']:
623
+ for line in sections['Content Analysis & Strengths/Areas for Development']:
624
+ line = line.strip()
625
+ if not line: continue
626
+ if line.startswith(('-', '•', '*')):
627
+ story.append(Paragraph(line.lstrip('-•* ').strip(), bullet_style))
628
+ else:
629
+ story.append(Paragraph(line, body_text))
630
+ else:
631
+ story.append(Paragraph("Content analysis not provided in the report.", body_text))
632
+
633
+ story.append(Spacer(1, 0.2*inch))
634
+
635
+ story.append(Paragraph("3. Recommendations", h2))
636
+ if sections['Actionable Recommendations']:
637
+ for line in sections['Actionable Recommendations']:
638
+ line = line.strip()
639
+ if not line: continue
640
+ if line.startswith(('-', '•', '*')):
641
+ story.append(Paragraph(line.lstrip('-•* ').strip(), bullet_style))
642
+ else:
643
+ story.append(Paragraph(line, body_text))
644
+ else:
645
+ story.append(Paragraph("Recommendations not provided in the report.", body_text))
646
+
647
+ doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
648
  return True
649
  except Exception as e:
650
+ logger.error(f"Enhanced PDF creation failed: {str(e)}", exc_info=True)
651
  return False
652
 
653
 
654
+
655
  def convert_to_serializable(obj):
656
  if isinstance(obj, np.generic): return obj.item()
657
  if isinstance(obj, dict): return {k: convert_to_serializable(v) for k, v in obj.items()}