CR7CAD commited on
Commit
88107c2
·
verified ·
1 Parent(s): 5be9ab6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -344
app.py CHANGED
@@ -6,8 +6,6 @@ import docx2txt
6
  import tempfile
7
  import time
8
  import re
9
- import math
10
- import concurrent.futures
11
  import pandas as pd
12
  from functools import lru_cache
13
  from transformers import pipeline
@@ -127,58 +125,6 @@ def extract_name(text_start):
127
 
128
  return "Unknown (please extract from resume)"
129
 
130
- def extract_age(text):
131
- """Extract candidate age from resume text"""
132
- # Simplified: just check a few common patterns
133
- age_patterns = [
134
- r'age:?\s*(\d{1,2})',
135
- r'(\d{1,2})\s*years\s*old',
136
- ]
137
-
138
- text_lower = text.lower()
139
- for pattern in age_patterns:
140
- matches = re.search(pattern, text_lower)
141
- if matches:
142
- return matches.group(1)
143
-
144
- return "Not specified"
145
-
146
- def extract_industry(text, base_summary):
147
- """Extract expected job industry from resume"""
148
- # Simplified industry keywords focused on the most common ones
149
- industry_keywords = {
150
- "technology": ["software", "programming", "developer", "IT", "tech", "computer"],
151
- "finance": ["banking", "financial", "accounting", "finance", "analyst"],
152
- "healthcare": ["medical", "health", "hospital", "clinical", "nurse", "doctor"],
153
- "education": ["teaching", "teacher", "professor", "education", "university"],
154
- "marketing": ["marketing", "advertising", "digital marketing", "social media"],
155
- "engineering": ["engineer", "engineering"],
156
- "data science": ["data science", "machine learning", "AI", "analytics"],
157
- "information systems": ["information systems", "ERP", "systems management"]
158
- }
159
-
160
- # Count occurrences of industry keywords - using the summary to speed up
161
- combined_text = base_summary.lower()
162
-
163
- counts = {}
164
- for industry, keywords in industry_keywords.items():
165
- counts[industry] = sum(combined_text.count(keyword.lower()) for keyword in keywords)
166
-
167
- # Get the industry with the highest count
168
- if counts:
169
- likely_industry = max(counts.items(), key=lambda x: x[1])
170
- if likely_industry[1] > 0:
171
- return likely_industry[0].capitalize()
172
-
173
- # Check for educational background that might indicate industry
174
- degrees = ["computer science", "business", "engineering", "medicine", "education", "finance", "marketing"]
175
-
176
- for degree in degrees:
177
- if degree in combined_text:
178
- return f"{degree.capitalize()}-related field"
179
-
180
- return "Not clearly specified"
181
-
182
  def extract_skills_and_work(text):
183
  """Extract both skills and work experience at once to save processing time"""
184
  # Common skill categories - reduced keyword list for speed
@@ -282,26 +228,26 @@ def summarize_resume_text(resume_text):
282
  text_to_summarize = resume_text[:min(len(resume_text), max_input_length)]
283
  base_summary = models['summarizer'](text_to_summarize)[0]['summary_text']
284
 
285
- # Extract information in parallel where possible
286
- with concurrent.futures.ThreadPoolExecutor() as executor:
287
- # These can run in parallel
288
- name_future = executor.submit(extract_name, resume_text[:500]) # Only use start of text
289
- age_future = executor.submit(extract_age, resume_text)
290
- industry_future = executor.submit(extract_industry, resume_text, base_summary)
291
- skills_work_future = executor.submit(extract_skills_and_work, resume_text)
292
-
293
- # Get results
294
- name = name_future.result()
295
- age = age_future.result()
296
- industry = industry_future.result()
297
- skills, work_experience = skills_work_future.result()
298
 
299
  # Format the structured summary
300
- formatted_summary = f"Name: {name}\n"
301
- formatted_summary += f"Age: {age}\n"
302
- formatted_summary += f"Expected Job Industry: {industry}\n\n"
303
  formatted_summary += f"Previous Work Experience: {work_experience}\n\n"
304
- formatted_summary += f"Skills: {skills}"
 
305
 
306
  execution_time = time.time() - start_time
307
 
@@ -312,19 +258,16 @@ def summarize_resume_text(resume_text):
312
  #####################################
313
  def extract_job_requirements(job_description):
314
  """
315
- Extract key requirements and skills from a job description
316
  """
