# -*- 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)