kaauh_ats/recruitment/signals.py

421 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 (12 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
)