CR7CAD commited on
Commit
5be9ab6
Β·
verified Β·
1 Parent(s): fc55093

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -157
app.py CHANGED
@@ -42,11 +42,10 @@ def load_models():
42
  truncation=True
43
  )
44
 
45
- # Load model for evaluation
46
  models['evaluator'] = pipeline(
47
- "text2text-generation",
48
- model="Qwen/Qwen2.5-0.5B-Instruct",
49
- max_length=300
50
  )
51
 
52
  return models
@@ -412,7 +411,7 @@ def extract_job_requirements(job_description):
412
  #####################################
413
  def analyze_job_fit(resume_summary, job_description):
414
  """
415
- Analyze how well the candidate fits the job requirements with detailed category breakdowns.
416
  """
417
  start_time = time.time()
418
 
@@ -570,149 +569,67 @@ def analyze_job_fit(resume_summary, job_description):
570
  # Apply final curve to keep scores in a realistic range
571
  match_percentage = min(95, max(35, int(weighted_score * 100)))
572
 
573
- # Determine fit/not fit status
574
- fit_status = "FIT" if match_percentage >= 70 else "NOT FIT"
 
 
 
575
 
576
- # Get more specific information for a better prompt
577
- # Get top skills across all categories (up to 5 total)
578
- all_matching_skills = []
579
- for category, matches in found_skills.items():
580
- if matches:
581
- all_matching_skills.extend(matches)
582
-
583
- top_skills = list(set(all_matching_skills))[:5] # Remove duplicates and take top 5
584
- skills_text = ", ".join(top_skills) if top_skills else "limited relevant skills"
585
-
586
- # Get strongest and weakest categories for more specific feedback
587
- categories_sorted = sorted(
588
- [(cat, category_details[cat]["adjusted_score"]) for cat in category_weights.keys() if cat in category_details],
589
- key=lambda x: x[1],
590
- reverse=True
591
- )
592
-
593
- top_category = category_weights[categories_sorted[0][0]]["label"] if categories_sorted else "Technical Skills"
594
- weak_category = category_weights[categories_sorted[-1][0]]["label"] if categories_sorted else "Domain Knowledge"
595
-
596
- # Create a prompt for the evaluation model
597
- prompt = f"""
598
- Generate a professional expert assessment for a job candidate applying for the position: {job_requirements['title']}.
599
- Skills detected in candidate: {skills_text}.
600
- Strongest area: {top_category} ({categories_sorted[0][1]}%).
601
- Weakest area: {weak_category} ({categories_sorted[-1][1]}%).
602
- Overall match: {match_percentage}%.
603
- Fit status: {fit_status}
604
-
605
- Write an evaluative assessment that analyzes the candidate's fit for this position.
606
- Start with "{fit_status}: This candidate" and provide a professional evaluation of their fit.
607
-
608
- {fit_status}: This candidate"""
609
-
610
- try:
611
- # Generate the assessment using the evaluation model
612
- assessment_results = models['evaluator'](
613
- prompt,
614
- max_length=300,
615
- do_sample=True,
616
- temperature=0.75,
617
- num_return_sequences=3
618
- )
619
 
620
- # Find the best response with thorough cleaning
621
- best_assessment = None
622
- for result in assessment_results:
623
- # Get the raw text
624
- raw_text = result['generated_text'].strip()
625
-
626
- # Extract just the part that starts with the fit status
627
- if f"{fit_status}: This candidate" in raw_text:
628
- # Find the start of the actual assessment
629
- start_idx = raw_text.find(f"{fit_status}: This candidate")
630
- text = raw_text[start_idx:]
631
-
632
- # Check if it's actually an assessment (not just instructions)
633
- if len(text) > 50 and not any(x in text.lower() for x in [
634
- "actionable advice",
635
- "include specific",
636
- "make an assessment",
637
- "evaluate their",
638
- "assess their",
639
- "provide specific areas"
640
- ]):
641
- best_assessment = text
642
- break
643
 
644
- # Use the best response or generate a fallback if none were ideal
645
- if best_assessment:
646
- assessment = best_assessment
647
- else:
648
- # Generate a completely manual assessment
649
- assessment = generate_fallback_assessment(
650
- resume_summary,
651
- job_requirements,
652
- match_percentage,
653
- top_skills,
654
- top_category,
655
- weak_category,
656
- fit_status
657
- )
658
-
659
- except Exception as e:
660
- # Fallback to a manual assessment
661
- assessment = generate_fallback_assessment(
662
- resume_summary,
663
- job_requirements,
664
- match_percentage,
665
- top_skills,
666
- top_category,
667
- weak_category,
668
- fit_status
669
- )
670
-
671
- # Final cleanup
672
- assessment = re.sub(r'include specific actionable advice.*?improvement\.', '', assessment, flags=re.DOTALL|re.IGNORECASE)
673
- assessment = re.sub(r'make an assessment.*?resume\.', '', assessment, flags=re.DOTALL|re.IGNORECASE)
674
- assessment = re.sub(r'evaluate their technical skills.*?position\.', '', assessment, flags=re.DOTALL|re.IGNORECASE)
675
- assessment = re.sub(r'assess their strengths.*?contributions', '', assessment, flags=re.DOTALL|re.IGNORECASE)
676
- assessment = re.sub(r'provide specific areas.*?needed', '', assessment, flags=re.DOTALL|re.IGNORECASE)
677
- assessment = re.sub(r'give an overall.*?position', '', assessment, flags=re.DOTALL|re.IGNORECASE)
678
-
679
- # Clean up any double spaces, newlines, etc.
680
- assessment = re.sub(r'\s+', ' ', assessment)
681
- assessment = assessment.strip()
682
-
683
- # If cleaning removed too much text, use the fallback
684
- if len(assessment) < 50 or not assessment.startswith(f"{fit_status}: This candidate"):
685
- assessment = generate_fallback_assessment(
686
- resume_summary,
687
- job_requirements,
688
- match_percentage,
689
- top_skills,
690
- top_category,
691
- weak_category,
692
- fit_status
693
- )
694
 
