ejqs commited on
Commit
039f514
·
1 Parent(s): a614ea8
Files changed (1) hide show
  1. app.py +183 -117
app.py CHANGED
@@ -506,14 +506,16 @@ def create_html_output(job_result: Dict, resume_results: List[Dict], filenames:
506
  if multiple_resumes:
507
  html += "<h3>Match Summary</h3>"
508
  html += "<table style='width: 100%; border-collapse: collapse; margin-bottom: 20px;'>"
509
- html += "<tr style='background-color: #eee;'><th style='padding: 8px; text-align: left; border: 1px solid #ddd;'>Resume</th>"
510
- html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Skills Matched</th>"
511
- html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Match Percentage</th>"
512
- html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Leadership</th>"
513
- html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Collaboration</th>"
514
- html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Years of Experience</th>"
515
- html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Category</th>"
516
- html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Actions</th></tr>"
 
 
517
 
518
  # Create a list of resume data for sorting
519
  resume_data = []
@@ -530,100 +532,135 @@ def create_html_output(job_result: Dict, resume_results: List[Dict], filenames:
530
 
531
  # Get total experience and determine category
532
  total_experience = total_experiences[i-1]
533
- if total_experience < 3:
534
- category = "Entry"
535
- elif total_experience < 5:
536
- category = "Intermediate"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  else:
538
- category = "Advanced"
 
 
 
 
 
 
539
 
540
- # Add quality modifier if 50% above average
541
- is_quality_leadership = leadership_count > avg_leadership * 1.5
542
- is_quality_collaboration = collaboration_count > avg_collaboration * 1.5
 
543
 
544
- quality_score = 0
545
- if is_quality_leadership:
546
- category = f"Quality {category} (Leadership)"
547
- quality_score = 2
548
- elif is_quality_collaboration:
549
- category = f"Quality {category} (Collaboration)"
550
- quality_score = 1
 
 
 
 
 
 
 
 
 
 
 
551
 
552
  # Add to resume data list for sorting
553
  resume_data.append({
 
554
  'index': i,
555
- 'match_count': match_count,
556
  'match_percentage': match_percentage,
557
- 'leadership_count': leadership_count,
558
- 'collaboration_count': collaboration_count,
559
- 'total_experience': total_experience,
560
- 'category': category,
561
- 'quality_score': quality_score,
562
- 'resume_result': resume_result,
563
- 'filename': filenames[i-1] # Store the filename
564
  })
565
 
566
- # Sort resume data:
567
- # 1. By matched skills (highest first)
568
- # 2. By years of experience (highest first)
569
- # 3. By quality category for resumes with experience within 1 year
570
- def sort_key(resume):
571
- return (-resume['match_count'], -resume['total_experience'], -resume['quality_score'])
572
 
573
- sorted_resumes = sorted(resume_data, key=sort_key)
574
-
575
- # Group resumes with experience within 1 year and sort by quality
576
- final_sorted = []
577
- i = 0
578
- while i < len(sorted_resumes):
579
- current = sorted_resumes[i]
580
- similar_exp = [current]
581
- indices_to_remove = []
582
- j = i + 1
583
-
584
- # Find all resumes with similar experience to the current one
585
- while j < len(sorted_resumes) and sorted_resumes[j]['match_count'] == current['match_count']:
586
- if abs(sorted_resumes[j]['total_experience'] - current['total_experience']) <= 1:
587
- similar_exp.append(sorted_resumes[j])
588
- indices_to_remove.append(j)
589
- j += 1
590
-
591
- # Sort by quality score if there are resumes with similar experience
592
- if len(similar_exp) > 1:
593
- similar_exp.sort(key=lambda x: -x['quality_score'])
594
 
595
- final_sorted.extend(similar_exp)
 
 
 
 
 
 
 
596
 
597
- # Create a new list without the removed indices
598
- sorted_resumes = [r for idx, r in enumerate(sorted_resumes) if idx != i and idx not in indices_to_remove]
599
-
600
- # We don't need to increment i since we've created a new list without the processed items
601
- # but if the list is empty, we need to break out of the loop
602
- if not sorted_resumes:
603
- break
604
-
605
- # Add sorted rows to summary table
606
- for resume_data in final_sorted:
607
- i = resume_data['index']
608
- match_count = resume_data['match_count']
609
- match_percentage = resume_data['match_percentage']
610
- leadership_count = resume_data['leadership_count']
611
- collaboration_count = resume_data['collaboration_count']
612
- total_experience = resume_data['total_experience']
613
- category = resume_data['category']
614
- filename = resume_data['filename'] # Get the filename
615
 
616
- # Add row to summary table - using filename instead of "Resume {i}"
617
- html += f"<tr><td style='padding: 8px; border: 1px solid #ddd;'>{filename}</td>"
618
- html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{match_count}/{job_result['total_skills']}</td>"
619
- html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{match_percentage}%</td>"
620
- html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{leadership_count}</td>"
621
- html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{collaboration_count}</td>"
622
- html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{total_experience}</td>"
623
- html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{category}</td>"
 
624
  html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>"
625
- html += f"<button onclick=\"const allDetails = document.querySelectorAll('.resume-detail'); allDetails.forEach(detail => {{ if(detail.id !== 'resume-detail-{i}') detail.style.display = 'none'; }}); document.getElementById('resume-detail-{i}').style.display = document.getElementById('resume-detail-{i}').style.display === 'none' ? 'block' : 'none';\" style='background-color: #4CAF50; color: white; padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;'>Toggle Details</button>"
626
- html += "</td></tr>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
 
628
  html += "</table>"
629
 
@@ -695,8 +732,8 @@ def create_html_output(job_result: Dict, resume_results: List[Dict], filenames:
695
  html += f"<table style='width: 100%; border-collapse: collapse; margin-bottom: 20px;' id='skillsTable{i}'>"
696
  html += "<tr style='background-color: #eee;'><th style='padding: 8px; text-align: left; border: 1px solid #ddd;'>Skill</th>"
697
  html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Years of Experience</th>"
698
- html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Leadership</th>"
699
- html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Collaboration</th>"
700
  html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Match</th></tr>"
701
 
702
  # Get all unique skills for this specific resume
@@ -711,24 +748,45 @@ def create_html_output(job_result: Dict, resume_results: List[Dict], filenames:
711
 
712
  # Create a list of skill data for sorting
713
  skill_data = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
  for skill in resume_skills:
715
- # Get years of experience for this skill
716
- years = skill_experience.get(skill, 0)
717
-
718
- # Check if skill has leadership or collaboration
719
- has_leadership = skill in skill_leadership
720
- has_collaboration = skill in skill_collaboration
721
-
722
- # Check if skill matches job requirements
723
- is_match = skill in job_skills
724
-
725
- skill_data.append({
726
- 'skill': skill,
727
- 'years': years,
728
- 'has_leadership': has_leadership,
729
- 'has_collaboration': has_collaboration,
730
- 'is_match': is_match
731
- })
 
 
 
 
732
 
733
  # Sort skills by years of experience (descending)
734
  skill_data.sort(key=lambda x: (-x['years'], x['skill']))
@@ -742,8 +800,8 @@ def create_html_output(job_result: Dict, resume_results: List[Dict], filenames:
742
  html += f"<tr class='skill-row{i}' data-match='{str(data['is_match']).lower()}' style='display: {display};'>"
743
  html += f"<td style='padding: 8px; border: 1px solid #ddd;'>{data['skill']}</td>"
744
  html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{data['years']:.1f}</td>"
745
- html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd; color: {'green' if data['has_leadership'] else 'red'};'>{'Yes' if data['has_leadership'] else 'No'}</td>"
746
- html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd; color: {'green' if data['has_collaboration'] else 'red'};'>{'Yes' if data['has_collaboration'] else 'No'}</td>"
747
  html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd; color: {'green' if data['is_match'] else 'red'};'>{'Yes' if data['is_match'] else 'No'}</td></tr>"
748
 
749
  html += "</table>"
@@ -752,16 +810,24 @@ def create_html_output(job_result: Dict, resume_results: List[Dict], filenames:
752
  html += "<p><strong>Skills Found:</strong></p>"
753
  html += "<div style='background-color: #f0f0f0; padding: 10px; border-radius: 5px;'>"
754
 
 
 
 
755
  for skill in resume_result['skills']:
756
- # Highlight matched skills
757
- is_match = skill['text'].lower() in job_skills
758
- bg_color = "#c8e6c9" if is_match else "#e0e0e0" # Green tint for matches
759
-
760
- # Add years of experience for this skill if available
761
- skill_years = skill_experience.get(skill['text'].lower(), 0)
762
- experience_text = f" ({skill_years} years)" if skill_years > 0 else ""
763
-
764
- html += f"<span style='background-color: {bg_color}; padding: 2px 5px; margin: 2px; border-radius: 3px; display: inline-block;'>{skill['text']}{experience_text}</span>"
 
 
 
 
 
765
  html += "</div>"
766
 
767
  # Job roles section
 
506
  if multiple_resumes:
507
  html += "<h3>Match Summary</h3>"
508
  html += "<table style='width: 100%; border-collapse: collapse; margin-bottom: 20px;'>"
509
+ html += "<tr style='background-color: #eee;'>"
510
+ html += "<th style='padding: 8px; text-align: left; border: 1px solid #ddd;'>JOB ID</th>"
511
+ html += "<th style='padding: 8px; text-align: left; border: 1px solid #ddd;'>CANDIDATE</th>"
512
+ html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>% MATCHED SKILLS</th>"
513
+ html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>SKILL</th>"
514
+ html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>YEARS OF EXPERIENCE</th>"
515
+ html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>MATCH CATEGORY</th>"
516
+ html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>LEADERSHIP QUALITY</th>"
517
+ html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>COLLABORATION QUALITY</th>"
518
+ html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>ACTIONS</th></tr>"
519
 
520
  # Create a list of resume data for sorting
521
  resume_data = []
 
532
 
533
  # Get total experience and determine category
534
  total_experience = total_experiences[i-1]
535
+
536
+ # Determine leadership quality (YES/NO)
537
+ leadership_quality = "YES" if leadership_count > avg_leadership * 1.2 else "NO"
538
+
539
+ # Determine thoroughness quality (YES/NO)
540
+ collaboration_quality = "YES" if collaboration_count > avg_collaboration * 1.2 else "NO"
541
+
542
+ # Determine match category (Strong, Close, Weak)
543
+ if match_percentage >= 80:
544
+ match_category = "Strong Match"
545
+ if leadership_quality == "YES" and collaboration_quality == "YES":
546
+ match_category = "Strong Quality Match (Leadership and Collaboration)"
547
+ elif leadership_quality == "YES":
548
+ match_category = "Strong Quality Match (Leadership)"
549
+ elif collaboration_quality == "YES":
550
+ match_category = "Strong Quality Match (Collaboration)"
551
+ elif match_percentage >= 50:
552
+ match_category = "Close Match"
553
+ if leadership_quality == "YES" and collaboration_quality == "YES":
554
+ match_category = "Close Quality Match (Leadership and Collaboration)"
555
+ elif leadership_quality == "YES":
556
+ match_category = "Close Quality Match (Leadership)"
557
+ elif collaboration_quality == "YES":
558
+ match_category = "Close Match (Collaboration)"
559
  else:
560
+ match_category = "Weak Match"
561
+ if leadership_quality == "YES" and collaboration_quality == "YES":
562
+ match_category = "Weak Quality Match (Leadership and Collaboration)"
563
+ elif leadership_quality == "YES":
564
+ match_category = "Weak Quality Match (Leadership)"
565
+ elif collaboration_quality == "YES":
566
+ match_category = "Weak Quality Match (Collaboration)"
567
 
568
+
569
+ # Get primary skills (Java and React in this case, as per image)
570
+ primary_skills = {}
571
+ skill_exp = {} # Create a new dict to aggregate experience
572
 
573
+ # First, aggregate all experience for each skill
574
+ for skill_name, years in skill_experience_maps[i-1].items():
575
+ skill_name_lower = skill_name.lower()
576
+ if skill_name_lower in skill_exp:
577
+ skill_exp[skill_name_lower] = max(skill_exp[skill_name_lower], years) # Take the max experience
578
+ else:
579
+ skill_exp[skill_name_lower] = years
580
+
581
+ # Get unique resume skills
582
+ resume_skills = set(skill['text'].lower() for skill in resume_result['skills'])
583
+
584
+ # Only include skills that are in both the resume and job requirements
585
+ matched_skills = []
586
+ for skill in job_skills:
587
+ if skill.lower() in resume_skills:
588
+ matched_skills.append(skill)
589
+ if skill.lower() in skill_exp:
590
+ primary_skills[skill] = skill_exp[skill.lower()]
591
 
592
  # Add to resume data list for sorting
593
  resume_data.append({
594
+ 'job_id': "JD-1",
595
  'index': i,
 
596
  'match_percentage': match_percentage,
597
+ 'matched_skills': matched_skills,
598
+ 'primary_skills': primary_skills,
599
+ 'match_category': match_category,
600
+ 'leadership_quality': leadership_quality,
601
+ 'collaboration_quality': collaboration_quality,
602
+ 'filename': filenames[i-1]
 
603
  })
604
 
605
+ # Sort resumes by match percentage (highest first)
606
+ sorted_resumes = sorted(resume_data, key=lambda x: -x['match_percentage'])
 
 
 
 
607
 
608
+ # Add rows to summary table
609
+ for resume_data in sorted_resumes:
610
+ # Generate table row for each candidate
611
+ candidate_letter = chr(65 + resume_data['index'] - 1) # Convert 1,2,3 to A,B,C
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
 
613
+ # Create a single row per candidate
614
+ html += "<tr>"
615
+ # Job ID
616
+ html += f"<td style='padding: 8px; border: 1px solid #ddd;'>{resume_data['job_id']}</td>"
617
+ # Candidate
618
+ html += f"<td style='padding: 8px; border: 1px solid #ddd;'>Candidate {candidate_letter}</td>"
619
+ # Match Percentage
620
+ html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{resume_data['match_percentage']}%</td>"
621
 
622
+ # Skills and Experience
623
+ if resume_data['matched_skills']:
624
+ # Show the first matched skill and its experience
625
+ skill = resume_data['matched_skills'][0]
626
+ html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{skill}</td>"
627
+ html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{resume_data['primary_skills'].get(skill, 0)}</td>"
628
+ else:
629
+ # Show dashes for no matches
630
+ html += "<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>-</td>"
631
+ html += "<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>-</td>"
 
 
 
 
 
 
 
 
632
 
633
+ # Match Category
634
+ html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{resume_data['match_category']}</td>"
635
+ # Leadership Quality
636
+ leadership_color = "#e6ffe6" if resume_data['leadership_quality'] == "YES" else "#f5f5f5"
637
+ html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd; background-color: {leadership_color};'>{resume_data['leadership_quality']}</td>"
638
+ # Collaboration Quality
639
+ collaboration_color = "#e6ffe6" if resume_data['collaboration_quality'] == "YES" else "#f5f5f5"
640
+ html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd; background-color: {collaboration_color};'>{resume_data['collaboration_quality']}</td>"
641
+ # Actions
642
  html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>"
643
+ html += f"<button onclick=\"const allDetails = document.querySelectorAll('.resume-detail'); allDetails.forEach(detail => {{ if(detail.id !== 'resume-detail-{resume_data['index']}') detail.style.display = 'none'; }}); document.getElementById('resume-detail-{resume_data['index']}').style.display = document.getElementById('resume-detail-{resume_data['index']}').style.display === 'none' ? 'block' : 'none';\" style='background-color: #4CAF50; color: white; padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;'>Toggle Details</button>"
644
+ html += "</td>"
645
+ html += "</tr>"
646
+
647
+ # If there are additional matched skills, add them in subsequent rows
648
+ if len(resume_data['matched_skills']) > 1:
649
+ for skill in resume_data['matched_skills'][1:]:
650
+ html += "<tr>"
651
+ # Empty cells for the first three columns
652
+ html += "<td style='padding: 8px; border: 1px solid #ddd;'></td>"
653
+ html += "<td style='padding: 8px; border: 1px solid #ddd;'></td>"
654
+ html += "<td style='padding: 8px; border: 1px solid #ddd;'></td>"
655
+ # Skill and Experience
656
+ html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{skill}</td>"
657
+ html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{resume_data['primary_skills'].get(skill, 0)}</td>"
658
+ # Empty cells for the remaining columns
659
+ html += "<td style='padding: 8px; border: 1px solid #ddd;'></td>"
660
+ html += "<td style='padding: 8px; border: 1px solid #ddd;'></td>"
661
+ html += "<td style='padding: 8px; border: 1px solid #ddd;'></td>"
662
+ html += "<td style='padding: 8px; border: 1px solid #ddd;'></td>"
663
+ html += "</tr>"
664
 
665
  html += "</table>"
666
 
 
732
  html += f"<table style='width: 100%; border-collapse: collapse; margin-bottom: 20px;' id='skillsTable{i}'>"
733
  html += "<tr style='background-color: #eee;'><th style='padding: 8px; text-align: left; border: 1px solid #ddd;'>Skill</th>"
734
  html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Years of Experience</th>"
735
+ html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Leadership Quality Count</th>"
736
+ html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Collaboration Quality Count</th>"
737
  html += "<th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Match</th></tr>"
738
 
739
  # Get all unique skills for this specific resume
 
748
 
749
  # Create a list of skill data for sorting
750
  skill_data = []
751
+ added_skills = set() # Track which skills we've already added
752
+
753
+ # Count quality statements per skill
754
+ skill_leadership_counts = {}
755
+ skill_collaboration_counts = {}
756
+
757
+ if 'roles' in resume_result:
758
+ for role in resume_result['roles']:
759
+ if 'quality_scores' in role:
760
+ role_skills = {skill['name'].lower() for skill in role.get('skills', [])}
761
+ for score in role['quality_scores']:
762
+ for skill in role_skills:
763
+ if score['is_leadership']:
764
+ skill_leadership_counts[skill] = skill_leadership_counts.get(skill, 0) + 1
765
+ elif score['is_collaboration']:
766
+ skill_collaboration_counts[skill] = skill_collaboration_counts.get(skill, 0) + 1
767
+
768
  for skill in resume_skills:
769
+ # Only add if we haven't seen this skill before
770
+ if skill not in added_skills:
771
+ # Get years of experience for this skill
772
+ years = skill_experience.get(skill, 0)
773
+
774
+ # Get quality counts for this skill
775
+ leadership_count = skill_leadership_counts.get(skill, 0)
776
+ collaboration_count = skill_collaboration_counts.get(skill, 0)
777
+
778
+ # Check if skill matches job requirements
779
+ is_match = skill in job_skills
780
+
781
+ skill_data.append({
782
+ 'skill': skill,
783
+ 'years': years,
784
+ 'leadership_count': leadership_count,
785
+ 'collaboration_count': collaboration_count,
786
+ 'is_match': is_match
787
+ })
788
+
789
+ added_skills.add(skill)
790
 
791
  # Sort skills by years of experience (descending)
792
  skill_data.sort(key=lambda x: (-x['years'], x['skill']))
 
800
  html += f"<tr class='skill-row{i}' data-match='{str(data['is_match']).lower()}' style='display: {display};'>"
801
  html += f"<td style='padding: 8px; border: 1px solid #ddd;'>{data['skill']}</td>"
802
  html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{data['years']:.1f}</td>"
803
+ html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{data['leadership_count']}</td>"
804
+ html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd;'>{data['collaboration_count']}</td>"
805
  html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd; color: {'green' if data['is_match'] else 'red'};'>{'Yes' if data['is_match'] else 'No'}</td></tr>"
806
 
807
  html += "</table>"
 
810
  html += "<p><strong>Skills Found:</strong></p>"
811
  html += "<div style='background-color: #f0f0f0; padding: 10px; border-radius: 5px;'>"
812
 
813
+ # Keep track of skills we've already added
814
+ added_skills = set()
815
+
816
  for skill in resume_result['skills']:
817
+ skill_text = skill['text'].lower()
818
+ # Only add if we haven't seen this skill before
819
+ if skill_text not in added_skills:
820
+ # Highlight matched skills
821
+ is_match = skill_text in job_skills
822
+ bg_color = "#c8e6c9" if is_match else "#e0e0e0" # Green tint for matches
823
+
824
+ # Add years of experience for this skill if available
825
+ skill_years = skill_experience.get(skill_text, 0)
826
+ experience_text = f" ({skill_years} years)" if skill_years > 0 else ""
827
+
828
+ html += f"<span style='background-color: {bg_color}; padding: 2px 5px; margin: 2px; border-radius: 3px; display: inline-block;'>{skill['text']}{experience_text}</span>"
829
+ added_skills.add(skill_text)
830
+
831
  html += "</div>"
832
 
833
  # Job roles section