File size: 2,787 Bytes
d2b7a80 9c72612 d2b7a80 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | from utilities.keyword_match import final_ats_score, experience_level_penalty
from utilities.skills import (
find_missing_skills,
calculate_skill_overlap,
extract_resume_skills,
extract_required_skills_from_jd,
)
from services.feedback import generate_resume_feedback
# ---------------------------------------------------------------------------
# Gap analysis
# ---------------------------------------------------------------------------
def extract_gaps(resume_text: str, jd_text: str) -> dict:
"""
Builds a structured gap report used both for the LLM feedback prompt
and for any structured API response fields you add later.
Fields
------
missing_keywords Top-10 skills in JD that are absent from resume.
skill_overlap_pct % of JD skills present in resume.
matched_skills Skills the candidate already has that JD wants.
high_priority_missing Missing skills that appear more than once in JD
(JD emphasises them → higher impact gaps).
seniority_penalty Penalty points from experience-level mismatch.
"""
missing = find_missing_skills(resume_text, jd_text)
overlap = calculate_skill_overlap(resume_text, jd_text)
resume_skills = extract_resume_skills(resume_text)
jd_skills_freq = extract_required_skills_from_jd(jd_text)
matched = sorted(resume_skills & set(jd_skills_freq.keys()))
# Skills the JD mentions more than once — candidate should prioritise these
high_priority_missing = [
skill for skill in missing
if jd_skills_freq.get(skill, 0) > 1
]
penalty = experience_level_penalty(resume_text, jd_text)
return {
"missing_keywords": missing[:10],
"skill_overlap_percentage": overlap,
"matched_skills": matched,
"high_priority_missing": high_priority_missing[:5],
"seniority_penalty": penalty,
}
# ---------------------------------------------------------------------------
# Main scoring entry point
# ---------------------------------------------------------------------------
def resume_score(resume_text: str, jd_text: str) -> dict:
"""
Orchestrates scoring → gap analysis → LLM feedback.
Returns a dict matching ScoreResponse schema plus a 'summary' field.
Note: text cleaning is intentionally left to each scoring/utility
function so that sentence boundaries (newlines, punctuation) are
preserved for semantic chunking before they are stripped.
"""
scores = final_ats_score(resume_text, jd_text)
gaps = extract_gaps(resume_text, jd_text)
feedback = generate_resume_feedback(scores, gaps)
return {
**scores,
"summary": feedback,
} |