Spaces:
Runtime error
Runtime error
File size: 9,354 Bytes
b7ba335 9280f32 b7ba335 d43d743 9280f32 b7ba335 9280f32 b7ba335 d43d743 b7ba335 5e1e7d9 62cc97f 5e1e7d9 d43d743 62cc97f 5e1e7d9 9280f32 62cc97f 5e1e7d9 d43d743 b7ba335 62cc97f 9280f32 62cc97f 5e1e7d9 9280f32 d43d743 62cc97f d43d743 62cc97f 9280f32 5e1e7d9 62cc97f d43d743 62cc97f d43d743 62cc97f d43d743 9280f32 62cc97f 5e1e7d9 62cc97f 5e1e7d9 62cc97f d43d743 5e1e7d9 9280f32 d43d743 5e1e7d9 9280f32 5e1e7d9 d43d743 9280f32 5e1e7d9 d43d743 5e1e7d9 d43d743 b7ba335 62cc97f 9280f32 b7ba335 5e1e7d9 9280f32 b7ba335 d43d743 b7ba335 5e1e7d9 d43d743 5e1e7d9 d43d743 5e1e7d9 d43d743 5e1e7d9 d43d743 5e1e7d9 d43d743 5e1e7d9 d43d743 5e1e7d9 b7ba335 d43d743 b7ba335 9280f32 5e1e7d9 9280f32 5e1e7d9 9280f32 5e1e7d9 62cc97f 5e1e7d9 9280f32 5e1e7d9 9280f32 d43d743 5e1e7d9 b7ba335 5e1e7d9 b7ba335 5e1e7d9 b7ba335 5e1e7d9 b7ba335 5e1e7d9 b7ba335 d43d743 5e1e7d9 d43d743 5e1e7d9 d43d743 5e1e7d9 d43d743 5e1e7d9 d43d743 5e1e7d9 d43d743 5e1e7d9 d43d743 9280f32 5e1e7d9 b7ba335 9280f32 5e1e7d9 d43d743 9280f32 5e1e7d9 d43d743 5e1e7d9 d43d743 5e1e7d9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | 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) |