import streamlit as st import pickle import re import docx import PyPDF2 from sklearn.metrics.pairwise import cosine_similarity # 1. CONFIG st.set_page_config( page_title="AI Resume Screening", layout="wide", initial_sidebar_state="collapsed" ) # Custom CSS for styling st.markdown(""" """, unsafe_allow_html=True) # 2. LOAD RESOURCES @st.cache_resource def load_resources(): try: clf = pickle.load(open('clf.pkl', 'rb')) tfidf = pickle.load(open('tfidf.pkl', 'rb')) le = pickle.load(open('encoder.pkl', 'rb')) ats = pickle.load(open('ats_scorer.pkl', 'rb')) prototypes = pickle.load(open('prototypes.pkl', 'rb')) return clf, tfidf, le, ats, prototypes except FileNotFoundError: return None, None, None, None, None clf, tfidf, le, ats_model, prototypes = load_resources() # 3. UTILS def clean_text(txt): txt = re.sub(r'http\S+\s', ' ', txt) txt = re.sub(r'[^\w\s]', ' ', txt) return txt.lower() def extract_text(file): try: if file.name.endswith('.pdf'): reader = PyPDF2.PdfReader(file) return " ".join([page.extract_text() for page in reader.pages]) elif file.name.endswith('.docx'): doc = docx.Document(file) return " ".join([p.text for p in doc.paragraphs]) elif file.name.endswith('.txt'): return file.read().decode('utf-8') except: return "" def calculate_scores(text, category): if category not in prototypes: return 0, 0, 0 master_profile = prototypes[category] cleaned_resume = clean_text(text) # Cosine Similarity vecs = tfidf.transform([cleaned_resume, master_profile]) cosine_sim = cosine_similarity(vecs[0], vecs[1])[0][0] # Keyword Match res_tokens = set(cleaned_resume.split()) mp_tokens = set(master_profile.split()) keyword_match = len(res_tokens.intersection(mp_tokens)) / len(mp_tokens) if mp_tokens else 0 # AI Prediction try: ml_score = ats_model.predict([[cosine_sim, keyword_match]])[0] except: ml_score = 0 # Fallback Logic if ml_score < 10: final_score = cosine_sim * 100 else: final_score = ml_score if final_score < 1: final_score *= 100 return round(final_score, 1), round(cosine_sim*100, 1), round(keyword_match*100, 1) # 4. MAIN APP def main(): # Header st.markdown("

🎯 AI Resume Screening

", unsafe_allow_html=True) st.markdown("

Powered by Machine Learning & Natural Language Processing

", unsafe_allow_html=True) if not clf: st.error("⚠️ Models missing! Run `train_model.py` then `train_ats_model.py`.") st.stop() # Upload section st.markdown("
", unsafe_allow_html=True) col1, col2, col3 = st.columns([1, 2, 1]) with col2: file = st.file_uploader( "📤 Upload Your Resume", type=['pdf', 'docx', 'txt'], help="Supported formats: PDF, DOCX, TXT" ) if file: # Custom loading animation loading_placeholder = st.empty() with loading_placeholder.container(): st.markdown("""
Analyzing your resume...
""", unsafe_allow_html=True) # Extract and process text = extract_text(file) # Clear loading animation loading_placeholder.empty() if len(text) > 20: clean = clean_text(text) vec = tfidf.transform([clean]) cat_id = clf.predict(vec)[0] category = le.inverse_transform([cat_id])[0] ats_score, raw_sim, key_match = calculate_scores(text, category) st.markdown("
", unsafe_allow_html=True) # Category prediction st.success(f"### 🎯 Predicted Role: **{category}**") # Score badge if ats_score >= 75: badge_class = "score-high" emoji = "🌟" elif ats_score >= 50: badge_class = "score-medium" emoji = "⚡" else: badge_class = "score-low" emoji = "💡" st.markdown(f"
{emoji} ATS Score: {ats_score}%
", unsafe_allow_html=True) # Metrics st.markdown("### 📊 Detailed Analysis") col1, col2, col3 = st.columns(3) with col1: st.metric( label="🤖 AI Score", value=f"{ats_score}%", delta="Primary Score" ) with col2: st.metric( label="📝 Content Match", value=f"{raw_sim}%", delta="Similarity" ) with col3: st.metric( label="🔑 Keywords", value=f"{key_match}%", delta="Overlap" ) # Progress bar st.markdown("#### Match Strength") st.progress(min(ats_score/100, 1.0)) # Feedback if ats_score > 75: st.balloons() st.info("🎉 Excellent match! Your resume aligns well with this role.") elif ats_score >= 50: st.info("✨ Good match! Consider adding more role-specific keywords to improve.") else: st.warning("💡 Low match. Try adding more relevant skills and experience keywords.") # Extracted text with st.expander("📄 View Extracted Text"): st.text_area("Resume Content", text, height=300) else: st.warning("⚠️ Could not extract text. File might be an image or scan. Please use a text-based document.") if __name__ == "__main__": main()