File size: 5,554 Bytes
b599927
e8ef9d3
b599927
e8ef9d3
ff761e7
b599927
4aa5719
b599927
 
 
e8ef9d3
b599927
f64ab2b
ff761e7
f64ab2b
1f5134c
 
ff761e7
 
b599927
 
 
 
1f5134c
ff761e7
596c61d
 
 
 
 
 
 
b599927
 
 
 
ff761e7
b599927
1f5134c
 
 
 
 
 
 
 
 
 
b599927
3f73843
dfa9f62
5b412c3
dfa9f62
5b412c3
dfa9f62
 
5b412c3
dfa9f62
5b412c3
 
dfa9f62
 
 
 
 
 
 
5b412c3
 
dfa9f62
 
 
5b412c3
dfa9f62
5b412c3
b599927
 
1f5134c
 
b599927
 
 
1f5134c
 
b599927
1f5134c
 
b599927
 
 
 
 
 
ff761e7
4aa5719
ff761e7
b599927
 
 
 
dfa9f62
 
 
 
 
3f73843
b599927
 
 
 
 
 
 
 
 
 
1f5134c
b599927
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4aa5719
b599927
 
 
 
 
 
ff761e7
 
b599927
 
ff761e7
 
b599927
 
 
 
 
 
 
ff761e7
 
1f5134c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# 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()