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)