michael-agent 1.0.3__tar.gz → 1.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- michael_agent-1.0.5/MANIFEST.in +4 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/PKG-INFO +1 -1
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/app.py +69 -29
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/templates/career_portal.html +79 -6
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/templates/jd_creation.html +44 -1
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/templates/resume_scoring.html +44 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/graph_builder.py +23 -1
- michael_agent-1.0.5/michael_agent/langgraph_workflow/nodes/jd_generator.py +226 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/nodes/question_generator.py +31 -9
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/nodes/resume_analyzer.py +183 -5
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/nodes/sentiment_analysis.py +29 -5
- michael_agent-1.0.5/michael_agent/main.py +159 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent.egg-info/PKG-INFO +1 -1
- {michael_agent-1.0.3 → michael_agent-1.0.5}/setup.py +1 -1
- michael_agent-1.0.3/MANIFEST.in +0 -3
- michael_agent-1.0.3/michael_agent/langgraph_workflow/nodes/jd_generator.py +0 -139
- michael_agent-1.0.3/michael_agent/main.py +0 -103
- {michael_agent-1.0.3 → michael_agent-1.0.5}/README.md +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/__init__.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/config/__init__.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/config/settings.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/__init__.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/static/__init__.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/static/styles.css +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/templates/__init__.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/templates/dashboard.html +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/templates/upload_resume.html +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/__init__.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/nodes/__init__.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/nodes/assessment_handler.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/nodes/jd_poster.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/nodes/recruiter_notifier.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/nodes/resume_ingestor.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/monitor.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/utils/__init__.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/utils/email_utils.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/utils/id_mapper.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/utils/jd_utils.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/utils/lms_api.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/utils/logging_utils.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/utils/monitor_utils.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/utils/node_tracer.py +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent.egg-info/SOURCES.txt +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent.egg-info/dependency_links.txt +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent.egg-info/requires.txt +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent.egg-info/top_level.txt +0 -0
- {michael_agent-1.0.3 → michael_agent-1.0.5}/setup.cfg +0 -0
@@ -267,12 +267,13 @@ def generate_jd():
|
|
267
267
|
"errors": []
|
268
268
|
}
|
269
269
|
|
270
|
-
# Use the JD generator node to create the job description
|
270
|
+
# Use the JD generator node to create the job description and screening questions
|
271
271
|
result_state = jd_generator.generate_job_description(state)
|
272
272
|
|
273
273
|
return jsonify({
|
274
274
|
'success': True,
|
275
|
-
'job_description_text': result_state.get('job_description_text', 'Error generating job description')
|
275
|
+
'job_description_text': result_state.get('job_description_text', 'Error generating job description'),
|
276
|
+
'screening_questions': result_state.get('screening_questions', [])
|
276
277
|
})
|
277
278
|
except Exception as e:
|
278
279
|
logger.error(f"Error generating job description: {str(e)}")
|
@@ -295,7 +296,8 @@ def save_jd():
|
|
295
296
|
"job_id": job_id,
|
296
297
|
"timestamp": datetime.now().isoformat(),
|
297
298
|
"job_data": data.get("metadata", {}),
|
298
|
-
"job_description": data.get("content", "")
|
299
|
+
"job_description": data.get("content", ""),
|
300
|
+
"screening_questions": data.get("screening_questions", [])
|
299
301
|
}
|
300
302
|
|
301
303
|
# Save to job_descriptions directory
|
@@ -521,6 +523,9 @@ def get_job(job_id):
|
|
521
523
|
required_skills = jd_data['job_data'].get('required_skills', [])
|
522
524
|
preferred_skills = jd_data['job_data'].get('preferred_skills', [])
|
523
525
|
|
526
|
+
# Extract screening questions if available
|
527
|
+
screening_questions = jd_data.get('screening_questions', [])
|
528
|
+
|
524
529
|
return jsonify({
|
525
530
|
'id': job_id,
|
526
531
|
'title': title,
|
@@ -530,7 +535,8 @@ def get_job(job_id):
|
|
530
535
|
'employment_type': employment_type,
|
531
536
|
'experience_level': experience_level,
|
532
537
|
'required_skills': required_skills,
|
533
|
-
'preferred_skills': preferred_skills
|
538
|
+
'preferred_skills': preferred_skills,
|
539
|
+
'screening_questions': screening_questions
|
534
540
|
})
|
535
541
|
except Exception as e:
|
536
542
|
logger.error(f"Error getting job details: {str(e)}")
|
@@ -1028,6 +1034,16 @@ def get_candidate(candidate_id):
|
|
1028
1034
|
# Add more detailed debug logging
|
1029
1035
|
logger.info(f"Returning candidate data for {candidate_id}, name={name}, has_experience={len(experience)}, has_education={len(education)}")
|
1030
1036
|
|
1037
|
+
# Extract screening questions and answers if available
|
1038
|
+
screening_responses = None
|
1039
|
+
if 'screening_questions' in candidate_data and 'screening_answers' in candidate_data:
|
1040
|
+
screening_responses = {
|
1041
|
+
'questions': candidate_data['screening_questions'],
|
1042
|
+
'answers': candidate_data['screening_answers']
|
1043
|
+
}
|
1044
|
+
elif 'resume_data' in candidate_data and 'screening_responses' in candidate_data['resume_data']:
|
1045
|
+
screening_responses = candidate_data['resume_data']['screening_responses']
|
1046
|
+
|
1031
1047
|
# Return data with sensible defaults for all fields to prevent frontend errors
|
1032
1048
|
response_data = {
|
1033
1049
|
'id': candidate_id,
|
@@ -1046,6 +1062,7 @@ def get_candidate(candidate_id):
|
|
1046
1062
|
'weaknesses': [],
|
1047
1063
|
'overall': 'No analysis available'
|
1048
1064
|
},
|
1065
|
+
'screening_responses': screening_responses,
|
1049
1066
|
'resume_path': resume_path or '',
|
1050
1067
|
'status': candidate_data.get('status', 'new') or 'new'
|
1051
1068
|
}
|
@@ -1170,6 +1187,7 @@ def apply_for_job():
|
|
1170
1187
|
try:
|
1171
1188
|
# Check if all required fields are present
|
1172
1189
|
if 'resume_file' not in request.files:
|
1190
|
+
logger.error("No resume file provided in request")
|
1173
1191
|
return jsonify({'success': False, 'error': 'No resume file provided'}), 400
|
1174
1192
|
|
1175
1193
|
resume_file = request.files['resume_file']
|
@@ -1178,8 +1196,12 @@ def apply_for_job():
|
|
1178
1196
|
email = request.form.get('email')
|
1179
1197
|
|
1180
1198
|
if not resume_file or not job_id or not name or not email:
|
1199
|
+
logger.error(f"Missing required fields: resume_file={bool(resume_file)}, job_id={job_id}, name={name}, email={email}")
|
1181
1200
|
return jsonify({'success': False, 'error': 'Missing required fields'}), 400
|
1182
1201
|
|
1202
|
+
# Log all form data for debugging
|
1203
|
+
logger.info(f"Received application form data: {request.form}")
|
1204
|
+
|
1183
1205
|
# Check if directory exists and create if not
|
1184
1206
|
import os
|
1185
1207
|
from datetime import datetime
|
@@ -1190,13 +1212,44 @@ def apply_for_job():
|
|
1190
1212
|
|
1191
1213
|
# Generate unique filename with timestamp
|
1192
1214
|
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
|
1193
|
-
filename = f"{job_id}_{request.form.get('name').replace(' ', '_')}.{resume_file.filename.split('.')[-1]}"
|
1215
|
+
filename = f"{timestamp}_{job_id}_{request.form.get('name').replace(' ', '_')}.{resume_file.filename.split('.')[-1]}"
|
1194
1216
|
filepath = os.path.join(incoming_dir, filename)
|
1195
1217
|
|
1196
1218
|
# Save the file
|
1197
1219
|
resume_file.save(filepath)
|
1198
1220
|
logger.info(f"Saved resume file to {filepath}")
|
1199
1221
|
|
1222
|
+
# Get job screening questions
|
1223
|
+
screening_questions = []
|
1224
|
+
job_file = os.path.join(settings.JOB_DESCRIPTIONS_DIR, f'{job_id}.json')
|
1225
|
+
if os.path.exists(job_file):
|
1226
|
+
try:
|
1227
|
+
with open(job_file, 'r') as f:
|
1228
|
+
job_data = json.load(f)
|
1229
|
+
screening_questions = job_data.get('screening_questions', [])
|
1230
|
+
logger.info(f"Found {len(screening_questions)} screening questions for job {job_id}")
|
1231
|
+
except Exception as e:
|
1232
|
+
logger.error(f"Error reading job file for screening questions: {str(e)}")
|
1233
|
+
|
1234
|
+
# Extract screening answers from form data
|
1235
|
+
# Look for fields named screening_answer_0, screening_answer_1, etc.
|
1236
|
+
screening_responses = []
|
1237
|
+
for i, question in enumerate(screening_questions):
|
1238
|
+
answer_key = f'screening_answer_{i}'
|
1239
|
+
if answer_key in request.form:
|
1240
|
+
answer = request.form.get(answer_key)
|
1241
|
+
logger.info(f"Found screening answer {i}: {answer}")
|
1242
|
+
screening_responses.append({
|
1243
|
+
"question": question,
|
1244
|
+
"answer": answer
|
1245
|
+
})
|
1246
|
+
else:
|
1247
|
+
logger.warning(f"Missing screening answer for question {i}")
|
1248
|
+
screening_responses.append({
|
1249
|
+
"question": question,
|
1250
|
+
"answer": "No answer provided"
|
1251
|
+
})
|
1252
|
+
|
1200
1253
|
# Create metadata file with applicant details
|
1201
1254
|
metadata = {
|
1202
1255
|
'job_id': job_id,
|
@@ -1204,41 +1257,28 @@ def apply_for_job():
|
|
1204
1257
|
'email': email,
|
1205
1258
|
'phone': request.form.get('phone', ''),
|
1206
1259
|
'cover_letter': request.form.get('cover_letter', ''),
|
1260
|
+
'screening_questions': screening_questions,
|
1261
|
+
'screening_responses': screening_responses,
|
1207
1262
|
'application_date': datetime.now().isoformat(),
|
1208
1263
|
'status': 'new',
|
1209
1264
|
'resume_path': filepath
|
1210
1265
|
}
|
1211
1266
|
|
1212
|
-
metadata_path =
|
1267
|
+
metadata_path = filepath.replace('.pdf', '.json')
|
1213
1268
|
with open(metadata_path, 'w') as f:
|
1214
1269
|
json.dump(metadata, f, indent=2)
|
1215
1270
|
|
1271
|
+
logger.info(f"Saved metadata with screening responses to {metadata_path}")
|
1272
|
+
|
1216
1273
|
# In a real implementation, trigger the workflow to process the resume
|
1217
1274
|
from langgraph_workflow.graph_builder import process_new_resume
|
1218
1275
|
|
1219
|
-
#
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
job_data = json.load(f)
|
1226
|
-
|
1227
|
-
# Extract title from job description using regex
|
1228
|
-
import re
|
1229
|
-
title_match = re.search(r'\*\*Job Title:\s*(.*?)\*\*', job_data.get('job_description', ''))
|
1230
|
-
title = title_match.group(1) if title_match else "Untitled Job"
|
1231
|
-
|
1232
|
-
job_description = {
|
1233
|
-
'id': job_id,
|
1234
|
-
'title': title,
|
1235
|
-
'content': job_data.get('job_description', ''),
|
1236
|
-
'metadata': job_data.get('job_data', {})
|
1237
|
-
}
|
1238
|
-
|
1239
|
-
# We only need to save the file and metadata - the file system watcher will handle the processing
|
1240
|
-
# This prevents duplicate workflow execution
|
1241
|
-
logger.info(f"Resume uploaded through career portal - file system watcher will process it automatically")
|
1276
|
+
# Start processing in a background thread to avoid blocking the response
|
1277
|
+
import threading
|
1278
|
+
thread = threading.Thread(target=process_new_resume, args=(filepath, job_id))
|
1279
|
+
thread.daemon = True
|
1280
|
+
thread.start()
|
1281
|
+
logger.info(f"Started background processing for resume: {filepath}")
|
1242
1282
|
|
1243
1283
|
return jsonify({
|
1244
1284
|
'success': True,
|
{michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/templates/career_portal.html
RENAMED
@@ -168,6 +168,12 @@
|
|
168
168
|
<label for="cover-letter" class="form-label">Cover Letter</label>
|
169
169
|
<textarea class="form-control" id="cover-letter" name="cover_letter" rows="4"></textarea>
|
170
170
|
</div>
|
171
|
+
|
172
|
+
<!-- Screening Questions Section (will be populated dynamically) -->
|
173
|
+
<div id="screening-questions-container" class="mb-3 d-none">
|
174
|
+
<h5 class="mb-3">Screening Questions</h5>
|
175
|
+
<div id="screening-questions-list"></div>
|
176
|
+
</div>
|
171
177
|
</form>
|
172
178
|
</div>
|
173
179
|
<div class="modal-footer">
|
@@ -222,20 +228,65 @@
|
|
222
228
|
|
223
229
|
// Apply button in job modal
|
224
230
|
document.getElementById('apply-button').addEventListener('click', function() {
|
225
|
-
// Get job details from the current modal
|
226
231
|
const jobId = this.getAttribute('data-job-id');
|
227
|
-
|
228
|
-
|
229
|
-
// Set values in application modal
|
232
|
+
if (!jobId) return;
|
233
|
+
// Set job ID in hidden field
|
230
234
|
document.getElementById('application-job-id').value = jobId;
|
235
|
+
|
236
|
+
// Get job title
|
237
|
+
const jobTitle = document.getElementById('modal-job-title').textContent;
|
231
238
|
document.getElementById('application-job-title').textContent = jobTitle;
|
232
239
|
|
240
|
+
console.log('Fetching job details for screening questions, job ID:', jobId);
|
241
|
+
|
242
|
+
// Fetch job details to get screening questions
|
243
|
+
fetch(`/api/jobs/${jobId}`)
|
244
|
+
.then(response => response.json())
|
245
|
+
.then(jobDetails => {
|
246
|
+
console.log('Job details received:', jobDetails);
|
247
|
+
// Show screening questions if available
|
248
|
+
if (jobDetails.screening_questions && jobDetails.screening_questions.length > 0) {
|
249
|
+
console.log('Screening questions found:', jobDetails.screening_questions);
|
250
|
+
const questionsContainer = document.getElementById('screening-questions-container');
|
251
|
+
const questionsList = document.getElementById('screening-questions-list');
|
252
|
+
|
253
|
+
// Show the container
|
254
|
+
questionsContainer.classList.remove('d-none');
|
255
|
+
|
256
|
+
// Clear previous questions
|
257
|
+
questionsList.innerHTML = '';
|
258
|
+
|
259
|
+
// Add each question
|
260
|
+
jobDetails.screening_questions.forEach((question, index) => {
|
261
|
+
const questionDiv = document.createElement('div');
|
262
|
+
questionDiv.className = 'mb-3';
|
263
|
+
questionDiv.innerHTML = `
|
264
|
+
<label class="form-label fw-bold">${index + 1}. ${question}</label>
|
265
|
+
<textarea class="form-control" name="screening_answer_${index}" rows="2" required></textarea>
|
266
|
+
`;
|
267
|
+
questionsList.appendChild(questionDiv);
|
268
|
+
});
|
269
|
+
} else {
|
270
|
+
console.log('No screening questions found for this job');
|
271
|
+
// Hide the container if no questions
|
272
|
+
document.getElementById('screening-questions-container').classList.add('d-none');
|
273
|
+
}
|
274
|
+
})
|
275
|
+
.catch(error => {
|
276
|
+
console.error('Error fetching job details:', error);
|
277
|
+
document.getElementById('screening-questions-container').classList.add('d-none');
|
278
|
+
});
|
279
|
+
|
233
280
|
// Hide job details modal and show application modal
|
234
281
|
const jobModal = bootstrap.Modal.getInstance(document.getElementById('job-details-modal'));
|
235
282
|
jobModal.hide();
|
236
283
|
|
237
|
-
|
238
|
-
|
284
|
+
// Show application modal after a short delay to ensure the previous modal is closed
|
285
|
+
setTimeout(() => {
|
286
|
+
const applicationModal = new bootstrap.Modal(document.getElementById('application-modal'));
|
287
|
+
applicationModal.show();
|
288
|
+
console.log('Application modal shown');
|
289
|
+
}, 300);
|
239
290
|
});
|
240
291
|
|
241
292
|
// Submit application
|
@@ -259,6 +310,24 @@
|
|
259
310
|
}
|
260
311
|
formData.append('resume_file', resumeFile);
|
261
312
|
|
313
|
+
// Collect screening question answers
|
314
|
+
const screeningAnswers = [];
|
315
|
+
document.querySelectorAll('[name^="screening_answer_"]').forEach(input => {
|
316
|
+
// Check if the answer field is empty
|
317
|
+
if (!input.value.trim() && input.required) {
|
318
|
+
alert('Please answer all screening questions');
|
319
|
+
valid = false;
|
320
|
+
return;
|
321
|
+
}
|
322
|
+
screeningAnswers.push(input.value);
|
323
|
+
});
|
324
|
+
|
325
|
+
// Add screening answers as JSON
|
326
|
+
if (screeningAnswers.length > 0) {
|
327
|
+
console.log('Adding screening answers to form data:', screeningAnswers);
|
328
|
+
formData.append('screening_answers', JSON.stringify(screeningAnswers));
|
329
|
+
}
|
330
|
+
|
262
331
|
// Show spinner
|
263
332
|
this.disabled = true;
|
264
333
|
document.getElementById('submit-spinner').classList.remove('d-none');
|
@@ -281,6 +350,10 @@
|
|
281
350
|
|
282
351
|
// Reset form
|
283
352
|
form.reset();
|
353
|
+
|
354
|
+
// Clear screening questions
|
355
|
+
document.getElementById('screening-questions-container').classList.add('d-none');
|
356
|
+
document.getElementById('screening-questions-list').innerHTML = '';
|
284
357
|
} else {
|
285
358
|
alert('Error submitting application: ' + data.error);
|
286
359
|
}
|
{michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/templates/jd_creation.html
RENAMED
@@ -105,6 +105,11 @@
|
|
105
105
|
<label for="application_deadline" class="form-label">Application Deadline (Optional)</label>
|
106
106
|
<input type="date" class="form-control" id="application_deadline" name="application_deadline">
|
107
107
|
</div>
|
108
|
+
|
109
|
+
<div class="alert alert-info mb-3">
|
110
|
+
<h5><i class="bi bi-info-circle"></i> Screening Questions</h5>
|
111
|
+
<p>When you generate a job description, 5 relevant screening questions will be automatically created based on the job details. These questions will be presented to candidates when they apply.</p>
|
112
|
+
</div>
|
108
113
|
|
109
114
|
<div class="text-center">
|
110
115
|
<button type="submit" class="btn btn-primary" id="generate-btn">
|
@@ -128,6 +133,15 @@
|
|
128
133
|
<div id="jd-sections">
|
129
134
|
<h3 id="jd-title" class="mb-3"></h3>
|
130
135
|
<div id="jd-content"></div>
|
136
|
+
|
137
|
+
<hr class="my-4">
|
138
|
+
<div id="screening-questions-section" class="mt-4 p-4 bg-light border rounded">
|
139
|
+
<h4 class="mb-3"><i class="bi bi-question-circle"></i> Screening Questions</h4>
|
140
|
+
<div class="alert alert-info">
|
141
|
+
<strong>Important:</strong> These questions will be presented to candidates when they apply for this position.
|
142
|
+
</div>
|
143
|
+
<ol id="screening-questions-list" class="fw-bold"></ol>
|
144
|
+
</div>
|
131
145
|
</div>
|
132
146
|
</div>
|
133
147
|
</div>
|
@@ -240,6 +254,25 @@
|
|
240
254
|
// Display the generated JD
|
241
255
|
document.getElementById('jd-title').textContent = jobData.position;
|
242
256
|
document.getElementById('jd-content').innerHTML = data.job_description_text.replace(/\n/g, '<br>');
|
257
|
+
|
258
|
+
// Display screening questions
|
259
|
+
const screeningQuestionsList = document.getElementById('screening-questions-list');
|
260
|
+
screeningQuestionsList.innerHTML = ''; // Clear previous questions
|
261
|
+
|
262
|
+
if (data.screening_questions && data.screening_questions.length > 0) {
|
263
|
+
data.screening_questions.forEach(question => {
|
264
|
+
const li = document.createElement('li');
|
265
|
+
li.className = 'mb-2';
|
266
|
+
li.textContent = question;
|
267
|
+
screeningQuestionsList.appendChild(li);
|
268
|
+
});
|
269
|
+
// Store screening questions for saving
|
270
|
+
window.screeningQuestions = data.screening_questions;
|
271
|
+
} else {
|
272
|
+
screeningQuestionsList.innerHTML = '<div class="alert alert-warning">No screening questions were generated.</div>';
|
273
|
+
window.screeningQuestions = [];
|
274
|
+
}
|
275
|
+
|
243
276
|
document.getElementById('result-card').style.display = 'block';
|
244
277
|
|
245
278
|
// Scroll to result
|
@@ -264,7 +297,16 @@
|
|
264
297
|
const jdText = document.getElementById('jd-content').innerText;
|
265
298
|
const title = document.getElementById('jd-title').innerText;
|
266
299
|
|
267
|
-
|
300
|
+
// Prepare screening questions text
|
301
|
+
let screeningQuestionsText = '';
|
302
|
+
if (window.screeningQuestions && window.screeningQuestions.length > 0) {
|
303
|
+
screeningQuestionsText = '\n\nScreening Questions:\n';
|
304
|
+
window.screeningQuestions.forEach((question, index) => {
|
305
|
+
screeningQuestionsText += `${index + 1}. ${question}\n`;
|
306
|
+
});
|
307
|
+
}
|
308
|
+
|
309
|
+
navigator.clipboard.writeText(title + '\n\n' + jdText + screeningQuestionsText)
|
268
310
|
.then(() => {
|
269
311
|
const originalText = this.textContent;
|
270
312
|
this.textContent = 'Copied!';
|
@@ -291,6 +333,7 @@
|
|
291
333
|
body: JSON.stringify({
|
292
334
|
title: title,
|
293
335
|
content: jdText,
|
336
|
+
screening_questions: window.screeningQuestions || [],
|
294
337
|
metadata: {
|
295
338
|
position: document.getElementById('position').value,
|
296
339
|
location: document.getElementById('location').value,
|
{michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/dashboard/templates/resume_scoring.html
RENAMED
@@ -177,6 +177,15 @@
|
|
177
177
|
</div>
|
178
178
|
</div>
|
179
179
|
|
180
|
+
<div class="card border-primary mb-3">
|
181
|
+
<div class="card-header bg-light">Screening Responses</div>
|
182
|
+
<div class="card-body">
|
183
|
+
<div id="screening-responses-container">
|
184
|
+
<p class="text-muted">No screening responses available</p>
|
185
|
+
</div>
|
186
|
+
</div>
|
187
|
+
</div>
|
188
|
+
|
180
189
|
<div class="card border-success">
|
181
190
|
<div class="card-header bg-light">Actions</div>
|
182
191
|
<div class="card-body">
|
@@ -482,6 +491,9 @@
|
|
482
491
|
|
483
492
|
// AI Analysis - make sure we don't pass undefined
|
484
493
|
renderAIAnalysis(candidate.ai_analysis || {});
|
494
|
+
|
495
|
+
// Screening Responses - make sure we don't pass undefined
|
496
|
+
renderScreeningResponses(candidate.screening_responses || {});
|
485
497
|
} catch (error) {
|
486
498
|
console.error('Error processing candidate data:', error);
|
487
499
|
document.getElementById('resume-details-card').style.display = 'block';
|
@@ -906,6 +918,38 @@
|
|
906
918
|
}
|
907
919
|
}
|
908
920
|
|
921
|
+
// Render screening questions and answers
|
922
|
+
function renderScreeningResponses(screeningData) {
|
923
|
+
const container = document.getElementById('screening-responses-container');
|
924
|
+
container.innerHTML = '';
|
925
|
+
|
926
|
+
if (!screeningData || !screeningData.questions || !screeningData.answers ||
|
927
|
+
screeningData.questions.length === 0 || screeningData.answers.length === 0) {
|
928
|
+
container.innerHTML = '<p class="text-muted">No screening responses available</p>';
|
929
|
+
return;
|
930
|
+
}
|
931
|
+
|
932
|
+
let html = '<div class="list-group">';
|
933
|
+
|
934
|
+
// Loop through questions and answers
|
935
|
+
for (let i = 0; i < Math.min(screeningData.questions.length, screeningData.answers.length); i++) {
|
936
|
+
const question = screeningData.questions[i];
|
937
|
+
const answer = screeningData.answers[i];
|
938
|
+
|
939
|
+
html += `
|
940
|
+
<div class="list-group-item">
|
941
|
+
<div class="d-flex w-100 justify-content-between">
|
942
|
+
<h6 class="mb-2">Q${i+1}: ${question}</h6>
|
943
|
+
</div>
|
944
|
+
<p class="mb-1"><strong>Answer:</strong> ${answer}</p>
|
945
|
+
</div>
|
946
|
+
`;
|
947
|
+
}
|
948
|
+
|
949
|
+
html += '</div>';
|
950
|
+
container.innerHTML = html;
|
951
|
+
}
|
952
|
+
|
909
953
|
// Update statistics
|
910
954
|
function updateStatistics(stats) {
|
911
955
|
document.getElementById('total-resumes').textContent = stats.total;
|
{michael_agent-1.0.3 → michael_agent-1.0.5}/michael_agent/langgraph_workflow/graph_builder.py
RENAMED
@@ -269,7 +269,7 @@ def start_workflow():
|
|
269
269
|
logger.error(traceback.format_exc())
|
270
270
|
raise
|
271
271
|
|
272
|
-
def process_new_resume(resume_path
|
272
|
+
def process_new_resume(resume_path, job_id=None):
|
273
273
|
"""Process a new resume through the workflow"""
|
274
274
|
try:
|
275
275
|
# Extract job ID from filename (e.g., 20250626225207_Michael_Jone.pdf)
|
@@ -280,6 +280,28 @@ def process_new_resume(resume_path: str, job_description: Dict[str, Any] = None)
|
|
280
280
|
# Create initial state with extracted job ID
|
281
281
|
initial_state = create_initial_state(resume_path=resume_path, job_id=job_id)
|
282
282
|
|
283
|
+
# Check for metadata file with screening questions/answers
|
284
|
+
metadata_path = f"{resume_path}.json"
|
285
|
+
if os.path.exists(metadata_path):
|
286
|
+
try:
|
287
|
+
with open(metadata_path, 'r') as f:
|
288
|
+
metadata = json.load(f)
|
289
|
+
|
290
|
+
# Extract screening questions and answers
|
291
|
+
if 'screening_questions' in metadata and 'screening_answers' in metadata:
|
292
|
+
logger.info(f"Found screening questions/answers for resume {resume_path}")
|
293
|
+
initial_state['screening_questions'] = metadata['screening_questions']
|
294
|
+
initial_state['screening_answers'] = metadata['screening_answers']
|
295
|
+
|
296
|
+
# Also extract candidate name and email if available
|
297
|
+
if 'name' in metadata:
|
298
|
+
initial_state['candidate_name'] = metadata['name']
|
299
|
+
if 'email' in metadata:
|
300
|
+
initial_state['candidate_email'] = metadata['email']
|
301
|
+
|
302
|
+
except Exception as e:
|
303
|
+
logger.error(f"Error loading metadata file: {str(e)}")
|
304
|
+
|
283
305
|
# Start workflow with initial state
|
284
306
|
workflow = build_workflow()
|
285
307
|
logger.info(f"[Job {job_id}] Starting workflow processing with job ID {job_id}")
|