Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -214,13 +214,152 @@ def extract_skills(text, summary):
|
|
214 |
else:
|
215 |
return "No specific technical skills clearly identified (review resume for details)"
|
216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
#####################################
|
218 |
# Function: Summarize Resume Text
|
219 |
#####################################
|
220 |
def summarize_resume_text(resume_text, models):
|
221 |
"""
|
222 |
Generates a structured summary of the resume text including name, age,
|
223 |
-
expected job industry, and
|
224 |
"""
|
225 |
start_time = time.time()
|
226 |
|
@@ -246,11 +385,13 @@ def summarize_resume_text(resume_text, models):
|
|
246 |
age = extract_age(resume_text)
|
247 |
industry = extract_industry(resume_text, base_summary)
|
248 |
skills = extract_skills(resume_text, base_summary)
|
|
|
249 |
|
250 |
# Format the structured summary
|
251 |
formatted_summary = f"Name: {name}\n"
|
252 |
formatted_summary += f"Age: {age}\n"
|
253 |
-
formatted_summary += f"Expected Job Industry: {industry}\n"
|
|
|
254 |
formatted_summary += f"Skills: {skills}"
|
255 |
|
256 |
execution_time = time.time() - start_time
|
@@ -292,7 +433,7 @@ st.markdown(
|
|
292 |
"""
|
293 |
Upload your resume file in **.docx**, **.doc**, or **.txt** format. The app performs the following tasks:
|
294 |
1. Extracts text from the resume.
|
295 |
-
2. Uses AI to generate a structured candidate summary with name, age, expected job industry, and skills.
|
296 |
3. Compares the candidate summary with a company profile to produce a suitability score.
|
297 |
"""
|
298 |
)
|
|
|
214 |
else:
|
215 |
return "No specific technical skills clearly identified (review resume for details)"
|
216 |
|
217 |
+
def extract_work_experience(text):
|
218 |
+
"""Extract work experience from resume"""
|
219 |
+
# Common section headers for work experience
|
220 |
+
work_headers = [
|
221 |
+
"work experience", "professional experience", "employment history",
|
222 |
+
"work history", "experience", "professional background", "career history"
|
223 |
+
]
|
224 |
+
|
225 |
+
# Common section headers that might come after work experience
|
226 |
+
next_section_headers = [
|
227 |
+
"education", "skills", "certifications", "projects", "achievements",
|
228 |
+
"languages", "interests", "references", "additional information"
|
229 |
+
]
|
230 |
+
|
231 |
+
text_lower = text.lower()
|
232 |
+
lines = text.split('\n')
|
233 |
+
|
234 |
+
# Find the start of work experience section
|
235 |
+
work_start_idx = -1
|
236 |
+
work_header_used = ""
|
237 |
+
|
238 |
+
for idx, line in enumerate(lines):
|
239 |
+
line_lower = line.lower().strip()
|
240 |
+
if any(header in line_lower for header in work_headers):
|
241 |
+
if any(header == line_lower or header + ":" == line_lower for header in work_headers):
|
242 |
+
work_start_idx = idx
|
243 |
+
work_header_used = line.strip()
|
244 |
+
break
|
245 |
+
|
246 |
+
if work_start_idx == -1:
|
247 |
+
# Try to find work experience by looking for date patterns (common in resumes)
|
248 |
+
date_pattern = r'(19|20)\d{2}\s*(-|–|to)\s*(19|20)\d{2}|present|current|now'
|
249 |
+
for idx, line in enumerate(lines):
|
250 |
+
if re.search(date_pattern, line.lower()):
|
251 |
+
# Check surrounding lines for job titles or company names
|
252 |
+
context = " ".join(lines[max(0, idx-2):min(len(lines), idx+3)])
|
253 |
+
if any(title.lower() in context.lower() for title in ["manager", "developer", "engineer", "analyst", "assistant", "director", "coordinator"]):
|
254 |
+
work_start_idx = max(0, idx-2)
|
255 |
+
break
|
256 |
+
|
257 |
+
if work_start_idx == -1:
|
258 |
+
return "No clear work experience section found"
|
259 |
+
|
260 |
+
# Find the end of work experience section
|
261 |
+
work_end_idx = len(lines)
|
262 |
+
for idx in range(work_start_idx + 1, len(lines)):
|
263 |
+
line_lower = lines[idx].lower().strip()
|
264 |
+
if any(header in line_lower for header in next_section_headers):
|
265 |
+
if any(header == line_lower or header + ":" == line_lower for header in next_section_headers):
|
266 |
+
work_end_idx = idx
|
267 |
+
break
|
268 |
+
|
269 |
+
# Extract the work experience section
|
270 |
+
work_section = lines[work_start_idx + 1:work_end_idx]
|
271 |
+
|
272 |
+
# Process the work experience to make it more concise
|
273 |
+
# Look for companies, positions, dates, and key responsibilities
|
274 |
+
companies = []
|
275 |
+
current_company = {"name": "", "position": "", "dates": "", "description": []}
|
276 |
+
|
277 |
+
for line in work_section:
|
278 |
+
line = line.strip()
|
279 |
+
if not line:
|
280 |
+
continue
|
281 |
+
|
282 |
+
# Check if this is likely a new company/position entry
|
283 |
+
if re.search(r'(19|20)\d{2}\s*(-|–|to)\s*(19|20)\d{2}|present|current|now', line.lower()):
|
284 |
+
# Save previous company if it exists
|
285 |
+
if current_company["name"] or current_company["position"]:
|
286 |
+
companies.append(current_company)
|
287 |
+
current_company = {"name": "", "position": "", "dates": "", "description": []}
|
288 |
+
|
289 |
+
# This line likely contains position/company and dates
|
290 |
+
current_company["dates"] = line
|
291 |
+
|
292 |
+
# Try to extract position and company
|
293 |
+
parts = re.split(r'(19|20)\d{2}', line, 1)
|
294 |
+
if len(parts) > 1:
|
295 |
+
current_company["position"] = parts[0].strip()
|
296 |
+
elif current_company["dates"] and not current_company["name"]:
|
297 |
+
# This line might be the company name or the continuation of position details
|
298 |
+
current_company["name"] = line
|
299 |
+
else:
|
300 |
+
# This is likely a responsibility or detail
|
301 |
+
current_company["description"].append(line)
|
302 |
+
|
303 |
+
# Add the last company if it exists
|
304 |
+
if current_company["name"] or current_company["position"]:
|
305 |
+
companies.append(current_company)
|
306 |
+
|
307 |
+
# Format the work experience
|
308 |
+
if not companies:
|
309 |
+
# Try a different approach - just extract text blocks that might be jobs
|
310 |
+
job_blocks = []
|
311 |
+
current_block = []
|
312 |
+
|
313 |
+
for line in work_section:
|
314 |
+
line = line.strip()
|
315 |
+
if not line:
|
316 |
+
if current_block:
|
317 |
+
job_blocks.append(" ".join(current_block))
|
318 |
+
current_block = []
|
319 |
+
else:
|
320 |
+
current_block.append(line)
|
321 |
+
|
322 |
+
if current_block:
|
323 |
+
job_blocks.append(" ".join(current_block))
|
324 |
+
|
325 |
+
if job_blocks:
|
326 |
+
return "\n• " + "\n• ".join(job_blocks[:3]) # Limit to top 3 entries
|
327 |
+
else:
|
328 |
+
return "Work experience information could not be clearly structured"
|
329 |
+
|
330 |
+
# Format the companies into a readable output
|
331 |
+
formatted_experience = []
|
332 |
+
for company in companies[:3]: # Limit to top 3 most recent positions
|
333 |
+
entry = []
|
334 |
+
if company["position"]:
|
335 |
+
entry.append(f"**{company['position']}**")
|
336 |
+
if company["name"]:
|
337 |
+
entry.append(f"at {company['name']}")
|
338 |
+
if company["dates"]:
|
339 |
+
entry.append(f"({company['dates']})")
|
340 |
+
|
341 |
+
position_line = " ".join(entry)
|
342 |
+
|
343 |
+
if company["description"]:
|
344 |
+
# Limit to first 2-3 bullet points for conciseness
|
345 |
+
description = company["description"][:3]
|
346 |
+
description_text = "; ".join(description)
|
347 |
+
formatted_experience.append(f"{position_line} - {description_text}")
|
348 |
+
else:
|
349 |
+
formatted_experience.append(position_line)
|
350 |
+
|
351 |
+
if formatted_experience:
|
352 |
+
return "\n• " + "\n• ".join(formatted_experience)
|
353 |
+
else:
|
354 |
+
return "Work experience information could not be clearly structured"
|
355 |
+
|
356 |
#####################################
|
357 |
# Function: Summarize Resume Text
|
358 |
#####################################
|
359 |
def summarize_resume_text(resume_text, models):
|
360 |
"""
|
361 |
Generates a structured summary of the resume text including name, age,
|
362 |
+
expected job industry, skills, and work experience of the candidate.
|
363 |
"""
|
364 |
start_time = time.time()
|
365 |
|
|
|
385 |
age = extract_age(resume_text)
|
386 |
industry = extract_industry(resume_text, base_summary)
|
387 |
skills = extract_skills(resume_text, base_summary)
|
388 |
+
work_experience = extract_work_experience(resume_text)
|
389 |
|
390 |
# Format the structured summary
|
391 |
formatted_summary = f"Name: {name}\n"
|
392 |
formatted_summary += f"Age: {age}\n"
|
393 |
+
formatted_summary += f"Expected Job Industry: {industry}\n\n"
|
394 |
+
formatted_summary += f"Previous Work Experience: {work_experience}\n\n"
|
395 |
formatted_summary += f"Skills: {skills}"
|
396 |
|
397 |
execution_time = time.time() - start_time
|
|
|
433 |
"""
|
434 |
Upload your resume file in **.docx**, **.doc**, or **.txt** format. The app performs the following tasks:
|
435 |
1. Extracts text from the resume.
|
436 |
+
2. Uses AI to generate a structured candidate summary with name, age, expected job industry, previous work experience, and skills.
|
437 |
3. Compares the candidate summary with a company profile to produce a suitability score.
|
438 |
"""
|
439 |
)
|