Spaces:
No application file
No application file
Update app.py
Browse files
app.py
CHANGED
@@ -16,7 +16,6 @@ logger = logging.getLogger(__name__)
|
|
16 |
CONVERSATION_PROMPT = """
|
17 |
You are an engaging and insightful career advisor. Have natural conversations to learn about their career.
|
18 |
Use an enthusiastic, supportive tone and show genuine interest in their journey.
|
19 |
-
|
20 |
CONVERSATION STYLE:
|
21 |
- Be warm and engaging
|
22 |
- Show genuine interest in their experiences
|
@@ -25,7 +24,6 @@ CONVERSATION STYLE:
|
|
25 |
- Use conversational language, not formal queries
|
26 |
- Express enthusiasm about their achievements
|
27 |
- Dig deeper into interesting points they make
|
28 |
-
|
29 |
INFORMATION TO GATHER (through natural conversation):
|
30 |
1. Current Role Details:
|
31 |
- Job title and responsibilities
|
@@ -33,47 +31,38 @@ INFORMATION TO GATHER (through natural conversation):
|
|
33 |
- Team size and structure
|
34 |
- Project scope and impact
|
35 |
- Current compensation (base, bonus, equity)
|
36 |
-
|
37 |
2. Experience Deep-Dive:
|
38 |
- Career progression story
|
39 |
- Leadership experience
|
40 |
- Major projects and achievements
|
41 |
- Technical skills and expertise
|
42 |
- Industry knowledge
|
43 |
-
|
44 |
3. Educational Background:
|
45 |
- Degrees and certifications
|
46 |
- Specialized training
|
47 |
- Continuous learning
|
48 |
-
|
49 |
4. Work Environment:
|
50 |
- Location and market
|
51 |
- Remote/hybrid setup
|
52 |
- Growth opportunities
|
53 |
- Company culture
|
54 |
-
|
55 |
CONVERSATION FLOW:
|
56 |
1. Start with: "Hi! I'd love to hear about your career journey. What kind of work are you doing currently?"
|
57 |
-
|
58 |
2. After each response:
|
59 |
- Pick up on specific details they mentioned
|
60 |
- Ask engaging follow-up questions
|
61 |
- Show genuine interest in their experiences
|
62 |
- Build on previous information shared
|
63 |
-
|
64 |
3. If they mention something interesting, probe deeper:
|
65 |
- "That project sounds fascinating! What were some unique challenges you faced?"
|
66 |
- "Leading a team must be exciting! How did you approach building and motivating your team?"
|
67 |
- "Interesting technology stack! What made you choose those specific tools?"
|
68 |
-
|
69 |
4. When compensation is mentioned:
|
70 |
- Be tactful and professional
|
71 |
- Acknowledge their goals
|
72 |
- Ask about their desired growth
|
73 |
-
|
74 |
5. Once you have enough information, say:
|
75 |
"I've got a good understanding of your career profile now! Would you like to see your personalized salary growth projection? Just click 'Generate Analysis' and I'll create a detailed forecast based on our discussion."
|
76 |
-
|
77 |
IMPORTANT:
|
78 |
- Keep conversation flowing naturally
|
79 |
- Don't rush to collect information
|
@@ -84,9 +73,7 @@ IMPORTANT:
|
|
84 |
|
85 |
EXTRACTION_PROMPT = """
|
86 |
Analyze the conversation and extract numerical scores from 0 to 1 based on salary growth potential.
|
87 |
-
|
88 |
SCORING GUIDELINES:
|
89 |
-
|
90 |
1. Industry Score (0-1):
|
91 |
Industry Type & Growth:
|
92 |
- 1.0: Cutting-edge AI/ML companies
|
@@ -99,7 +86,6 @@ SCORING GUIDELINES:
|
|
99 |
+0.1: Market leader
|
100 |
+0.1: High growth trajectory
|
101 |
-0.1: Declining market position
|
102 |
-
|
103 |
2. Experience Score (0-1):
|
104 |
Years and Level:
|
105 |
- 1.0: 15+ years with executive experience
|
@@ -112,7 +98,6 @@ SCORING GUIDELINES:
|
|
112 |
+0.1: Rapid promotions
|
113 |
+0.1: Significant achievements
|
114 |
+0.1: High-impact projects
|
115 |
-
|
116 |
3. Education Score (0-1):
|
117 |
Formal Education:
|
118 |
- 1.0: PhD from top institution
|
@@ -125,7 +110,6 @@ SCORING GUIDELINES:
|
|
125 |
+0.1: Relevant certifications
|
126 |
+0.1: Continuous learning
|
127 |
+0.1: Field-specific expertise
|
128 |
-
|
129 |
4. Skills Score (0-1):
|
130 |
Technical Depth:
|
131 |
- 1.0: Industry-leading expertise
|
@@ -138,7 +122,6 @@ SCORING GUIDELINES:
|
|
138 |
+0.1: Multiple in-demand skills
|
139 |
+0.1: Proven implementation
|
140 |
+0.1: Cross-functional expertise
|
141 |
-
|
142 |
5. Location Score (0-1):
|
143 |
Market Strength:
|
144 |
- 1.0: Major tech hubs (SF, NYC)
|
@@ -151,7 +134,6 @@ SCORING GUIDELINES:
|
|
151 |
+0.1: Remote work option
|
152 |
+0.1: High-growth market
|
153 |
+0.1: Strategic location
|
154 |
-
|
155 |
Return a JSON object with exactly these fields:
|
156 |
{
|
157 |
"industry_score": float,
|
@@ -161,7 +143,6 @@ Return a JSON object with exactly these fields:
|
|
161 |
"location_score": float,
|
162 |
"current_salary": float
|
163 |
}
|
164 |
-
|
165 |
Base scores on available information. Make reasonable assumptions for missing data based on context clues.
|
166 |
"""
|
167 |
|
@@ -169,10 +150,7 @@ class CodeEnvironment:
|
|
169 |
"""Environment for executing visualization code"""
|
170 |
|
171 |
def __init__(self):
|
172 |
-
self.globals = {
|
173 |
-
'np': np,
|
174 |
-
'plt': plt
|
175 |
-
}
|
176 |
self.locals = {}
|
177 |
|
178 |
def execute(self, code: str, paths: np.ndarray = None) -> Dict[str, Any]:
|
@@ -181,81 +159,59 @@ class CodeEnvironment:
|
|
181 |
self.globals['paths'] = paths
|
182 |
|
183 |
result = {'figures': [], 'error': None}
|
184 |
-
|
185 |
try:
|
186 |
exec(code, self.globals, self.locals)
|
187 |
-
|
188 |
-
# Save current figure
|
189 |
buf = io.BytesIO()
|
190 |
plt.gcf().savefig(buf, format='png', dpi=300, bbox_inches='tight')
|
191 |
buf.seek(0)
|
192 |
result['figures'].append(buf.getvalue())
|
193 |
plt.close('all')
|
194 |
-
|
195 |
except Exception as e:
|
196 |
-
result['error'] = str(e)
|
197 |
plt.close('all')
|
198 |
-
|
199 |
return result
|
200 |
|
201 |
class SalarySimulator:
|
202 |
"""Monte Carlo simulation for salary projections"""
|
203 |
|
204 |
-
def __init__(self):
|
205 |
-
self.years =
|
206 |
-
self.num_paths =
|
207 |
|
208 |
def run_simulation(self, profile: Dict[str, float]) -> np.ndarray:
|
209 |
-
"""Generate salary growth paths"""
|
210 |
paths = np.zeros((self.num_paths, self.years + 1))
|
211 |
paths[:, 0] = profile['current_salary']
|
212 |
|
213 |
-
# Base growth parameters
|
214 |
base_growth = 0.02 + (profile['industry_score'] * 0.04)
|
215 |
skill_premium = 0.01 + (profile['skills_score'] * 0.02)
|
216 |
exp_premium = 0.01 + (profile['experience_score'] * 0.02)
|
217 |
edu_premium = 0.005 + (profile['education_score'] * 0.015)
|
218 |
location_premium = 0.01 + (profile['location_score'] * 0.02)
|
219 |
|
220 |
-
# Risk parameters
|
221 |
volatility = 0.05 + (profile['industry_score'] * 0.05)
|
222 |
disruption_chance = 0.1
|
223 |
disruption_impact = 0.2
|
224 |
|
225 |
-
# Generate paths
|
226 |
for path in range(self.num_paths):
|
227 |
salary = paths[path, 0]
|
228 |
for year in range(1, self.years + 1):
|
229 |
-
|
230 |
-
growth = (base_growth + skill_premium + exp_premium +
|
231 |
-
edu_premium + location_premium)
|
232 |
-
|
233 |
-
# Add volatility
|
234 |
growth += np.random.normal(0, volatility)
|
235 |
-
|
236 |
-
# Possible disruption
|
237 |
if np.random.random() < disruption_chance:
|
238 |
impact = disruption_impact * np.random.random()
|
239 |
-
if np.random.random() < 0.7
|
240 |
-
|
241 |
-
else:
|
242 |
-
growth -= impact
|
243 |
-
|
244 |
-
# Apply bounds
|
245 |
-
growth = max(min(growth, 0.25), -0.1) # -10% to +25%
|
246 |
-
|
247 |
-
# Update salary
|
248 |
salary *= (1 + growth)
|
249 |
paths[path, year] = salary
|
250 |
-
|
251 |
return paths
|
252 |
|
253 |
class CareerAdvisor:
|
254 |
"""Main career advisor system"""
|
255 |
|
256 |
-
def __init__(self):
|
257 |
self.chat_history = []
|
258 |
-
self.simulator = SalarySimulator()
|
259 |
self.code_env = CodeEnvironment()
|
260 |
|
261 |
def reset(self):
|
@@ -265,146 +221,94 @@ class CareerAdvisor:
|
|
265 |
def chat(self, message: str, api_key: str) -> str:
|
266 |
"""Process user message and generate response"""
|
267 |
if not api_key.strip().startswith("sk-"):
|
268 |
-
return "Please enter a valid OpenAI API key."
|
269 |
-
|
270 |
try:
|
271 |
-
messages = [
|
272 |
-
|
273 |
-
|
274 |
-
{"role": "user", "content": message}
|
275 |
-
]
|
276 |
-
|
277 |
-
response = completion(
|
278 |
-
model="gpt-4o-mini",
|
279 |
-
messages=messages,
|
280 |
-
api_key=api_key
|
281 |
-
)
|
282 |
-
|
283 |
-
# Update chat history
|
284 |
self.chat_history.extend([
|
285 |
{"role": "user", "content": message},
|
286 |
{"role": "assistant", "content": response.choices[0].message.content}
|
287 |
])
|
288 |
-
|
289 |
return response.choices[0].message.content
|
290 |
-
|
291 |
except Exception as e:
|
292 |
-
return f"
|
293 |
|
294 |
def generate_analysis(self, api_key: str) -> Tuple[str, bytes]:
|
295 |
"""Generate complete analysis with visualization"""
|
|
|
|
|
296 |
try:
|
297 |
-
# Extract profile
|
298 |
profile = self._extract_profile(api_key)
|
299 |
-
|
300 |
-
# Run simulation
|
301 |
paths = self.simulator.run_simulation(profile)
|
302 |
|
303 |
-
# Generate visualization
|
304 |
viz_code = """
|
305 |
import matplotlib.pyplot as plt
|
306 |
import numpy as np
|
307 |
-
|
308 |
-
# Setup
|
309 |
plt.style.use('dark_background')
|
310 |
fig = plt.figure(figsize=(12, 16))
|
311 |
-
|
312 |
-
# Top plot - Salary paths
|
313 |
ax1 = plt.subplot2grid((2, 1), (0, 0))
|
314 |
-
|
315 |
-
# Plot paths with transparency
|
316 |
-
for path in paths[::20]: # Plot every 20th path to reduce density
|
317 |
ax1.plot(range(paths.shape[1]), path, color='#4a90e2', alpha=0.1, linewidth=1)
|
318 |
-
|
319 |
-
# Calculate and plot percentiles
|
320 |
percentiles = [10, 25, 50, 75, 90]
|
321 |
colors = ['#ff9999', '#ffcc99', '#ffffff', '#ffcc99', '#ff9999']
|
322 |
labels = ['10th', '25th', 'Median', '75th', '90th']
|
323 |
-
|
324 |
for p, color, label in zip(percentiles, colors, labels):
|
325 |
line = np.percentile(paths, p, axis=0)
|
326 |
ax1.plot(range(paths.shape[1]), line, color=color, linewidth=2, label=f'{label} percentile')
|
327 |
-
|
328 |
ax1.set_title('Salary Growth Projections\n', fontsize=16, pad=20)
|
329 |
ax1.set_xlabel('Years', fontsize=12)
|
330 |
ax1.set_ylabel('Salary ($)', fontsize=12)
|
331 |
ax1.grid(True, alpha=0.2)
|
332 |
ax1.legend(fontsize=10)
|
333 |
-
|
334 |
-
# Format y-axis as currency
|
335 |
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
|
336 |
-
|
337 |
-
# Customize x-axis
|
338 |
ax1.set_xticks(range(paths.shape[1]))
|
339 |
ax1.set_xticklabels(['Current'] + [f'Year {i+1}' for i in range(paths.shape[1]-1)])
|
340 |
-
|
341 |
-
# Bottom plot - Final distribution
|
342 |
ax2 = plt.subplot2grid((2, 1), (1, 0))
|
343 |
final_salaries = paths[:, -1]
|
344 |
-
|
345 |
-
# Create histogram
|
346 |
ax2.hist(final_salaries, bins=50, color='#4a90e2', alpha=0.7)
|
347 |
ax2.set_title('Final Salary Distribution\n', fontsize=16, pad=20)
|
348 |
ax2.set_xlabel('Salary ($)', fontsize=12)
|
349 |
ax2.set_ylabel('Frequency', fontsize=12)
|
350 |
ax2.grid(True, alpha=0.2)
|
351 |
-
|
352 |
-
# Format x-axis as currency
|
353 |
ax2.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
|
354 |
-
|
355 |
-
# Add percentile lines
|
356 |
for p, color in zip(percentiles, colors):
|
357 |
value = np.percentile(final_salaries, p)
|
358 |
ax2.axvline(x=value, color=color, linestyle='--', alpha=0.5)
|
359 |
-
|
360 |
plt.tight_layout(pad=4)
|
361 |
"""
|
362 |
-
|
363 |
-
# Execute visualization code
|
364 |
viz_result = self.code_env.execute(viz_code, paths)
|
365 |
-
|
366 |
if viz_result['error']:
|
367 |
-
|
368 |
-
|
369 |
-
# Generate summary
|
370 |
summary = self._generate_summary(profile, paths)
|
371 |
-
|
372 |
return summary, viz_result['figures'][0]
|
373 |
-
|
374 |
except Exception as e:
|
375 |
-
return f"
|
376 |
|
377 |
def _extract_profile(self, api_key: str) -> Dict[str, float]:
|
378 |
"""Extract profile scores from conversation"""
|
379 |
-
conversation = "\n".join([
|
380 |
-
f"{msg['role']}: {msg['content']}"
|
381 |
-
for msg in self.chat_history
|
382 |
-
])
|
383 |
-
|
384 |
messages = [
|
385 |
{"role": "system", "content": EXTRACTION_PROMPT},
|
386 |
{"role": "user", "content": f"Extract profile from:\n{conversation}"}
|
387 |
]
|
388 |
-
|
389 |
response = completion(
|
390 |
model="gpt-4o-mini",
|
391 |
messages=messages,
|
392 |
api_key=api_key,
|
393 |
response_format={"type": "json_object"}
|
394 |
)
|
395 |
-
|
396 |
return json.loads(response.choices[0].message.content)
|
397 |
|
398 |
def _generate_summary(self, profile: Dict[str, float], paths: np.ndarray) -> str:
|
399 |
"""Generate analysis summary"""
|
400 |
final_salaries = paths[:, -1]
|
401 |
initial_salary = paths[0, 0]
|
402 |
-
cagr = (np.median(final_salaries) / initial_salary) ** (1/
|
403 |
|
404 |
return f"""
|
405 |
Career Profile Analysis
|
406 |
======================
|
407 |
-
|
408 |
Current Situation:
|
409 |
• Salary: ${profile['current_salary']:,.2f}
|
410 |
• Industry Position: {profile['industry_score']:.2f}/1.0
|
@@ -412,110 +316,66 @@ plt.tight_layout(pad=4)
|
|
412 |
• Education Rating: {profile['education_score']:.2f}/1.0
|
413 |
• Skills Assessment: {profile['skills_score']:.2f}/1.0
|
414 |
• Location Impact: {profile['location_score']:.2f}/1.0
|
415 |
-
|
416 |
-
5-Year Projection:
|
417 |
• Conservative (25th percentile): ${np.percentile(final_salaries, 25):,.2f}
|
418 |
• Most Likely (Median): ${np.percentile(final_salaries, 50):,.2f}
|
419 |
• Optimistic (75th percentile): ${np.percentile(final_salaries, 75):,.2f}
|
420 |
• Expected Annual Growth: {cagr*100:.1f}%
|
421 |
-
|
422 |
Key Insights:
|
423 |
• Your profile suggests {cagr*100:.1f}% annual growth potential
|
424 |
• {profile['industry_score']:.2f} industry score indicates {'strong' if profile['industry_score'] > 0.7 else 'moderate' if profile['industry_score'] > 0.5 else 'challenging'} growth environment
|
425 |
• Skills rating of {profile['skills_score']:.2f} suggests {'excellent' if profile['skills_score'] > 0.7 else 'good' if profile['skills_score'] > 0.5 else 'potential for'} career advancement
|
426 |
• Location score {profile['location_score']:.2f} {'enhances' if profile['location_score'] > 0.7 else 'supports' if profile['location_score'] > 0.5 else 'may limit'} opportunities
|
427 |
-
|
428 |
-
Based on {paths.shape[0]:,} simulated career paths
|
429 |
"""
|
430 |
|
431 |
def create_interface():
|
432 |
-
"""Create Gradio interface"""
|
433 |
-
advisor =
|
|
|
|
|
|
|
|
|
|
|
434 |
|
435 |
def user_message(message: str, history: List, api_key: str) -> Tuple[str, List]:
|
436 |
-
"""Handle user message"""
|
437 |
if not message.strip():
|
438 |
return "", history
|
439 |
-
|
|
|
440 |
response = advisor.chat(message, api_key)
|
441 |
-
|
442 |
-
return "", history
|
443 |
|
444 |
def generate_analysis(api_key: str, history: List) -> Tuple[str, gr.Image]:
|
445 |
-
|
446 |
-
|
447 |
-
return "Please chat about your career first.", None
|
448 |
-
|
449 |
summary, figure_data = advisor.generate_analysis(api_key)
|
450 |
-
if figure_data
|
451 |
-
return summary, figure_data
|
452 |
-
return summary, None
|
453 |
|
454 |
-
# Create interface
|
455 |
with gr.Blocks(title="Monte Carlo Salary Prediction", theme=gr.themes.Soft()) as demo:
|
456 |
-
gr.Markdown(""
|
457 |
-
# 💰 Monte Carlo Simulation of Salary Prediction
|
458 |
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
label="OpenAI API Key",
|
464 |
-
placeholder="Enter your OpenAI API key",
|
465 |
-
type="password"
|
466 |
-
)
|
467 |
|
468 |
-
chatbot = gr.Chatbot(
|
469 |
-
value=[],
|
470 |
-
height=400,
|
471 |
-
show_copy_button=True,
|
472 |
-
avatar_images=["/gradio/user.png", "/gradio/assistant.png"],
|
473 |
-
bubble_full_width=False
|
474 |
-
)
|
475 |
|
476 |
with gr.Row():
|
477 |
-
msg = gr.Textbox(
|
478 |
-
label="Your message",
|
479 |
-
placeholder="Tell me about your career...",
|
480 |
-
lines=2,
|
481 |
-
show_copy_button=False
|
482 |
-
)
|
483 |
send = gr.Button("Send", variant="primary", scale=0)
|
484 |
|
485 |
analyze = gr.Button("Generate Analysis", variant="secondary", size="lg")
|
486 |
|
487 |
with gr.Row():
|
488 |
-
analysis = gr.Textbox(
|
489 |
-
|
490 |
-
lines=20,
|
491 |
-
show_copy_button=True
|
492 |
-
)
|
493 |
-
plot = gr.Image(
|
494 |
-
label="Projections",
|
495 |
-
show_download_button=True,
|
496 |
-
height=600
|
497 |
-
)
|
498 |
-
|
499 |
-
# Wire up the interface
|
500 |
-
msg.submit(
|
501 |
-
user_message,
|
502 |
-
inputs=[msg, chatbot, api_key],
|
503 |
-
outputs=[msg, chatbot]
|
504 |
-
)
|
505 |
-
|
506 |
-
send.click(
|
507 |
-
user_message,
|
508 |
-
inputs=[msg, chatbot, api_key],
|
509 |
-
outputs=[msg, chatbot]
|
510 |
-
)
|
511 |
-
|
512 |
-
analyze.click(
|
513 |
-
generate_analysis,
|
514 |
-
inputs=[api_key, chatbot],
|
515 |
-
outputs=[analysis, plot]
|
516 |
-
)
|
517 |
|
518 |
-
demo.load(lambda:
|
|
|
|
|
|
|
519 |
|
520 |
return demo
|
521 |
|
|
|
16 |
CONVERSATION_PROMPT = """
|
17 |
You are an engaging and insightful career advisor. Have natural conversations to learn about their career.
|
18 |
Use an enthusiastic, supportive tone and show genuine interest in their journey.
|
|
|
19 |
CONVERSATION STYLE:
|
20 |
- Be warm and engaging
|
21 |
- Show genuine interest in their experiences
|
|
|
24 |
- Use conversational language, not formal queries
|
25 |
- Express enthusiasm about their achievements
|
26 |
- Dig deeper into interesting points they make
|
|
|
27 |
INFORMATION TO GATHER (through natural conversation):
|
28 |
1. Current Role Details:
|
29 |
- Job title and responsibilities
|
|
|
31 |
- Team size and structure
|
32 |
- Project scope and impact
|
33 |
- Current compensation (base, bonus, equity)
|
|
|
34 |
2. Experience Deep-Dive:
|
35 |
- Career progression story
|
36 |
- Leadership experience
|
37 |
- Major projects and achievements
|
38 |
- Technical skills and expertise
|
39 |
- Industry knowledge
|
|
|
40 |
3. Educational Background:
|
41 |
- Degrees and certifications
|
42 |
- Specialized training
|
43 |
- Continuous learning
|
|
|
44 |
4. Work Environment:
|
45 |
- Location and market
|
46 |
- Remote/hybrid setup
|
47 |
- Growth opportunities
|
48 |
- Company culture
|
|
|
49 |
CONVERSATION FLOW:
|
50 |
1. Start with: "Hi! I'd love to hear about your career journey. What kind of work are you doing currently?"
|
|
|
51 |
2. After each response:
|
52 |
- Pick up on specific details they mentioned
|
53 |
- Ask engaging follow-up questions
|
54 |
- Show genuine interest in their experiences
|
55 |
- Build on previous information shared
|
|
|
56 |
3. If they mention something interesting, probe deeper:
|
57 |
- "That project sounds fascinating! What were some unique challenges you faced?"
|
58 |
- "Leading a team must be exciting! How did you approach building and motivating your team?"
|
59 |
- "Interesting technology stack! What made you choose those specific tools?"
|
|
|
60 |
4. When compensation is mentioned:
|
61 |
- Be tactful and professional
|
62 |
- Acknowledge their goals
|
63 |
- Ask about their desired growth
|
|
|
64 |
5. Once you have enough information, say:
|
65 |
"I've got a good understanding of your career profile now! Would you like to see your personalized salary growth projection? Just click 'Generate Analysis' and I'll create a detailed forecast based on our discussion."
|
|
|
66 |
IMPORTANT:
|
67 |
- Keep conversation flowing naturally
|
68 |
- Don't rush to collect information
|
|
|
73 |
|
74 |
EXTRACTION_PROMPT = """
|
75 |
Analyze the conversation and extract numerical scores from 0 to 1 based on salary growth potential.
|
|
|
76 |
SCORING GUIDELINES:
|
|
|
77 |
1. Industry Score (0-1):
|
78 |
Industry Type & Growth:
|
79 |
- 1.0: Cutting-edge AI/ML companies
|
|
|
86 |
+0.1: Market leader
|
87 |
+0.1: High growth trajectory
|
88 |
-0.1: Declining market position
|
|
|
89 |
2. Experience Score (0-1):
|
90 |
Years and Level:
|
91 |
- 1.0: 15+ years with executive experience
|
|
|
98 |
+0.1: Rapid promotions
|
99 |
+0.1: Significant achievements
|
100 |
+0.1: High-impact projects
|
|
|
101 |
3. Education Score (0-1):
|
102 |
Formal Education:
|
103 |
- 1.0: PhD from top institution
|
|
|
110 |
+0.1: Relevant certifications
|
111 |
+0.1: Continuous learning
|
112 |
+0.1: Field-specific expertise
|
|
|
113 |
4. Skills Score (0-1):
|
114 |
Technical Depth:
|
115 |
- 1.0: Industry-leading expertise
|
|
|
122 |
+0.1: Multiple in-demand skills
|
123 |
+0.1: Proven implementation
|
124 |
+0.1: Cross-functional expertise
|
|
|
125 |
5. Location Score (0-1):
|
126 |
Market Strength:
|
127 |
- 1.0: Major tech hubs (SF, NYC)
|
|
|
134 |
+0.1: Remote work option
|
135 |
+0.1: High-growth market
|
136 |
+0.1: Strategic location
|
|
|
137 |
Return a JSON object with exactly these fields:
|
138 |
{
|
139 |
"industry_score": float,
|
|
|
143 |
"location_score": float,
|
144 |
"current_salary": float
|
145 |
}
|
|
|
146 |
Base scores on available information. Make reasonable assumptions for missing data based on context clues.
|
147 |
"""
|
148 |
|
|
|
150 |
"""Environment for executing visualization code"""
|
151 |
|
152 |
def __init__(self):
|
153 |
+
self.globals = {'np': np, 'plt': plt}
|
|
|
|
|
|
|
154 |
self.locals = {}
|
155 |
|
156 |
def execute(self, code: str, paths: np.ndarray = None) -> Dict[str, Any]:
|
|
|
159 |
self.globals['paths'] = paths
|
160 |
|
161 |
result = {'figures': [], 'error': None}
|
|
|
162 |
try:
|
163 |
exec(code, self.globals, self.locals)
|
|
|
|
|
164 |
buf = io.BytesIO()
|
165 |
plt.gcf().savefig(buf, format='png', dpi=300, bbox_inches='tight')
|
166 |
buf.seek(0)
|
167 |
result['figures'].append(buf.getvalue())
|
168 |
plt.close('all')
|
|
|
169 |
except Exception as e:
|
170 |
+
result['error'] = f"Visualization failed: {str(e)}"
|
171 |
plt.close('all')
|
|
|
172 |
return result
|
173 |
|
174 |
class SalarySimulator:
|
175 |
"""Monte Carlo simulation for salary projections"""
|
176 |
|
177 |
+
def __init__(self, years: int = 5, num_paths: int = 1000):
|
178 |
+
self.years = years
|
179 |
+
self.num_paths = num_paths
|
180 |
|
181 |
def run_simulation(self, profile: Dict[str, float]) -> np.ndarray:
|
182 |
+
"""Generate salary growth paths using Monte Carlo simulation"""
|
183 |
paths = np.zeros((self.num_paths, self.years + 1))
|
184 |
paths[:, 0] = profile['current_salary']
|
185 |
|
|
|
186 |
base_growth = 0.02 + (profile['industry_score'] * 0.04)
|
187 |
skill_premium = 0.01 + (profile['skills_score'] * 0.02)
|
188 |
exp_premium = 0.01 + (profile['experience_score'] * 0.02)
|
189 |
edu_premium = 0.005 + (profile['education_score'] * 0.015)
|
190 |
location_premium = 0.01 + (profile['location_score'] * 0.02)
|
191 |
|
|
|
192 |
volatility = 0.05 + (profile['industry_score'] * 0.05)
|
193 |
disruption_chance = 0.1
|
194 |
disruption_impact = 0.2
|
195 |
|
|
|
196 |
for path in range(self.num_paths):
|
197 |
salary = paths[path, 0]
|
198 |
for year in range(1, self.years + 1):
|
199 |
+
growth = base_growth + skill_premium + exp_premium + edu_premium + location_premium
|
|
|
|
|
|
|
|
|
200 |
growth += np.random.normal(0, volatility)
|
|
|
|
|
201 |
if np.random.random() < disruption_chance:
|
202 |
impact = disruption_impact * np.random.random()
|
203 |
+
growth += impact if np.random.random() < 0.7 else -impact
|
204 |
+
growth = max(min(growth, 0.25), -0.1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
205 |
salary *= (1 + growth)
|
206 |
paths[path, year] = salary
|
|
|
207 |
return paths
|
208 |
|
209 |
class CareerAdvisor:
|
210 |
"""Main career advisor system"""
|
211 |
|
212 |
+
def __init__(self, years: int = 5, num_paths: int = 1000):
|
213 |
self.chat_history = []
|
214 |
+
self.simulator = SalarySimulator(years, num_paths)
|
215 |
self.code_env = CodeEnvironment()
|
216 |
|
217 |
def reset(self):
|
|
|
221 |
def chat(self, message: str, api_key: str) -> str:
|
222 |
"""Process user message and generate response"""
|
223 |
if not api_key.strip().startswith("sk-"):
|
224 |
+
return "Please enter a valid OpenAI API key starting with 'sk-'."
|
|
|
225 |
try:
|
226 |
+
messages = [{"role": "system", "content": CONVERSATION_PROMPT}] + \
|
227 |
+
self.chat_history + [{"role": "user", "content": message}]
|
228 |
+
response = completion(model="gpt-4o-mini", messages=messages, api_key=api_key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
self.chat_history.extend([
|
230 |
{"role": "user", "content": message},
|
231 |
{"role": "assistant", "content": response.choices[0].message.content}
|
232 |
])
|
|
|
233 |
return response.choices[0].message.content
|
|
|
234 |
except Exception as e:
|
235 |
+
return f"Chat error: {str(e)}. Please check your API key or try again."
|
236 |
|
237 |
def generate_analysis(self, api_key: str) -> Tuple[str, bytes]:
|
238 |
"""Generate complete analysis with visualization"""
|
239 |
+
if not self.chat_history:
|
240 |
+
return "Please chat about your career first to generate an analysis.", None
|
241 |
try:
|
|
|
242 |
profile = self._extract_profile(api_key)
|
|
|
|
|
243 |
paths = self.simulator.run_simulation(profile)
|
244 |
|
|
|
245 |
viz_code = """
|
246 |
import matplotlib.pyplot as plt
|
247 |
import numpy as np
|
|
|
|
|
248 |
plt.style.use('dark_background')
|
249 |
fig = plt.figure(figsize=(12, 16))
|
|
|
|
|
250 |
ax1 = plt.subplot2grid((2, 1), (0, 0))
|
251 |
+
for path in paths[::20]:
|
|
|
|
|
252 |
ax1.plot(range(paths.shape[1]), path, color='#4a90e2', alpha=0.1, linewidth=1)
|
|
|
|
|
253 |
percentiles = [10, 25, 50, 75, 90]
|
254 |
colors = ['#ff9999', '#ffcc99', '#ffffff', '#ffcc99', '#ff9999']
|
255 |
labels = ['10th', '25th', 'Median', '75th', '90th']
|
|
|
256 |
for p, color, label in zip(percentiles, colors, labels):
|
257 |
line = np.percentile(paths, p, axis=0)
|
258 |
ax1.plot(range(paths.shape[1]), line, color=color, linewidth=2, label=f'{label} percentile')
|
|
|
259 |
ax1.set_title('Salary Growth Projections\n', fontsize=16, pad=20)
|
260 |
ax1.set_xlabel('Years', fontsize=12)
|
261 |
ax1.set_ylabel('Salary ($)', fontsize=12)
|
262 |
ax1.grid(True, alpha=0.2)
|
263 |
ax1.legend(fontsize=10)
|
|
|
|
|
264 |
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
|
|
|
|
|
265 |
ax1.set_xticks(range(paths.shape[1]))
|
266 |
ax1.set_xticklabels(['Current'] + [f'Year {i+1}' for i in range(paths.shape[1]-1)])
|
|
|
|
|
267 |
ax2 = plt.subplot2grid((2, 1), (1, 0))
|
268 |
final_salaries = paths[:, -1]
|
|
|
|
|
269 |
ax2.hist(final_salaries, bins=50, color='#4a90e2', alpha=0.7)
|
270 |
ax2.set_title('Final Salary Distribution\n', fontsize=16, pad=20)
|
271 |
ax2.set_xlabel('Salary ($)', fontsize=12)
|
272 |
ax2.set_ylabel('Frequency', fontsize=12)
|
273 |
ax2.grid(True, alpha=0.2)
|
|
|
|
|
274 |
ax2.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
|
|
|
|
|
275 |
for p, color in zip(percentiles, colors):
|
276 |
value = np.percentile(final_salaries, p)
|
277 |
ax2.axvline(x=value, color=color, linestyle='--', alpha=0.5)
|
|
|
278 |
plt.tight_layout(pad=4)
|
279 |
"""
|
|
|
|
|
280 |
viz_result = self.code_env.execute(viz_code, paths)
|
|
|
281 |
if viz_result['error']:
|
282 |
+
return f"Analysis generated, but {viz_result['error']}", None
|
|
|
|
|
283 |
summary = self._generate_summary(profile, paths)
|
|
|
284 |
return summary, viz_result['figures'][0]
|
|
|
285 |
except Exception as e:
|
286 |
+
return f"Analysis error: {str(e)}. Please ensure sufficient chat history.", None
|
287 |
|
288 |
def _extract_profile(self, api_key: str) -> Dict[str, float]:
|
289 |
"""Extract profile scores from conversation"""
|
290 |
+
conversation = "\n".join([f"{msg['role']}: {msg['content']}" for msg in self.chat_history])
|
|
|
|
|
|
|
|
|
291 |
messages = [
|
292 |
{"role": "system", "content": EXTRACTION_PROMPT},
|
293 |
{"role": "user", "content": f"Extract profile from:\n{conversation}"}
|
294 |
]
|
|
|
295 |
response = completion(
|
296 |
model="gpt-4o-mini",
|
297 |
messages=messages,
|
298 |
api_key=api_key,
|
299 |
response_format={"type": "json_object"}
|
300 |
)
|
|
|
301 |
return json.loads(response.choices[0].message.content)
|
302 |
|
303 |
def _generate_summary(self, profile: Dict[str, float], paths: np.ndarray) -> str:
|
304 |
"""Generate analysis summary"""
|
305 |
final_salaries = paths[:, -1]
|
306 |
initial_salary = paths[0, 0]
|
307 |
+
cagr = (np.median(final_salaries) / initial_salary) ** (1/self.simulator.years) - 1
|
308 |
|
309 |
return f"""
|
310 |
Career Profile Analysis
|
311 |
======================
|
|
|
312 |
Current Situation:
|
313 |
• Salary: ${profile['current_salary']:,.2f}
|
314 |
• Industry Position: {profile['industry_score']:.2f}/1.0
|
|
|
316 |
• Education Rating: {profile['education_score']:.2f}/1.0
|
317 |
• Skills Assessment: {profile['skills_score']:.2f}/1.0
|
318 |
• Location Impact: {profile['location_score']:.2f}/1.0
|
319 |
+
{self.simulator.years}-Year Projection:
|
|
|
320 |
• Conservative (25th percentile): ${np.percentile(final_salaries, 25):,.2f}
|
321 |
• Most Likely (Median): ${np.percentile(final_salaries, 50):,.2f}
|
322 |
• Optimistic (75th percentile): ${np.percentile(final_salaries, 75):,.2f}
|
323 |
• Expected Annual Growth: {cagr*100:.1f}%
|
|
|
324 |
Key Insights:
|
325 |
• Your profile suggests {cagr*100:.1f}% annual growth potential
|
326 |
• {profile['industry_score']:.2f} industry score indicates {'strong' if profile['industry_score'] > 0.7 else 'moderate' if profile['industry_score'] > 0.5 else 'challenging'} growth environment
|
327 |
• Skills rating of {profile['skills_score']:.2f} suggests {'excellent' if profile['skills_score'] > 0.7 else 'good' if profile['skills_score'] > 0.5 else 'potential for'} career advancement
|
328 |
• Location score {profile['location_score']:.2f} {'enhances' if profile['location_score'] > 0.7 else 'supports' if profile['location_score'] > 0.5 else 'may limit'} opportunities
|
329 |
+
Based on {self.simulator.num_paths:,} simulated career paths
|
|
|
330 |
"""
|
331 |
|
332 |
def create_interface():
|
333 |
+
"""Create Gradio interface with configurable simulation parameters"""
|
334 |
+
advisor = None
|
335 |
+
|
336 |
+
def init_advisor(years: int, num_paths: int):
|
337 |
+
nonlocal advisor
|
338 |
+
advisor = CareerAdvisor(years=max(1, years), num_paths=max(100, num_paths))
|
339 |
+
advisor.reset()
|
340 |
|
341 |
def user_message(message: str, history: List, api_key: str) -> Tuple[str, List]:
|
|
|
342 |
if not message.strip():
|
343 |
return "", history
|
344 |
+
if not advisor:
|
345 |
+
return "Please set simulation parameters first.", history
|
346 |
response = advisor.chat(message, api_key)
|
347 |
+
return "", history + [(message, response)]
|
|
|
348 |
|
349 |
def generate_analysis(api_key: str, history: List) -> Tuple[str, gr.Image]:
|
350 |
+
if not advisor or not history:
|
351 |
+
return "Please chat about your career and set parameters first.", None
|
|
|
|
|
352 |
summary, figure_data = advisor.generate_analysis(api_key)
|
353 |
+
return summary, figure_data if figure_data else None
|
|
|
|
|
354 |
|
|
|
355 |
with gr.Blocks(title="Monte Carlo Salary Prediction", theme=gr.themes.Soft()) as demo:
|
356 |
+
gr.Markdown("# 💰 Monte Carlo Simulation of Salary Prediction\nChat about your career to see your growth potential!")
|
|
|
357 |
|
358 |
+
with gr.Row():
|
359 |
+
api_key = gr.Textbox(label="OpenAI API Key", placeholder="Enter your OpenAI API key", type="password")
|
360 |
+
years = gr.Number(label="Simulation Years", value=5, minimum=1, step=1)
|
361 |
+
num_paths = gr.Number(label="Number of Paths", value=1000, minimum=100, step=100)
|
|
|
|
|
|
|
|
|
362 |
|
363 |
+
chatbot = gr.Chatbot(value=[], height=400, show_copy_button=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
364 |
|
365 |
with gr.Row():
|
366 |
+
msg = gr.Textbox(label="Your message", placeholder="Tell me about your career...", lines=2)
|
|
|
|
|
|
|
|
|
|
|
367 |
send = gr.Button("Send", variant="primary", scale=0)
|
368 |
|
369 |
analyze = gr.Button("Generate Analysis", variant="secondary", size="lg")
|
370 |
|
371 |
with gr.Row():
|
372 |
+
analysis = gr.Textbox(label="Analysis", lines=10, show_copy_button=True)
|
373 |
+
plot = gr.Image(label="Projections", show_download_button=True, height=600)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
374 |
|
375 |
+
demo.load(lambda y, n: init_advisor(y, n), inputs=[years, num_paths], outputs=None)
|
376 |
+
msg.submit(user_message, inputs=[msg, chatbot, api_key], outputs=[msg, chatbot])
|
377 |
+
send.click(user_message, inputs=[msg, chatbot, api_key], outputs=[msg, chatbot])
|
378 |
+
analyze.click(generate_analysis, inputs=[api_key, chatbot], outputs=[analysis, plot])
|
379 |
|
380 |
return demo
|
381 |
|