317
- # Common technical skill categories to look for
318
- tech_skill_categories = {
319
- "programming_languages": ["Python", "Java", "C++", "JavaScript", "TypeScript", "Go", "Rust", "SQL", "Ruby", "PHP", "Swift", "Kotlin"],
320
- "web_technologies": ["React", "Angular", "Vue", "Node.js", "HTML", "CSS", "Django", "Flask", "Spring", "REST API", "GraphQL"],
321
- "data_tech": ["Machine Learning", "TensorFlow", "PyTorch", "Data Science", "AI", "Big Data", "Deep Learning", "NLP", "Computer Vision"],
322
- "cloud_devops": ["AWS", "Azure", "GCP", "Docker", "Kubernetes", "CI/CD", "Jenkins", "GitHub Actions", "Terraform", "Serverless"],
323
- "database": ["SQL", "MySQL", "PostgreSQL", "MongoDB", "Redis", "Elasticsearch", "DynamoDB", "Cassandra"],
324
- }
325
-
326
- # Common soft skills to look for
327
- soft_skills = ["Communication", "Leadership", "Teamwork", "Problem-solving", "Critical thinking", "Adaptability", "Creativity", "Time management"]
328
 
329
  # Clean the text for processing
330
  clean_job_text = job_description.lower()
@@ -361,47 +304,18 @@ def extract_job_requirements(job_description):
361
  except:
362
  pass
363
 
364
- # Extract technical skills
365
- found_tech_skills = {}
366
- all_tech_skills = []
367
 
368
- for category, skills in tech_skill_categories.items():
369
- category_skills = []
370
- for skill in skills:
371
- if re.search(r'\b' + re.escape(skill.lower()) + r'\b', clean_job_text):
372
- category_skills.append(skill)
373
- all_tech_skills.append(skill)
374
-
375
- if category_skills:
376
- found_tech_skills[category] = category_skills
377
-
378
- # Extract soft skills
379
- found_soft_skills = []
380
- for skill in soft_skills:
381
- if re.search(r'\b' + re.escape(skill.lower()) + r'\b', clean_job_text):
382
- found_soft_skills.append(skill)
383
-
384
- # Extract educational requirements
385
- edu_patterns = [
386
- r"bachelor'?s degree|bs|b\.s\.",
387
- r"master'?s degree|ms|m\.s\.",
388
- r"phd|ph\.d\.|doctorate",
389
- r"mba|m\.b\.a\."
390
- ]
391
-
392
- education_required = []
393
- for pattern in edu_patterns:
394
- if re.search(pattern, clean_job_text, re.IGNORECASE):
395
- edu_match = re.search(pattern, clean_job_text, re.IGNORECASE).group(0)
396
- education_required.append(edu_match.capitalize())
397
 
398
  # Format the job requirements
399
  job_requirements = {
400
  "title": job_title,
401
  "years_experience": years_required,
402
- "technical_skills": all_tech_skills,
403
- "soft_skills": found_soft_skills,
404
- "education": education_required,
405
  }
406
 
407
  return job_requirements
@@ -412,94 +326,24 @@ def extract_job_requirements(job_description):
412
  def analyze_job_fit(resume_summary, job_description):
413
  """
414
  Analyze how well the candidate fits the job requirements with the DistilBERT sentiment model.
 
415
  """
416
  start_time = time.time()
417
 
418
  # Extract job requirements
419
  job_requirements = extract_job_requirements(job_description)
420
 
421
- # Define skill categories to evaluate against
422
  resume_lower = resume_summary.lower()
423
- job_lower = job_description.lower()
424
-
425
- # Define keyword categories based on the job description
426
- # We'll dynamically build these based on the job requirements
427
- skill_keywords = {
428
- "technical_skills": job_requirements["technical_skills"],
429
- "soft_skills": job_requirements["soft_skills"],
430
- "education": job_requirements["education"],
431
- }
432
 
433
- # Add additional keywords from the job description for comprehensive analysis
434
- additional_keywords = {
435
- "problem_solving": ["problem solving", "analytical", "critical thinking", "troubleshooting", "debugging",
436
- "optimization", "solution", "resolve", "analyze"],
437
- "domain_knowledge": ["industry", "experience", "expertise", "knowledge", "familiar with", "understanding of"],
438
- "collaboration": ["team", "collaborate", "cooperation", "cross-functional", "communication", "stakeholder"]
439
- }
440
 
