File size: 8,482 Bytes
cf8a522
92f45fe
7716c5c
8e1d297
92f45fe
 
cccaa8e
8e1d297
 
4c77f62
8e1d297
 
92f45fe
7716c5c
 
92f45fe
 
 
 
7716c5c
 
9753cc9
92f45fe
 
9753cc9
92f45fe
 
 
6637415
92f45fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8e1d297
 
7716c5c
 
 
 
6637415
7716c5c
 
6637415
50528fd
 
 
 
7716c5c
 
 
 
50528fd
 
 
7716c5c
 
6637415
 
7716c5c
 
 
cccaa8e
7716c5c
 
 
 
d836318
6637415
7716c5c
 
6637415
 
cccaa8e
 
6637415
 
 
7716c5c
cccaa8e
6637415
 
 
50528fd
 
 
 
 
 
6637415
 
 
50528fd
6637415
 
 
cccaa8e
6637415
 
 
7716c5c
 
 
d836318
 
 
 
 
6637415
d836318
 
50528fd
d836318
50528fd
 
 
 
d836318
 
50528fd
 
6637415
50528fd
 
 
 
 
6637415
50528fd
 
d836318
 
cccaa8e
 
 
 
 
 
 
 
 
 
 
 
 
 
7716c5c
 
8e1d297
6088e9d
8e1d297
cccaa8e
8e1d297
7716c5c
d836318
cccaa8e
 
 
 
 
 
 
 
 
 
8e1d297
 
586dcd2
8e1d297
cccaa8e
7716c5c
cccaa8e
 
 
7716c5c
8e1d297
7716c5c
8e1d297
d836318
8e1d297
9753cc9
8e1d297
d836318
cccaa8e
7716c5c
cccaa8e
50528fd
 
cccaa8e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import os
import tempfile
import re
import streamlit as st
import docx
import textract
from sentence_transformers import SentenceTransformer, util

#####################################
# Function: Extract Text from File
#####################################
def extract_text_from_file(file_obj):
    """
    Extract text from .doc and .docx files.
    Returns the extracted text or an error message if extraction fails.
    """
    filename = file_obj.name
    ext = os.path.splitext(filename)[1].lower()
    text = ""

    if ext == ".docx":
        try:
            document = docx.Document(file_obj)
            text = "\n".join([para.text for para in document.paragraphs])
        except Exception as e:
            text = f"Error processing DOCX file: {e}"
    elif ext == ".doc":
        try:
            # textract requires a file name; save the file temporarily.
            with tempfile.NamedTemporaryFile(delete=False, suffix=".doc") as tmp:
                tmp.write(file_obj.read())
                tmp.flush()
                tmp_filename = tmp.name
            text = textract.process(tmp_filename).decode("utf-8")
        except Exception as e:
            text = f"Error processing DOC file: {e}"
        finally:
            try:
                os.remove(tmp_filename)
            except Exception:
                pass
    else:
        text = "Unsupported file type."
    return text

#####################################
# Function: Extract Basic Resume Information
#####################################
def extract_basic_resume_info(text):
    """
    Parse the extracted text to extract/summarize:
    - Name
    - Age
    - Job Experience (capturing the block under the "experience" section)
    - Skills
    - Education

    Returns a dictionary with the extracted elements.
    """
    info = {
        "Name": None,
        "Age": None,
        "Job Experience": None,
        "Skills": None,
        "Education": None,
    }

    # Extract Name (e.g., "Name: John Doe" or from heuristics)
    name_match = re.search(r"[Nn]ame[:\-]\s*([A-Za-z\s,]+)", text)
    if name_match:
        info["Name"] = name_match.group(1).strip()
    else:
        # Heuristic: assume a line with two or three capitalized words might be the candidate's name.
        potential_names = re.findall(r"\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){1,2}\b", text)
        if potential_names:
            info["Name"] = potential_names[0]

    # Extract Age (e.g., "Age: 28")
    age_match = re.search(r"[Aa]ge[:\-]\s*(\d{1,3})", text)
    if age_match:
        info["Age"] = age_match.group(1)

    # Extract Job Experience using the "experience" section.
    # Capture everything after the word "experience" until a new section or the end.
    experience_match = re.search(r"experience\s*(.*?)(?:\n\s*\n|additional information|skills|education|$)", text, re.IGNORECASE | re.DOTALL)
    if experience_match:
        job_experience = experience_match.group(1).strip()
        info["Job Experience"] = " ".join(job_experience.split())
    else:
        # Fallback if not a labeled section.
        exp_match = re.search(r"(\d+)\s+(years|yrs)\s+(?:of\s+)?experience", text, re.IGNORECASE)
        if exp_match:
            info["Job Experience"] = f"{exp_match.group(1)} {exp_match.group(2)}"

    # Extract Skills (e.g., "Skills: Python, Java, SQL")
    skills_match = re.search(r"(Skills|Technical Skills)[:\-]\s*(.+)", text, re.IGNORECASE)
    if skills_match:
        skills_str = skills_match.group(2).strip()
        info["Skills"] = skills_str.rstrip(".")

    # Extract Education (e.g., "Education: ...")
    edu_match = re.search(r"education\s*(.*?)(?:\n\s*\n|experience|$)", text, re.IGNORECASE | re.DOTALL)
    if edu_match:
        education_block = edu_match.group(1).strip()
        info["Education"] = " ".join(education_block.split())
    else:
        # Fallback: search for common degree identifiers.
        edu_match = re.search(r"(Bachelor|Master|B\.Sc|M\.Sc|Ph\.D)[^\n]+", text)
        if edu_match:
            info["Education"] = edu_match.group(0)

    return info

