ATS-Friendly.CV / app.py
SamiKhokhar's picture
Update app.py
7acbc39 verified
import streamlit as st
from docx import Document
import pdfplumber
import re
from collections import Counter
# Custom CSS for UI enhancements
def apply_custom_css():
st.markdown("""
<style>
.title {
font-size: 2.5rem;
color: #4CAF50;
text-align: center;
margin-bottom: 20px;
}
.subtitle {
font-size: 1.5rem;
color: #FF5722;
margin-top: 20px;
margin-bottom: 10px;
}
.metric {
font-size: 1.2rem;
text-align: center;
}
.separator {
border-top: 2px solid #eee;
margin-top: 30px;
margin-bottom: 30px;
}
.suggestions {
background-color: #f9f9f9;
border-left: 4px solid #2196F3;
padding: 10px;
margin: 10px 0;
}
</style>
""", unsafe_allow_html=True)
# Function to extract text from PDF
def extract_text_from_pdf(pdf_file):
with pdfplumber.open(pdf_file) as pdf:
text = ""
for page in pdf.pages:
text += page.extract_text() + "\n"
return text
# Function to extract text from Word Document
def extract_text_from_docx(docx_file):
doc = Document(docx_file)
text = ""
for paragraph in doc.paragraphs:
text += paragraph.text + "\n"
return text
# Function to check ATS-friendliness
def check_ats_friendly(text):
score = 100
suggestions = []
# Keywords check
keywords = ["experience", "skills", "education", "certification", "achievements"]
for keyword in keywords:
if keyword not in text.lower():
score -= 10
suggestions.append(f"Add more emphasis on '{keyword}' in your resume.")
# Formatting check
if re.search(r"[\t]", text): # Tabs are not ATS-friendly
score -= 10
suggestions.append("Avoid using tabs for formatting; use spaces or standard bullet points.")
# Font styles check
if re.search(r"[{}<>]", text): # Symbols like curly braces and angled brackets can confuse ATS
score -= 10
suggestions.append("Remove any special symbols like {}, <>, etc., which might confuse ATS.")
# Contact info check
if not re.search(r"\b\d{10}\b", text): # Check for a 10-digit phone number
score -= 10
suggestions.append("Ensure your resume includes a valid phone number.")
if not re.search(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text): # Check for an email
score -= 10
suggestions.append("Include a valid professional email address.")
# Suggestion for simplicity
if len(text.split()) > 1000: # Resume too long
score -= 10
suggestions.append("Try to keep your resume concise and limit it to one or two pages.")
return score, suggestions
# Function to calculate job fit score and recommend missing keywords
def calculate_job_fit_score(resume_text, job_description):
resume_words = Counter(resume_text.lower().split())
job_words = Counter(job_description.lower().split())
# Find overlap between resume and job description keywords
common_words = resume_words & job_words
total_job_words = sum(job_words.values())
missing_keywords = set(job_words.keys()) - set(resume_words.keys())
# Calculate match percentage
if total_job_words == 0:
match_percentage = 0
else:
match_percentage = sum(common_words.values()) / total_job_words * 100
suggestions = []
if match_percentage < 100:
suggestions.append(
"Consider including the missing keywords in your resume to improve matching."
)
return match_percentage, suggestions, missing_keywords
# Main App
def main():
apply_custom_css()
st.markdown("<div class='title'>ATS Resume Checker</div>", unsafe_allow_html=True)
st.write(
"Upload your resume to check its ATS-friendliness and compare it with a job description for keyword matching."
)
st.markdown("<hr class='separator'>", unsafe_allow_html=True)
uploaded_resume = st.file_uploader(
"πŸ“‚ Upload your Resume (PDF or Word):", type=["pdf", "docx"]
)
job_description = st.text_area("πŸ“‹ Paste the Job Description:")
if uploaded_resume:
# Extract text based on file type
if uploaded_resume.name.endswith(".pdf"):
resume_text = extract_text_from_pdf(uploaded_resume)
elif uploaded_resume.name.endswith(".docx"):
resume_text = extract_text_from_docx(uploaded_resume)
else:
st.error("Unsupported file format. Please upload a PDF or Word document.")
return
# Analyze ATS-friendliness
st.markdown("<div class='subtitle'>πŸ› οΈ ATS Analysis</div>", unsafe_allow_html=True)
ats_score, ats_suggestions = check_ats_friendly(resume_text)
st.metric("ATS Score", f"{ats_score}/100")
if ats_score >= 80:
st.success("βœ… Your resume is ATS-friendly!")
else:
st.warning("⚠️ Your resume needs improvement to be ATS-friendly.")
# Display ATS suggestions
st.markdown("<div class='subtitle'>πŸ“ Suggestions for ATS Improvement</div>", unsafe_allow_html=True)
if ats_suggestions:
for idx, suggestion in enumerate(ats_suggestions, 1):
st.markdown(f"<div class='suggestions'>{idx}. {suggestion}</div>", unsafe_allow_html=True)
else:
st.write("Your resume is well-optimized for ATS!")
st.markdown("<hr class='separator'>", unsafe_allow_html=True)
# Analyze Job Fit
if job_description.strip():
st.markdown("<div class='subtitle'>πŸ” Job Description Matching</div>", unsafe_allow_html=True)
job_fit_score, job_fit_suggestions, missing_keywords = calculate_job_fit_score(
resume_text, job_description
)
st.metric("Job Fit Score", f"{job_fit_score:.2f}%")
if job_fit_score >= 70:
st.success("βœ… Your resume matches well with the job description!")
else:
st.warning("⚠️ Your resume could be tailored better to match the job description.")
# Display job fit suggestions
st.markdown("<div class='subtitle'>πŸ”‘ Missing Keywords</div>", unsafe_allow_html=True)
if missing_keywords:
st.write("To improve matching, consider including these keywords:")
st.markdown(f"<div class='suggestions'>{', '.join(missing_keywords)}</div>", unsafe_allow_html=True)
else:
st.write("Your resume already includes all the required keywords!")
if __name__ == "__main__":
main()