From 7050bca2e4547097619bbb68db0db272c114e863 Mon Sep 17 00:00:00 2001 From: Anirudh S Date: Mon, 20 Oct 2025 23:06:12 +0530 Subject: [PATCH] Add job description analysis and job match scoring Introduces job description analysis to extract key requirements and integrates job match scoring into resume evaluation. Updates evaluator.py to process job descriptions, adds a new Jinja template for job description analysis, and modifies the resume evaluation template and EvaluationData model to include job_match_score. Also adds sample job description and resume text files for testing. --- evaluator.py | 41 ++++++++++++-- job_description.txt | 13 +++++ models.py | 1 + .../templates/job_description_analysis.jinja | 29 ++++++++++ .../resume_evaluation_criteria.jinja | 13 +++++ resume.txt | 19 +++++++ score.py | 56 ++++++++++++++----- 7 files changed, 155 insertions(+), 17 deletions(-) create mode 100644 job_description.txt create mode 100644 prompts/templates/job_description_analysis.jinja create mode 100644 resume.txt diff --git a/evaluator.py b/evaluator.py index 1f9e91f..d3690b4 100644 --- a/evaluator.py +++ b/evaluator.py @@ -37,17 +37,50 @@ def _initialize_llm_provider(self): """Initialize the appropriate LLM provider based on the model.""" self.provider = initialize_llm_provider(self.model_name) - def _load_evaluation_prompt(self, resume_text: str) -> str: + def _load_evaluation_prompt(self, resume_text: str, job_requirements: dict = None) -> str: criteria_template = self.template_manager.render_template( - "resume_evaluation_criteria", text_content=resume_text + "resume_evaluation_criteria", + text_content=resume_text, + job_requirements=job_requirements, ) if criteria_template is None: raise ValueError("Failed to load resume evaluation criteria template") return criteria_template - def evaluate_resume(self, resume_text: str) -> EvaluationData: + def _analyze_job_description(self, job_description: str) -> dict: + """Analyze the job description to extract key requirements.""" + try: + prompt = self.template_manager.render_template( + "job_description_analysis", text_content=job_description + ) + if prompt is None: + raise ValueError("Failed to load job description analysis template") + + response = self.provider.chat( + model=self.model_name, + messages=[{"role": "user", "content": prompt}], + options={ + "stream": False, + "temperature": 0.2, + }, + ) + response_text = extract_json_from_response(response["message"]["content"]) + return json.loads(response_text) + except Exception as e: + logger.error(f"Error analyzing job description: {str(e)}") + return {} + + def evaluate_resume( + self, resume_text: str, job_description: str = None + ) -> EvaluationData: self._last_resume_text = resume_text - full_prompt = self._load_evaluation_prompt(resume_text) + job_requirements = None + if job_description: + job_requirements = self._analyze_job_description(job_description) + + full_prompt = self._load_evaluation_prompt( + resume_text, job_requirements=job_requirements + ) # logger.info(f"šŸ”¤ Evaluation prompt being sent: {full_prompt}") try: system_message = self.template_manager.render_template( diff --git a/job_description.txt b/job_description.txt new file mode 100644 index 0000000..42fff25 --- /dev/null +++ b/job_description.txt @@ -0,0 +1,13 @@ +**Software Engineer Intern** + +**Responsibilities:** +- Develop and maintain web applications using Python and JavaScript. +- Collaborate with the team to design and implement new features. +- Write clean, efficient, and well-documented code. +- Troubleshoot and debug issues. + +**Requirements:** +- Experience with Python and Django. +- Familiarity with JavaScript, HTML, and CSS. +- Strong problem-solving skills. +- Excellent communication and teamwork skills. diff --git a/models.py b/models.py index e83779e..3f51ca1 100644 --- a/models.py +++ b/models.py @@ -243,6 +243,7 @@ class Deductions(BaseModel): class EvaluationData(BaseModel): scores: Scores + job_match_score: Optional[CategoryScore] = None bonus_points: BonusPoints deductions: Deductions key_strengths: List[str] = Field(min_items=1, max_items=5) diff --git a/prompts/templates/job_description_analysis.jinja b/prompts/templates/job_description_analysis.jinja new file mode 100644 index 0000000..faa849d --- /dev/null +++ b/prompts/templates/job_description_analysis.jinja @@ -0,0 +1,29 @@ +You are an expert in analyzing job descriptions. Your task is to extract the key skills and requirements from the provided job description and categorize them. + +**Instructions:** + +1. Read the job description carefully. +2. Identify the key technical skills, software, and qualifications required for the role. +3. Categorize the extracted information into a structured JSON format. + +**JSON Output Structure:** + +```json +{ + "key_skills": [ + "skill1", + "skill2", + "skill3" + ], + "required_experience": [ + "experience1", + "experience2" + ] +} +``` + +**Job Description to Analyze:** + +{{ text_content }} + +**IMPORTANT: Respond with ONLY the JSON structure specified above. Do not include any other text or explanations.** diff --git a/prompts/templates/resume_evaluation_criteria.jinja b/prompts/templates/resume_evaluation_criteria.jinja index 45c0daf..214a8ce 100644 --- a/prompts/templates/resume_evaluation_criteria.jinja +++ b/prompts/templates/resume_evaluation_criteria.jinja @@ -29,6 +29,18 @@ You are evaluating a resume for a Software Intern position at HackerRank. Analyz - Use GitHub data (if provided in === GITHUB DATA === section) as additional context - Use blog data (if provided in === BLOG DATA === section) for technical communication assessment +{% if job_requirements %} +## JOB DESCRIPTION ALIGNMENT +- You will be provided with a set of key skills and required experience from a job description. +- Compare the candidate's resume against these requirements to assess their suitability for the role. +- Provide a `job_match_score` from 0 to 100, where 100 is a perfect match. +- The `job_match_score` should be included in the final JSON output. + +**Job Requirements:** +- Key Skills: {{ job_requirements.key_skills | join(', ') }} +- Required Experience: {{ job_requirements.required_experience | join(', ') }} +{% endif %} + ## SCORING CRITERIA ### Open Source (0-35 points) @@ -174,6 +186,7 @@ Analyze the following resume and provide a JSON response with this EXACT structu "production": {"score": 0, "max": 25, "evidence": "string"}, "technical_skills": {"score": 0, "max": 10, "evidence": "string"} }, + "job_match_score": {"score": 0, "max": 100, "evidence": "string"}, "bonus_points": {"total": 0, "breakdown": "string"}, "deductions": {"total": 0, "reasons": "string"}, "key_strengths": ["strength1", "strength2", "strength3", "strength4", "strength5"], diff --git a/resume.txt b/resume.txt new file mode 100644 index 0000000..0b50af7 --- /dev/null +++ b/resume.txt @@ -0,0 +1,19 @@ +**John Doe** +john.doe@email.com | (123) 456-7890 | linkedin.com/in/johndoe | github.com/johndoe + +**Summary** +Aspiring Software Engineer with a passion for web development and a strong foundation in Python and JavaScript. + +**Experience** +**Software Developer Intern**, ABC Corp - Anytown, USA (May 2023 - Aug 2023) +- Developed and maintained a web application using Python, Django, and JavaScript. +- Collaborated with the team to design and implement new features. + +**Education** +**B.S. in Computer Science**, University of Example - Exampleton, USA (2020 - 2024) + +**Skills** +- **Programming Languages:** Python, JavaScript, HTML, CSS +- **Frameworks:** Django, React +- **Databases:** PostgreSQL, MongoDB +- **Tools:** Git, Docker diff --git a/score.py b/score.py index b0944dd..e0548e7 100644 --- a/score.py +++ b/score.py @@ -3,6 +3,7 @@ import json import logging import csv +import argparse from pdf import PDFHandler from github import fetch_and_display_github_info from models import JSONResume, EvaluationData @@ -71,6 +72,15 @@ def print_evaluation_results( # Overall Score print(f"\nšŸŽÆ OVERALL SCORE: {total_score:.1f}/{max_score}") + # Job Match Score + if hasattr(evaluation, "job_match_score") and evaluation.job_match_score: + print("\n" + "-" * 80) + print("šŸ¤ JOB DESCRIPTION ALIGNMENT:") + js_score = evaluation.job_match_score + print(f" Match Score: {js_score.score}/{js_score.max}") + print(f" Evidence: {js_score.evidence}") + print("-" * 80) + # Detailed Scores print("\nšŸ“ˆ DETAILED SCORES:") print("-" * 60) @@ -160,7 +170,10 @@ def print_evaluation_results( def _evaluate_resume( - resume_data: JSONResume, github_data: dict = None, blog_data: dict = None + resume_data: JSONResume, + github_data: dict = None, + blog_data: dict = None, + job_description: str = None, ) -> Optional[EvaluationData]: """Evaluate the resume using AI and display results.""" @@ -181,7 +194,9 @@ def _evaluate_resume( resume_text += blog_text # Evaluate the enhanced resume - evaluation_result = evaluator.evaluate_resume(resume_text) + evaluation_result = evaluator.evaluate_resume( + resume_text, job_description=job_description + ) # print(evaluation_result) @@ -197,7 +212,7 @@ def find_profile(profiles, network): ) -def main(pdf_path): +def main(pdf_path, job_description_path=None): # Create cache filename based on PDF path cache_filename = ( f"cache/resumecache_{os.path.basename(pdf_path).replace('.pdf', '')}.json" @@ -206,6 +221,13 @@ def main(pdf_path): f"cache/githubcache_{os.path.basename(pdf_path).replace('.pdf', '')}.json" ) + job_description = None + if job_description_path: + if os.path.exists(job_description_path): + job_description = Path(job_description_path).read_text() + else: + print(f"Warning: Job description file not found at '{job_description_path}'") + # Check if cache exists and we're in development mode if DEVELOPMENT_MODE and os.path.exists(cache_filename): print(f"Loading cached data from {cache_filename}") @@ -226,7 +248,7 @@ def main(pdf_path): os.makedirs(os.path.dirname(cache_filename), exist_ok=True) Path(cache_filename).write_text( json.dumps(resume_data.model_dump(), indent=2, ensure_ascii=False), - encoding='utf-8' + encoding="utf-8", ) # Check if cache exists and we're in development mode @@ -252,10 +274,10 @@ def main(pdf_path): os.makedirs(os.path.dirname(github_cache_filename), exist_ok=True) Path(github_cache_filename).write_text( json.dumps(github_data, indent=2, ensure_ascii=False), - encoding='utf-8' + encoding="utf-8", ) - score = _evaluate_resume(resume_data, github_data) + score = _evaluate_resume(resume_data, github_data, job_description=job_description) # Get candidate name for display candidate_name = os.path.basename(pdf_path).replace(".pdf", "") @@ -297,13 +319,21 @@ def main(pdf_path): if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python score.py ") - exit(1) - pdf_path = sys.argv[1] + parser = argparse.ArgumentParser( + description="Evaluate a resume PDF and optionally compare it against a job description." + ) + parser.add_argument("pdf_path", help="Path to the resume PDF file.") + parser.add_argument( + "-j", + "--job-description", + dest="job_description_path", + help="Path to a text file containing the job description.", + required=False, + ) + args = parser.parse_args() - if not os.path.exists(pdf_path): - print(f"Error: File '{pdf_path}' does not exist.") + if not os.path.exists(args.pdf_path): + print(f"Error: File '{args.pdf_path}' does not exist.") exit(1) - main(pdf_path) + main(args.pdf_path, args.job_description_path)