import os import re import json from datetime import datetime from typing import Dict, List, Optional, Tuple from fastapi import FastAPI, UploadFile, File, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.wsgi import WSGIMiddleware from pydantic import BaseModel import gradio as gr import pdfplumber from sentence_transformers import SentenceTransformer, util print("Loading Semantic Model...") model = SentenceTransformer('all-MiniLM-L6-v2') print("Model Loaded!") SKILLS_DB = { "python": ["python", "django", "flask", "fastapi", "numpy", "pandas"], "java": ["java", "spring", "spring boot", "maven", "gradle"], "javascript": ["javascript", "node.js", "express", "vue"], "react": ["react", "react.js", "reactjs", "next.js"], "angular": ["angular", "angular.js"], "typescript": ["typescript", "ts"], "html": ["html", "html5"], "css": ["css", "css3", "tailwind", "bootstrap"], "aws": ["aws", "ec2", "s3", "lambda"], "azure": ["azure", "microsoft azure"], "gcp": ["gcp", "google cloud"], "docker": ["docker", "container", "dockerfile"], "kubernetes": ["kubernetes", "k8s", "aks", "eks"], "jenkins": ["jenkins", "ci/cd"], "git": ["git", "github", "gitlab"], "sql": ["sql", "mysql", "postgresql", "mongodb"], "machine_learning": ["machine learning", "ml", "tensorflow", "pytorch"], "data_science": ["data science", "analytics", "visualization"], } CAREERS = { "AI/ML Engineer": ["machine learning", "tensorflow", "pytorch", "ai", "nlp"], "Web Developer": ["react", "angular", "vue", "django", "flask", "html", "css"], "DevOps Engineer": ["docker", "kubernetes", "aws", "jenkins", "ci/cd"], "Data Scientist": ["python", "pandas", "numpy", "data science"], "Mobile Developer": ["android", "ios", "flutter", "react native"], } def detect_skills(text: str) -> Tuple[List[str], Dict[str, float]]: skills = [] scores = {} sentences = [s.strip() for s in text.split('\n') if len(s.strip()) > 15][:50] for skill, variations in SKILLS_DB.items(): best_score = 0.0 for var in variations: if var in text.lower(): best_score = 1.0 break try: var_emb = model.encode(var, convert_to_tensor=True) for sentence in sentences[:20]: sent_emb = model.encode(sentence, convert_to_tensor=True) sim = util.pytorch_cos_sim(var_emb, sent_emb).item() if sim > best_score: best_score = sim except: continue if best_score >= 0.60: skill_name = skill.replace('_', ' ').title() skills.append(skill_name) scores[skill_name] = round(best_score * 100, 1) skills = list(dict.fromkeys(skills))[:15] return skills, scores def detect_career(skills: List[str], text: str) -> Tuple[Dict[str, float], str]: interests = {} for career, keywords in CAREERS.items(): score = 0 for skill in skills: for keyword in keywords: if keyword.lower() in skill.lower(): score += 25 for keyword in keywords: if keyword in text.lower(): score += 10 if score > 15: interests[career] = min(100, score) interests = dict(sorted(interests.items(), key=lambda x: x[1], reverse=True)) suggested = list(interests.keys())[0] if interests else "Software Developer" return interests, suggested def skill_gap(student_skills: List[str], job_skills: List[str]) -> Dict: if not student_skills or not job_skills: return {"match_score": 0, "missing_skills": job_skills, "recommendation": "Add skills to analyze"} student_set = set([s.lower() for s in student_skills]) job_set = set([s.lower() for s in job_skills]) missing = list(job_set - student_set) matched = len([s for s in job_set if s in student_set]) score = (matched / len(job_set)) * 100 if job_set else 0 return { "match_score": round(score, 1), "missing_skills": missing[:10], "recommendation": f"Match: {round(score,1)}%. Missing: {', '.join(missing[:5])}" if missing else "Perfect match!" } def extract_email(text: str) -> Optional[str]: pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' matches = re.findall(pattern, text) return matches[0] if matches else None def extract_name(text: str) -> str: lines = text.split('\n')[:20] for line in lines: line = line.strip() if 2 <= len(line.split()) <= 4 and line[0].isupper(): if not any(w in line.lower() for w in ['resume', 'cv', 'email']): return line return "Candidate" def parse_resume(file_path: str) -> Dict: text = "" with pdfplumber.open(file_path) as pdf: for page in pdf.pages: t = page.extract_text() if t: text += t + "\n" if not text: return {"error": "No text found"} skills, scores = detect_skills(text) interests, suggested = detect_career(skills, text) return { "name": extract_name(text), "email": extract_email(text), "skills": skills, "skill_scores": scores, "interests": interests, "suggested_career": suggested, "status": "success" } app = FastAPI(title="Career Path API", version="4.0.0") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) @app.get("/") def root(): return { "service": "Career Path Resume Parser", "version": "4.0.0", "endpoints": { "POST /api/parse": "Parse resume", "POST /api/match": "Match with job", "GET /api/health": "Health check", "GET /ui": "Gradio UI" } } @app.get("/api/health") def health(): return {"status": "healthy", "model": "all-MiniLM-L6-v2"} @app.post("/api/parse") async def api_parse(file: UploadFile = File(...)): if not file.filename.endswith('.pdf'): raise HTTPException(400, "PDF required") temp = f"temp_{file.filename}" content = await file.read() with open(temp, "wb") as f: f.write(content) result = parse_resume(temp) os.remove(temp) if "error" in result: raise HTTPException(400, result["error"]) return {"success": True, "data": result} @app.post("/api/match") async def api_match(file: UploadFile = File(...), required_skills: str = ""): if not required_skills: raise HTTPException(400, "required_skills required") job_skills = [s.strip() for s in required_skills.split(',')] temp = f"temp_{file.filename}" content = await file.read() with open(temp, "wb") as f: f.write(content) result = parse_resume(temp) os.remove(temp) gap = skill_gap(result.get("skills", []), job_skills) return {"success": True, "match_analysis": gap} def gradio_parse(file): if not file: return "Upload PDF file", None result = parse_resume(file.name) if "error" in result: return f"Error: {result['error']}", None output = "=" * 50 + "\n" output += "PARSING RESULTS\n" output += "=" * 50 + "\n\n" output += f"Name: {result.get('name', 'N/A')}\n" output += f"Email: {result.get('email', 'N/A')}\n\n" output += f"Skills: {', '.join(result.get('skills', []))}\n\n" output += f"Suggested Career: {result.get('suggested_career', 'N/A')}\n" return output, result def gradio_match(file, job_skills): if not file or not job_skills: return "Upload file and enter skills", None job_list = [s.strip() for s in job_skills.split(',')] result = parse_resume(file.name) gap = skill_gap(result.get("skills", []), job_list) output = "=" * 50 + "\n" output += "SKILL GAP ANALYSIS\n" output += "=" * 50 + "\n\n" output += f"Match Score: {gap['match_score']}%\n\n" output += f"Missing Skills: {', '.join(gap['missing_skills']) if gap['missing_skills'] else 'None!'}\n\n" output += f"Recommendation: {gap['recommendation']}\n" return output, gap with gr.Blocks(title="Career Path") as demo: gr.Markdown("# Career Path Resume Parser") with gr.Tabs(): with gr.Tab("Parse Resume"): file_in = gr.File(label="Upload PDF") btn = gr.Button("Parse") out_text = gr.Textbox(label="Results", lines=15) out_json = gr.JSON(label="Data") btn.click(gradio_parse, [file_in], [out_text, out_json]) with gr.Tab("Match Job"): file_match = gr.File(label="Resume PDF") skills_in = gr.Textbox(label="Required Skills (comma separated)", placeholder="Python, React, AWS") match_btn = gr.Button("Analyze") match_out = gr.Textbox(label="Analysis", lines=15) match_json = gr.JSON(label="Data") match_btn.click(gradio_match, [file_match, skills_in], [match_out, match_json]) app.mount("/ui", WSGIMiddleware(demo)) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7860)