Sa-m commited on
Commit
f9d7e93
·
verified ·
1 Parent(s): 8bb6bd2

Update interview_logic.py

Browse files
Files changed (1) hide show
  1. interview_logic.py +654 -58
interview_logic.py CHANGED
@@ -1,61 +1,657 @@
1
- # PrepGenie_Gradio/interview_logic.py
2
-
3
- # ... (existing imports) ...
4
- import datetime # Add this import at the top
5
- import interview_history # Import the new history module
6
-
7
- # ... (existing functions: file_processing, getallinfo, etc.) ...
8
-
9
- def finish_interview(all_questions_list, all_answers_list, all_feedback_list, resume_overview, selected_roles, user_id):
10
- """Gradio function to compile final results and save history."""
11
- if not all_questions_list or not all_answers_list:
12
- return "No interview data to display."
13
-
14
- # --- Compile Results for Display ---
15
- results = "**Interview Summary:**\n\n"
16
- for i, q in enumerate(all_questions_list):
17
- results += f"**Q{i+1}:** {q}\n"
18
- if i < len(all_answers_list):
19
- results += f"**Your Answer:** {all_answers_list[i]}\n"
20
- if i < len(all_feedback_list):
21
- results += f"**Feedback:** {all_feedback_list[i]}\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  else:
23
- results += "**Feedback:** Not available.\n\n"
24
-
25
- # --- Prepare Data for Saving ---
26
- # Only save if user is logged in (user_id is provided)
27
- if user_id:
28
- interview_summary_data = {
29
- "timestamp": datetime.datetime.now().isoformat(), # Add timestamp
30
- "resume_overview": resume_overview,
31
- "selected_roles": selected_roles, # Make sure this is passed
32
- "questions": all_questions_list,
33
- "answers": all_answers_list,
34
- "feedback": all_feedback_list,
35
- "summary": results # Save the formatted summary as well
36
- }
37
- # --- Save to Firestore ---
38
- success = interview_history.save_interview_history(user_id, interview_summary_data)
39
- if success:
40
- results += "\n---\n*Interview history saved successfully.*"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  else:
42
- results += "\n---\n*Failed to save interview history.*"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  else:
44
- results += "\n---\n*Not logged in. Interview history was not saved.*"
45
-
46
- return results
47
-
48
- # ... (rest of the file remains mostly the same, but update the call to finish_interview) ...
49
-
50
- # In the part of interview_logic.py where `finish_interview` is called from the Gradio interface:
51
- # Update the call to pass the additional arguments:
52
- # OLD:
53
- # finish_interview_btn.click(
54
- # fn=finish_interview_wrapper,
55
- # inputs=[user_state],
56
- # outputs=[interview_results]
57
- # )
58
- # NEW (inside the wrapper function or by modifying the wrapper):
59
- # You need to pass resume_overview, selected_roles, and user_id to the actual `finish_interview` function.
60
- # This requires modifying the `finish_interview_wrapper` or the way it's called.
61
- # Let's modify the wrapper function definition in app.py to pass these values.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PrepGenie/interview_logic.py
2
+ """Core logic for the mock interview process."""
3
+
4
+ import os
5
+ import tempfile
6
+ import PyPDF2
7
+ import google.generativeai as genai
8
+ from transformers import BertTokenizer, TFBertModel
9
+ import numpy as np
10
+ import speech_recognition as sr
11
+ import soundfile as sf
12
+ import json
13
+ import matplotlib.pyplot as plt
14
+ import io
15
+ import re
16
+
17
+ # --- Configuration ---
18
+ # These could potentially be moved to a config file or environment variables
19
+ # For now, they are initialized here or passed in.
20
+ # genai.configure(api_key=os.getenv("GOOGLE_API_KEY") or "YOUR_DEFAULT_API_KEY_HERE")
21
+ # text_model = genai.GenerativeModel("gemini-1.5-flash") # This should be initialized in app.py or a central config
22
+
23
+ # --- BERT Model Loading ---
24
+ # It's generally better to load large models once. This can be handled in app.py and passed if needed,
25
+ # or loaded here if this module is imported once at startup.
26
+ # For simplicity, we'll handle loading here, assuming it's imported once.
27
+ try:
28
+ model = TFBertModel.from_pretrained("bert-base-uncased")
29
+ tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
30
+ BERT_AVAILABLE = True
31
+ print("BERT model loaded successfully in interview_logic.")
32
+ except Exception as e:
33
+ print(f"Warning: Could not load BERT model/tokenizer in interview_logic: {e}")
34
+ BERT_AVAILABLE = False
35
+ model = None
36
+ tokenizer = None
37
+
38
+ # --- Core Logic Functions ---
39
+
40
+ def getallinfo(data, text_model):
41
+ """Processes raw resume text into a structured overview."""
42
+ if not data or not data.strip():
43
+ return "No data provided or data is empty."
44
+ text = f"""{data} is given by the user. Make sure you are getting the details like name, experience,
45
+ education, skills of the user like in a resume. If the details are not provided return: not a resume.
46
+ If details are provided then please try again and format the whole in a single paragraph covering all the information. """
47
+ try:
48
+ response = text_model.generate_content(text)
49
+ response.resolve()
50
+ return response.text
51
+ except Exception as e:
52
+ print(f"Error in getallinfo: {e}")
53
+ return "Error processing resume data."
54
+
55
+ def file_processing(pdf_file_path):
56
+ """Processes the uploaded PDF file given its path."""
57
+ if not pdf_file_path or not os.path.exists(pdf_file_path):
58
+ print(f"File path is invalid or file does not exist: {pdf_file_path}")
59
+ return ""
60
+ try:
61
+ print(f"Attempting to process file at path: {pdf_file_path}")
62
+ with open(pdf_file_path, "rb") as f:
63
+ reader = PyPDF2.PdfReader(f)
64
+ text = ""
65
+ for page in reader.pages:
66
+ text += page.extract_text() or "" # Handle None from extract_text
67
+ return text
68
+ except FileNotFoundError:
69
+ error_msg = f"File not found at path: {pdf_file_path}"
70
+ print(error_msg)
71
+ return ""
72
+ except PyPDF2.errors.PdfReadError as e:
73
+ error_msg = f"Error reading PDF file {pdf_file_path}: {e}"
74
+ print(error_msg)
75
+ return ""
76
+ except Exception as e:
77
+ error_msg = f"Unexpected error processing PDF from path {pdf_file_path}: {e}"
78
+ print(error_msg)
79
+ return ""
80
+
81
+ def get_embedding(text):
82
+ """Generates BERT embedding for a given text."""
83
+ if not text or not text.strip():
84
+ return np.zeros((1, 768))
85
+ if not BERT_AVAILABLE or not model or not tokenizer:
86
+ print("BERT model not available for embedding in interview_logic.")
87
+ return np.zeros((1, 768))
88
+ try:
89
+ encoded_text = tokenizer(text, return_tensors="tf", truncation=True, padding=True, max_length=512)
90
+ output = model(encoded_text)
91
+ embedding = output.last_hidden_state[:, 0, :]
92
+ return embedding.numpy()
93
+ except Exception as e:
94
+ print(f"Error getting embedding in interview_logic: {e}")
95
+ return np.zeros((1, 768))
96
+
97
+ def generate_feedback(question, answer):
98
+ """Calculates similarity score between question and answer."""
99
+ if not question or not question.strip() or not answer or not answer.strip():
100
+ return "0.00"
101
+ try:
102
+ question_embedding = get_embedding(question)
103
+ answer_embedding = get_embedding(answer)
104
+ q_emb = np.squeeze(question_embedding)
105
+ a_emb = np.squeeze(answer_embedding)
106
+ dot_product = np.dot(q_emb, a_emb)
107
+ norms = np.linalg.norm(q_emb) * np.linalg.norm(a_emb)
108
+ if norms == 0:
109
+ similarity_score = 0.0
110
+ else:
111
+ similarity_score = dot_product / norms
112
+ return f"{similarity_score:.2f}"
113
+ except Exception as e:
114
+ print(f"Error generating feedback in interview_logic: {e}")
115
+ return "0.00"
116
+
117
+ def generate_questions(roles, data, text_model):
118
+ """Generates interview questions based on resume and roles."""
119
+ if not roles or (isinstance(roles, list) and not any(roles)) or not data or not data.strip():
120
+ return ["Could you please introduce yourself based on your resume?"]
121
+ questions = []
122
+ if isinstance(roles, list):
123
+ roles_str = ", ".join(roles)
124
+ else:
125
+ roles_str = str(roles)
126
+ text = f"""If this is not a resume then return text uploaded pdf is not a resume. this is a resume overview of the candidate.
127
+ The candidate details are in {data}. The candidate has applied for the role of {roles_str}.
128
+ Generate questions for the candidate based on the role applied and on the Resume of the candidate.
129
+ Not always necessary to ask only technical questions related to the role but the logic of question
130
+ should include the job applied for because there might be some deep tech questions which the user might not know.
131
+ Ask some personal questions too. Ask no additional questions. Don't categorize the questions.
132
+ ask 2 questions only. directly ask the questions not anything else.
133
+ Also ask the questions in a polite way. Ask the questions in a way that the candidate can understand the question.
134
+ and make sure the questions are related to these metrics: Communication skills, Teamwork and collaboration,
135
+ Problem-solving and critical thinking, Time management and organization, Adaptability and resilience."""
136
+ try:
137
+ response = text_model.generate_content(text)
138
+ response.resolve()
139
+ questions_text = response.text.strip()
140
+ questions = [q.strip() for q in questions_text.split('\n') if q.strip()]
141
+ if not questions:
142
+ questions = [q.strip() for q in questions_text.split('?') if q.strip()]
143
+ if not questions:
144
+ questions = [q.strip() for q in questions_text.split('.') if q.strip()]
145
+ questions = questions[:2] if questions else ["Could you please introduce yourself based on your resume?"]
146
+ except Exception as e:
147
+ print(f"Error generating questions in interview_logic: {e}")
148
+ questions = ["Could you please introduce yourself based on your resume?"]
149
+ return questions
150
+
151
+ def generate_overall_feedback(data, percent, answer, question, text_model):
152
+ """Generates overall feedback for an answer."""
153
+ if not data or not data.strip() or not answer or not answer.strip() or not question or not question.strip():
154
+ return "Unable to generate feedback due to missing information."
155
+ if isinstance(percent, float):
156
+ percent_str = f"{percent:.2f}"
157
+ else:
158
+ percent_str = str(percent)
159
+ prompt = f"""As an interviewer, provide concise feedback (max 150 words) for candidate {data}.
160
+ Questions asked: {question} # Pass single question
161
+ Candidate's answers: {answer}
162
+ Score: {percent_str}
163
+ Feedback should include:
164
+ 1. Overall performance assessment (2-3 sentences)
165
+ 2. Key strengths (2-3 points)
166
+ 3. Areas for improvement (2-3 points)
167
+ Be honest and constructive. Do not mention the exact score, but rate the candidate out of 10 based on their answers."""
168
+ try:
169
+ response = text_model.generate_content(prompt)
170
+ response.resolve()
171
+ return response.text
172
+ except Exception as e:
173
+ print(f"Error generating overall feedback in interview_logic: {e}")
174
+ return "Feedback could not be generated."
175
+
176
+ def generate_metrics(data, answer, question, text_model):
177
+ """Generates skill metrics for an answer."""
178
+ if not data or not data.strip() or not answer or not answer.strip() or not question or not question.strip():
179
+ return {
180
+ "Communication skills": 0.0, "Teamwork and collaboration": 0.0,
181
+ "Problem-solving and critical thinking": 0.0, "Time management and organization": 0.0,
182
+ "Adaptability and resilience": 0.0
183
+ }
184
+ metrics = {}
185
+ text = f"""Here is the overview of the candidate {data}. In the interview the question asked was {question}.
186
+ The candidate has answered the question as follows: {answer}. Based on the answers provided, give me the metrics related to:
187
+ Communication skills, Teamwork and collaboration, Problem-solving and critical thinking, Time management and organization,
188
+ Adaptability and resilience.
189
+ Rules for rating:
190
+ - Rate each skill from 0 to 10
191
+ - If the answer is empty, 'Sorry could not recognize your voice', meaningless, or irrelevant: rate all skills as 0
192
+ - Only provide numeric ratings without any additional text or '/10'
193
+ - Ratings must reflect actual content quality - do not give courtesy points
194
+ - Consider answer relevance to the specific skill being rated
195
+ Format:
196
+ Communication skills: [rating]
197
+ Teamwork and collaboration: [rating]
198
+ Problem-solving and critical thinking: [rating]
199
+ Time management and organization: [rating]
200
+ Adaptability and resilience: [rating]"""
201
+ try:
202
+ response = text_model.generate_content(text)
203
+ response.resolve()
204
+ metrics_text = response.text.strip()
205
+ for line in metrics_text.split('\n'):
206
+ if ':' in line:
207
+ key, value_str = line.split(':', 1)
208
+ key = key.strip()
209
+ try:
210
+ value_clean = value_str.strip().split()[0]
211
+ value = float(value_clean)
212
+ metrics[key] = value
213
+ except (ValueError, IndexError):
214
+ metrics[key] = 0.0
215
+ expected_metrics = [
216
+ "Communication skills", "Teamwork and collaboration",
217
+ "Problem-solving and critical thinking", "Time management and organization",
218
+ "Adaptability and resilience"
219
+ ]
220
+ for m in expected_metrics:
221
+ if m not in metrics:
222
+ metrics[m] = 0.0
223
+ except Exception as e:
224
+ print(f"Error generating metrics in interview_logic: {e}")
225
+ metrics = {
226
+ "Communication skills": 0.0, "Teamwork and collaboration": 0.0,
227
+ "Problem-solving and critical thinking": 0.0, "Time management and organization": 0.0,
228
+ "Adaptability and resilience": 0.0
229
+ }
230
+ return metrics
231
+
232
+ def getmetrics(interaction, resume, text_model):
233
+ """Gets overall metrics from AI based on interaction."""
234
+ interaction_text = "\n".join([f"{q}: {a}" for q, a in interaction.items()])
235
+ text = f"""This is the user's resume: {resume}.
236
+ And here is the interaction of the interview: {interaction_text}.
237
+ Please evaluate the interview based on the interaction and the resume.
238
+ Rate me the following metrics on a scale of 1 to 10. 1 being the lowest and 10 being the highest.
239
+ Communication skills, Teamwork and collaboration, Problem-solving and critical thinking,
240
+ Time management and organization, Adaptability and resilience. Just give the ratings for the metrics.
241
+ I do not need the feedback. Just the ratings in the format:
242
+ Communication skills: X
243
+ Teamwork and collaboration: Y
244
+ Problem-solving and critical thinking: Z
245
+ Time management and organization: A
246
+ Adaptability and resilience: B
247
+ """
248
+ try:
249
+ response = text_model.generate_content(text)
250
+ response.resolve()
251
+ return response.text
252
+ except Exception as e:
253
+ print(f"Error fetching metrics from AI in interview_logic: {e}")
254
+ return ""
255
+
256
+ def parse_metrics(metric_text):
257
+ """Parses raw metric text into a dictionary."""
258
+ metrics = {
259
+ "Communication skills": 0,
260
+ "Teamwork and collaboration": 0,
261
+ "Problem-solving and critical thinking": 0,
262
+ "Time management and organization": 0,
263
+ "Adaptability and resilience": 0
264
+ }
265
+ if not metric_text:
266
+ return metrics
267
+ for line in metric_text.split("\n"):
268
+ if ":" in line:
269
+ key, value = line.split(":", 1)
270
+ key = key.strip()
271
+ value = value.strip()
272
+ if value and value not in ['N/A', 'nan'] and not value.isspace():
273
+ try:
274
+ numbers = re.findall(r'\d+\.?\d*', value)
275
+ if numbers:
276
+ metrics[key] = int(float(numbers[0]))
277
+ else:
278
+ metrics[key] = 0
279
+ except (ValueError, IndexError, TypeError):
280
+ print(f"Warning: Could not parse metric value '{value}' for '{key}' in interview_logic. Setting to 0.")
281
+ metrics[key] = 0
282
+ else:
283
+ metrics[key] = 0
284
+ return metrics
285
+
286
+ def create_metrics_chart(metrics_dict):
287
+ """Creates a pie chart image from metrics."""
288
+ try:
289
+ labels = list(metrics_dict.keys())
290
+ sizes = list(metrics_dict.values())
291
+ if not any(sizes):
292
+ fig, ax = plt.subplots(figsize=(4, 4))
293
+ ax.text(0.5, 0.5, 'No Data Available', ha='center', va='center', transform=ax.transAxes)
294
+ ax.axis('off')
295
+ else:
296
+ fig, ax = plt.subplots(figsize=(6, 6))
297
+ wedges, texts, autotexts = ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)
298
+ ax.axis('equal')
299
+ for autotext in autotexts:
300
+ autotext.set_color('white')
301
+ autotext.set_fontsize(8)
302
+ buf = io.BytesIO()
303
+ plt.savefig(buf, format='png', bbox_inches='tight')
304
+ buf.seek(0)
305
+ plt.close(fig)
306
+ return buf
307
+ except Exception as e:
308
+ print(f"Error creating chart in interview_logic: {e}")
309
+ fig, ax = plt.subplots(figsize=(4, 4))
310
+ ax.text(0.5, 0.5, 'Chart Error', ha='center', va='center', transform=ax.transAxes)
311
+ ax.axis('off')
312
+ buf = io.BytesIO()
313
+ plt.savefig(buf, format='png')
314
+ buf.seek(0)
315
+ plt.close(fig)
316
+ return buf
317
+
318
+ def generate_evaluation_report(metrics_data, average_rating, feedback_list, interaction_dict):
319
+ """Generates a formatted evaluation report."""
320
+ try:
321
+ report_lines = [f"## Hey Candidate, here is your interview evaluation:\n"]
322
+ report_lines.append("### Skill Ratings:\n")
323
+ for metric, rating in metrics_data.items():
324
+ report_lines.append(f"* **{metric}:** {rating}/10\n")
325
+ report_lines.append(f"\n### Overall Average Rating: {average_rating:.2f}/10\n")
326
+ report_lines.append("### Feedback Summary:\n")
327
+ if feedback_list:
328
+ last_feedback = feedback_list[-1] if feedback_list else "No feedback available."
329
+ report_lines.append(last_feedback)
330
  else:
