Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -167,10 +167,10 @@ def basic_summarize(text, max_length=100):
|
|
167 |
summary = " ".join(summary_sentences)
|
168 |
return summary
|
169 |
|
170 |
-
#
|
171 |
def evaluate_job_fit(resume_summary, job_requirements, models):
|
172 |
"""
|
173 |
-
Use
|
174 |
"""
|
175 |
start_time = time.time()
|
176 |
|
@@ -180,121 +180,41 @@ def evaluate_job_fit(resume_summary, job_requirements, models):
|
|
180 |
job_title = job_requirements["title"]
|
181 |
job_summary = job_requirements["summary"]
|
182 |
|
183 |
-
#
|
184 |
-
|
185 |
-
RESUME SUMMARY:
|
186 |
-
{resume_summary}
|
187 |
-
|
188 |
-
JOB DESCRIPTION:
|
189 |
-
Title: {job_title}
|
190 |
-
Required experience: {years_required} years
|
191 |
-
Required skills: {', '.join(required_skills)}
|
192 |
-
Description: {job_summary}
|
193 |
-
|
194 |
-
TASK: Analyze how well the candidate matches this job based on:
|
195 |
-
1. Technical skills match
|
196 |
-
2. Experience level match
|
197 |
-
3. Role/position alignment
|
198 |
-
4. Industry familiarity
|
199 |
-
5. Potential for success in this position
|
200 |
-
|
201 |
-
Assign a score from 0-2 where:
|
202 |
-
0 = NOT FIT (major gaps in requirements)
|
203 |
-
1 = POTENTIAL FIT (meets some key requirements)
|
204 |
-
2 = GOOD FIT (meets most or all key requirements)
|
205 |
-
"""
|
206 |
-
|
207 |
-
# Truncate prompt if needed to fit model's input limits
|
208 |
-
max_prompt_length = 1024 # Set a reasonable limit
|
209 |
-
if len(analysis_prompt) > max_prompt_length:
|
210 |
-
analysis_prompt = analysis_prompt[:max_prompt_length]
|
211 |
-
|
212 |
-
# Use sentiment analysis model for evaluation
|
213 |
-
fit_score = 0 # Default score
|
214 |
-
|
215 |
-
# Run multiple sub-analyses to build confidence in our result
|
216 |
-
sub_analyses = []
|
217 |
-
|
218 |
-
# Function to run model evaluation
|
219 |
-
def run_model_evaluation(prompt_text):
|
220 |
-
if has_pipeline and 'evaluator' in models:
|
221 |
-
result = models['evaluator'](prompt_text)
|
222 |
-
# Convert sentiment to score
|
223 |
-
if result[0]['label'] == 'POSITIVE' and result[0]['score'] > 0.8:
|
224 |
-
return 2 # Strong positive = good fit
|
225 |
-
elif result[0]['label'] == 'NEUTRAL':
|
226 |
-
return 1 # neutral fit = potential fit
|
227 |
-
else:
|
228 |
-
return 0 # Negative = not fit
|
229 |
-
else:
|
230 |
-
# Manual implementation if pipeline not available
|
231 |
-
tokenizer = models['evaluator_tokenizer']
|
232 |
-
model = models['evaluator_model']
|
233 |
-
|
234 |
-
# Truncate to avoid exceeding model's max length
|
235 |
-
max_length = tokenizer.model_max_length if hasattr(tokenizer, 'model_max_length') else 512
|
236 |
-
truncated_text = " ".join(prompt_text.split()[:max_length])
|
237 |
-
|
238 |
-
inputs = tokenizer(truncated_text, return_tensors="pt", truncation=True, max_length=max_length)
|
239 |
-
with torch.no_grad():
|
240 |
-
outputs = model(**inputs)
|
241 |
-
|
242 |
-
probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)
|
243 |
-
positive_prob = probabilities[0][1].item() # Positive class probability
|
244 |
-
|
245 |
-
# Convert probability to score
|
246 |
-
if positive_prob > 0.8:
|
247 |
-
return 2
|
248 |
-
elif positive_prob > 0.6:
|
249 |
-
return 1
|
250 |
-
else:
|
251 |
-
return 0
|
252 |
|
253 |
-
#
|
254 |
-
|
255 |
-
|
256 |
-
JOB REQUIRED SKILLS: {', '.join(required_skills)}
|
257 |
-
|
258 |
-
Does the candidate have most of the required technical skills for this position?
|
259 |
-
"""
|
260 |
-
skills_score = run_model_evaluation(skills_prompt)
|
261 |
-
sub_analyses.append(skills_score)
|
262 |
|
263 |
-
#
|
264 |
-
|
265 |
-
|
266 |
-
|
|
|
|
|
|
|
|
|
|
|
267 |
|
268 |
-
|
269 |
-
|
270 |
-
experience_score = run_model_evaluation(experience_prompt)
|
271 |
-
sub_analyses.append(experience_score)
|
272 |
|
273 |
-
#
|
274 |
-
|
275 |
-
|
276 |
-
JOB ROLE: {job_title}, {job_summary}
|
277 |
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
|
283 |
-
# Calculate
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
|
288 |
-
#
|
289 |
-
if weighted_score >= 1.5:
|
290 |
-
fit_score = 2
|
291 |
-
elif weighted_score >= 0.8:
|
292 |
-
fit_score = 1
|
293 |
-
else:
|
294 |
-
fit_score = 0
|
295 |
-
|
296 |
-
# Extract key information from resume for assessment
|
297 |
-
# Parse name, age, industry from resume summary
|
298 |
name_match = re.search(r'Name:\s*(.*?)(?=\n|\Z)', resume_summary)
|
299 |
name = name_match.group(1).strip() if name_match else "The candidate"
|
300 |
|
@@ -304,19 +224,27 @@ def evaluate_job_fit(resume_summary, job_requirements, models):
|
|
304 |
industry_match = re.search(r'Expected Industry:\s*(.*?)(?=\n|\Z)', resume_summary)
|
305 |
industry = industry_match.group(1).strip() if industry_match else "unspecified industry"
|
306 |
|
307 |
-
#
|
308 |
-
|
309 |
-
|
310 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
311 |
|
312 |
-
# Generate assessment text based on score with more holistic evaluation
|
313 |
if fit_score == 2:
|
314 |
-
fit_assessment = f"{fit_score}: {name} demonstrates strong alignment with the {job_title} position. Their background in {industry} and professional experience appear well-suited for this role's requirements. The technical expertise matches what the position demands."
|
315 |
elif fit_score == 1:
|
316 |
-
fit_assessment = f"{fit_score}: {name} shows potential for the {job_title} role with some relevant experience, though there are gaps in certain technical areas. Their {industry} background provides partial alignment with the position requirements. Additional training might be needed in {', '.join(missing_skills[:2])} if pursuing this opportunity."
|
317 |
else:
|
318 |
-
|
319 |
-
fit_assessment = f"{fit_score}: {name}'s current background shows limited alignment with this {job_title} position. Their experience level and technical background differ significantly from the role requirements. A position better matching their {industry} expertise might be more suitable."
|
320 |
|
321 |
execution_time = time.time() - start_time
|
322 |
|
@@ -511,15 +439,15 @@ def extract_skills(text):
|
|
511 |
"""Extract key skills from the resume"""
|
512 |
# Common skill categories - reduced keyword list for speed
|
513 |
skill_categories = {
|
514 |
-
"Programming": ["Python", "Java", "JavaScript", "HTML", "CSS", "SQL", "C++", "C#", "Go"],
|
515 |
-
"Data Science": ["Machine Learning", "Data Analysis", "Statistics", "TensorFlow", "PyTorch", "AI", "Algorithms"],
|
516 |
-
"Database": ["SQL", "MySQL", "MongoDB", "Database", "NoSQL", "PostgreSQL"],
|
517 |
-
"Web Development": ["React", "Angular", "Node.js", "Frontend", "Backend", "Full-Stack"],
|
518 |
-
"Software Development": ["Agile", "Scrum", "Git", "DevOps", "Docker", "System Design"],
|
519 |
-
"Cloud": ["AWS", "Azure", "Google Cloud", "Cloud Computing"],
|
520 |
"Security": ["Cybersecurity", "Network Security", "Encryption", "Security"],
|
521 |
-
"Business": ["Project Management", "Business Analysis", "Leadership", "Teamwork"],
|
522 |
-
"Design": ["UX/UI", "User Experience", "Design Thinking", "Adobe"]
|
523 |
}
|
524 |
|
525 |
# Process everything at once
|
@@ -589,13 +517,19 @@ def extract_job_requirements(job_description, models):
|
|
589 |
"""
|
590 |
Extract key requirements from a job description
|
591 |
"""
|
592 |
-
# Common technical skills to look for
|
593 |
tech_skills = [
|
594 |
"Python", "Java", "C++", "JavaScript", "TypeScript", "Go", "Rust", "SQL", "Ruby", "PHP", "Swift", "Kotlin",
|
595 |
"React", "Angular", "Vue", "Node.js", "HTML", "CSS", "Django", "Flask", "Spring", "REST API", "GraphQL",
|
596 |
"Machine Learning", "TensorFlow", "PyTorch", "Data Science", "AI", "Big Data", "Deep Learning", "NLP",
|
597 |
"AWS", "Azure", "GCP", "Docker", "Kubernetes", "CI/CD", "Jenkins", "GitHub Actions", "Terraform",
|
598 |
-
"MySQL", "PostgreSQL", "MongoDB", "Redis", "Elasticsearch", "DynamoDB", "Cassandra"
|
|
|
|
|
|
|
|
|
|
|
|
|
599 |
]
|
600 |
|
601 |
# Clean the text for processing
|
@@ -636,6 +570,19 @@ def extract_job_requirements(job_description, models):
|
|
636 |
# Extract required skills
|
637 |
required_skills = [skill for skill in tech_skills if re.search(r'\b' + re.escape(skill.lower()) + r'\b', clean_job_text)]
|
638 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
639 |
# Create a simple summary of the job using the summarize_text function
|
640 |
job_summary = summarize_text(job_description, models, max_length=100)
|
641 |
|
@@ -729,8 +676,10 @@ def main():
|
|
729 |
2: "GOOD FIT"
|
730 |
}
|
731 |
|
732 |
-
# Show the score prominently
|
733 |
-
|
|
|
|
|
734 |
|
735 |
# Display assessment
|
736 |
st.markdown(assessment)
|
|
|
167 |
summary = " ".join(summary_sentences)
|
168 |
return summary
|
169 |
|
170 |
+
# Modified job fit evaluation function that uses a direct scoring approach
|
171 |
def evaluate_job_fit(resume_summary, job_requirements, models):
|
172 |
"""
|
173 |
+
Use a more direct method to evaluate job fit, rather than relying solely on sentiment analysis
|
174 |
"""
|
175 |
start_time = time.time()
|
176 |
|
|
|
180 |
job_title = job_requirements["title"]
|
181 |
job_summary = job_requirements["summary"]
|
182 |
|
183 |
+
# Extract skills from resume
|
184 |
+
skills_mentioned = extract_skills(resume_summary)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
|
186 |
+
# Calculate skill match percentage
|
187 |
+
matching_skills = [skill for skill in required_skills if skill in skills_mentioned]
|
188 |
+
skill_match_percentage = len(matching_skills) / len(required_skills) if required_skills else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
189 |
|
190 |
+
# Extract experience level from resume
|
191 |
+
experience_pattern = r'(\d+)\+?\s*years?\s*(?:of)?\s*experience'
|
192 |
+
experience_match = re.search(experience_pattern, resume_summary, re.IGNORECASE)
|
193 |
+
years_experience = 0
|
194 |
+
if experience_match:
|
195 |
+
try:
|
196 |
+
years_experience = int(experience_match.group(1))
|
197 |
+
except:
|
198 |
+
years_experience = 0
|
199 |
|
200 |
+
# Experience match
|
201 |
+
exp_match_ratio = min(1.0, years_experience / max(1, years_required)) if years_required > 0 else 0.5
|
|
|
|
|
202 |
|
203 |
+
# Check job title match
|
204 |
+
job_title_lower = job_title.lower()
|
205 |
+
title_match = 0
|
|
|
206 |
|
207 |
+
# Look for job title words in resume
|
208 |
+
title_words = [word for word in job_title_lower.split() if len(word) > 3]
|
209 |
+
title_matches = sum(1 for word in title_words if word in resume_summary.lower())
|
210 |
+
title_match = title_matches / len(title_words) if title_words else 0
|
211 |
|
212 |
+
# Calculate scores for each dimension
|
213 |
+
skill_score = min(2, skill_match_percentage * 3) # 0-2 scale
|
214 |
+
exp_score = min(2, exp_match_ratio * 2) # 0-2 scale
|
215 |
+
title_score = min(2, title_match * 2) # 0-2 scale
|
216 |
|
217 |
+
# Extract name, age, industry from resume summary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
name_match = re.search(r'Name:\s*(.*?)(?=\n|\Z)', resume_summary)
|
219 |
name = name_match.group(1).strip() if name_match else "The candidate"
|
220 |
|
|
|
224 |
industry_match = re.search(r'Expected Industry:\s*(.*?)(?=\n|\Z)', resume_summary)
|
225 |
industry = industry_match.group(1).strip() if industry_match else "unspecified industry"
|
226 |
|
227 |
+
# Calculate weighted final score
|
228 |
+
# Skills: 50%, Experience: 30%, Title match: 20%
|
229 |
+
weighted_score = (skill_score * 0.5) + (exp_score * 0.3) + (title_score * 0.2)
|
230 |
+
|
231 |
+
# Convert to integer score (0-2)
|
232 |
+
if weighted_score >= 1.5:
|
233 |
+
fit_score = 2 # Good fit
|
234 |
+
elif weighted_score >= 0.8:
|
235 |
+
fit_score = 1 # Potential fit
|
236 |
+
else:
|
237 |
+
fit_score = 0 # Not a fit
|
238 |
+
|
239 |
+
# Generate assessment text based on score
|
240 |
+
missing_skills = [skill for skill in required_skills if skill not in skills_mentioned]
|
241 |
|
|
|
242 |
if fit_score == 2:
|
243 |
+
fit_assessment = f"{fit_score}: GOOD FIT - {name} demonstrates strong alignment with the {job_title} position. Their background in {industry} and professional experience appear well-suited for this role's requirements. The technical expertise matches what the position demands."
|
244 |
elif fit_score == 1:
|
245 |
+
fit_assessment = f"{fit_score}: POTENTIAL FIT - {name} shows potential for the {job_title} role with some relevant experience, though there are gaps in certain technical areas. Their {industry} background provides partial alignment with the position requirements. Additional training might be needed in {', '.join(missing_skills[:2])} if pursuing this opportunity."
|
246 |
else:
|
247 |
+
fit_assessment = f"{fit_score}: NOT FIT - {name}'s current background shows limited alignment with this {job_title} position. Their experience level and technical background differ significantly from the role requirements. A position better matching their {industry} expertise might be more suitable."
|
|
|
248 |
|
249 |
execution_time = time.time() - start_time
|
250 |
|
|
|
439 |
"""Extract key skills from the resume"""
|
440 |
# Common skill categories - reduced keyword list for speed
|
441 |
skill_categories = {
|
442 |
+
"Programming": ["Python", "Java", "JavaScript", "HTML", "CSS", "SQL", "C++", "C#", "Go", "React", "Angular", "Vue", "Node.js"],
|
443 |
+
"Data Science": ["Machine Learning", "Data Analysis", "Statistics", "TensorFlow", "PyTorch", "AI", "Algorithms", "NLP", "Deep Learning"],
|
444 |
+
"Database": ["SQL", "MySQL", "MongoDB", "Database", "NoSQL", "PostgreSQL", "Oracle", "Redis"],
|
445 |
+
"Web Development": ["React", "Angular", "Node.js", "Frontend", "Backend", "Full-Stack", "REST API", "GraphQL"],
|
446 |
+
"Software Development": ["Agile", "Scrum", "Git", "DevOps", "Docker", "System Design", "CI/CD", "Jenkins"],
|
447 |
+
"Cloud": ["AWS", "Azure", "Google Cloud", "Cloud Computing", "Lambda", "S3", "EC2"],
|
448 |
"Security": ["Cybersecurity", "Network Security", "Encryption", "Security"],
|
449 |
+
"Business": ["Project Management", "Business Analysis", "Leadership", "Teamwork", "Agile", "Scrum"],
|
450 |
+
"Design": ["UX/UI", "User Experience", "Design Thinking", "Adobe", "Figma"]
|
451 |
}
|
452 |
|
453 |
# Process everything at once
|
|
|
517 |
"""
|
518 |
Extract key requirements from a job description
|
519 |
"""
|
520 |
+
# Common technical skills to look for - expanded list for better matching
|
521 |
tech_skills = [
|
522 |
"Python", "Java", "C++", "JavaScript", "TypeScript", "Go", "Rust", "SQL", "Ruby", "PHP", "Swift", "Kotlin",
|
523 |
"React", "Angular", "Vue", "Node.js", "HTML", "CSS", "Django", "Flask", "Spring", "REST API", "GraphQL",
|
524 |
"Machine Learning", "TensorFlow", "PyTorch", "Data Science", "AI", "Big Data", "Deep Learning", "NLP",
|
525 |
"AWS", "Azure", "GCP", "Docker", "Kubernetes", "CI/CD", "Jenkins", "GitHub Actions", "Terraform",
|
526 |
+
"MySQL", "PostgreSQL", "MongoDB", "Redis", "Elasticsearch", "DynamoDB", "Cassandra", "Oracle",
|
527 |
+
"Project Management", "Agile", "Scrum", "UX/UI", "Design", "Leadership", "Team Management",
|
528 |
+
"Communication Skills", "Problem Solving", "Critical Thinking", "Blockchain", "Information Security",
|
529 |
+
"Networking", "Linux", "Windows Server", "Excel", "PowerPoint", "Word", "Tableau", "Power BI", "R",
|
530 |
+
"SPSS", "SAS", "Spark", "Hadoop", "JIRA", "Confluence", "Git", "SVN", "Testing", "QA", "DevOps",
|
531 |
+
"Full Stack", "Mobile Development", "Android", "iOS", "React Native", "Flutter", "SEO", "Marketing",
|
532 |
+
"Sales", "Customer Service", "Business Analysis", "Data Analysis", "Accounting", "Finance"
|
533 |
]
|
534 |
|
535 |
# Clean the text for processing
|
|
|
570 |
# Extract required skills
|
571 |
required_skills = [skill for skill in tech_skills if re.search(r'\b' + re.escape(skill.lower()) + r'\b', clean_job_text)]
|
572 |
|
573 |
+
# If no skills found, use some default important ones to avoid empty lists
|
574 |
+
if not required_skills:
|
575 |
+
# Extract some common words that might be skills
|
576 |
+
words = re.findall(r'\b\w{4,}\b', clean_job_text)
|
577 |
+
word_counts = {}
|
578 |
+
for word in words:
|
579 |
+
if word not in ["with", "that", "this", "have", "from", "they", "will", "what", "your", "their", "about"]:
|
580 |
+
word_counts[word] = word_counts.get(word, 0) + 1
|
581 |
+
|
582 |
+
# Get the top 5 most common words as potential skills
|
583 |
+
sorted_words = sorted(word_counts.items(), key=lambda x: x[1], reverse=True)
|
584 |
+
required_skills = [word.capitalize() for word, _ in sorted_words[:5]]
|
585 |
+
|
586 |
# Create a simple summary of the job using the summarize_text function
|
587 |
job_summary = summarize_text(job_description, models, max_length=100)
|
588 |
|
|
|
676 |
2: "GOOD FIT"
|
677 |
}
|
678 |
|
679 |
+
# Show the score prominently with appropriate coloring
|
680 |
+
score_label = fit_labels[fit_score]
|
681 |
+
score_colors = {0: "red", 1: "orange", 2: "green"}
|
682 |
+
st.markdown(f"<h2 style='color: {score_colors[fit_score]};'>{score_label}</h2>", unsafe_allow_html=True)
|
683 |
|
684 |
# Display assessment
|
685 |
st.markdown(assessment)
|