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("""
""", 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(
'
🤖 AI Resume ATS
',
unsafe_allow_html=True
)
st.markdown(
'Upload multiple resumes and instantly rank the best candidates
',
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('', 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('
', 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(
'',
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"""
{skill}
"""
st.markdown(
skill_html,
unsafe_allow_html=True
)
st.markdown(
'
',
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("""
Developed by Ashvinkumar Bari • AI Resume Screening Platform
""", unsafe_allow_html=True)