Parimal Kalpande commited on
Commit
209e20b
·
1 Parent(s): f117b03
Files changed (2) hide show
  1. modules/llm_handler.py +50 -23
  2. modules/report_generator.py +19 -60
modules/llm_handler.py CHANGED
@@ -1,36 +1,63 @@
1
  # modules/llm_handler.py
2
- import os
3
- from groq import Groq
 
4
  from modules.web_search import search_for_example_answers
5
 
6
- # Initialize the Groq client
7
- # The API key will be automatically read from the HF Space's secrets
8
- client = Groq(api_key=os.environ.get("GROQ_API_KEY"))
9
-
10
  def generate_question(interview_type, document_text):
11
- prompt = f"As an expert {interview_type} interviewer, ask one relevant question based on this document: --- {document_text} ---"
12
  try:
13
- chat_completion = client.chat.completions.create(
14
- messages=[{"role": "user", "content": prompt}],
15
- model="llama3-8b-8192",
16
- )
17
- return chat_completion.choices[0].message.content
18
  except Exception as e:
19
  return f"Error generating question: {e}"
20
 
21
  def evaluate_answer(question, answer):
22
- example_answers = search_for_example_answers(question)
23
  prompt = f"""
24
- You are an interview coach. Compare the candidate's answer to the expert examples and provide a concise evaluation.
25
- Question: "{question}"
26
- Candidate's Answer: "{answer}"
27
- Expert Examples: --- {example_answers} ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  """
29
  try:
30
- chat_completion = client.chat.completions.create(
31
- messages=[{"role": "user", "content": prompt}],
32
- model="llama3-8b-8192",
33
- )
34
- return chat_completion.choices[0].message.content
35
  except Exception as e:
36
- return f"An error occurred during evaluation: {e}"
 
1
  # modules/llm_handler.py
2
+ import ollama
3
+ import config
4
+ import regex as re
5
  from modules.web_search import search_for_example_answers
6
 
 
 
 
 
7
  def generate_question(interview_type, document_text):
8
+ prompt = f"As an expert {interview_type} interviewer, ask one relevant, open-ended question based on this document:\n\n---\n{document_text}\n---"
9
  try:
10
+ response = ollama.chat(model=config.OLLAMA_MODEL, messages=[{'role': 'user', 'content': prompt}])
11
+ return response['message']['content'].strip()
 
 
 
12
  except Exception as e:
13
  return f"Error generating question: {e}"
14
 
15
  def evaluate_answer(question, answer):
 
16
  prompt = f"""
17
+ You are an interview coach. Your task is to evaluate a candidate's answer.
18
+ You MUST provide a score from 1-10 for each of the following three categories.
19
+ The format MUST be exactly `Category: [SCORE]/10`.
20
+
21
+ Factual Accuracy: [SCORE]/10
22
+ Relevance & Directness: [SCORE]/10
23
+ Structure & Clarity: [SCORE]/10
24
+
25
+ After the scores, you MUST provide a brief written evaluation and suggest improvements.
26
+ """
27
+ try:
28
+ response = ollama.chat(model=config.OLLAMA_MODEL, messages=[{'role': 'user', 'content': prompt}], stream=False)
29
+ return response['message']['content']
30
+ except Exception as e:
31
+ return f"An error occurred during evaluation: {e}"
32
+
33
+ def parse_scores_from_evaluation(evaluation_text: str) -> dict:
34
+ scores = {
35
+ 'Factual Accuracy': 0,
36
+ 'Relevance & Directness': 0,
37
+ 'Structure & Clarity': 0
38
+ }
39
+ pattern = r"(Factual Accuracy|Relevance & Directness|Structure & Clarity):\s*\[?(\d{1,2})\]?\/10"
40
+ matches = re.findall(pattern, evaluation_text, re.IGNORECASE)
41
+
42
+ for match in matches:
43
+ category_name, score_value = match[0].strip(), int(match[1])
44
+ if category_name in scores:
45
+ scores[category_name] = score_value
46
+
47
+ print(f"📊 Parsed scores: {scores}")
48
+ return scores
49
+
50
+ def generate_holistic_feedback(full_interview_log):
51
+ prompt = f"""
52
+ You are a senior interview coach reviewing a candidate's full interview performance.
53
+ Based on the entire Q&A log, provide a high-level "Overall Performance Summary" and an "Actionable Improvement Plan".
54
+ **FULL INTERVIEW LOG:** --- {full_interview_log} ---
55
+ **INSTRUCTIONS:**
56
+ 1. **Overall Performance Summary:** Summarize performance, identifying patterns of strengths and weaknesses.
57
+ 2. **Actionable Improvement Plan:** Provide a bulleted list of the top 3 most critical actions to take.
58
  """
