Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import re | |
| import numpy as np | |
| import pandas as pd | |
| from PyPDF2 import PdfReader | |
| from docx import Document | |
| from sentence_transformers import SentenceTransformer | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| import spacy | |
| from fpdf import FPDF | |
| import subprocess | |
| # --------------------------- | |
| # Load SpaCy model (runtime download if needed) | |
| # --------------------------- | |
| try: | |
| nlp = spacy.load("en_core_web_sm") | |
| except OSError: | |
| subprocess.run(["python", "-m", "spacy", "download", "en_core_web_sm"]) | |
| nlp = spacy.load("en_core_web_sm") | |
| # Load sentence-transformers model | |
| model = SentenceTransformer('all-MiniLM-L6-v2') | |
| # --------------------------- | |
| # Resume Parsing Functions | |
| # --------------------------- | |
| def extract_text_from_pdf(file): | |
| try: | |
| reader = PdfReader(file) | |
| text = "" | |
| for page in reader.pages: | |
| text += page.extract_text() or "" | |
| return text | |
| except: | |
| return "" | |
| def extract_text_from_docx(file): | |
| try: | |
| doc = Document(file) | |
| text = "\n".join([p.text for p in doc.paragraphs]) | |
| return text | |
| except: | |
| return "" | |
| def extract_skills(jd_text): | |
| skills = re.split(r"[,\n;]", jd_text) | |
| return [s.strip() for s in skills if s.strip()] | |
| def split_sections(resume_text): | |
| sections = {"Education":"","Experience":"","Skills":""} | |
| try: | |
| edu = re.search(r'(Education|EDUCATION)(.*?)(Experience|EXPERIENCE|Skills|SKILLS|$)', resume_text, re.DOTALL) | |
| exp = re.search(r'(Experience|EXPERIENCE)(.*?)(Skills|SKILLS|$)', resume_text, re.DOTALL) | |
| skills = re.search(r'(Skills|SKILLS)(.*)', resume_text, re.DOTALL) | |
| if edu: sections["Education"] = edu.group(2).strip() | |
| if exp: sections["Experience"] = exp.group(2).strip() | |
| if skills: sections["Skills"] = skills.group(2).strip() | |
| except: | |
| pass | |
| return sections | |
| def compute_scores(resume_text, jd_text, required_skills): | |
| try: | |
| present_skills = [kw for kw in required_skills if kw.lower() in resume_text.lower()] | |
| keyword_score = len(present_skills)/max(len(required_skills),1) | |
| res_vec = model.encode(resume_text) | |
| jd_vec = model.encode(jd_text) | |
| semantic_score = cosine_similarity([res_vec],[jd_vec])[0][0] | |
| sections = split_sections(resume_text) | |
| section_scores = {} | |
| for sec, text in sections.items(): | |
| sec_present = [kw for kw in required_skills if kw.lower() in text.lower()] | |
| section_scores[sec] = len(sec_present)/max(len(required_skills),1) | |
| final_score = 0.6*keyword_score + 0.4*semantic_score | |
| tips = [f"⚠️ Add '{skill}' to improve ATS match" for skill in required_skills if skill.lower() not in resume_text.lower()] | |
| return final_score, keyword_score, semantic_score, section_scores, tips | |
| except: | |
| return 0,0,0,{"Education":0,"Experience":0,"Skills":0},[] | |
| # --------------------------- | |
| # CSV & PDF Export | |
| # --------------------------- | |
| def export_csv(df, filename="ats_report.csv"): | |
| try: | |
| df.to_csv(filename, index=False) | |
| except: | |
| pass | |
| return filename | |
| def export_pdf(df, filename="ats_report.pdf"): | |
| try: | |
| pdf = FPDF() | |
| pdf.add_page() | |
| pdf.set_font("Arial", size=12) | |
| pdf.cell(200, 10, txt="ATS Resume Screening Report", ln=True, align="C") | |
| pdf.ln(10) | |
| for i, row in df.iterrows(): | |
| pdf.cell(200, 10, txt=f"JD {i+1}: {row['JD']}", ln=True) | |
| pdf.cell(200, 10, txt=f"Final Score: {row['Final Score']}", ln=True) | |
| pdf.cell(200, 10, txt=f"Keyword Score: {row['Keyword Score']}", ln=True) | |
| pdf.cell(200, 10, txt=f"Semantic Score: {row['Semantic Score']}", ln=True) | |
| pdf.cell(200, 10, txt="Section Scores:", ln=True) | |
| pdf.multi_cell(0, 10, row["Section Scores"]) | |
| pdf.cell(200, 10, txt="Tips:", ln=True) | |
| pdf.multi_cell(0, 10, row["Tips"]) | |
| pdf.ln(5) | |
| pdf.output(filename) | |
| except: | |
| pass | |
| return filename | |
| # --------------------------- | |
| # AI Resume Rewriter & Feedback | |
| # --------------------------- | |
| def ai_resume_rewriter(resume_text, jd_text): | |
| required_skills = extract_skills(jd_text) | |
| missing_skills = [skill for skill in required_skills if skill.lower() not in resume_text.lower()] | |
| rewritten = resume_text | |
| if missing_skills: | |
| rewritten += "\n\n### Suggested Skills to Add:\n" + "\n".join([f"- {s}" for s in missing_skills]) | |
| return rewritten | |
| skill_course_mapping = { | |
| "Python": ["Complete 'Python for Everybody' on Coursera", "Try Python projects on GitHub"], | |
| "Machine Learning": ["Take 'Machine Learning' by Andrew Ng on Coursera", "Kaggle ML competitions"], | |
| "Deep Learning": ["DeepLearning.AI TensorFlow Developer Course", "Build neural network projects"], | |
| "SQL": ["SQL for Data Science - Coursera", "Practice on LeetCode SQL problems"], | |
| "AWS": ["AWS Certified Solutions Architect - Associate", "AWS Free Tier practice"], | |
| "TensorFlow": ["TensorFlow in Practice Specialization - Coursera", "Hands-on DL projects"] | |
| } | |
| certification_mapping = { | |
| "AWS": "AWS Certified Solutions Architect", | |
| "ML": "Machine Learning by Andrew Ng", | |
| "Python": "PCAP: Python Certified Associate Programmer", | |
| "TensorFlow": "TensorFlow Developer Certificate" | |
| } | |
| def generate_feedback(resume_text, jd_text): | |
| required_skills = extract_skills(jd_text) | |
| resume_lower = resume_text.lower() | |
| missing_skills = [skill for skill in required_skills if skill.lower() not in resume_lower] | |
| skill_suggestions = [f"{s}: {', '.join(skill_course_mapping[s])}" for s in missing_skills if s in skill_course_mapping] | |
| cert_suggestions = [f"Consider certification: {certification_mapping[s]}" for s in missing_skills if s in certification_mapping] | |
| resume_tips = [] | |
| if "Education" not in resume_text: | |
| resume_tips.append("Include an Education section if missing.") | |
| if "Experience" not in resume_text: | |
| resume_tips.append("Include an Experience section with quantified achievements.") | |
| if "Skills" not in resume_text: | |
| resume_tips.append("Add a Skills section highlighting relevant skills.") | |
| if len(resume_text.split()) < 200: | |
| resume_tips.append("Consider adding more details to increase resume length and content richness.") | |
| feedback_text = "### Missing Skills:\n" + ("\n".join(missing_skills) if missing_skills else "None") | |
| feedback_text += "\n\n### Suggested Courses:\n" + ("\n".join(skill_suggestions) if skill_suggestions else "No suggestions") | |
| feedback_text += "\n\n### Suggested Certifications:\n" + ("\n".join(cert_suggestions) if cert_suggestions else "No suggestions") | |
| feedback_text += "\n\n### Resume Optimization Tips:\n" + ("\n".join(resume_tips) if resume_tips else "Your resume looks well-structured.") | |
| return feedback_text | |
| # --------------------------- | |
| # Multi-JD Analysis | |
| # --------------------------- | |
| def analyze_multi_jd(resume_file, jd_texts): | |
| file_ext = resume_file.name.split('.')[-1].lower() | |
| if file_ext == "pdf": | |
| resume_text = extract_text_from_pdf(resume_file) | |
| elif file_ext == "docx": | |
| resume_text = extract_text_from_docx(resume_file) | |
| else: | |
| resume_text = "" | |
| jd_list = [jd.strip() for jd in jd_texts.split("\n\n") if jd.strip()] | |
| results = [] | |
| for jd in jd_list: | |
| required_skills = extract_skills(jd) | |
| final_score, keyword_score, semantic_score, section_scores, tips = compute_scores(resume_text, jd, required_skills) | |
| section_scores_str = "\n".join([f"{k}: {v:.2%}" for k,v in section_scores.items()]) | |
| tips_str = "\n".join(tips) if tips else "No suggestions" | |
| results.append({ | |
| "JD": jd[:50]+"..." if len(jd)>50 else jd, | |
| "Final Score": f"{final_score:.2%}", | |
| "Keyword Score": f"{keyword_score:.2%}", | |
| "Semantic Score": f"{semantic_score:.2%}", | |
| "Section Scores": section_scores_str, | |
| "Tips": tips_str | |
| }) | |
| df = pd.DataFrame(results) | |
| export_csv(df) | |
| export_pdf(df) | |
| feedback = generate_feedback(resume_text, jd_texts) | |
| rewritten_resume = ai_resume_rewriter(resume_text, jd_texts) | |
| return "ats_report.csv", "ats_report.pdf", feedback, rewritten_resume | |
| # --------------------------- | |
| # Gradio Interface | |
| # --------------------------- | |
| iface = gr.Interface( | |
| fn=analyze_multi_jd, | |
| inputs=[ | |
| gr.File(label="Upload Resume (PDF/DOCX)"), | |
| gr.Textbox(label="Paste Job Description(s) (Separate multiple JDs with double line breaks)", lines=10) | |
| ], | |
| outputs=[ | |
| gr.File(label="Download CSV Report"), | |
| gr.File(label="Download PDF Report"), | |
| gr.Textbox(label="Personalized Feedback", lines=15), | |
| gr.Textbox(label="AI Suggested Resume Revisions", lines=15) | |
| ], | |
| title="AI-Powered Resume Screening System", | |
| description="Upload your resume, paste job descriptions, and get ATS scoring, personalized feedback, and AI suggestions." | |
| ) | |
| iface.launch() | |