mkshari commited on
Commit
f0da6d0
Β·
verified Β·
1 Parent(s): a25590b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +175 -53
app.py CHANGED
@@ -5,16 +5,21 @@ from docx import Document
5
  from sentence_transformers import SentenceTransformer, util
6
  import re
7
  import plotly.graph_objects as go
 
 
 
8
 
9
  # Initialize Models once at startup
10
  print("πŸš€ Initializing SETHU AI Engine...")
11
  try:
12
  nlp = spacy.load("en_core_web_sm")
13
  except:
14
- import os
15
- os.system("python -m spacy download en_core_web_sm")
16
  nlp = spacy.load("en_core_web_sm")
17
 
 
 
18
  model = SentenceTransformer('all-MiniLM-L6-v2')
19
 
20
  TECH_SKILLS = [
@@ -45,7 +50,6 @@ def extract_text(file_obj):
45
  if file_obj is None:
46
  return ""
47
 
48
- # Gradio might pass a file-like object or a string path
49
  file_path = file_obj.name if hasattr(file_obj, 'name') else str(file_obj)
50
 
51
  try:
@@ -55,6 +59,10 @@ def extract_text(file_obj):
55
  elif file_path.lower().endswith('.docx'):
56
  doc = Document(file_path)
57
  return "\n".join([p.text for p in doc.paragraphs])
 
 
 
 
58
  except Exception as e:
59
  print(f"Extraction error on {file_path}: {e}")
60
  return ""
@@ -68,89 +76,203 @@ def discover_skills(text):
68
  found.add(skill)
69
  return found
70
 
71
- def create_gauge(score):
72
- fig = go.Figure(go.Indicator(
73
- mode = "gauge+number",
74
- value = score,
75
- domain = {'x': [0, 1], 'y': [0, 1]},
76
- gauge = {
77
- 'axis': {'range': [0, 100]},
78
- 'bar': {'color': "#003366"},
79
- 'steps': [
80
- {'range': [0, 50], 'color': "#ffcccc"},
81
- {'range': [50, 80], 'color': "#fff3cd"},
82
- {'range': [80, 100], 'color': "#d4edda"}
83
- ],
84
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  ))
86
- fig.update_layout(height=250, margin=dict(l=30, r=30, t=30, b=30), paper_bgcolor="rgba(0,0,0,0)")
 
 
 
 
 
 
 
 
 
 
 
 
87
  return fig
88
 
89
- def main_process(resume_file, jd_text):
 
 
 
 
 
 
 
 
90
  print("--- New Analysis Request ---")
91
  if not resume_file or not jd_text.strip():
92
- return "⚠️ Error: Please upload a resume and paste the JD.", "", None, [], gr.update(visible=False)
 
 
 
93
 
94
- # 1. Extraction
95
  resume_text = extract_text(resume_file)
96
  if not resume_text.strip():
97
- return "⚠️ Error: Failed to extract text from resume. Ensure it's not and image-only PDF.", "", None, [], gr.update(visible=False)
 
 
 
98
 
99
- # 2. Skill Matching
100
  r_skills = discover_skills(resume_text)
101
  j_skills = discover_skills(jd_text)
102
  match_skills = sorted(list(r_skills.intersection(j_skills)))
103
  gap_skills = sorted(list(j_skills - r_skills))
104
 
105
- # 3. AI scoring
106
  emb1 = model.encode(resume_text, convert_to_tensor=True)
107
  emb2 = model.encode(jd_text, convert_to_tensor=True)
108
  score = round(util.pytorch_cos_sim(emb1, emb2).item() * 100, 1)
109
 
110
- # 4. Results Formatting
111
- present_str = ", ".join([s.upper() for s in match_skills]) if match_skills else "No direct skill matches found."
112
- gap_str = ", ".join([s.upper() for s in gap_skills]) if gap_skills else "No major skill gaps detected!"
113
- plot = create_gauge(score)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  print(f"Analysis Complete. Score: {score}")
116
- return present_str, gap_str, plot, gap_skills, gr.update(visible=True)
 
 
 
117
 
118
  def generate_roadmap(gap_skills):
119
  if not gap_skills:
120
- return "### 🌟 Career Ready!\nYour profile is an excellent match for this role. Focus on practicing behavioral interview questions."
121
 
122
  roadmap = "### πŸ›€οΈ Personalized Readiness Roadmap\n\n"
123
  for s in gap_skills:
124
- res = ROADMAP_DB.get(s.lower(), f"Learn **{s.upper()}** through hands-on projects and documentation.")
125
  roadmap += f"- **{s.upper()}**: {res}\n"
126
  return roadmap
127
 
128
- # UI Layout
129
- with gr.Blocks(theme=gr.themes.Soft(), title="SETHU AI") as demo:
130
- with gr.Row():
131
- with gr.Column(scale=1):
132
- gr.Image("logo.png", show_label=False, height=120, container=False)
133
- with gr.Column(scale=4):
134
- gr.Markdown("# SETHU AI - Career Intelligence Hub")
135
- gr.Markdown("### From Resume to Career Readiness | Powered by SASTRA DEEMED UNIVERSITY")
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
- gr.Markdown("---")
 
 
 
 
 
 
138
 
139
  with gr.Row():
140
- with gr.Column(scale=1):
141
  gr.Markdown("### πŸ“₯ 1. Upload Requirements")
142
- resume_input = gr.File(label="Upload Resume (PDF/DOCX)")
143
- jd_input = gr.Textbox(label="Job Description", lines=12, placeholder="Paste the job requirements here...")
144
- run_btn = gr.Button("πŸ” Run AI Analysis", variant="primary")
145
 
146
- with gr.Column(scale=1):
147
- gr.Markdown("### πŸ“Š 2. Match Intelligence")
148
- gauge_plot = gr.Plot()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  match_display = gr.Textbox(label="Identified Matching Skills", interactive=False)
 
150
  gap_display = gr.Textbox(label="Identified Skill Gaps", interactive=False)
151
-
152
  with gr.Row(visible=False) as roadmap_container:
153
- with gr.Column():
154
  gr.Markdown("---")
155
  roadmap_btn = gr.Button("πŸš€ Generate Knowledge Upgrade Roadmap", variant="secondary")
156
  roadmap_output = gr.Markdown()
@@ -162,7 +284,10 @@ with gr.Blocks(theme=gr.themes.Soft(), title="SETHU AI") as demo:
162
  run_btn.click(
163
  fn=main_process,
164
  inputs=[resume_input, jd_input],
165
- outputs=[match_display, gap_display, gauge_plot, gap_state, roadmap_container]
 
 
 
166
  )
167
 
168
  roadmap_btn.click(
@@ -173,6 +298,3 @@ with gr.Blocks(theme=gr.themes.Soft(), title="SETHU AI") as demo:
173
 
174
  if __name__ == "__main__":
175
  demo.launch()
176
-
177
- if __name__ == "__main__":
178
- demo.launch()
 
5
  from sentence_transformers import SentenceTransformer, util
6
  import re
7
  import plotly.graph_objects as go
8
+ import sys
9
+ import os
10
+ import torch
11
 
12
  # Initialize Models once at startup
13
  print("πŸš€ Initializing SETHU AI Engine...")
14
  try:
15
  nlp = spacy.load("en_core_web_sm")
16
  except:
17
+ print("πŸ“₯ Downloading spaCy model...")
18
+ os.system(f"{sys.executable} -m spacy download en_core_web_sm")
19
  nlp = spacy.load("en_core_web_sm")
20
 
21
+ # Use a high-quality Hugging Face model for embeddings
22
+ # 'all-MiniLM-L6-v2' is fast and efficient for local use
23
  model = SentenceTransformer('all-MiniLM-L6-v2')
24
 
25
  TECH_SKILLS = [
 
50
  if file_obj is None:
51
  return ""
52
 
 
53
  file_path = file_obj.name if hasattr(file_obj, 'name') else str(file_obj)
54
 
55
  try:
 
59
  elif file_path.lower().endswith('.docx'):
60
  doc = Document(file_path)
61
  return "\n".join([p.text for p in doc.paragraphs])
62
+ else:
63
+ # Try reading as plain text
64
+ with open(file_path, 'r', encoding='utf-8') as f:
65
+ return f.read()
66
  except Exception as e:
67
  print(f"Extraction error on {file_path}: {e}")
68
  return ""
 
76
  found.add(skill)
77
  return found
78
 
79
+ def create_score_gauges(match_score, content_score, search_score, ats_score):
80
+ def make_gauge(val, title, color):
81
+ return go.Indicator(
82
+ mode="gauge+number",
83
+ value=val,
84
+ title={'text': title, 'font': {'size': 14, 'color': "white"}},
85
+ domain={'x': [0, 1], 'y': [0, 1]},
86
+ gauge={
87
+ 'axis': {'range': [0, 100], 'tickwidth': 1, 'tickcolor': "white"},
88
+ 'bar': {'color': color},
89
+ 'bgcolor': "rgba(0,0,0,0)",
90
+ 'borderwidth': 2,
91
+ 'bordercolor': "gray",
92
+ 'steps': [
93
+ {'range': [0, 40], 'color': 'rgba(255, 0, 0, 0.1)'},
94
+ {'range': [40, 70], 'color': 'rgba(255, 255, 0, 0.1)'},
95
+ {'range': [70, 100], 'color': 'rgba(0, 255, 0, 0.1)'}
96
+ ],
97
+ }
98
+ )
99
+
100
+ fig = go.Figure()
101
+ fig.add_trace(make_gauge(match_score, "Match Score", "#00dfd8"))
102
+ # In a real dashboard we'd have 4 separate plots, but for simplicity we'll show the main one
103
+ # and use different layout for others or just focus on the primary one.
104
+
105
+ fig.update_layout(
106
+ paper_bgcolor='rgba(0,0,0,0)',
107
+ plot_bgcolor='rgba(0,0,0,0)',
108
+ font={'color': "white", 'family': "Arial"},
109
+ height=300,
110
+ margin=dict(l=40, r=40, t=80, b=40)
111
+ )
112
+ return fig
113
+
114
+ def create_radar_chart(skills, exp, edu, readiness, search):
115
+ categories = ['Skills', 'Experience', 'Education', 'Readiness', 'Searchability']
116
+ fig = go.Figure()
117
+
118
+ fig.add_trace(go.Scatterpolar(
119
+ r=[skills, exp, edu, readiness, search],
120
+ theta=categories,
121
+ fill='toself',
122
+ name='Competency Profile',
123
+ line_color='#00dfd8',
124
+ fillcolor='rgba(0, 223, 216, 0.3)'
125
  ))
126
+
127
+ fig.update_layout(
128
+ polar=dict(
129
+ radialaxis=dict(visible=True, range=[0, 100], color="white", gridcolor="gray"),
130
+ angularaxis=dict(color="white", gridcolor="gray"),
131
+ bgcolor='rgba(0,0,0,0)'
132
+ ),
133
+ showlegend=False,
134
+ paper_bgcolor='rgba(0,0,0,0)',
135
+ plot_bgcolor='rgba(0,0,0,0)',
136
+ height=350,
137
+ margin=dict(l=60, r=60, t=20, b=20)
138
+ )
139
  return fig
140
 
141
+ def estimate_salary(score, skills):
142
+ base = 70
143
+ multiplier = 1 + (score / 100)
144
+ skill_bonus = len(skills) * 2
145
+ low = round(base * multiplier + skill_bonus)
146
+ high = round(low * 1.5)
147
+ return f"${low}k - ${high}k"
148
+
149
+ def main_process(resume_file, jd_text, progress=gr.Progress()):
150
  print("--- New Analysis Request ---")
151
  if not resume_file or not jd_text.strip():
152
+ return [
153
+ "⚠️ Please upload resume/JD.", "", None, None,
154
+ "Waiting for analysis...", "N/A", [], gr.update(visible=False)
155
+ ]
156
 
157
+ progress(0.1, desc="Extracting Resume Content...")
158
  resume_text = extract_text(resume_file)
159
  if not resume_text.strip():
160
+ return [
161
+ "❌ Extraction failed.", "", None, None,
162
+ "Failed to read resume.", "N/A", [], gr.update(visible=False)
163
+ ]
164
 
165
+ progress(0.3, desc="Analyzing Skills...")
166
  r_skills = discover_skills(resume_text)
167
  j_skills = discover_skills(jd_text)
168
  match_skills = sorted(list(r_skills.intersection(j_skills)))
169
  gap_skills = sorted(list(j_skills - r_skills))
170
 
171
+ progress(0.6, desc="Calculating Match Intelligence...")
172
  emb1 = model.encode(resume_text, convert_to_tensor=True)
173
  emb2 = model.encode(jd_text, convert_to_tensor=True)
174
  score = round(util.pytorch_cos_sim(emb1, emb2).item() * 100, 1)
175
 
176
+ # Heuristic metrics for the dashboard
177
+ content_score = min(100, len(resume_text.split()) / 5)
178
+ search_score = min(100, len(r_skills) * 10)
179
+ ats_score = round(score * 0.9, 1)
180
+
181
+ progress(0.8, desc="Generating Visualizations...")
182
+ gauge_plot = create_score_gauges(score, content_score, search_score, ats_score)
183
+ radar_plot = create_radar_chart(len(match_skills)*10, 75, 80, score, search_score)
184
+
185
+ salary_range = estimate_salary(score, match_skills)
186
+
187
+ ai_analysis = f"Based on our AI alignment, your profile shows {score}% match for this role. "
188
+ if score > 80:
189
+ ai_analysis += "Excellent match! You are a top-tier candidate."
190
+ elif score > 50:
191
+ ai_analysis += "Strong foundation, but some gaps in technical stacks were detected."
192
+ else:
193
+ ai_analysis += "Needs improvement. Focus on the learning roadmap to bridge gaps."
194
+
195
+ present_str = ", ".join([s.upper() for s in match_skills]) if match_skills else "No direct matches."
196
+ gap_str = ", ".join([s.upper() for s in gap_skills]) if gap_skills else "No gaps detected!"
197
 
198
  print(f"Analysis Complete. Score: {score}")
199
+ return [
200
+ present_str, gap_str, gauge_plot, radar_plot,
201
+ ai_analysis, salary_range, gap_skills, gr.update(visible=True)
202
+ ]
203
 
204
  def generate_roadmap(gap_skills):
205
  if not gap_skills:
206
+ return "### 🌟 Career Ready!\nYour profile is an excellent match. Focus on practice."
207
 
208
  roadmap = "### πŸ›€οΈ Personalized Readiness Roadmap\n\n"
209
  for s in gap_skills:
210
+ res = ROADMAP_DB.get(s.lower(), f"Learn **{s.upper()}** through hands-on projects.")
211
  roadmap += f"- **{s.upper()}**: {res}\n"
212
  return roadmap
213
 
214
+ # Premium CSS for Glassmorphism
215
+ STYLE = """
216
+ .gradio-container {
217
+ background-color: #0d1117 !important;
218
+ color: white !important;
219
+ }
220
+ .glass-panel {
221
+ background: rgba(255, 255, 255, 0.05) !important;
222
+ border-radius: 15px !important;
223
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
224
+ padding: 20px !important;
225
+ backdrop-filter: blur(10px);
226
+ }
227
+ .stat-card {
228
+ text-align: center;
229
+ padding: 15px;
230
+ background: rgba(0, 223, 216, 0.1);
231
+ border-radius: 10px;
232
+ border: 1px solid #00dfd8;
233
+ }
234
+ """
235
 
236
+ with gr.Blocks(theme=gr.themes.Soft(), css=STYLE, title="Career Compass AI") as demo:
237
+ gr.HTML("""
238
+ <div style="text-align: center; padding: 20px;">
239
+ <h1 style="color: #00dfd8; margin-bottom: 0;">🧭 Career Compass</h1>
240
+ <p style="color: #8b949e;">AI-Powered Resume Analysis & Career Readiness Hub</p>
241
+ </div>
242
+ """)
243
 
244
  with gr.Row():
245
+ with gr.Column(scale=1, variant="panel"):
246
  gr.Markdown("### πŸ“₯ 1. Upload Requirements")
247
+ resume_input = gr.File(label="Resume (PDF/DOCX)")
248
+ jd_input = gr.Textbox(label="Job Description", lines=10, placeholder="Paste JD here...")
249
+ run_btn = gr.Button("πŸ” Run AI Analysis", variant="primary", scale=1)
250
 
251
+ with gr.Column(scale=2):
252
+ with gr.Row():
253
+ with gr.Column():
254
+ gr.Markdown("### πŸ“Š Analysis Results")
255
+ gauge_plot = gr.Plot(label="Match Score")
256
+ with gr.Column():
257
+ gr.Markdown("### πŸ•ΈοΈ Competency Profile")
258
+ radar_plot = gr.Plot()
259
+
260
+ with gr.Row():
261
+ with gr.Column(elem_classes=["glass-panel"]):
262
+ gr.Markdown("### πŸ’‘ Strategic AI Analysis")
263
+ analysis_text = gr.Markdown("Waiting for analysis...")
264
+ with gr.Column(elem_classes=["glass-panel"]):
265
+ gr.Markdown("### πŸ’° Salary Insight")
266
+ salary_display = gr.Text(label="Estimated Range", interactive=False)
267
+
268
+ with gr.Row():
269
+ with gr.Column():
270
  match_display = gr.Textbox(label="Identified Matching Skills", interactive=False)
271
+ with gr.Column():
272
  gap_display = gr.Textbox(label="Identified Skill Gaps", interactive=False)
273
+
274
  with gr.Row(visible=False) as roadmap_container:
275
+ with gr.Column(elem_classes=["glass-panel"]):
276
  gr.Markdown("---")
277
  roadmap_btn = gr.Button("πŸš€ Generate Knowledge Upgrade Roadmap", variant="secondary")
278
  roadmap_output = gr.Markdown()
 
284
  run_btn.click(
285
  fn=main_process,
286
  inputs=[resume_input, jd_input],
287
+ outputs=[
288
+ match_display, gap_display, gauge_plot, radar_plot,
289
+ analysis_text, salary_display, gap_state, roadmap_container
290
+ ]
291
  )
292
 
293
  roadmap_btn.click(
 
298
 
299
  if __name__ == "__main__":
300
  demo.launch()