""" app.py โ AI Resume ATS Analyzer ================================ Main Streamlit application entry point. Features: - Upload PDF or DOCX resume - Automatic text extraction - Named entity recognition (name, orgs, locations) - Skills extraction (technical + soft) - Section detection (Skills, Experience, Projects, Education, Summary) - Resume base score (0โ100) with breakdown - Job description semantic similarity via Sentence-BERT - ATS Score = 0.6 ร base_score + 0.4 ร job_match - Classification: Good / Average / Poor - Actionable suggestions - AI-powered resume rewrite via FLAN-T5 - Manual resume editing + re-evaluation Run locally: streamlit run app.py Deploy on Hugging Face Spaces: 1. Create a new Space (SDK: Streamlit) 2. Upload all project files 3. The Space will install requirements.txt and launch automatically """ import streamlit as st import pandas as pd import time # Local utility modules from utils.parser import parse_resume from utils.nlp_utils import ( extract_entities, detect_sections, extract_skills, get_missing_sections, classify_resume, generate_suggestions, ) from utils.scorer import compute_base_score, compute_ats_score from utils.similarity import compute_similarity, keyword_overlap_score from utils.generator import generate_improved_resume, generate_professional_summary # --------------------------------------------------------------------------- # Page configuration # --------------------------------------------------------------------------- st.set_page_config( page_title="AI Resume ATS Analyzer", page_icon="๐", layout="wide", initial_sidebar_state="expanded", ) # --------------------------------------------------------------------------- # Custom CSS โ clean, professional look # --------------------------------------------------------------------------- st.markdown( """ """, unsafe_allow_html=True, ) # --------------------------------------------------------------------------- # Sidebar โ About & Instructions # --------------------------------------------------------------------------- with st.sidebar: st.image( "https://img.icons8.com/color/96/resume.png", width=70, ) st.markdown("## ๐ AI Resume Analyzer") st.markdown( """ **How to use:** 1. Upload your resume (PDF or DOCX) 2. Paste the job description 3. Click **Analyze Resume** 4. Review your scores & suggestions 5. Use **Generate Improved Resume** for AI rewrite 6. Edit & **Re-evaluate** anytime --- **Scoring Formula:** - Resume Score: up to 100 pts - Skills (20) + Experience (30) - Projects (20) + Education (10) - Length (10) + Diversity (10) - ATS Score = 0.6 ร Resume Score + 0.4 ร Job Match % --- **Powered by:** - ๐ค FLAN-T5 (text generation) - ๐ค Sentence-BERT (similarity) - ๐ง spaCy (NER) - ๐ PyMuPDF + python-docx """ ) st.markdown("---") st.caption("Built with โค๏ธ using open-source AI") # --------------------------------------------------------------------------- # Main UI โ Header # --------------------------------------------------------------------------- st.markdown('
๐ฏ AI Resume ATS Analyzer
', unsafe_allow_html=True) st.markdown( "Upload your resume and a job description to get an **AI-powered ATS score**, " "skill analysis, and personalized improvement suggestions.", unsafe_allow_html=True, ) st.markdown("---") # --------------------------------------------------------------------------- # Input Section โ two columns # --------------------------------------------------------------------------- col_upload, col_jd = st.columns([1, 1], gap="large") with col_upload: st.markdown("### ๐ค Upload Resume") uploaded_file = st.file_uploader( "Supported formats: PDF, DOCX", type=["pdf", "docx"], help="Upload your resume in PDF or Word format", ) with col_jd: st.markdown("### ๐ Job Description") job_description = st.text_area( "Paste the job description here", height=200, placeholder=( "e.g., We are looking for a Python Developer with experience in " "FastAPI, PostgreSQL, and Docker. You will work on backend systems, " "REST APIs, and cloud deployments on AWS..." ), help="The job description is used for semantic matching and tailored suggestions.", ) # --------------------------------------------------------------------------- # Session state โ persist results across reruns # --------------------------------------------------------------------------- if "results" not in st.session_state: st.session_state.results = None if "resume_text_editable" not in st.session_state: st.session_state.resume_text_editable = "" if "improved_resume" not in st.session_state: st.session_state.improved_resume = "" # --------------------------------------------------------------------------- # Helper: run the full analysis pipeline # --------------------------------------------------------------------------- def run_analysis(resume_text: str, job_desc: str) -> dict: """ Execute the complete resume analysis pipeline. Args: resume_text: extracted or edited resume text job_desc : job description text Returns: Dictionary with all analysis results. """ # 1. NLP Analysis with st.spinner("๐ Running NLP analysis..."): entities = extract_entities(resume_text) sections = detect_sections(resume_text) skills = extract_skills(resume_text) missing_sections = get_missing_sections(sections) # 2. Scoring with st.spinner("๐ Computing resume score..."): score_result = compute_base_score(resume_text, sections, skills) # 3. Job Description Similarity with st.spinner("๐ฏ Matching against job description..."): if job_desc.strip(): job_match = compute_similarity(resume_text, job_desc) kw_overlap = keyword_overlap_score(resume_text, job_desc) else: job_match = 0.0 kw_overlap = {"overlap_pct": 0.0, "matched": [], "missing": []} # 4. ATS Score + Classification ats_score = compute_ats_score(score_result["total"], job_match) classification = classify_resume(ats_score) # 5. Suggestions suggestions = generate_suggestions( sections, skills, score_result["total"], job_match ) return { "entities": entities, "sections": sections, "skills": skills, "missing_sections": missing_sections, "base_score": score_result["total"], "score_breakdown": score_result["breakdown"], "job_match": job_match, "kw_overlap": kw_overlap, "ats_score": ats_score, "classification": classification, "suggestions": suggestions, } # --------------------------------------------------------------------------- # Helper: render analysis results # --------------------------------------------------------------------------- def render_results(results: dict, resume_text: str): """Render the full analysis results in the Streamlit UI.""" st.markdown("---") st.markdown("## ๐ Analysis Results") # โโ Score Dashboard โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ c1, c2, c3, c4 = st.columns(4) def _color_score(score, thresholds=(70, 45)): high, mid = thresholds if score >= high: return "#28a745" elif score >= mid: return "#ffc107" return "#dc3545" with c1: color = _color_score(results["base_score"]) st.markdown( f"""{results["base_score"]}
Resume Score
{results["job_match"]}%
Job Match
{results["ats_score"]}%
ATS Score
Classification
๐ Detected Information
', unsafe_allow_html=True) entities = results["entities"] if entities.get("name"): st.markdown(f"**๐ค Candidate Name:** {entities['name']}") if entities.get("organizations"): st.markdown(f"**๐ข Organizations:** {', '.join(entities['organizations'][:5])}") if entities.get("locations"): st.markdown(f"**๐ Locations:** {', '.join(entities['locations'][:5])}") # โโ Sections Detected โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ st.markdown('๐ Sections Detected
', unsafe_allow_html=True) section_display = { "skills": "๐ ๏ธ Skills", "education": "๐ Education", "experience": "๐ผ Experience", "projects": "๐ Projects", "summary": "๐ Summary", "certifications": "๐ Certifications", } cols = st.columns(2) for i, (key, label) in enumerate(section_display.items()): present = results["sections"].get(key, False) icon = "โ " if present else "โ" cols[i % 2].markdown(f"{icon} {label}") # โโ Missing Sections โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ if results["missing_sections"]: st.markdown('โ ๏ธ Missing Sections
', unsafe_allow_html=True) for ms in results["missing_sections"]: st.warning(f"Missing: **{ms}**") with right_col: # โโ Technical Skills โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ st.markdown('โ๏ธ Technical Skills Found
', unsafe_allow_html=True) tech_skills = results["skills"].get("technical", []) if tech_skills: badges = "".join( f'{s.title()}' for s in tech_skills ) st.markdown(badges, unsafe_allow_html=True) else: st.info("No technical skills detected. Add a Skills section.") # โโ Soft Skills โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ st.markdown('๐ค Soft Skills Found
', unsafe_allow_html=True) soft_skills = results["skills"].get("soft", []) if soft_skills: badges = "".join( f'{s.title()}' for s in soft_skills ) st.markdown(badges, unsafe_allow_html=True) else: st.info("No soft skills detected.") # โโ Keyword Overlap (if JD provided) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ kw = results["kw_overlap"] if kw["matched"] or kw["missing"]: st.markdown('๐ Keyword Overlap with JD
', unsafe_allow_html=True) k1, k2 = st.columns(2) with k1: st.metric("Overlap", f"{kw['overlap_pct']}%") if kw["matched"]: st.markdown("**โ Matched:**") st.markdown(", ".join(kw["matched"][:10])) with k2: if kw["missing"]: st.markdown("**โ Missing from JD:**") st.markdown(", ".join(kw["missing"][:10])) # โโ Suggestions โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ st.markdown("---") st.markdown("## ๐ก Improvement Suggestions") for suggestion in results["suggestions"]: st.markdown( f'