ATS / app.py
Teststrelema's picture
Deploy ATS App
9dd6490
Raw History Blame Contribute Delete
11.3 kB
import os
# ---------------- SAFE CACHE ----------------
os.environ["HF_HOME"] = "/tmp/hf"
os.environ["TRANSFORMERS_CACHE"] = "/tmp/hf"
os.environ["TORCH_HOME"] = "/tmp/torch"
# ---------------- IMPORTS ----------------
import streamlit as st
import pdfplumber
import re
import pandas as pd
import spacy
import json
from keybert import KeyBERT
from sentence_transformers import SentenceTransformer, util
from streamlit_lottie import st_lottie
# =========================================================
# PAGE CONFIG
# =========================================================
st.set_page_config(
page_title="AI Resume ATS Matcher",
page_icon="πŸ“„",
layout="wide"
)
# =========================================================
# CUSTOM CSS (LIGHT MODERN UI)
# =========================================================
st.markdown("""
<style>
/* Main Background */
.stApp {
background-color: #f8fafc;
}
/* Header */
.main-title {
font-size: 42px;
font-weight: 800;
color: #111827;
text-align: center;
margin-bottom: 5px;
}
.sub-title {
font-size: 18px;
color: #6b7280;
text-align: center;
margin-bottom: 30px;
}
/* Upload Box */
.upload-box {
background: white;
padding: 25px;
border-radius: 18px;
border: 1px solid #e5e7eb;
box-shadow: 0px 2px 10px rgba(0,0,0,0.04);
margin-bottom: 20px;
}
/* Candidate Card */
.candidate-card {
background: white;
padding: 20px;
border-radius: 18px;
border: 1px solid #e5e7eb;
margin-bottom: 20px;
box-shadow: 0px 2px 10px rgba(0,0,0,0.05);
}
/* Skill Tags */
.skill-tag {
display: inline-block;
background: #eff6ff;
color: #2563eb;
padding: 6px 12px;
border-radius: 30px;
margin: 4px;
font-size: 13px;
font-weight: 500;
}
/* Buttons */
.stButton > button {
width: 100%;
height: 55px;
border-radius: 14px;
background-color: #2563eb;
color: white;
font-size: 18px;
font-weight: 700;
border: none;
}
.stButton > button:hover {
background-color: #1d4ed8;
color: white;
}
/* Dataframe */
[data-testid="stDataFrame"] {
border-radius: 15px;
overflow: hidden;
}
/* Hide Streamlit Footer */
footer {
visibility: hidden;
}
</style>
""", unsafe_allow_html=True)
# =========================================================
# LOAD MODELS
# =========================================================
@st.cache_resource
def load_models():
model = SentenceTransformer("all-MiniLM-L6-v2")
nlp = spacy.load("en_core_web_sm")
kw_model = KeyBERT(model=model)
return model, nlp, kw_model
model, nlp, kw_model = load_models()
# =========================================================
# LOAD ANIMATION
# =========================================================
def load_lottiefile(filepath):
try:
with open(filepath, "r") as f:
return json.load(f)
except:
return None
lottie_animation = load_lottiefile("Animation.json")
# =========================================================
# HEADER
# =========================================================
if lottie_animation:
st_lottie(lottie_animation, height=180)
st.markdown(
'<div class="main-title">πŸ€– AI Resume ATS </div>',
unsafe_allow_html=True
)
st.markdown(
'<div class="sub-title">Upload multiple resumes and instantly rank the best candidates</div>',
unsafe_allow_html=True
)
# =========================================================
# HELPERS
# =========================================================
def extract_text_from_pdf(pdf_file):
text = ""
with pdfplumber.open(pdf_file) as pdf:
for page in pdf.pages:
page_text = page.extract_text()
if page_text:
text += page_text + "\n"
return text.strip()
def clean_text(text):
return re.sub(r"\s+", " ", text).strip()
def extract_skills_auto(text, top_n=15):
text = text[:3000]
keywords = kw_model.extract_keywords(
text,
keyphrase_ngram_range=(1, 3),
stop_words='english',
top_n=top_n
)
return [kw[0] for kw in keywords]
def calculate_similarity(text1, text2):
emb1 = model.encode(text1, convert_to_tensor=True)
emb2 = model.encode(text2, convert_to_tensor=True)
score = util.pytorch_cos_sim(
emb1,
emb2
).item()
return round(score * 100, 2)
# =========================================================
# INPUT SECTION
# =========================================================
#st.markdown('<div class="upload-box">', unsafe_allow_html=True)
st.markdown("### πŸ“„ Upload Resumes")
resume_files = st.file_uploader(
"Upload Multiple PDF Resumes",
type=["pdf"],
accept_multiple_files=True
)
st.markdown("### πŸ“ Paste Job Description")
job_description = st.text_area(
"Enter Job Description",
height=220,
placeholder="Paste the job description here..."
)
st.markdown('</div>', unsafe_allow_html=True)
# =========================================================
# MATCH BUTTON
# =========================================================
if st.button("πŸš€ Match Resumes"):
if resume_files and job_description:
with st.spinner("Analyzing resumes..."):
jd_skills = extract_skills_auto(job_description)
results = []
progress_bar = st.progress(0)
total_files = len(resume_files)
# =========================================================
# PROCESS RESUMES
# =========================================================
for idx, resume_file in enumerate(resume_files):
try:
resume_text = clean_text(
extract_text_from_pdf(resume_file)
)
resume_skills = extract_skills_auto(
resume_text
)
score = calculate_similarity(
" ".join(resume_skills),
" ".join(jd_skills)
)
results.append({
"Resume": resume_file.name,
"ATS Score": score,
"Skills": resume_skills[:8]
})
except Exception as e:
results.append({
"Resume": resume_file.name,
"ATS Score": 0,
"Skills": [f"Error: {str(e)}"]
})
progress_bar.progress((idx + 1) / total_files)
# =========================================================
# SORT RESULTS
# =========================================================
results = sorted(
results,
key=lambda x: x["ATS Score"],
reverse=True
)
# =========================================================
# TOP STATS
# =========================================================
st.markdown("## πŸ“Š ATS Analytics")
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Total Resumes",
len(results)
)
with col2:
st.metric(
"Top ATS Score",
f"{results[0]['ATS Score']}%"
)
with col3:
avg_score = round(
sum(r["ATS Score"] for r in results) / len(results),
2
)
st.metric(
"Average Score",
f"{avg_score}%"
)
st.divider()
# =========================================================
# TABLE
# =========================================================
st.markdown("## πŸ† Candidate Rankings")
table_data = []
for idx, item in enumerate(results, start=1):
table_data.append({
"Rank": idx,
"Resume": item["Resume"],
"ATS Score": f"{item['ATS Score']}%"
})
df = pd.DataFrame(table_data)
st.dataframe(
df,
use_container_width=True
)
# =========================================================
# TOP CANDIDATES
# =========================================================
st.markdown("## ⭐ Best Candidates")
top_candidates = results[:5]
for idx, candidate in enumerate(top_candidates, start=1):
score = candidate["ATS Score"]
# Status
if score >= 85:
status = "Excellent Match βœ…"
elif score >= 65:
status = "Good Match πŸ‘"
else:
status = "Average Match ⚠️"
# CARD
st.markdown(
'<div class="candidate-card">',
unsafe_allow_html=True
)
col1, col2 = st.columns([1, 5])
# LEFT
with col1:
st.metric(
label=f"πŸ… Rank #{idx}",
value=f"{score}%"
)
# RIGHT
with col2:
st.markdown(
f"### πŸ“„ {candidate['Resume']}"
)
st.progress(score / 100)
st.markdown(
f"**Status:** {status}"
)
st.markdown("#### πŸ›  Matching Skills")
skill_html = ""
for skill in candidate["Skills"]:
skill_html += f"""
<span class="skill-tag">
{skill}
</span>
"""
st.markdown(
skill_html,
unsafe_allow_html=True
)
st.markdown(
'</div>',
unsafe_allow_html=True
)
# =========================================================
# DOWNLOAD CSV
# =========================================================
csv = pd.DataFrame(results).to_csv(index=False).encode("utf-8")
st.download_button(
label="πŸ“₯ Download ATS Report CSV",
data=csv,
file_name="ats_report.csv",
mime="text/csv"
)
else:
st.warning(
"⚠️ Please upload resumes and enter a job description."
)
# =========================================================
# FOOTER
# =========================================================
st.markdown("""
<br><br>
<hr>
<p style='text-align:center; color:gray;'>
Developed by <b>Ashvinkumar Bari</b> β€’ AI Resume Screening Platform
</p>
""", unsafe_allow_html=True)