# app.py import gradio as gr import pdfplumber import spacy import os import json import matplotlib.pyplot as plt import tempfile import re from wordcloud import WordCloud # Load NLP model try: nlp = spacy.load("en_core_web_sm") except: from spacy.cli import download download("en_core_web_sm") nlp = spacy.load("en_core_web_sm") # --- Helper functions --- def extract_text_from_pdf(file): with pdfplumber.open(file) as pdf: text = "\n".join([page.extract_text() or "" for page in pdf.pages]) return clean_text(text) def clean_text(text): import re text = re.sub(r'\n+', '\n', text) text = re.sub(r'[^\x00-\x7F]+', ' ', text) # remove non-ASCII text = re.sub(r'\s+', ' ', text) # collapse multiple spaces return text.strip() def extract_contact_info(text): email = re.search(r"[\w.+-]+@[\w-]+\.[\w.-]+", text) phone = re.search(r"\b\d{4}[-.\s]?\d{7}\b|\b\d{11}\b", text) return email.group(0) if email else None, phone.group(0) if phone else None def extract_skills(text): words = set(re.findall(r"\b\w+\b", text.lower())) custom_keywords = re.findall(r"(?i)(?<=skills[:\-\n]).+?(?=education|experience|projects|certification|$)", text, re.DOTALL) skill_keywords = [ "python", "sql", "ml", "ai", "keras", "tensorflow", "html", "css", "nltk", "pandas", "numpy", "power bi", "excel", "scikit-learn", "seaborn", "flask", "django" ] found = {k for k in skill_keywords if k in words} for chunk in custom_keywords: found.update([s.strip().lower() for s in re.split(r",|\n|\||\s{2,}", chunk) if len(s.strip()) >= 2]) return sorted(set(found)) def extract_section(text, section_titles): lines = text.split("\n") section_data = [] capture = False for line in lines: clean_line = line.strip() if not clean_line: continue # Detect section start if any(title.lower() in clean_line.lower() for title in section_titles): capture = True continue # Stop if a new main section starts if capture and re.match(r"^(skills|certifications|projects|summary|languages|contact|references|achievements|publications)\s*[:\-]?$", clean_line, re.IGNORECASE): break # Collect section lines if capture: section_data.append(clean_line) return section_data def match_job_description(skills, job_desc): job_desc = job_desc.lower() matched_skills = [s for s in skills if s.lower() in job_desc] return matched_skills, len(matched_skills), len(skills) def visualize_skills(skills): if not skills: return None plt.figure(figsize=(6, 4)) freq = {s: skills.count(s) for s in skills} plt.bar(freq.keys(), freq.values(), color="skyblue") plt.xticks(rotation=45) plt.title("Skills Frequency") plt.tight_layout() temp_chart = tempfile.NamedTemporaryFile(delete=False, suffix=".png") plt.savefig(temp_chart.name) return temp_chart.name def parse_resume(file): text = extract_text_from_pdf(file.name) doc = nlp(text) name = next((ent.text for ent in doc.ents if ent.label_ == "PERSON"), "Not Found") email, phone = extract_contact_info(text) skills = extract_skills(text) education = extract_section(text, ["education", "academic background", "qualification", "university", "degree", "bachelor", "master"]) experience = extract_section(text, ["experience", "professional experience", "work history", "employment", "internship", "projects"]) print("=== DEBUG TEXT ===") print(text) parsed = { "Name": name, "Email": email, "Phone": phone, "Skills": skills, "Education": education, "Experience": experience, } chart = visualize_skills(skills) return parsed, chart def download_json(data): with tempfile.NamedTemporaryFile(delete=False, suffix=".json", mode="w") as f: json.dump(data, f, indent=4) return f.name def job_matcher(resume_json, job_desc): skills = resume_json.get("Skills", []) matched_skills, matched_count, total_skills = match_job_description(skills, job_desc) return f"โœ… Matched Skills: {matched_skills}\n๐ŸŽฏ Match Score: {matched_count} / {total_skills}" # --- Gradio Interface --- with gr.Blocks(title="๐Ÿง  AI Resume Parser") as demo: gr.Markdown(""" # ๐Ÿง  AI Resume Parser (with Skills Chart, JSON Download, and Job Match) Upload a resume and extract structured data with NLP """) with gr.Row(): resume_input = gr.File(label="Upload Resume (PDF)", file_types=[".pdf"]) parse_btn = gr.Button("๐Ÿ“„ Parse Resume") parsed_output = gr.JSON(label="๐Ÿงพ Parsed Resume") chart_output = gr.Image(label="๐Ÿ“Š Skills Chart") download_btn = gr.File(label="โฌ‡๏ธ Download Resume JSON") with gr.Accordion("๐Ÿ“Œ Match with Job Description", open=False): job_desc_input = gr.Textbox(label="Paste Job Description", lines=5) match_btn = gr.Button("๐Ÿ” Match Skills") match_output = gr.Text(label="Match Result") def full_pipeline(file): parsed, chart_path = parse_resume(file) json_path = download_json(parsed) return parsed, chart_path, json_path parse_btn.click(fn=full_pipeline, inputs=[resume_input], outputs=[parsed_output, chart_output, download_btn]) match_btn.click(fn=job_matcher, inputs=[parsed_output, job_desc_input], outputs=[match_output]) if __name__ == "__main__": demo.launch()