#####################################
# Function: Summarize Basic Info into a Paragraph
#####################################
def summarize_basic_info(info):
    """
    Combine the extracted resume elements into a concise summary paragraph.
    """
    parts = []

    if info.get("Name"):
        parts.append(f"Candidate {info['Name']}")
    else:
        parts.append("The candidate")
    
    if info.get("Age"):
        parts.append(f"aged {info['Age']}")
    
    if info.get("Job Experience"):
        parts.append(f"with job experience: {info['Job Experience']}")
    
    if info.get("Skills"):
        parts.append(f"skilled in {info['Skills']}")
    
    if info.get("Education"):
        parts.append(f"and educated in {info['Education']}")
    
    summary_paragraph = ", ".join(parts) + "."
    return summary_paragraph

#####################################
# Function: Compare Candidate Summary to Company Prompt
#####################################
def compute_suitability(candidate_summary, company_prompt, model):
    """
    Compute the cosine similarity between candidate summary and company prompt embeddings.
    Returns a score in the range [0, 1].
    """
    candidate_embed = model.encode(candidate_summary, convert_to_tensor=True)
    company_embed = model.encode(company_prompt, convert_to_tensor=True)
    cosine_sim = util.cos_sim(candidate_embed, company_embed)
    score = float(cosine_sim.item())
    return score

#####################################
# Main Resume Processing Logic
#####################################
def process_resume(file_obj):
    if file_obj is None:
        return None
    resume_text = extract_text_from_file(file_obj)
    basic_info = extract_basic_resume_info(resume_text)
    summary_paragraph = summarize_basic_info(basic_info)
    return summary_paragraph

#####################################
# Load the Sentence-BERT Model
#####################################
@st.cache_resource(show_spinner=False)
def load_model():
    return SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")

model = load_model()

#####################################
# Streamlit Interface
#####################################
st.title("Resume Analyzer and Company Suitability Checker")
st.markdown("""
Upload your resume file in **.doc** or **.docx** format. The app extracts key details (such as name, age, job experience, skills, 
and education) and summarizes them into a single paragraph. It then compares the candidate summary with the company profile
(using a pre-defined prompt for Google LLC) to produce a suitability score.
""")

uploaded_file = st.file_uploader("Upload Resume", type=["doc", "docx"])

if st.button("Process Resume"):
    if uploaded_file is None:
        st.error("Please upload a file first.")
    else:
        with st.spinner("Processing resume..."):
            summary_paragraph = process_resume(uploaded_file)

        st.subheader("Candidate Summary")
        st.markdown(summary_paragraph)

        st.subheader("Company Information (Prompt)")
        default_company_prompt = (
            "Google LLC, a global leader in technology and innovation, specializes in internet services, cloud computing, "
            "artificial intelligence, and software development. As part of Alphabet Inc., Google seeks candidates with strong "
            "problem-solving skills, adaptability, and collaboration abilities. Technical roles require proficiency in programming "
            "languages such as Python, Java, C++, Go, or JavaScript, with expertise in data structures, algorithms, and system design. "
            "Additionally, skills in AI, cybersecurity, UX/UI design, and digital marketing are highly valued. Google fosters a culture "
            "of innovation, expecting candidates to demonstrate creativity, analytical thinking, and a passion for cutting-edge technology."
        )
        company_prompt = st.text_area("Enter company details:", value=default_company_prompt, height=150)

        if st.button("Compute Suitability Score"):
            if not company_prompt.strip():
                st.error("Please enter the company information.")
            else:
                with st.spinner("Computing suitability score..."):
                    score = compute_suitability(summary_paragraph, company_prompt, model)
                st.success(f"Suitability Score: {score:.2f} (range 0 to 1)")