KhanOmerKhan's picture
Update app.py
37b2010 verified
# -*- coding: utf-8 -*-
"""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
# -----------------------------
openai.api_key = "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" # <-- Replace with your key
# -----------------------------
# User credentials
# -----------------------------
USER_CREDENTIALS = {"admin": "password123"}
# -----------------------------
# Functions to extract resume text
# -----------------------------
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 ""
# -----------------------------
# Function to evaluate resumes
# -----------------------------
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()
# Extract numeric score
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."
)
# -----------------------------
# Function to handle uploaded files
# -----------------------------
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)
# -----------------------------
# Login function
# -----------------------------
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)
# -----------------------------
# Gradio UI
# -----------------------------
def create_interface():
with gr.Blocks() as interface:
# Login group
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")
# Main app group
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
# -----------------------------
# Launch app
# -----------------------------
iface = create_interface()
iface.launch(debug=True, share=True)