331
+ report_lines.append("No detailed feedback was generated.")
332
+ report_lines.append("\n### Interview Interaction:\n")
333
+ if interaction_dict:
334
+ for q, a in interaction_dict.items():
335
+ report_lines.append(f"* **{q}**\n {a}\n")
336
+ else:
337
+ report_lines.append("Interaction data not available.")
338
+ improvement_content = """
339
+ ### Areas for Improvement:
340
+ * **Communication:** Focus on clarity, conciseness, and tailoring your responses to the audience. Use examples and evidence to support your points.
341
+ * **Teamwork and collaboration:** Highlight your teamwork skills through specific examples and demonstrate your ability to work effectively with others.
342
+ * **Problem-solving and critical thinking:** Clearly explain your problem-solving approach and thought process. Show your ability to analyze information and arrive at logical solutions.
343
+ * **Time management and organization:** Emphasize your ability to manage time effectively and stay organized during challenging situations.
344
+ * **Adaptability and resilience:** Demonstrate your ability to adapt to new situations and overcome challenges. Share examples of how you have handled unexpected situations or setbacks in the past.
345
+ **Remember:** This is just a starting point. Customize the feedback based on the specific strengths and weaknesses identified in your interview.
346
+ """
347
+ report_lines.append(improvement_content)
348
+ report_text = "".join(report_lines)
349
+ return report_text
350
+ except Exception as e:
351
+ error_msg = f"Error generating evaluation report in interview_logic: {e}"
352
+ print(error_msg)
353
+ return error_msg
354
+
355
+ # --- Interview State Management Functions ---
356
+ # These functions operate on the interview_state dictionary
357
+
358
+ def process_resume_logic(file_obj):
359
+ """Handles resume upload and processing logic."""
360
+ print(f"process_resume_logic called with: {file_obj}")
361
+ if not file_obj:
362
+ return {
363
+ "status": "Please upload a PDF resume.",
364
+ "processed_data": "",
365
+ "ui_updates": {
366
+ "role_selection": "gr_hide", "start_interview_btn": "gr_hide",
367
+ "question_display": "gr_hide", "answer_instructions": "gr_hide",
368
+ "audio_input": "gr_hide", "submit_answer_btn": "gr_hide",
369
+ "next_question_btn": "gr_hide", "submit_interview_btn": "gr_hide",
370
+ "answer_display": "gr_hide", "feedback_display": "gr_hide",
371
+ "metrics_display": "gr_hide"
372
+ }
373
+ }
374
+ try:
375
+ if hasattr(file_obj, 'name'):
376
+ file_path = file_obj.name
377
  else:
