Update process_interview.py
Browse files- process_interview.py +119 -119
process_interview.py
CHANGED
|
@@ -788,81 +788,81 @@ def create_company_pdf_report(analysis_data: Dict, output_path: str, gemini_repo
|
|
| 788 |
canvas.drawRightString(doc.width + doc.leftMargin, doc.height + 0.95*inch, time.strftime('%B %d, %Y'))
|
| 789 |
canvas.restoreState()
|
| 790 |
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 811 |
table_data = [
|
| 812 |
-
['Metric', 'Value'],
|
| 813 |
-
['
|
| 814 |
-
['
|
| 815 |
-
['
|
|
|
|
|
|
|
| 816 |
]
|
| 817 |
-
table = Table(table_data, colWidths=[
|
| 818 |
table.setStyle(TableStyle([
|
| 819 |
-
('
|
| 820 |
('TEXTCOLOR', (0,0), (-1,0), colors.white),
|
| 821 |
('ALIGN', (0,0), (-1,-1), 'LEFT'),
|
| 822 |
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
|
| 823 |
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
|
| 824 |
('FONTSIZE', (0,0), (-1,-1), 8),
|
| 825 |
-
('
|
| 826 |
-
('
|
| 827 |
-
('
|
| 828 |
('GRID', (0,0), (-1,-1), 0.4, colors.HexColor('#DDE4EB')),
|
| 829 |
-
('LEFTPAD', (1,3), (1,3), 10),
|
| 830 |
-
('WORDWRAP', (1,3), (1,3), 'CJK'),
|
| 831 |
]))
|
| 832 |
story.append(table)
|
| 833 |
-
story.append(Spacer(1, 0.3*inch))
|
| 834 |
-
story.append(Paragraph("Prepared by EvalBot - AI-Powered HR Analysis", body_text))
|
| 835 |
-
story.append(PageBreak())
|
| 836 |
-
|
| 837 |
-
# Detailed Analysis
|
| 838 |
-
story.append(Paragraph("Detailed Candidate Evaluation", h1))
|
| 839 |
-
|
| 840 |
-
# Communication and Vocal Dynamics
|
| 841 |
-
story.append(Paragraph("1. Communication & Vocal Dynamics", h2))
|
| 842 |
-
voice_analysis = analysis_data.get('voice_analysis', {})
|
| 843 |
-
if voice_analysis and 'error' not in voice_analysis:
|
| 844 |
-
table_data = [
|
| 845 |
-
['Metric', 'Value', 'HR Insight'],
|
| 846 |
-
['Speaking Rate', f"{voice_analysis.get('speaking_rate', 0):.2f} words/sec", 'Benchmark: 2.0-3.0 wps; impacts clarity'],
|
| 847 |
-
['Filler Words', f"{voice_analysis.get('filler_ratio', 0) * 100:.1f}%", 'High usage reduces credibility'],
|
| 848 |
-
['Anxiety', voice_analysis.get('interpretation', {}).get('anxiety_level', 'N/A'), f"Score: {voice_analysis.get('composite_scores'), {}).get('anxiety', 0):.3f}"],
|
| 849 |
-
['Confidence', voice_analysis.get('('interpretation', {}).get('confidence_level'), 'N/A'), f"Score: {voice_analysis.get('('composite_scores', {}).get('confidence', 0):.3f}"],
|
| 850 |
-
['Fluency', voice_analysis.get('interpretation', {}).get('fluency_level', 'N/A'), 'Drives engagement'),
|
| 851 |
-
]
|
| 852 |
-
table = Table(table_data, colWidths=[1.5*inch, 1.3*inch, 3.2*inch])
|
| 853 |
-
table.setStyle(TableStyle([
|
| 854 |
-
('Background', (0,0), (-1,0), colors.HexColor('#0050BC')),
|
| 855 |
-
('TEXTCOLOR', (0,0), (-1,0), colors.white),
|
| 856 |
-
('ALIGN', (0,0), (-1,-1), 'LEFT'),
|
| 857 |
-
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
|
| 858 |
-
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
|
| 859 |
-
('FONTSIZE', (0,0), (-1,-1), 8),
|
| 860 |
-
('BOTTOMPADING', (0,0), (-1,0), 6),
|
| 861 |
-
('TOPPADING', (0,0), (-1,0), 6),
|
| 862 |
-
('Background', (0,1), (-1,-1), colors.HexColor('#F5F6FA')),
|
| 863 |
-
('GRID', (0,0), (-1,-1), 0.4, colors.HexColor('#DDE4EB')),
|
| 864 |
-
]))
|
| 865 |
-
story.append(table)
|
| 866 |
story.append(Spacer(1, 0.15*inch))
|
| 867 |
chart_buffer = io.BytesIO()
|
| 868 |
generate_anxiety_confidence_chart(voice_analysis.get('composite_scores', {}), chart_buffer)
|
|
@@ -898,7 +898,7 @@ def create_company_pdf_report(analysis_data: Dict, output_path: str, gemini_repo
|
|
| 898 |
if 'Executive Summary' in section_title:
|
| 899 |
current_section = 'Executive Summary'
|
| 900 |
current_subsection = None
|
| 901 |
-
elif
|
| 902 |
current_section = 'Communication'
|
| 903 |
current_subsection = None
|
| 904 |
elif 'Competency' in section_title:
|
|
@@ -907,7 +907,7 @@ def create_company_pdf_report(analysis_data: Dict, output_path: str, gemini_repo
|
|
| 907 |
elif 'Role Fit' in section_title:
|
| 908 |
current_section = 'Role Fit'
|
| 909 |
current_subsection = None
|
| 910 |
-
elif
|
| 911 |
current_section = 'Recommendations'
|
| 912 |
current_subsection = None
|
| 913 |
logger.debug(f"Set section: {current_section}")
|
|
@@ -916,14 +916,14 @@ def create_company_pdf_report(analysis_data: Dict, output_path: str, gemini_repo
|
|
| 916 |
if not clean_line:
|
| 917 |
continue
|
| 918 |
clean_line = re.sub(r'[^\w\s.,;:-]', '', clean_line)
|
| 919 |
-
logger.debug(f"Processing bullet: {clean_line}, section: {current_section},
|
| 920 |
if current_section in ['Competency', 'Recommendations']:
|
| 921 |
if current_subsection is None:
|
| 922 |
if current_section == 'Competency':
|
| 923 |
current_subsection = 'Strengths'
|
| 924 |
elif current_section == 'Recommendations':
|
| 925 |
current_subsection = 'Development'
|
| 926 |
-
logger.debug(f"Default
|
| 927 |
if current_subsection:
|
| 928 |
sections[current_section][current_subsection].append(clean_line)
|
| 929 |
else:
|
|
@@ -939,63 +939,63 @@ def create_company_pdf_report(analysis_data: Dict, output_path: str, gemini_repo
|
|
| 939 |
else:
|
| 940 |
current_subsection = 'Strengths' if current_section == 'Competency' else 'Development'
|
| 941 |
sections[current_section][current_subsection].append(clean_line)
|
| 942 |
-
logger.debug(f"Default
|
| 943 |
else:
|
| 944 |
sections[current_section].append(clean_line)
|
| 945 |
|
| 946 |
-
|
| 947 |
-
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
|
| 951 |
-
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
| 955 |
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
|
| 959 |
-
|
| 960 |
-
|
| 961 |
-
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
|
| 970 |
-
|
| 971 |
-
|
| 972 |
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
|
| 982 |
-
|
| 983 |
-
|
| 984 |
-
|
| 985 |
-
|
| 986 |
-
|
| 987 |
-
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
| 994 |
-
|
| 995 |
-
|
| 996 |
-
|
| 997 |
-
|
| 998 |
-
|
| 999 |
|
| 1000 |
doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
|
| 1001 |
logger.info(f"Company PDF report successfully generated at {output_path}")
|
|
|
|
| 788 |
canvas.drawRightString(doc.width + doc.leftMargin, doc.height + 0.95*inch, time.strftime('%B %d, %Y'))
|
| 789 |
canvas.restoreState()
|
| 790 |
|
| 791 |
+
# Title Page
|
| 792 |
+
story.append(Paragraph("Candidate Interview Analysis", h1))
|
| 793 |
+
story.append(Paragraph(f"Generated {time.strftime('%B %d, %Y')}", ParagraphStyle(name='Date', alignment=1, fontSize=8, textColor=colors.HexColor('#666666'), fontName='Helvetica')))
|
| 794 |
+
story.append(Spacer(1, 0.3*inch))
|
| 795 |
+
acceptance_prob = analysis_data.get('acceptance_probability', 50.0)
|
| 796 |
+
story.append(Paragraph("Hiring Suitability Snapshot", h2))
|
| 797 |
+
prob_color = colors.HexColor('#2E7D32') if acceptance_prob >= 80 else colors.HexColor('#F57C00') if acceptance_prob >= 60 else colors.HexColor('#D32F2F')
|
| 798 |
+
story.append(Paragraph(f"Suitability Score: <font size=14 color='{prob_color.hexval()}'><b>{acceptance_prob:.2f}%</b></font>",
|
| 799 |
+
ParagraphStyle(name='Prob', fontSize=10, spaceAfter=8, alignment=1, fontName='Helvetica-Bold')))
|
| 800 |
+
if acceptance_prob >= 80:
|
| 801 |
+
story.append(Paragraph("<b>HR Verdict:</b> Outstanding candidate, recommended for immediate advancement.", body_text))
|
| 802 |
+
elif acceptance_prob >= 60:
|
| 803 |
+
story.append(Paragraph("<b>HR Verdict:</b> Strong candidate, suitable for further evaluation.", body_text))
|
| 804 |
+
elif acceptance_prob >= 40:
|
| 805 |
+
story.append(Paragraph("<b>HR Verdict:</b> Moderate potential, needs additional assessment.", body_text))
|
| 806 |
+
else:
|
| 807 |
+
story.append(Paragraph("<b>HR Verdict:</b> Limited fit, significant improvement required.", body_text))
|
| 808 |
+
story.append(Spacer(1, 0.2*inch))
|
| 809 |
+
participants = sorted(set(u['speaker'] for u in analysis_data['transcript'] if u['speaker'] != 'Unknown'))
|
| 810 |
+
participants_str = ', '.join(participants)
|
| 811 |
+
table_data = [
|
| 812 |
+
['Metric', 'Value'],
|
| 813 |
+
['Interview Duration', f"{analysis_data['text_analysis']['total_duration']:.2f} seconds"],
|
| 814 |
+
['Speaker Turns', f"{analysis_data['text_analysis']['speaker_turns']}"],
|
| 815 |
+
['Participants', participants_str],
|
| 816 |
+
]
|
| 817 |
+
table = Table(table_data, colWidths=[2.0*inch, 4.0*inch])
|
| 818 |
+
table.setStyle(TableStyle([
|
| 819 |
+
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#0050BC')),
|
| 820 |
+
('TEXTCOLOR', (0,0), (-1,0), colors.white),
|
| 821 |
+
('ALIGN', (0,0), (-1,-1), 'LEFT'),
|
| 822 |
+
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
|
| 823 |
+
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
|
| 824 |
+
('FONTSIZE', (0,0), (-1,-1), 8),
|
| 825 |
+
('BOTTOMPADDING', (0,0), (-1,0), 6),
|
| 826 |
+
('TOPPADDING', (0,0), (-1,0), 6),
|
| 827 |
+
('BACKGROUND', (0,1), (-1,-1), colors.HexColor('#F5F6FA')),
|
| 828 |
+
('GRID', (0,0), (-1,-1), 0.4, colors.HexColor('#DDE4EB')),
|
| 829 |
+
('LEFTPADDING', (1,3), (1,3), 10),
|
| 830 |
+
('WORDWRAP', (1,3), (1,3), 'CJK'),
|
| 831 |
+
]))
|
| 832 |
+
story.append(table)
|
| 833 |
+
story.append(Spacer(1, 0.3*inch))
|
| 834 |
+
story.append(Paragraph("Prepared by EvalBot - AI-Powered HR Analysis", body_text))
|
| 835 |
+
story.append(PageBreak())
|
| 836 |
+
|
| 837 |
+
# Detailed Analysis
|
| 838 |
+
story.append(Paragraph("Detailed Candidate Evaluation", h1))
|
| 839 |
+
|
| 840 |
+
# Communication and Vocal Dynamics
|
| 841 |
+
story.append(Paragraph("1. Communication & Vocal Dynamics", h2))
|
| 842 |
+
voice_analysis = analysis_data.get('voice_analysis', {})
|
| 843 |
+
if voice_analysis and 'error' not in voice_analysis:
|
| 844 |
table_data = [
|
| 845 |
+
['Metric', 'Value', 'HR Insight'],
|
| 846 |
+
['Speaking Rate', f"{voice_analysis.get('speaking_rate', 0):.2f} words/sec", 'Benchmark: 2.0-3.0 wps; impacts clarity'],
|
| 847 |
+
['Filler Words', f"{voice_analysis.get('filler_ratio', 0) * 100:.1f}%", 'High usage reduces credibility'],
|
| 848 |
+
['Anxiety', voice_analysis.get('interpretation', {}).get('anxiety_level', 'N/A'), f"Score: {voice_analysis.get('composite_scores', {}).get('anxiety', 0):.3f}"],
|
| 849 |
+
['Confidence', voice_analysis.get('interpretation', {}).get('confidence_level', 'N/A'), f"Score: {voice_analysis.get('composite_scores', {}).get('confidence', 0):.3f}"],
|
| 850 |
+
['Fluency', voice_analysis.get('interpretation', {}).get('fluency_level', 'N/A'), 'Drives engagement'],
|
| 851 |
]
|
| 852 |
+
table = Table(table_data, colWidths=[1.5*inch, 1.3*inch, 3.2*inch])
|
| 853 |
table.setStyle(TableStyle([
|
| 854 |
+
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#0050BC')),
|
| 855 |
('TEXTCOLOR', (0,0), (-1,0), colors.white),
|
| 856 |
('ALIGN', (0,0), (-1,-1), 'LEFT'),
|
| 857 |
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
|
| 858 |
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
|
| 859 |
('FONTSIZE', (0,0), (-1,-1), 8),
|
| 860 |
+
('BOTTOMPADDING', (0,0), (-1,0), 6),
|
| 861 |
+
('TOPPADDING', (0,0), (-1,0), 6),
|
| 862 |
+
('BACKGROUND', (0,1), (-1,-1), colors.HexColor('#F5F6FA')),
|
| 863 |
('GRID', (0,0), (-1,-1), 0.4, colors.HexColor('#DDE4EB')),
|
|
|
|
|
|
|
| 864 |
]))
|
| 865 |
story.append(table)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 866 |
story.append(Spacer(1, 0.15*inch))
|
| 867 |
chart_buffer = io.BytesIO()
|
| 868 |
generate_anxiety_confidence_chart(voice_analysis.get('composite_scores', {}), chart_buffer)
|
|
|
|
| 898 |
if 'Executive Summary' in section_title:
|
| 899 |
current_section = 'Executive Summary'
|
| 900 |
current_subsection = None
|
| 901 |
+
elif 'Communication' in section_title:
|
| 902 |
current_section = 'Communication'
|
| 903 |
current_subsection = None
|
| 904 |
elif 'Competency' in section_title:
|
|
|
|
| 907 |
elif 'Role Fit' in section_title:
|
| 908 |
current_section = 'Role Fit'
|
| 909 |
current_subsection = None
|
| 910 |
+
elif 'Recommendations' in section_title:
|
| 911 |
current_section = 'Recommendations'
|
| 912 |
current_subsection = None
|
| 913 |
logger.debug(f"Set section: {current_section}")
|
|
|
|
| 916 |
if not clean_line:
|
| 917 |
continue
|
| 918 |
clean_line = re.sub(r'[^\w\s.,;:-]', '', clean_line)
|
| 919 |
+
logger.debug(f"Processing bullet: {clean_line}, section: {current_section}, subsection: {current_subsection}")
|
| 920 |
if current_section in ['Competency', 'Recommendations']:
|
| 921 |
if current_subsection is None:
|
| 922 |
if current_section == 'Competency':
|
| 923 |
current_subsection = 'Strengths'
|
| 924 |
elif current_section == 'Recommendations':
|
| 925 |
current_subsection = 'Development'
|
| 926 |
+
logger.debug(f"Default subsection set to: {current_subsection}")
|
| 927 |
if current_subsection:
|
| 928 |
sections[current_section][current_subsection].append(clean_line)
|
| 929 |
else:
|
|
|
|
| 939 |
else:
|
| 940 |
current_subsection = 'Strengths' if current_section == 'Competency' else 'Development'
|
| 941 |
sections[current_section][current_subsection].append(clean_line)
|
| 942 |
+
logger.debug(f"Default subsection for non-bullet set to: {current_subsection}")
|
| 943 |
else:
|
| 944 |
sections[current_section].append(clean_line)
|
| 945 |
|
| 946 |
+
# Executive Summary
|
| 947 |
+
story.append(Paragraph("2. Executive Summary", h2))
|
| 948 |
+
if sections['Executive Summary']:
|
| 949 |
+
for line in sections['Executive Summary']:
|
| 950 |
+
story.append(Paragraph(line, bullet_style))
|
| 951 |
+
else:
|
| 952 |
+
story.append(Paragraph("Candidate showed moderate engagement; further assessment needed.", bullet_style))
|
| 953 |
+
story.append(Paragraph(f"Interview lasted {analysis_data['text_analysis']['total_duration']:.2f} seconds with {analysis_data['text_analysis']['speaker_turns']} turns.", bullet_style))
|
| 954 |
+
story.append(Spacer(1, 0.15*inch))
|
| 955 |
|
| 956 |
+
# Competency and Content
|
| 957 |
+
story.append(Paragraph("3. Competency & Content", h2))
|
| 958 |
+
story.append(Paragraph("Strengths", h3))
|
| 959 |
+
if sections['Competency']['Strengths']:
|
| 960 |
+
for line in sections['Competency']['Strengths']:
|
| 961 |
+
story.append(Paragraph(line, bullet_style))
|
| 962 |
+
else:
|
| 963 |
+
story.append(Paragraph("Strengths not fully assessed; candidate demonstrated consistent communication.", bullet_style))
|
| 964 |
+
story.append(Spacer(1, 0.1*inch))
|
| 965 |
+
story.append(Paragraph("Growth Areas", h3))
|
| 966 |
+
if sections['Competency']['Growth Areas']:
|
| 967 |
+
for line in sections['Competency']['Growth Areas']:
|
| 968 |
+
story.append(Paragraph(line, bullet_style))
|
| 969 |
+
else:
|
| 970 |
+
story.append(Paragraph("Consider enhancing specificity in responses to highlight expertise.", bullet_style))
|
| 971 |
+
story.append(Spacer(1, 0.15*inch))
|
| 972 |
|
| 973 |
+
# Role Fit
|
| 974 |
+
story.append(Paragraph("4. Role Fit & Potential", h2))
|
| 975 |
+
if sections['Role Fit']:
|
| 976 |
+
for line in sections['Role Fit']:
|
| 977 |
+
story.append(Paragraph(line, bullet_style))
|
| 978 |
+
else:
|
| 979 |
+
story.append(Paragraph("Potential for role fit exists; further evaluation needed to confirm alignment.", bullet_style))
|
| 980 |
+
story.append(Spacer(1, 0.15*inch))
|
| 981 |
|
| 982 |
+
# Recommendations
|
| 983 |
+
story.append(Paragraph("5. Recommendations", h2))
|
| 984 |
+
story.append(Paragraph("Development Priorities", h3))
|
| 985 |
+
if sections['Recommendations']['Development']:
|
| 986 |
+
for line in sections['Recommendations']['Development']:
|
| 987 |
+
story.append(Paragraph(line, bullet_style))
|
| 988 |
+
else:
|
| 989 |
+
story.append(Paragraph("Enrollment in communication training to reduce filler words.", bullet_style))
|
| 990 |
+
story.append(Spacer(1, 0.1*inch))
|
| 991 |
+
story.append(Paragraph("Next Steps for Hiring Managers", h3))
|
| 992 |
+
if sections['Recommendations']['Next Steps']:
|
| 993 |
+
for line in sections['Recommendations']['Next Steps']:
|
| 994 |
+
story.append(Paragraph(line, bullet_style))
|
| 995 |
+
else:
|
| 996 |
+
story.append(Paragraph("Schedule a technical assessment to evaluate role-specific skills.", bullet_style))
|
| 997 |
+
story.append(Spacer(1, 0.15*inch))
|
| 998 |
+
story.append(Paragraph("This report provides actionable insights to support hiring decisions.", body_text))
|
| 999 |
|
| 1000 |
doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
|
| 1001 |
logger.info(f"Company PDF report successfully generated at {output_path}")
|