441
- # Merge the keywords
442
- skill_keywords.update(additional_keywords)
443
-
444
- # Category weights with descriptive labels
445
- category_weights = {
446
- "technical_skills": {"weight": 0.40, "label": "Technical Skills"},
447
- "soft_skills": {"weight": 0.15, "label": "Soft Skills"},
448
- "education": {"weight": 0.10, "label": "Education"},
449
- "problem_solving": {"weight": 0.15, "label": "Problem Solving"},
450
- "domain_knowledge": {"weight": 0.10, "label": "Domain Knowledge"},
451
- "collaboration": {"weight": 0.10, "label": "Collaboration"}
452
- }
453
-
454
- # Calculate category scores and store detailed information
455
- category_scores = {}
456
- category_details = {}
457
- found_skills = {}
458
-
459
- for category, keywords in skill_keywords.items():
460
- if not keywords: # Skip empty categories
461
- category_scores[category] = 0.0
462
- category_details[category] = {
463
- "raw_percentage": 0,
464
- "adjusted_score": 0,
465
- "matching_keywords": [],
466
- "total_keywords": 0,
467
- "matches": 0
468
- }
469
- found_skills[category] = []
470
- continue
471
-
472
- # Find the specific matching keywords for feedback
473
- category_matches = []
474
- for keyword in keywords:
475
- if keyword.lower() in resume_lower:
476
- category_matches.append(keyword)
477
-
478
- found_skills[category] = category_matches
479
-
480
- # Count matches but cap at a reasonable level
481
- matches = len(category_matches)
482
- total_keywords = len(keywords)
483
-
484
- # Calculate raw percentage for this category
485
- raw_percentage = int((matches / max(1, total_keywords)) * 100)
486
-
487
- # Apply logarithmic scaling for more realistic scores
488
- if matches == 0:
489
- adjusted_score = 0.0
490
- else:
491
- # Logarithmic scaling to prevent perfect scores
492
- adjusted_score = min(0.95, (math.log(matches + 1) / math.log(min(total_keywords, 8) + 1)))
493
-
494
- # Store both raw and adjusted scores for feedback
495
- category_scores[category] = adjusted_score
496
- category_details[category] = {
497
- "raw_percentage": raw_percentage,
498
- "adjusted_score": int(adjusted_score * 100),
499
- "matching_keywords": category_matches,
500
- "total_keywords": total_keywords,
501
- "matches": matches
502
- }
503
 
504
  # Check for years of experience match
505
  years_required = job_requirements["years_experience"]
@@ -540,96 +384,53 @@ def analyze_job_fit(resume_summary, job_description):
540
 
541
  experience_years = total_years
542
 
543
- # Calculate experience match score
544
- if years_required > 0:
545
- if experience_years >= years_required:
546
- exp_score = 1.0
547
- else:
548
- exp_score = experience_years / years_required
549
- else:
550
- exp_score = 1.0 # If no specific years required, assume full match
551
-
552
- category_scores["experience"] = exp_score
553
- category_details["experience"] = {
554
- "raw_percentage": int(exp_score * 100),
555
- "adjusted_score": int(exp_score * 100),
556
- "candidate_years": experience_years,
557
- "required_years": years_required
558
- }
559
-
560
- # Calculate weighted score
561
- weighted_score = 0
562
- for category, score in category_scores.items():
563
- if category in category_weights:
564
- weighted_score += score * category_weights[category]["weight"]
565
 
566
- # Add experience separately (not in the original weights)
567
- weighted_score = (weighted_score * 0.8) + (category_scores["experience"] * 0.2)
568
-
569
- # Apply final curve to keep scores in a realistic range
570
- match_percentage = min(95, max(35, int(weighted_score * 100)))
571
-
572
- # Prepare input for sentiment analysis
573
- # Create a structured summary of the match for sentiment model
574
- match_summary = f"""
575
  Job title: {job_requirements['title']}
576
- Match percentage: {match_percentage}%
577
 
578
- Technical skills match: {category_details['technical_skills']['adjusted_score']}%
579
- Required technical skills: {', '.join(job_requirements['technical_skills'][:5])}
580
- Candidate has: {', '.join(found_skills['technical_skills'][:5])}
581
 
582
- Experience match: {category_details['experience']['adjusted_score']}%
583
- Required years: {job_requirements['years_experience']}
584
- Candidate years: {experience_years}
585
 
586
- Education match: {category_details['education']['adjusted_score']}%
 
 
587
 
588
- Overall profile match: The candidate's skills and experience appear to {match_percentage >= 70 and "match well with" or "partially match with"} the job requirements.
589
- """
 