378
+ file_path = str(file_obj)
379
+ print(f"File path to process: {file_path}")
380
+ raw_text = file_processing(file_path)
381
+ print(f"Raw text extracted (length: {len(raw_text) if raw_text else 0})")
382
+ if not raw_text or not raw_text.strip():
383
+ print("Failed to extract text or text is empty.")
384
+ return {
385
+ "status": "Could not extract text from the PDF.",
386
+ "processed_data": "",
387
+ "ui_updates": {
388
+ "role_selection": "gr_hide", "start_interview_btn": "gr_hide",
389
+ "question_display": "gr_hide", "answer_instructions": "gr_hide",
390
+ "audio_input": "gr_hide", "submit_answer_btn": "gr_hide",
391
+ "next_question_btn": "gr_hide", "submit_interview_btn": "gr_hide",
392
+ "answer_display": "gr_hide", "feedback_display": "gr_hide",
393
+ "metrics_display": "gr_hide"
394
+ }
395
+ }
396
+ # processed_data = getallinfo(raw_text, text_model) # text_model needs to be passed
397
+ # Placeholder, actual call in app.py
398
+ return {
399
+ "status": f"File processed successfully!",
400
+ "processed_data": raw_text, # Return raw text, let app.py call getallinfo
401
+ "ui_updates": {
402
+ "role_selection": "gr_show", "start_interview_btn": "gr_show",
403
+ "question_display": "gr_hide", "answer_instructions": "gr_hide",
404
+ "audio_input": "gr_hide", "submit_answer_btn": "gr_hide",
405
+ "next_question_btn": "gr_hide", "submit_interview_btn": "gr_hide",
406
+ "answer_display": "gr_hide", "feedback_display": "gr_hide",
407
+ "metrics_display": "gr_hide"
408
+ }
409
+ }
410
+ except Exception as e:
411
+ error_msg = f"Error processing file in interview_logic: {str(e)}"
412
+ print(error_msg)
413
+ import traceback
414
+ traceback.print_exc()
415
+ return {
416
+ "status": error_msg,
417
+ "processed_data": "",
418
+ "ui_updates": {
419
+ "role_selection": "gr_hide", "start_interview_btn": "gr_hide",
420
+ "question_display": "gr_hide", "answer_instructions": "gr_hide",
421
+ "audio_input": "gr_hide", "submit_answer_btn": "gr_hide",
422
+ "next_question_btn": "gr_hide", "submit_interview_btn": "gr_hide",
423
+ "answer_display": "gr_hide", "feedback_display": "gr_hide",
424
+ "metrics_display": "gr_hide"
425
+ }
426
+ }
427
+
428
+ def start_interview_logic(roles, processed_resume_data, text_model):
429
+ """Starts the interview process logic."""
430
+ if not roles or (isinstance(roles, list) and not any(roles)) or not processed_resume_data or not processed_resume_data.strip():
431
+ return {
432
+ "status": "Please select a role and ensure resume is processed.",
433
+ "initial_question": "",
434
+ "interview_state": {},
435
+ "ui_updates": {
436
+ "audio_input": "gr_hide", "submit_answer_btn": "gr_hide", "next_question_btn": "gr_hide",
437
+ "submit_interview_btn": "gr_hide", "feedback_display": "gr_hide", "metrics_display": "gr_hide",
438
+ "question_display": "gr_hide", "answer_instructions": "gr_hide"
439
+ }
440
+ }
441
+ try:
442
+ questions = generate_questions(roles, processed_resume_data, text_model)
443
+ initial_question = questions[0] if questions else "Could you please introduce yourself?"
444
+ interview_state = {
445
+ "questions": questions,
446
+ "current_q_index": 0,
447
+ "answers": [],
448
+ "feedback": [],
449
+ "interactions": {},
450
+ "metrics_list": [],
451
+ "resume_data": processed_resume_data,
452
+ "selected_roles": roles # Store roles for history
453
+ }
454
+ return {
455
+ "status": "Interview started. Please answer the first question.",
456
+ "initial_question": initial_question,
457
+ "interview_state": interview_state,
458
+ "ui_updates": {
459
+ "audio_input": "gr_show", "submit_answer_btn": "gr_show", "next_question_btn": "gr_show",
460
+ "submit_interview_btn": "gr_hide", "feedback_display": "gr_hide", "metrics_display": "gr_hide",
461
+ "question_display": "gr_show", "answer_instructions": "gr_show"
462
+ }
463
+ }
464
+ except Exception as e:
465
+ error_msg = f"Error starting interview in interview_logic: {str(e)}"
466
+ print(error_msg)
467
+ return {
468
+ "status": error_msg,
469
+ "initial_question": "",
470
+ "interview_state": {},
471
+ "ui_updates": {
472
+ "audio_input": "gr_hide", "submit_answer_btn": "gr_hide", "next_question_btn": "gr_hide",
473
+ "submit_interview_btn": "gr_hide", "feedback_display": "gr_hide", "metrics_display": "gr_hide",
474
+ "question_display": "gr_hide", "answer_instructions": "gr_hide"
475
+ }
476
+ }
477
+
478
+ def submit_answer_logic(audio, interview_state, text_model):
479
+ """Handles submitting an answer via audio logic."""
480
+ if not audio or not interview_state:
481
+ return {
482
+ "status": "No audio recorded or interview not started.",
483
+ "answer_text": "",
484
+ "interview_state": interview_state,
485
+ "feedback_text": "",
486
+ "metrics": {},
487
+ "ui_updates": {
488
+ "feedback_display": "gr_hide", "metrics_display": "gr_hide",
489
+ "audio_input": "gr_show", "submit_answer_btn": "gr_show", "next_question_btn": "gr_show",
490
+ "submit_interview_btn": "gr_hide", "question_display": "gr_show", "answer_instructions": "gr_show"
491
+ }
492
+ }
493
+ try:
494
+ temp_dir = tempfile.mkdtemp()
495
+ audio_file_path = os.path.join(temp_dir, "recorded_audio.wav")
496
+ sample_rate, audio_data = audio
497
+ sf.write(audio_file_path, audio_data, sample_rate)
498
+ r = sr.Recognizer()
499
+ with sr.AudioFile(audio_file_path) as source:
500
+ audio_data_sr = r.record(source)
501
+ answer_text = r.recognize_google(audio_data_sr)
502
+ print(f"Recognized Answer: {answer_text}")
503
+ os.remove(audio_file_path)
504
+ os.rmdir(temp_dir)
505
+ interview_state["answers"].append(answer_text)
506
+ current_q_index = interview_state["current_q_index"]
507
+ current_question = interview_state["questions"][current_q_index]
508
+ interview_state["interactions"][f"Q{current_q_index + 1}: {current_question}"] = f"A{current_q_index + 1}: {answer_text}"
509
+ percent_str = generate_feedback(current_question, answer_text)
510
+ try:
511
+ percent = float(percent_str)
512
+ except ValueError:
513
+ percent = 0.0
514
+ feedback_text = generate_overall_feedback(interview_state["resume_data"], percent_str, answer_text, current_question, text_model)
515
+ interview_state["feedback"].append(feedback_text)
516
+ metrics = generate_metrics(interview_state["resume_data"], answer_text, current_question, text_model)
517
+ interview_state["metrics_list"].append(metrics)
518
+ interview_state["current_q_index"] += 1
519
+ return {
520
+ "status": f"Answer submitted: {answer_text}",
521
+ "answer_text": answer_text,
522
+ "interview_state": interview_state,
523
+ "feedback_text": feedback_text,
524
+ "metrics": metrics,
525
+ "ui_updates": {
526
+ "feedback_display": "gr_show_and_update", "metrics_display": "gr_show_and_update",
527
+ "audio_input": "gr_show", "submit_answer_btn": "gr_show", "next_question_btn": "gr_show",
528
+ "submit_interview_btn": "gr_hide", "question_display": "gr_show", "answer_instructions": "gr_show"
529
+ }
530
+ }
531
+ except Exception as e:
532
+ print(f"Error processing audio answer in interview_logic: {e}")
533
+ return {
534
+ "status": "Error processing audio. Please try again.",
535
+ "answer_text": "",
536
+ "interview_state": interview_state,
537
+ "feedback_text": "",
538
+ "metrics": {},
539
+ "ui_updates": {
540
+ "feedback_display": "gr_hide", "metrics_display": "gr_hide",
541
+ "audio_input": "gr_show", "submit_answer_btn": "gr_show", "next_question_btn": "gr_show",
542
+ "submit_interview_btn": "gr_hide", "question_display": "gr_show", "answer_instructions": "gr_show"
543
+ }
544
+ }
545
+
546
+ def next_question_logic(interview_state):
547
+ """Moves to the next question or ends the interview logic."""
548
+ if not interview_state:
549
+ return {
550
+ "status": "Interview not started.",
551
+ "next_q": "",
552
+ "interview_state": interview_state,
553
+ "ui_updates": {
554
+ "audio_input": "gr_show", "submit_answer_btn": "gr_show", "next_question_btn": "gr_show",
555
+ "feedback_display": "gr_hide", "metrics_display": "gr_hide", "submit_interview_btn": "gr_hide",
556
+ "question_display": "gr_hide", "answer_instructions": "gr_hide",
557
+ "answer_display": "gr_clear", "metrics_display_clear": "gr_clear"
558
+ }
559
+ }
560
+ current_q_index = interview_state["current_q_index"]
561
+ total_questions = len(interview_state["questions"])
562
+ if current_q_index < total_questions:
563
+ next_q = interview_state["questions"][current_q_index]
564
+ return {
565
+ "status": f"Question {current_q_index + 1}/{total_questions}",
566
+ "next_q": next_q,
567
+ "interview_state": interview_state,
568
+ "ui_updates": {
569
+ "audio_input": "gr_show", "submit_answer_btn": "gr_show", "next_question_btn": "gr_show",
570
+ "feedback_display": "gr_hide", "metrics_display": "gr_hide", "submit_interview_btn": "gr_hide",
571
+ "question_display": "gr_show", "answer_instructions": "gr_show",
572
+ "answer_display": "gr_clear", "metrics_display_clear": "gr_clear"
573
+ }
574
+ }
575
  else:
