421 lines
15 KiB
Python
421 lines
15 KiB
Python
from . import models
|
||
from django.urls import reverse
|
||
from django.db import transaction
|
||
from django.dispatch import receiver
|
||
from django.db.models.signals import post_save
|
||
from .models import FormField,FormStage,FormTemplate
|
||
|
||
# @receiver(post_save, sender=models.Candidate)
|
||
# def parse_resume(sender, instance, created, **kwargs):
|
||
# if instance.resume and not instance.summary:
|
||
# from .utils import extract_summary_from_pdf,match_resume_with_job_description
|
||
# summary = extract_summary_from_pdf(instance.resume.path)
|
||
# if 'error' not in summary:
|
||
# instance.summary = summary
|
||
# instance.save()
|
||
|
||
# match_resume_with_job_description
|
||
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
import os
|
||
from .utils import extract_text_from_pdf,score_resume_with_openrouter
|
||
import asyncio
|
||
|
||
@receiver(post_save, sender=models.Candidate)
|
||
def score_candidate_resume(sender, instance, created, **kwargs):
|
||
try:
|
||
# Get absolute file path
|
||
file_path = instance.resume.path
|
||
if not os.path.exists(file_path):
|
||
logger.warning(f"Resume file not found: {file_path}")
|
||
return
|
||
|
||
resume_text = extract_text_from_pdf(file_path)
|
||
# if not resume_text:
|
||
# instance.scoring_error = "Could not extract text from resume."
|
||
# instance.save(update_fields=['scoring_error'])
|
||
# return
|
||
job_detail=str(instance.job.description)+str(instance.job.qualifications)
|
||
prompt1 = f"""
|
||
You are an expert resume parser and summarizer. Given a resume in plain text format, extract and organize the following key-value information into a clean, valid JSON object:
|
||
|
||
full_name: Full name of the candidate
|
||
current_title: Most recent or current job title
|
||
location: City and state (or country if outside the U.S.)
|
||
contact: Phone number and email (as a single string or separate fields)
|
||
linkedin: LinkedIn profile URL (if present)
|
||
github: GitHub or portfolio URL (if present)
|
||
summary: Brief professional profile or summary (1–2 sentences)
|
||
education: List of degrees, each with:
|
||
institution
|
||
degree
|
||
year
|
||
gpa (if provided)
|
||
relevant_courses (as a list, if mentioned)
|
||
skills: Grouped by category if possible (e.g., programming, big data, visualization), otherwise as a flat list of technologies/tools
|
||
experience: List of roles, each with:
|
||
company
|
||
job_title
|
||
location
|
||
start_date and end_date (or "Present" if applicable)
|
||
key_achievements (as a list of concise bullet points)
|
||
projects: List of notable projects (if clearly labeled), each with:
|
||
name
|
||
year
|
||
technologies_used
|
||
brief_description
|
||
Instructions:
|
||
|
||
Be concise but preserve key details.
|
||
Normalize formatting (e.g., “Jun. 2014” → “2014-06”).
|
||
Omit redundant or promotional language.
|
||
If a section is missing, omit the key or set it to null/empty list as appropriate.
|
||
Output only valid JSON—no markdown, no extra text.
|
||
Now, process the following resume text:
|
||
{resume_text}
|
||
"""
|
||
result = score_resume_with_openrouter(prompt1)
|
||
prompt = f"""
|
||
You are an expert technical recruiter. Your task is to score the following candidate for the role of a Senior Data Analyst based on the provided job criteria.
|
||
|
||
**Job Criteria:**
|
||
{job_detail}
|
||
|
||
**Candidate's Extracted Resume Json:**
|
||
\"\"\"
|
||
{result}
|
||
\"\"\"
|
||
|
||
**Your Task:**
|
||
Provide a response in strict JSON format with the following keys:
|
||
1. 'match_score': A score from 0 to 100 representing how well the candidate fits the role.
|
||
2. 'strengths': A brief summary of why the candidate is a strong fit, referencing specific criteria.
|
||
3. 'weaknesses': A brief summary of where the candidate falls short or what criteria are missing.
|
||
4. 'criteria_checklist': An object where you rate the candidate's match for each specific criterion (e.g., {{'Python': 'Met', 'AWS': 'Not Mentioned'}}).
|
||
|
||
|
||
Only output valid JSON. Do not include any other text.
|
||
"""
|
||
|
||
result1 = score_resume_with_openrouter(prompt)
|
||
|
||
instance.parsed_summary = str(result)
|
||
|
||
# Update candidate with scoring results
|
||
instance.match_score = result1.get('match_score')
|
||
instance.strengths = result1.get('strengths', '')
|
||
instance.weaknesses = result1.get('weaknesses', '')
|
||
instance.criteria_checklist = result1.get('criteria_checklist', {})
|
||
|
||
|
||
|
||
# Save only scoring-related fields to avoid recursion
|
||
instance.save(update_fields=[
|
||
'match_score', 'strengths', 'weaknesses',
|
||
'criteria_checklist','parsed_summary'
|
||
])
|
||
|
||
logger.info(f"Successfully scored resume for candidate {instance.id}")
|
||
|
||
except Exception as e:
|
||
# error_msg = str(e)[:500] # Truncate to fit TextField
|
||
# instance.scoring_error = error_msg
|
||
# instance.save(update_fields=['scoring_error'])
|
||
logger.error(f"Failed to score resume for candidate {instance.id}: {e}")
|
||
|
||
|
||
# @receiver(post_save,sender=models.Candidate)
|
||
# def trigger_scoring(sender,intance,created,**kwargs):
|
||
|
||
|
||
|
||
@receiver(post_save, sender=FormTemplate)
|
||
def create_default_stages(sender, instance, created, **kwargs):
|
||
"""
|
||
Create default resume stages when a new FormTemplate is created
|
||
"""
|
||
if created:
|
||
with transaction.atomic():
|
||
# Stage 1: Contact Information
|
||
contact_stage = FormStage.objects.create(
|
||
template=instance,
|
||
name='Contact Information',
|
||
order=0,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=contact_stage,
|
||
label='First Name',
|
||
field_type='text',
|
||
required=True,
|
||
order=0,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=contact_stage,
|
||
label='Last Name',
|
||
field_type='text',
|
||
required=True,
|
||
order=1,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=contact_stage,
|
||
label='Email Address',
|
||
field_type='email',
|
||
required=True,
|
||
order=2,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=contact_stage,
|
||
label='Phone Number',
|
||
field_type='phone',
|
||
required=True,
|
||
order=3,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=contact_stage,
|
||
label='Address',
|
||
field_type='text',
|
||
required=False,
|
||
order=4,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=contact_stage,
|
||
label='Resume Upload',
|
||
field_type='file',
|
||
required=True,
|
||
order=5,
|
||
is_predefined=True,
|
||
file_types='.pdf,.doc,.docx',
|
||
max_file_size=1
|
||
)
|
||
|
||
# Stage 2: Resume Objective
|
||
objective_stage = FormStage.objects.create(
|
||
template=instance,
|
||
name='Resume Objective',
|
||
order=1,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=objective_stage,
|
||
label='Career Objective',
|
||
field_type='textarea',
|
||
required=False,
|
||
order=0,
|
||
is_predefined=True
|
||
)
|
||
|
||
# Stage 3: Education
|
||
education_stage = FormStage.objects.create(
|
||
template=instance,
|
||
name='Education',
|
||
order=2,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=education_stage,
|
||
label='Degree',
|
||
field_type='text',
|
||
required=True,
|
||
order=0,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=education_stage,
|
||
label='Institution',
|
||
field_type='text',
|
||
required=True,
|
||
order=1,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=education_stage,
|
||
label='Location',
|
||
field_type='text',
|
||
required=False,
|
||
order=2,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=education_stage,
|
||
label='Graduation Date',
|
||
field_type='date',
|
||
required=False,
|
||
order=3,
|
||
is_predefined=True
|
||
)
|
||
|
||
# Stage 4: Experience
|
||
experience_stage = FormStage.objects.create(
|
||
template=instance,
|
||
name='Experience',
|
||
order=3,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=experience_stage,
|
||
label='Position Title',
|
||
field_type='text',
|
||
required=True,
|
||
order=0,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=experience_stage,
|
||
label='Company Name',
|
||
field_type='text',
|
||
required=True,
|
||
order=1,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=experience_stage,
|
||
label='Location',
|
||
field_type='text',
|
||
required=False,
|
||
order=2,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=experience_stage,
|
||
label='Start Date',
|
||
field_type='date',
|
||
required=True,
|
||
order=3,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=experience_stage,
|
||
label='End Date',
|
||
field_type='date',
|
||
required=True,
|
||
order=4,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=experience_stage,
|
||
label='Responsibilities & Achievements',
|
||
field_type='textarea',
|
||
required=False,
|
||
order=5,
|
||
is_predefined=True
|
||
)
|
||
|
||
# Stage 5: Skills
|
||
skills_stage = FormStage.objects.create(
|
||
template=instance,
|
||
name='Skills',
|
||
order=4,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=skills_stage,
|
||
label='Technical Skills',
|
||
field_type='checkbox',
|
||
required=False,
|
||
order=0,
|
||
is_predefined=True,
|
||
options=['Programming Languages', 'Frameworks', 'Tools & Technologies']
|
||
)
|
||
|
||
# Stage 6: Summary
|
||
summary_stage = FormStage.objects.create(
|
||
template=instance,
|
||
name='Summary',
|
||
order=5,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=summary_stage,
|
||
label='Professional Summary',
|
||
field_type='textarea',
|
||
required=False,
|
||
order=0,
|
||
is_predefined=True
|
||
)
|
||
|
||
# Stage 7: Certifications
|
||
certifications_stage = FormStage.objects.create(
|
||
template=instance,
|
||
name='Certifications',
|
||
order=6,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=certifications_stage,
|
||
label='Certification Name',
|
||
field_type='text',
|
||
required=False,
|
||
order=0,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=certifications_stage,
|
||
label='Issuing Organization',
|
||
field_type='text',
|
||
required=False,
|
||
order=1,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=certifications_stage,
|
||
label='Issue Date',
|
||
field_type='date',
|
||
required=False,
|
||
order=2,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=certifications_stage,
|
||
label='Expiration Date',
|
||
field_type='date',
|
||
required=False,
|
||
order=3,
|
||
is_predefined=True
|
||
)
|
||
|
||
# Stage 8: Awards and Recognitions
|
||
awards_stage = FormStage.objects.create(
|
||
template=instance,
|
||
name='Awards and Recognitions',
|
||
order=7,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=awards_stage,
|
||
label='Award Name',
|
||
field_type='text',
|
||
required=False,
|
||
order=0,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=awards_stage,
|
||
label='Issuing Organization',
|
||
field_type='text',
|
||
required=False,
|
||
order=1,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=awards_stage,
|
||
label='Date Received',
|
||
field_type='date',
|
||
required=False,
|
||
order=2,
|
||
is_predefined=True
|
||
)
|
||
FormField.objects.create(
|
||
stage=awards_stage,
|
||
label='Description',
|
||
field_type='textarea',
|
||
required=False,
|
||
order=3,
|
||
is_predefined=True
|
||
) |