590
 
591
- # Use the sentiment model to get a fit classification
592
- sentiment_result = models['evaluator'](match_summary)
593
 
594
- # Map sentiment analysis to our score:
595
- # NEGATIVE = 0 (poor fit)
596
- # POSITIVE = 1 (good fit)
597
- score_mapping = {
598
- "NEGATIVE": 0,
599
- "POSITIVE": 1
600
- }
601
 
602
- # Get the sentiment score
603
- sentiment_score = score_mapping.get(sentiment_result[0]['label'], 0)
604
 
605
- # Adjust the score based on the match percentage to get our 0,1,2 scale
606
- if sentiment_score == 1 and match_percentage >= 85:
607
- final_score = 2 # Excellent fit
608
- elif sentiment_score == 1:
609
- final_score = 1 # Good fit
610
  else:
611
- final_score = 0 # Poor fit
612
-
613
- # Map to fit status
614
- fit_status_map = {
615
- 0: "NOT FIT",
616
- 1: "POTENTIAL FIT",
617
- 2: "STRONG FIT"
618
- }
619
 
620
- fit_status = fit_status_map[final_score]
621
-
622
- # Generate assessment summary based on the score
623
  if final_score == 2:
624
- assessment = f"{final_score}: The candidate is a strong match for this {job_requirements['title']} position, with excellent alignment in technical skills and experience. Their background demonstrates the required expertise in key areas such as {', '.join(found_skills['technical_skills'][:3]) if found_skills['technical_skills'] else 'relevant technical domains'}, and they possess the necessary {experience_years} years of experience (required: {years_required})."
625
  elif final_score == 1:
626
- assessment = f"{final_score}: The candidate shows potential for this {job_requirements['title']} position, with some good matches in required skills. They demonstrate experience with {', '.join(found_skills['technical_skills'][:2]) if found_skills['technical_skills'] else 'some relevant technologies'}, but may need development in areas like {', '.join(set(job_requirements['technical_skills']) - set(found_skills['technical_skills']))[:2] if set(job_requirements['technical_skills']) - set(found_skills['technical_skills']) else 'specific technical requirements'}."
627
  else:
628
- assessment = f"{final_score}: The candidate does not appear to be a strong match for this {job_requirements['title']} position. Their profile shows limited alignment with key requirements, particularly in {', '.join(set(job_requirements['technical_skills']) - set(found_skills['technical_skills']))[:3] if set(job_requirements['technical_skills']) - set(found_skills['technical_skills']) else 'required technical skills'}, and they have {experience_years} years of experience (required: {years_required})."
629
 
630
  execution_time = time.time() - start_time
631
 
632
- return assessment, final_score, match_percentage, category_details, job_requirements, execution_time
633
 
634
  #####################################
635
  # Main Streamlit Interface
@@ -637,10 +438,7 @@ def analyze_job_fit(resume_summary, job_description):
637
  st.title("Resume-Job Fit Analyzer")
638
  st.markdown(
639
  """
640
- Upload your resume file in **.docx**, **.doc**, or **.txt** format and enter a job description to see how well you match with the job requirements. The app performs the following tasks:
641
- 1. Extracts text from your resume.
642
- 2. Uses AI to generate a structured candidate summary.
643
- 3. Analyzes how well your profile fits the specific job requirements.
644
  """
645
  )
646
 