59
  try:
60
+ response = ollama.chat(model=config.OLLAMA_MODEL, messages=[{'role': 'user', 'content': prompt}], stream=False)
61
+ return response['message']['content']
 
 
 
62
  except Exception as e:
63
+ return "Could not generate holistic feedback due to an error."
modules/report_generator.py CHANGED
@@ -1,91 +1,54 @@
1
  # modules/report_generator.py
2
-
3
  import datetime
4
  import os
5
  import numpy as np
6
  import matplotlib.pyplot as plt
7
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Image, Frame, PageTemplate
8
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
9
- from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER, TA_LEFT
10
  from reportlab.lib.units import inch
11
  from reportlab.lib import colors
12
  from modules.llm_handler import generate_holistic_feedback, parse_scores_from_evaluation
13
  import config
14
 
15
- # --- Page Template with Header and Footer (Unchanged) ---
16
- class ReportPageTemplate(PageTemplate):
17
- def __init__(self, id, pagesize):
18
- frame = Frame(inch, inch, pagesize[0] - 2 * inch, pagesize[1] - 2 * inch, id='normal')
19
- PageTemplate.__init__(self, id, [frame])
20
-
21
- def beforeDrawPage(self, canvas, doc):
22
- canvas.saveState()
23
- canvas.setFont('Helvetica', 9)
24
- canvas.setFillColor(colors.grey)
25
- footer_text = f"Page {doc.page} | AI Interview Coach Report | Generated on {datetime.datetime.now().strftime('%Y-%m-%d')}"
26
- canvas.drawCentredString(doc.width / 2 + inch, 0.75 * inch, footer_text)
27
- canvas.restoreState()
28
-
29
  def create_radar_chart(labels, scores, file_path):
30
- # This function is unchanged
31
  num_vars = len(labels)
32
  angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()
33
  scores += scores[:1]
34
  angles += angles[:1]
35
  fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
36
- ax.fill(angles, scores, color='#4A90E2', alpha=0.2)
37
- ax.plot(angles, scores, color='#4A90E2', linewidth=2, linestyle='solid')
38
  ax.set_yticklabels([])
39
  ax.set_xticks(angles[:-1])
40
- ax.set_xticklabels(labels, size=12, color='grey')
41
- ax.set_rlabel_position(30)
42
- ax.set_ylim(0, 10)
43
- for angle, score in zip(angles[:-1], scores[:-1]):
44
- ax.text(angle, score + 1.5, str(score), ha='center', va='center', size=14, color="#000000", weight='bold')
45
- plt.title('Performance Snapshot', size=20, color='#333333', y=1.1)
46
- os.makedirs(os.path.dirname(file_path), exist_ok=True)
47
- plt.savefig(file_path, transparent=True, dpi=150)
48
  plt.close(fig)
49
  print(f"📈 Radar chart saved to {file_path}")
50
 
51
  def generate_pdf_report(interview_data, file_path):
52
- doc = SimpleDocTemplate(file_path, pagesize=(8.5 * inch, 11 * inch),
53
- leftMargin=inch, rightMargin=inch, topMargin=inch, bottomMargin=inch)
54
- doc.addPageTemplates([ReportPageTemplate('main_template', (8.5 * inch, 11 * inch))])
55
-
56
  styles = getSampleStyleSheet()
57
-
58
- # --- THIS IS THE CORRECTED SECTION ---
59
- # Instead of adding styles with existing names, we create new ones with unique names.
60
  styles.add(ParagraphStyle(name='ReportTitle', parent=styles['h1'], fontSize=28, alignment=TA_CENTER, spaceAfter=24))
