import os import re import tempfile import uuid from pathlib import Path import gradio as gr import torch from transformers import ( AutoTokenizer, AutoModelForTokenClassification, pipeline, AutoModelForSeq2SeqLM, ) from sentence_transformers import SentenceTransformer, util from pdfminer.high_level import extract_text import nltk # ------------------------------------------------------------------ # 1. One-time NLTK resource # ------------------------------------------------------------------ try: nltk.data.find("tokenizers/punkt") except LookupError: nltk.download("punkt") # ------------------------------------------------------------------ # 2. Global model / pipeline setup (cached on first run) # ------------------------------------------------------------------ device = 0 if torch.cuda.is_available() else -1 # NER ner_pipe = pipeline( "ner", model="dslim/bert-base-NER", tokenizer="dslim/bert-base-NER", aggregation_strategy="simple", device=device, ) # Sentence similarity sbert = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2") # LLM for suggestions sugg_tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-base") sugg_model = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-base") if device >= 0: sugg_model = sugg_model.to("cuda") # Multilingual translation (NLLB-200) trans_tokenizer = AutoTokenizer.from_pretrained("facebook/nllb-200-distilled-600M") trans_model = AutoModelForSeq2SeqLM.from_pretrained( "facebook/nllb-200-distilled-600M" ) # ------------------------------------------------------------------ # 3. Helper functions # ------------------------------------------------------------------ def translate_to_english(text: str) -> str: """ Detects language via heuristics and translates to English using NLLB-200. """ try: text.encode("ascii") return text except UnicodeEncodeError: pass inputs = trans_tokenizer( text, return_tensors="pt", truncation=True, max_length=512, ) if device >= 0: inputs = {k: v.to("cuda") for k, v in inputs.items()} bos_token_id = trans_tokenizer.convert_tokens_to_ids("eng_Latn") translated = trans_model.generate( **inputs, forced_bos_token_id=bos_token_id, max_length=512, ) return trans_tokenizer.batch_decode(translated, skip_special_tokens=True)[0] def extract_text_from_file(file_path: str) -> str: path = Path(file_path) if path.suffix.lower() == ".pdf": return extract_text(file_path) with open(file_path, encoding="utf-8") as f: return f.read() def extract_skills_and_experience(resume_text: str): ner_results = ner_pipe(resume_text) entities = {ent["word"].strip() for ent in ner_results} skill_pattern = re.compile( r"\b(Python|SQL|Java|C\+\+|JavaScript|React|AWS|Docker|Kubernetes|TensorFlow|PyTorch|Scikit-learn|Pandas|Numpy|Excel|PowerBI|Tableau|SEO|Photoshop|Figma|CAD|SolidWorks|MATLAB|R|Go|Node\.js|Vue|Angular|MongoDB|MySQL|PostgreSQL|GCP|Azure|Linux|Git|CI/CD|Agile|Scrum|Machine Learning|Deep Learning|NLP|Computer Vision|Data Engineering|Data Science|Marketing|Sales|Design|UI|UX|DevOps|SRE|Frontend|Backend|Full-stack)\b", flags=re.I, ) regex_skills = set(skill_pattern.findall(resume_text)) skills = entities.union(regex_skills) return list(skills) def classify_domain(skills: list[str]) -> str: domain_keywords = { "Data Science": {"python", "sql", "pandas", "numpy", "machine learning"}, "Software Engineering": {"java", "c++", "python", "javascript", "react"}, "Marketing": {"seo", "google ads", "facebook ads", "content marketing"}, "Design": {"photoshop", "figma", "ui", "ux"}, "DevOps": {"docker", "kubernetes", "aws", "ci/cd"}, } skill_set = {s.lower() for s in skills} best_domain = "General" best_score = 0 for domain, keywords in domain_keywords.items(): score = len(skill_set & keywords) if score > best_score: best_score = score best_domain = domain return best_domain def suggest_job_titles(domain: str, skills: list[str], top_k: int = 5) -> list[str]: job_bank = { "Data Science": [ "Data Scientist", "Machine Learning Engineer", "Data Analyst", "AI Researcher", "Business Intelligence Analyst", ], "Software Engineering": [ "Backend Developer", "Frontend Developer", "Full-stack Developer", "Software Engineer", "DevOps Engineer", ], "Marketing": [ "Digital Marketing Specialist", "SEO Specialist", "Content Marketing Manager", "Growth Hacker", "Social Media Manager", ], "Design": [ "UI/UX Designer", "Product Designer", "Graphic Designer", "Web Designer", "Motion Designer", ], "DevOps": [ "DevOps Engineer", "Site Reliability Engineer", "Cloud Engineer", "Platform Engineer", "System Administrator", ], "General": [ "Project Manager", "Product Manager", "Consultant", "Technical Writer", "Operations Analyst", ], } pool = job_bank.get(domain, job_bank["General"]) skill_text = " ".join(skills).lower() skill_emb = sbert.encode(skill_text, convert_to_tensor=True) job_embs = sbert.encode(pool, convert_to_tensor=True) scores = util.cos_sim(skill_emb, job_embs)[0] top_indices = torch.topk(scores, k=min(top_k, len(pool))).indices return [pool[i] for i in top_indices] def generate_recommendations(skills: list[str], domain: str, job: str) -> str: prompt = ( f"Give concise advice for a candidate applying for {job} in {domain}. " f"They currently have these skills: {', '.join(skills)}. " f"What skills are missing and what should they improve?" ) inputs = sugg_tokenizer.encode(prompt, return_tensors="pt") if device >= 0: inputs = inputs.to("cuda") outputs = sugg_model.generate( inputs, max_length=150, num_beams=3, early_stopping=True ) return sugg_tokenizer.decode(outputs[0], skip_special_tokens=True) def build_report( skills: list[str], domain: str, job_titles: list[str], recommendations: str, ): report_lines = ["=== ResumeRanker AI Report ===", ""] report_lines.append("Extracted Skills:") report_lines.extend(f"- {s}" for s in skills) report_lines.append("") report_lines.append(f"Inferred Career Domain: {domain}") report_lines.append("") report_lines.append("Suggested Job Titles:") report_lines.extend(f"- {j}" for j in job_titles) report_lines.append("") report_lines.append("Recommendations / Skill Gaps:") report_lines.append(recommendations) return "\n".join(report_lines) # ------------------------------------------------------------------ # 4. Gradio UI # ------------------------------------------------------------------ def process_resume(file_obj): with tempfile.NamedTemporaryFile( delete=False, suffix=Path(file_obj.name).suffix ) as tmp: tmp.write(file_obj.read()) tmp_path = tmp.name raw_text = extract_text_from_file(tmp_path) english_text = translate_to_english(raw_text) skills = extract_skills_and_experience(english_text) domain = classify_domain(skills) job_titles = suggest_job_titles(domain, skills) recommendations = generate_recommendations(skills, domain, job_titles[0]) report = build_report(skills, domain, job_titles, recommendations) os.remove(tmp_path) return ( ", ".join(skills), domain, ", ".join(job_titles), recommendations, report, ) # ------------------------------------------------------------------ # 5. Launch # ------------------------------------------------------------------ if __name__ == "__main__": demo = gr.Interface( fn=process_resume, inputs=gr.File( label="Upload Resume (PDF or TXT)", file_types=[".pdf", ".txt"], type="binary", ), outputs=[ gr.Textbox(label="Extracted Skills"), gr.Textbox(label="Career Domain"), gr.Textbox(label="Suggested Job Titles"), gr.Textbox(label="Recommendations"), gr.Textbox(label="Full Report"), ], title="ResumeRanker AI", description="Upload your resume and get smart AI-powered job matching & improvement suggestions.", allow_flagging="never", ) demo.launch()