576
+ return {
577
+ "status": "Interview completed! Click 'Submit Interview' to see your evaluation.",
578
+ "next_q": "Interview Finished",
579
+ "interview_state": interview_state,
580
+ "ui_updates": {
581
+ "audio_input": "gr_hide", "submit_answer_btn": "gr_hide", "next_question_btn": "gr_hide",
582
+ "feedback_display": "gr_hide", "metrics_display": "gr_hide", "submit_interview_btn": "gr_show", # Show submit button
583
+ "question_display": "gr_show", "answer_instructions": "gr_hide",
584
+ "answer_display": "gr_clear", "metrics_display_clear": "gr_clear"
585
+ }
586
+ }
587
+
588
+ def submit_interview_logic(interview_state, text_model):
589
+ """Handles final submission, triggers evaluation, prepares results logic."""
590
+ if not interview_state or not isinstance(interview_state, dict):
591
+ return {
592
+ "status": "Interview state is missing or invalid.",
593
+ "interview_state": interview_state,
594
+ "report_text": "",
595
+ "chart_buffer": None,
596
+ "ui_updates": {
597
+ "evaluation_report_display": "gr_hide", "evaluation_chart_display": "gr_hide"
598
+ }
599
+ }
600
+ try:
601
+ print("Interview submitted for evaluation in interview_logic.")
602
+ interactions = interview_state.get("interactions", {})
603
+ resume_data = interview_state.get("resume_data", "")
604
+ feedback_list = interview_state.get("feedback", [])
605
+ metrics_history = interview_state.get("metrics_list", [])
606
+ # selected_roles = interview_state.get("selected_roles", []) # Not used here directly
607
+
608
+ if not interactions:
609
+ error_msg = "No interview interactions found to evaluate."
610
+ print(error_msg)
611
+ return {
612
+ "status": error_msg,
613
+ "interview_state": interview_state,
614
+ "report_text": "",
615
+ "chart_buffer": None,
616
+ "ui_updates": {
617
+ "evaluation_report_display": "gr_hide", "evaluation_chart_display": "gr_hide"
618
+ }
619
+ }
620
+ raw_metrics_text = getmetrics(interactions, resume_data, text_model)
621
+ print(f"Raw Metrics Text:\n{raw_metrics_text}")
622
+ final_metrics = parse_metrics(raw_metrics_text)
623
+ print(f"Parsed Metrics: {final_metrics}")
624
+ if final_metrics:
625
+ average_rating = sum(final_metrics.values()) / len(final_metrics)
626
+ else:
627
+ average_rating = 0.0
628
+ report_text = generate_evaluation_report(final_metrics, average_rating, feedback_list, interactions)
629
+ print("Evaluation report generated in interview_logic.")
630
+ chart_buffer = create_metrics_chart(final_metrics)
631
+ print("Evaluation chart generated in interview_logic.")
632
+
633
+ return {
634
+ "status": "Evaluation Complete! See your results below.",
635
+ "interview_state": interview_state, # Pass through
636
+ "report_text": report_text,
637
+ "chart_buffer": chart_buffer,
638
+ "ui_updates": {
639
+ "evaluation_report_display": "gr_show_and_update", "evaluation_chart_display": "gr_show_and_update"
640
+ }
641
+ }
642
+ except Exception as e:
643
+ error_msg = f"Error during evaluation submission in interview_logic: {str(e)}"
644
+ print(error_msg)
645
+ import traceback
646
+ traceback.print_exc()
647
+ return {
648
+ "status": error_msg,
649
+ "interview_state": interview_state,
650
+ "report_text": error_msg,
651
+ "chart_buffer": None,
652
+ "ui_updates": {
653
+ "evaluation_report_display": "gr_show_and_update_error", "evaluation_chart_display": "gr_hide"
654
+ }
655
+ }
656
+
657
+ # Add similar logic functions for chat if needed, or keep chat in its own module.