61
- styles.add(ParagraphStyle(name='ReportSubTitle', parent=styles['h2'], fontSize=16, alignment=TA_CENTER, spaceAfter=12, textColor=colors.HexColor('#555555')))
62
- styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY, spaceAfter=12, leading=14))
63
- styles.add(ParagraphStyle(name='MainHeader', parent=styles['h1'], fontSize=22, spaceBefore=12, spaceAfter=20, alignment=TA_LEFT, textColor=colors.HexColor('#2c3e50')))
64
- styles.add(ParagraphStyle(name='QuestionTitle', parent=styles['h2'], spaceBefore=20, spaceAfter=10, textColor=colors.HexColor('#2980b9')))
65
- styles.add(ParagraphStyle(name='SectionTitle', parent=styles['h3'], spaceBefore=12, spaceAfter=6, textColor=colors.HexColor('#34495e')))
66
- # --- END OF CORRECTION ---
67
-
68
  story = []
69
-
70
- # --- 1. The Title Page ---
71
  story.append(Paragraph("Interview Performance Report", styles['ReportTitle']))
72
- story.append(Spacer(1, 0.5 * inch))
73
- story.append(Paragraph(f"Prepared for: <b>{interview_data.get('name', 'N/A')}</b>", styles['ReportSubTitle']))
74
- story.append(Spacer(1, 0.2 * inch))
75
- story.append(Paragraph(f"Interview Type: <b>{interview_data['type']}</b>", styles['ReportSubTitle']))
76
- story.append(Spacer(1, 0.2 * inch))
77
- story.append(Paragraph(f"Date of Report: <b>{datetime.datetime.now().strftime('%B %d, %Y')}</b>", styles['ReportSubTitle']))
78
  story.append(PageBreak())
79
 
80
- # --- 2. Overall Performance & Graph Section ---
81
- story.append(Paragraph("Overall Performance Analysis", styles['MainHeader']))
82
  full_log_text = ""
83
  all_scores = []
84
  skill_labels = ['Factual Accuracy', 'Relevance & Directness', 'Structure & Clarity']
 
85
  for i, qa in enumerate(interview_data['q_and_a']):
86
  full_log_text += f"Q{i+1}: {qa['question']}\nA: {qa['answer']}\n---\n"
87
  scores = parse_scores_from_evaluation(qa['evaluation'])
88
  all_scores.append([scores.get(label, 0) for label in skill_labels])
 
89
  holistic_feedback = generate_holistic_feedback(full_log_text).replace('\n', '<br/>')
90
  story.append(Paragraph(holistic_feedback, styles['Justify']))
91
  story.append(Spacer(1, 0.3 * inch))
@@ -95,20 +58,16 @@ def generate_pdf_report(interview_data, file_path):
95
  chart_path = os.path.join(config.REPORT_FOLDER, "skill_chart.png")
96
  if os.path.exists(chart_path): os.remove(chart_path)
97
  create_radar_chart(skill_labels, avg_scores, chart_path)
98
- story.append(Image(chart_path, width=4.5*inch, height=4.5*inch, hAlign='CENTER'))
99
  story.append(PageBreak())
100
 
101
- # --- 3. Detailed Question-by-Question Analysis ---
102
- story.append(Paragraph("Detailed Question Analysis", styles['MainHeader']))
103
  for i, qa in enumerate(interview_data['q_and_a']):
104
  story.append(Paragraph(f"Question {i+1}: {qa['question']}", styles['QuestionTitle']))
105
  story.append(Paragraph("Your Answer:", styles['SectionTitle']))
106
  story.append(Paragraph(qa.get('answer', 'N/A'), styles['Justify']))
107
  story.append(Paragraph("AI Evaluation:", styles['SectionTitle']))
108
  story.append(Paragraph(qa.get('evaluation', 'N/A').replace('\n', '<br/>'), styles['Justify']))
109
-
110
- try:
111
- doc.build(story)
112
- print(f"\n✅ Professional report generated successfully: {file_path}")
113
- except Exception as e:
114
- print(f"💥 Error generating professional PDF report: {e}")
 
