jzou19950715 commited on
Commit
f447aec
·
verified ·
1 Parent(s): c9cbc5e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -191
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 = 5
206
- self.num_paths = 1000
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
- # Calculate base growth
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: # 70% positive
240
- growth += impact
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
- {"role": "system", "content": CONVERSATION_PROMPT}
273
- ] + self.chat_history + [
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"Error: {str(e)}"
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
- raise Exception(f"Visualization error: {viz_result['error']}")
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"Error generating analysis: {str(e)}", None
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/5) - 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 = CareerAdvisor()
 
 
 
 
 
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
- history = history + [(message, response)]
442
- return "", history
443
 
444
  def generate_analysis(api_key: str, history: List) -> Tuple[str, gr.Image]:
445
- """Generate analysis"""
446
- if not history:
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
- Chat about your career, and I'll analyze your growth potential using Monte Carlo simulation!
460
- """)
461
-
462
- api_key = gr.Textbox(
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
- label="Analysis",
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: advisor.reset())
 
 
 
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