@@ -672,11 +470,10 @@ if uploaded_file is not None and job_description and st.button("Analyze Job Fit"
672
  # Display summary
673
  st.subheader("Your Resume Summary")
674
  st.markdown(summary)
675
- st.info(f"Summary generated in {summarization_time:.2f} seconds")
676
 
677
  # Step 3: Generate job fit assessment
678
  status_text.text("Step 3/3: Evaluating job fit...")
679
- assessment, fit_score, match_percentage, category_details, job_requirements, assessment_time = analyze_job_fit(summary, job_description)
680
  progress_bar.progress(100)
681
 
682
  # Clear status messages
@@ -689,92 +486,35 @@ if uploaded_file is not None and job_description and st.button("Analyze Job Fit"
689
  fit_labels = {
690
  0: "NOT FIT ❌",
691
  1: "POTENTIAL FIT ⚠️",
692
- 2: "STRONG FIT ✅"
693
  }
694
 
695
  # Show the score prominently
696
- st.markdown(f"## Overall Result: {fit_labels[fit_score]}")
697
-
698
- # Display match percentage
699
- if match_percentage >= 85:
700
- st.success(f"**Match Score:** {match_percentage}% 🌟")
701
- elif match_percentage >= 70:
702
- st.success(f"**Match Score:** {match_percentage}% ✅")
703
- elif match_percentage >= 50:
704
- st.warning(f"**Match Score:** {match_percentage}% ⚠️")
705
- else:
706
- st.error(f"**Match Score:** {match_percentage}% 🔍")
707
 
708
  # Display assessment
709
- st.markdown("### Assessment")
710
  st.markdown(assessment)
711
 
712
- # Add detailed score breakdown
713
- st.markdown("### Score Breakdown")
714
-
715
- # Create a neat table with category scores
716
- breakdown_data = []
717
- for category, details in category_details.items():
718
- if category == "experience":
719
- label = "Experience"
720
- matching_info = f"{details['candidate_years']} years (Required: {details['required_years']} years)"
721
- else:
722
- # Get the nice label for the category
723
- label = {"technical_skills": "Technical Skills",
724
- "soft_skills": "Soft Skills",
725
- "education": "Education",
726
- "problem_solving": "Problem Solving",
727
- "domain_knowledge": "Domain Knowledge",
728
- "collaboration": "Collaboration"}[category]
729
-
730
- matching_info = ", ".join(details["matching_keywords"][:3]) if details.get("matching_keywords") else "None detected"
731
-
732
- # Add formatted breakdown row
733
- breakdown_data.append({
734
- "Category": label,
735
- "Score": f"{details['adjusted_score']}%",
736
- "Matching Items": matching_info
737
- })
738
-
739
- # Convert to DataFrame and display
740
- breakdown_df = pd.DataFrame(breakdown_data)
741
- # Remove the index column entirely
742
- st.table(breakdown_df.set_index('Category').reset_index()) # This removes the numerical index
743
-
744
- # Show a note about how scores are calculated
745
- with st.expander("How are these scores calculated?"):
746
- st.markdown("""
747
- - **Technical Skills** (40% of total): Evaluates programming languages, software tools, and technical requirements
748
- - **Soft Skills** (15% of total): Assesses communication, teamwork, and interpersonal abilities
749
- - **Education** (10% of total): Compares educational requirements with candidate's background
750
- - **Problem Solving** (15% of total): Measures analytical thinking and approach to challenges
751
- - **Domain Knowledge** (10% of total): Evaluates industry-specific experience and knowledge
752
- - **Collaboration** (10% of total): Assesses team skills and cross-functional collaboration
753
- - **Experience** (20% overall modifier): Years of relevant experience compared to job requirements
754
-
755
- Scores are calculated based on keyword matches in your resume, with diminishing returns applied (first few skills matter more than later ones).
756
- """)
757
-
758
- st.info(f"Assessment completed in {assessment_time:.2f} seconds")
759
 
760
  # Add potential next steps based on the fit score
761
  st.subheader("Recommended Next Steps")
762
 
763
  if fit_score == 2:
764
  st.markdown("""
765
- - Consider applying for this position as you appear to be a strong match
766
- - Prepare for technical interviews by focusing on your strongest skills
767
- - Review the job description again to prepare for specific interview questions
768
  """)
769
  elif fit_score == 1:
770
  st.markdown("""
771
- - Focus on highlighting your strongest matching skills in your application
772
- - Consider addressing skill gaps in your cover letter by connecting your experience to the requirements
773
- - Prepare to discuss how your transferable skills apply to this position
774
  """)
775
  else:
776
  st.markdown("""
777
- - This position may not be the best fit for your current skills and experience
778
- - Consider roles that better align with your demonstrated strengths
779
- - If you're set on this type of position, focus on developing skills in the areas mentioned in the job description
780
  """)
 
6
  import tempfile
7
  import time
8
  import re
 
 
9
  import pandas as pd
10
  from functools import lru_cache
11
  from transformers import pipeline
 
125
 
126
  return "Unknown (please extract from resume)"
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  def extract_skills_and_work(text):
129
  """Extract both skills and work experience at once to save processing time"""
130
  # Common skill categories - reduced keyword list for speed
 
228
  text_to_summarize = resume_text[:min(len(resume_text), max_input_length)]
229
  base_summary = models['summarizer'](text_to_summarize)[0]['summary_text']
230
 
231
+ # Extract name from the beginning of the resume
232
+ name = extract_name(resume_text[:500])
233
+
234
+ # Extract skills and work experience
235
+ skills, work_experience = extract_skills_and_work(resume_text)
236
+
237
+ # Extract education level - simplified approach
238
+ education_level = "Not specified"
239
+ education_terms = ["bachelor", "master", "phd", "doctorate", "mba", "degree"]
240
+ for term in education_terms:
241
+ if term in resume_text.lower():
242
+ education_level = "Higher education degree mentioned"
243
+ break
244
 
245
  # Format the structured summary
246
+ formatted_summary = f"Name: {name}\n\n"
247
+ formatted_summary += f"Summary: {base_summary}\n\n"
 
248
  formatted_summary += f"Previous Work Experience: {work_experience}\n\n"
249
+ formatted_summary += f"Skills: {skills}\n\n"
250
+ formatted_summary += f"Education: {education_level}"
251
 
252
  execution_time = time.time() - start_time
253
 
 
258
  #####################################
259
  def extract_job_requirements(job_description):
260
  """
261
+ Extract key requirements from a job description
262
  """
263
+ # Common technical skills to look for
264
+ tech_skills = [
265
+ "Python", "Java", "C++", "JavaScript", "TypeScript", "Go", "Rust", "SQL", "Ruby", "PHP", "Swift", "Kotlin",
266
+ "React", "Angular", "Vue", "Node.js", "HTML", "CSS", "Django", "Flask", "Spring", "REST API", "GraphQL",
267
+ "Machine Learning", "TensorFlow", "PyTorch", "Data Science", "AI", "Big Data", "Deep Learning", "NLP",
268
+ "AWS", "Azure", "GCP", "Docker", "Kubernetes", "CI/CD", "Jenkins", "GitHub Actions", "Terraform",
269
+ "MySQL", "PostgreSQL", "MongoDB", "Redis", "Elasticsearch", "DynamoDB", "Cassandra"
270
+ ]
 
 
 
271
 
272
  # Clean the text for processing
273
  clean_job_text = job_description.lower()
 
304
  except:
305
  pass
306
 
307
+ # Extract required skills
308
+ required_skills = [skill for skill in tech_skills if re.search(r'\b' + re.escape(skill.lower()) + r'\b', clean_job_text)]
 
309
 
310
+ # Create a simple summary of the job
311
+ job_summary = models['summarizer'](job_description[:1024])[0]['summary_text']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
  # Format the job requirements
314
  job_requirements = {
315
  "title": job_title,
316
  "years_experience": years_required,
317
+ "required_skills": required_skills,
318
+ "summary": job_summary
 
319
  }
320
 
321
  return job_requirements
 
326
  def analyze_job_fit(resume_summary, job_description):
327
  """
328
  Analyze how well the candidate fits the job requirements with the DistilBERT sentiment model.
329
+ Returns a fit score (0-2) and an assessment.
330
  """
331
  start_time = time.time()
332
 
333
  # Extract job requirements
334
  job_requirements = extract_job_requirements(job_description)
335
 
336
+ # Now prepare a comparison text for sentiment analysis
337
  resume_lower = resume_summary.lower()
 
 
 
 
 
 
 
 
 
338
 
339
+ # Extract skills mentioned in resume
340
+ skills_in_resume = []
341
+ for skill in job_requirements["required_skills"]:
342
+ if skill.lower() in resume_lower:
343
+ skills_in_resume.append(skill)
 
 
344
 
345
+ # Count how many required skills are found in the resume
346
+ skills_match_percentage = int((len(skills_in_resume) / max(1, len(job_requirements["required_skills"]))) * 100)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
 
348
  # Check for years of experience match
349
  years_required = job_requirements["years_experience"]
 
384
 
385
  experience_years = total_years
386
 
387
+ # Check experience match
388
+ experience_match = "sufficient" if experience_years >= years_required else "insufficient"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
+ # Prepare a comparison summary for sentiment analysis
391
+ comparison_text = f"""
 
 
 
 
 
 
 
392
  Job title: {job_requirements['title']}
 
393
 
394
+ Job summary: {job_requirements['summary']}
 
 
395
 
396
+ Candidate summary: {resume_summary[:500]}
 
 
397
 
398
+ Required skills: {', '.join(job_requirements['required_skills'])}
399
+ Skills in resume: {', '.join(skills_in_resume)}
400
+ Skills match: {skills_match_percentage}%
401
 
402
+ Required experience: {years_required} years
403
+ Candidate experience: {experience_years} years
404
+ Experience match: {experience_match}
405
 
406
+ Overall assessment: The candidate's skills and experience {"appear to match well with" if skills_match_percentage >= 60 and experience_match == "sufficient" else "have some gaps compared to"} the job requirements.
407
+ """
408
 
409
+ # Use sentiment analysis model to evaluate the comparison
410
+ sentiment_result = models['evaluator'](comparison_text)
 
 
 
 
 
411
 
412
+ # Map sentiment to score: NEGATIVE = 0, POSITIVE = 1
413
+ sentiment_score = 1 if sentiment_result[0]['label'] == 'POSITIVE' else 0
414
 
415
+ # Derive final score based on sentiment and match metrics
416
+ if sentiment_score == 1 and skills_match_percentage >= 70 and experience_match == "sufficient":
417
+ final_score = 2 # Good fit
418
+ elif sentiment_score == 1 and skills_match_percentage >= 50:
419
+ final_score = 1 # Potential fit
420
  else:
421
+ final_score = 0 # Not fit
 
 
 
 
 
 
 
422
 
423
+ # Generate assessment text based on the score
 
 
424
  if final_score == 2:
425
+ assessment = f"{final_score}: The candidate is a good match for this {job_requirements['title']} position. They have the required {experience_years} years of experience and demonstrate proficiency in key skills including {', '.join(skills_in_resume[:5])}. Their background aligns well with the job requirements."
426
  elif final_score == 1:
427
+ assessment = f"{final_score}: The candidate shows potential for this {job_requirements['title']} position, but has some skill gaps. They match on {skills_match_percentage}% of required skills including {', '.join(skills_in_resume[:3]) if skills_in_resume else 'minimal required skills'}, and their experience is {experience_match}."
428
  else:
429
+ assessment = f"{final_score}: The candidate does not appear to be a good match for this {job_requirements['title']} position. Their profile shows limited alignment with key requirements, matching only {skills_match_percentage}% of required skills, and their experience level is {experience_match}."
430
 
431
  execution_time = time.time() - start_time
432
 
433
+ return assessment, final_score, execution_time
434
 
435
  #####################################
436
  # Main Streamlit Interface
 
438
  st.title("Resume-Job Fit Analyzer")
439
  st.markdown(
440
  """
441
+ Upload your resume file in **.docx**, **.doc**, or **.txt** format and enter a job description to see how well you match with the job requirements.
 
 
 
442
  """
443
  )
444
 
 
470
  # Display summary
471
  st.subheader("Your Resume Summary")
472
  st.markdown(summary)
 
473
 
474
  # Step 3: Generate job fit assessment
475
  status_text.text("Step 3/3: Evaluating job fit...")
476
+ assessment, fit_score, assessment_time = analyze_job_fit(summary, job_description)
477
  progress_bar.progress(100)
478
 
479
  # Clear status messages
 
486
  fit_labels = {
487
  0: "NOT FIT ❌",
488
  1: "POTENTIAL FIT ⚠️",
489
+ 2: "GOOD FIT ✅"
490
  }
491
 
492
  # Show the score prominently
493
+ st.markdown(f"## {fit_labels[fit_score]}")
 
 
 
 
 
 
 
 
 
 
494
 
495
  # Display assessment
 
496
  st.markdown(assessment)
497
 
498
+ st.info(f"Analysis completed in {(summarization_time + assessment_time):.2f} seconds")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
 
500
  # Add potential next steps based on the fit score
501
  st.subheader("Recommended Next Steps")
502
 
503
  if fit_score == 2:
504
  st.markdown("""
505
+ - Apply for this position as you appear to be a good match
506
+ - Prepare for interviews by focusing on your relevant experience
507
+ - Highlight your matching skills in your cover letter
508
  """)
509
  elif fit_score == 1:
510
  st.markdown("""
511
+ - Consider applying but address skill gaps in your cover letter
512
+ - Emphasize transferable skills and relevant experience
513
+ - Prepare to discuss how you can quickly develop missing skills
514
  """)
515
  else:
516
  st.markdown("""
517
+ - Look for positions better aligned with your current skills
518
+ - If interested in this field, focus on developing the required skills
519
+ - Consider similar roles with fewer experience requirements
520
  """)