1
  # modules/report_generator.py
 
2
  import datetime
3
  import os
4
  import numpy as np
5
  import matplotlib.pyplot as plt
6
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Image, Frame, PageTemplate
7
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
8
+ from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER
9
  from reportlab.lib.units import inch
10
  from reportlab.lib import colors
11
  from modules.llm_handler import generate_holistic_feedback, parse_scores_from_evaluation
12
  import config
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  def create_radar_chart(labels, scores, file_path):
15
+ """Generates and saves a radar chart as a PNG image."""
16
  num_vars = len(labels)
17
  angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()
18
  scores += scores[:1]
19
  angles += angles[:1]
20
  fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
21
+ ax.fill(angles, scores, color='#4A90E2', alpha=0.25)
22
+ ax.plot(angles, scores, color='#4A90E2', linewidth=2)
23
  ax.set_yticklabels([])
24
  ax.set_xticks(angles[:-1])
25
+ ax.set_xticklabels(labels)
26
+ plt.savefig(file_path, transparent=True)
 
 
 
 
 
 
27
  plt.close(fig)
28
  print(f"📈 Radar chart saved to {file_path}")
29
 
30
  def generate_pdf_report(interview_data, file_path):
31
+ doc = SimpleDocTemplate(file_path, pagesize=(8.5 * inch, 11 * inch))
 
 
 
32
  styles = getSampleStyleSheet()
 
 
 
33
  styles.add(ParagraphStyle(name='ReportTitle', parent=styles['h1'], fontSize=28, alignment=TA_CENTER, spaceAfter=24))
34
+ styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY, spaceAfter=12))
35
+ styles.add(ParagraphStyle(name='QuestionTitle', parent=styles['h2'], spaceBefore=20, spaceAfter=10))
36
+ styles.add(ParagraphStyle(name='SectionTitle', parent=styles['h3'], spaceBefore=12, spaceAfter=6, textColor=colors.darkblue))
37
+
 
 
 
38
  story = []
 
 
39
  story.append(Paragraph("Interview Performance Report", styles['ReportTitle']))
 
 
 
 
 
 
40
  story.append(PageBreak())
41
 
42
+ story.append(Paragraph("Overall Performance Analysis", styles['h1']))
 
43
  full_log_text = ""
44
  all_scores = []
45
  skill_labels = ['Factual Accuracy', 'Relevance & Directness', 'Structure & Clarity']
46
+
47
  for i, qa in enumerate(interview_data['q_and_a']):
48
  full_log_text += f"Q{i+1}: {qa['question']}\nA: {qa['answer']}\n---\n"
49
  scores = parse_scores_from_evaluation(qa['evaluation'])
50
  all_scores.append([scores.get(label, 0) for label in skill_labels])
51
+
52
  holistic_feedback = generate_holistic_feedback(full_log_text).replace('\n', '<br/>')
53
  story.append(Paragraph(holistic_feedback, styles['Justify']))
54
  story.append(Spacer(1, 0.3 * inch))
 
58
  chart_path = os.path.join(config.REPORT_FOLDER, "skill_chart.png")
59
  if os.path.exists(chart_path): os.remove(chart_path)
60
  create_radar_chart(skill_labels, avg_scores, chart_path)
61
+ story.append(Image(chart_path, width=4*inch, height=4*inch, hAlign='CENTER'))
62
  story.append(PageBreak())
63
 
64
+ story.append(Paragraph("Detailed Question Analysis", styles['h1']))
 
65
  for i, qa in enumerate(interview_data['q_and_a']):
66
  story.append(Paragraph(f"Question {i+1}: {qa['question']}", styles['QuestionTitle']))
67
  story.append(Paragraph("Your Answer:", styles['SectionTitle']))
68
  story.append(Paragraph(qa.get('answer', 'N/A'), styles['Justify']))
69
  story.append(Paragraph("AI Evaluation:", styles['SectionTitle']))
70
  story.append(Paragraph(qa.get('evaluation', 'N/A').replace('\n', '<br/>'), styles['Justify']))
71
+
72
+ doc.build(story)
73
+ print(f"\n✅ Data-driven report generated successfully: {file_path}")