695
- # Make sure percentages are consistent
696
- assessment = re.sub(r'\b\d{1,2}%\b', f"{match_percentage}%", assessment)
697
 
698
- execution_time = time.time() - start_time
 
699
 
700
- return assessment, match_percentage, category_details, job_requirements, execution_time
701
-
702
- # Generate fallback assessment
703
- def generate_fallback_assessment(resume_summary, job_requirements, match_percentage, top_skills, top_category, weak_category, fit_status):
704
- """Generate a fallback assessment if the model fails"""
705
- job_title = job_requirements["title"]
706
- skills_text = ", ".join(top_skills) if top_skills else "relevant skills"
707
-
708
- if fit_status == "FIT":
709
- assessment = f"""{fit_status}: This candidate demonstrates strong alignment with the {job_title} position, achieving an overall match score of {match_percentage}%. Their proficiency in {skills_text} positions them well to contribute effectively, with particular strength in {top_category}. The candidate's experience level is suitable for the role's requirements. To maximize their success, they could consider developing expertise in {weak_category} to round out their skill set for this position.
710
- """
 
 
 
 
 
711
  else:
712
- assessment = f"""{fit_status}: This candidate currently shows limited alignment with the {job_title} position, with an overall match score of {match_percentage}%. While they demonstrate some capabilities in {top_category} and have experience with {skills_text}, they would need to develop expertise in {weak_category} to be more competitive for this role. The candidate may become a stronger fit by focusing on these skill gaps and gaining more relevant experience in the key requirements for this position.
713
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
 
715
- return assessment
716
 
717
  #####################################
718
  # Main Streamlit Interface
@@ -759,7 +676,7 @@ if uploaded_file is not None and job_description and st.button("Analyze Job Fit"
759
 
760
  # Step 3: Generate job fit assessment
761
  status_text.text("Step 3/3: Evaluating job fit...")
762
- assessment, match_percentage, category_details, job_requirements, assessment_time = analyze_job_fit(summary, job_description)
763
  progress_bar.progress(100)
764
 
765
  # Clear status messages
@@ -768,15 +685,29 @@ if uploaded_file is not None and job_description and st.button("Analyze Job Fit"
768
  # Display job fit results
769
  st.subheader("Job Fit Assessment")
770
 
771
- # Display match percentage with appropriate color and emoji
 
 
 
 
 
 
 
 
 
 
772
  if match_percentage >= 85:
773
- st.success(f"**Overall Job Match Score:** {match_percentage}% 🌟")
774
  elif match_percentage >= 70:
775
- st.success(f"**Overall Job Match Score:** {match_percentage}% βœ…")
776
  elif match_percentage >= 50:
777
- st.warning(f"**Overall Job Match Score:** {match_percentage}% ⚠️")
778
  else:
779
- st.error(f"**Overall Job Match Score:** {match_percentage}% πŸ”")
 
 
 
 
780
 
781
  # Add detailed score breakdown
782
  st.markdown("### Score Breakdown")
@@ -824,30 +755,26 @@ if uploaded_file is not None and job_description and st.button("Analyze Job Fit"
824
  Scores are calculated based on keyword matches in your resume, with diminishing returns applied (first few skills matter more than later ones).
825
  """)
826
 
827
- # Display assessment
828
- st.markdown("### Expert Assessment")
829
- st.markdown(assessment)
830
-
831
  st.info(f"Assessment completed in {assessment_time:.2f} seconds")
832
 
833
- # Add potential next steps based on the match percentage
834
  st.subheader("Recommended Next Steps")
835
 
836
- if match_percentage >= 80:
837
  st.markdown("""
838
  - Consider applying for this position as you appear to be a strong match
839
  - Prepare for technical interviews by focusing on your strongest skills
840
  - Review the job description again to prepare for specific interview questions
841
  """)
842
- elif match_percentage >= 60:
843
  st.markdown("""
844
- - Focus on strengthening your weaker areas before applying
845
- - Highlight your strongest skills and experience in your cover letter
846
- - Consider gaining additional experience or certifications in key required areas
847
  """)
848
  else:
849
  st.markdown("""
850
  - This position may not be the best fit for your current skills and experience
851
- - Consider roles that better align with your strengths
852
  - If you're set on this type of position, focus on developing skills in the areas mentioned in the job description
853
  """)
 
42
  truncation=True
43
  )
44
 
45
+ # Load sentiment model for evaluation
46
  models['evaluator'] = pipeline(
47
+ "sentiment-analysis",
48
+ model="distilbert/distilbert-base-uncased-finetuned-sst-2-english"
 
49
  )
50
 
51
  return models
 
411
  #####################################
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
 
 
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
 
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
 
685
  # Display job fit results
686
  st.subheader("Job Fit Assessment")
687
 
688
+ # Display fit score with label
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")
 
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
  """)