|
|
|
|
|
"""Feedalytics Resume Intelligence - Version2 - Block 2 (Colab-Ready)""" |
|
|
|
|
|
from docx import Document |
|
|
import openai |
|
|
import gradio as gr |
|
|
import PyPDF2 |
|
|
import re |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
openai.api_key = "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" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
USER_CREDENTIALS = {"admin": "password123"} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def extract_text_from_docx(file): |
|
|
doc = Document(file.name) |
|
|
return '\n'.join([para.text for para in doc.paragraphs]) |
|
|
|
|
|
def extract_text_from_pdf(file): |
|
|
try: |
|
|
reader = PyPDF2.PdfReader(file.name) |
|
|
text = '' |
|
|
for page in reader.pages: |
|
|
text += page.extract_text() or '' |
|
|
return text.strip() |
|
|
except Exception as e: |
|
|
return "" |
|
|
|
|
|
def get_resume_text(file): |
|
|
if file.name.endswith(".docx"): |
|
|
return extract_text_from_docx(file) |
|
|
elif file.name.endswith(".pdf"): |
|
|
return extract_text_from_pdf(file) |
|
|
else: |
|
|
return "" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def evaluate_resumes(resumes, job_description, model="gpt-3.5-turbo-16k"): |
|
|
shortlisted, potential, rejected = [], [], [] |
|
|
|
|
|
for i, resume_text in enumerate(resumes): |
|
|
if not resume_text: |
|
|
continue |
|
|
|
|
|
prompt = f""" |
|
|
Job Description: |
|
|
{job_description} |
|
|
|
|
|
Candidate Resume: |
|
|
{resume_text} |
|
|
|
|
|
Evaluate this resume and provide: |
|
|
1. Candidate's Name |
|
|
2. Match score (0-10), based on the below-mentioned criteria |
|
|
Score Range Category Interpretation\n |
|
|
"0 β 4 β Poor Match Resume content does not align with the job description. Few or no required skills, responsibilities, or relevant experience are mentioned.\n" |
|
|
"5 β 6 β οΈ Partial Match Resume shows some alignment, but several required skills, experiences, or educational requirements are missing. Candidate might need retraining or be applying for a mismatched role.\n" |
|
|
"6 β 7 βοΈ Moderate Match Resume meets most of the requirements but lacks depth or one major qualification (e.g., missing a key tool or specific experience).\n" |
|
|
"7 β 8 β
Good Match Resume matches the majority of the job description. Required skills and relevant experience are clearly stated. Candidate seems capable of performing well.\n" |
|
|
"8 β 9 πͺ Strong Match Resume closely matches both skills and experience. The candidate clearly meets or exceeds most requirements, including tools, certifications, and outcomes.\n" |
|
|
"9 β 10 π Excellent Match Resume is a near-perfect fit β covers all required skills, relevant experience, and qualifications. Uses similar language and metrics to the job description.\n\n" |
|
|
3. Brief justification (2-4 sentences), solely on the basis of match and ignore errors caused by varying resume types (pdf.word.docx) |
|
|
""" |
|
|
|
|
|
try: |
|
|
response = openai.ChatCompletion.create( |
|
|
model=model, |
|
|
messages=[{"role": "user", "content": prompt}], |
|
|
max_tokens=500, |
|
|
temperature=0 |
|
|
) |
|
|
|
|
|
evaluation = response['choices'][0]['message']['content'].strip() |
|
|
|
|
|
|
|
|
match = re.search(r"(\d{1,3})\s*%", evaluation) |
|
|
score = int(match.group(1)) if match else 0 |
|
|
|
|
|
candidate_label = f"Candidate {i+1} ({resumes[i].name})" |
|
|
|
|
|
if score >= 90: |
|
|
shortlisted.append(f"{candidate_label}:\n{evaluation}\n{'-'*30}") |
|
|
elif score >= 65: |
|
|
potential.append(f"{candidate_label}:\n{evaluation}\n{'-'*30}") |
|
|
else: |
|
|
rejected.append(f"{candidate_label}:\n{evaluation}\n{'-'*30}") |
|
|
|
|
|
except Exception as e: |
|
|
rejected.append(f"Candidate {i+1} ({resumes[i].name}): Error evaluating resume - {e}") |
|
|
|
|
|
return ( |
|
|
"\n\n".join(shortlisted) if shortlisted else "No shortlisted candidates.", |
|
|
"\n\n".join(potential) if potential else "No potential candidates.", |
|
|
"\n\n".join(rejected) if rejected else "No rejected candidates." |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def process_uploaded_resumes(job_file, resume_files): |
|
|
if not job_file: |
|
|
return "Job description required.", "", "" |
|
|
if not resume_files: |
|
|
return "Upload at least one resume.", "", "" |
|
|
|
|
|
job_description = extract_text_from_docx(job_file) |
|
|
resume_texts = [get_resume_text(f) for f in resume_files] |
|
|
|
|
|
return evaluate_resumes(resume_files, job_description) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def authenticate(username, password): |
|
|
if username in USER_CREDENTIALS and USER_CREDENTIALS[username] == password: |
|
|
return gr.update(visible=False), gr.update(visible=True) |
|
|
else: |
|
|
return gr.update(visible=True), gr.update(visible=False) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_interface(): |
|
|
with gr.Blocks() as interface: |
|
|
|
|
|
login_group = gr.Group(visible=True) |
|
|
with login_group: |
|
|
gr.Markdown("### Login to Resume Shortlisting App") |
|
|
username = gr.Textbox(label="Username") |
|
|
password = gr.Textbox(label="Password", type="password") |
|
|
login_btn = gr.Button("Login") |
|
|
|
|
|
|
|
|
app_group = gr.Group(visible=False) |
|
|
with app_group: |
|
|
gr.Markdown("### Resume Shortlisting Application") |
|
|
job_file = gr.File(label="Upload Job Description (.docx)") |
|
|
resume_files = gr.File(label="Upload Resumes (.pdf or .docx)", file_types=[".pdf", ".docx"], file_count="multiple") |
|
|
submit_btn = gr.Button("Evaluate Resumes") |
|
|
|
|
|
shortlisted = gr.Textbox(label="Shortlisted Candidates", lines=10) |
|
|
potential = gr.Textbox(label="Potential Candidates", lines=10) |
|
|
rejected = gr.Textbox(label="Rejected Candidates", lines=10) |
|
|
|
|
|
submit_btn.click(process_uploaded_resumes, [job_file, resume_files], [shortlisted, potential, rejected]) |
|
|
|
|
|
login_btn.click(authenticate, [username, password], [login_group, app_group]) |
|
|
|
|
|
return interface |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
iface = create_interface() |
|
|
iface.launch(debug=True, share=True) |