Spaces:
Runtime error
Runtime error
| 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=["*"], | |
| ) | |
| 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" | |
| } | |
| } | |
| def health(): | |
| return {"status": "healthy", "model": "all-MiniLM-L6-v2"} | |
| 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} | |
| 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) |