Spaces:
Sleeping
Sleeping
feat: enhance career analysis functionality with detailed career path options and scoring metrics for user profiles
Browse files- app/constants.py +146 -0
- app/main.py +141 -42
- app/utils.py +4 -84
- linkedinadvice/career_analysis.py +34 -50
app/constants.py
CHANGED
@@ -13,3 +13,149 @@ ROLE_PLACEHOLDER = [
|
|
13 |
"e.g. Site Reliability Engineer",
|
14 |
"e.g. Infrastructure Engineer",
|
15 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
"e.g. Site Reliability Engineer",
|
14 |
"e.g. Infrastructure Engineer",
|
15 |
]
|
16 |
+
|
17 |
+
SAMPLE_RESULT = """
|
18 |
+
Based on the provided career profile, we can identify several potential career paths that align with the individual's experience, skills, and aspirations.
|
19 |
+
For each option, I’ll assess its performance in the short, medium, and long term for each criterion. I’ll assign rough scores (1-5, where 1 is low and 5 is high) based on your background and goals, then explain the reasoning.
|
20 |
+
|
21 |
+
### Option 1: Pursue a PhD
|
22 |
+
- **Short-term** (1-3 years):
|
23 |
+
Financial Success: 2 (Stipends are modest, typically $20K-$40K/year, depending on location and funding.)
|
24 |
+
Impact on Helping Others: 2 (Focus is on learning and early research, with limited immediate impact.)
|
25 |
+
Winning Track Record: 3 (You’re building toward a prestigious degree, but no major achievements yet.)
|
26 |
+
- **Medium-term** (3-10 years):
|
27 |
+
Financial Success: 4 (Post-PhD, you could secure high-paying roles in industry or academia, e.g., $100K+ in tech.)
|
28 |
+
Impact on Helping Others: 4 (Research in AI or data science could address significant problems.)
|
29 |
+
Winning Track Record: 5 (A PhD is a globally recognized achievement, opening doors to elite roles.)
|
30 |
+
- **Long-term** (10+ years):
|
31 |
+
Financial Success: 4 (Potential for six-figure salaries in industry or stable academic positions.)
|
32 |
+
Impact on Helping Others: 5 (Breakthrough research or leadership in impactful projects.)
|
33 |
+
Winning Track Record: 5 (A PhD can lead to a career of notable contributions.)
|
34 |
+
|
35 |
+
### Option 2: Continue in Data Science (Industry)
|
36 |
+
- **Short-term**:
|
37 |
+
Financial Success: 4 (With 2 years of experience, you can likely secure $80K-$120K roles.)
|
38 |
+
Impact on Helping Others: 3 (Depends on projects—e.g., healthcare analytics could be impactful.)
|
39 |
+
Winning Track Record: 4 (Successful projects or promotions enhance your resume.)
|
40 |
+
- **Medium-term**:
|
41 |
+
Financial Success: 4 (Senior roles could exceed $150K with experience.)
|
42 |
+
Impact on Helping Others: 4 (Leading impactful initiatives becomes possible.)
|
43 |
+
Winning Track Record: 4 (A string of achievements strengthens your profile.)
|
44 |
+
- **Long-term**:
|
45 |
+
Financial Success: 4 (Potential for executive or specialized roles with high pay.)
|
46 |
+
Impact on Helping Others: 4 (Influence grows with seniority or project scope.)
|
47 |
+
Winning Track Record: 4 (Consistent success signals expertise.)
|
48 |
+
**Notes**: Your current path offers stability and growth, leveraging your existing skills and experience.
|
49 |
+
|
50 |
+
### Option 3: Switch to Quantitative Finance or Software Engineering
|
51 |
+
- **Short-term**:
|
52 |
+
Financial Success: 5 (Entry-level roles in finance or tech often start at $100K+.)
|
53 |
+
Impact on Helping Others: 3 (Varies—e.g., less direct in finance, more in software for social good.)
|
54 |
+
Winning Track Record: 3 (You’d need to build new credentials, but your skills transfer well.)
|
55 |
+
- **Medium-term**:
|
56 |
+
Financial Success: 5 (High earning potential, e.g., $200K+ in quant finance.)
|
57 |
+
Impact on Helping Others: 3 (Depends on the role—less predictable than data science.)
|
58 |
+
Winning Track Record: 4 (Success in these fields is visible and respected.)
|
59 |
+
- **Long-term**:
|
60 |
+
Financial Success: 5 (Top earners in these fields can make millions.)
|
61 |
+
Impact on Helping Others: 3 (Impact varies widely by role.)
|
62 |
+
Winning Track Record: 4 (Strong achievements possible.)
|
63 |
+
**Notes**: Switching requires some upskilling, but your math and programming background make it feasible.
|
64 |
+
|
65 |
+
### Option 4: Entrepreneurship
|
66 |
+
- **Short-term**:
|
67 |
+
Financial Success: 1 (High risk—little to no income initially.)
|
68 |
+
Impact on Helping Others: 4 (A venture solving real problems could have early impact.)
|
69 |
+
Winning Track Record: 4 (Starting something is notable, even if small-scale.)
|
70 |
+
- **Medium-term**:
|
71 |
+
Financial Success: 3 (Success could yield moderate to high returns; failure is common.)
|
72 |
+
Impact on Helping Others: 5 (A thriving business could scale solutions widely.)
|
73 |
+
Winning Track Record: 5 (A successful startup is a major credential.)
|
74 |
+
- **Long-term**:
|
75 |
+
Financial Success: 5 (Potential for significant wealth if successful.)
|
76 |
+
Impact on Helping Others: 5 (Large-scale impact possible.)
|
77 |
+
Winning Track Record: 5 (Entrepreneurial success opens elite opportunities.)
|
78 |
+
Notes: High risk, high reward—requires a strong idea and execution, but aligns with your AI and data science skills.
|
79 |
+
|
80 |
+
### Option 5: Academia/Research Without a PhD
|
81 |
+
- **Short-term**:
|
82 |
+
Financial Success: 2 (Low-paying roles like research assistant, ~$30K-$50K.)
|
83 |
+
Impact on Helping Others: 2 (Supportive roles with limited influence.)
|
84 |
+
Winning Track Record: 2 (Few notable achievements possible.)
|
85 |
+
- **Medium-term**:
|
86 |
+
Financial Success: 2 (Pay remains modest without a PhD.)
|
87 |
+
Impact on Helping Others: 3 (Contributions grow if you co-author or assist.)
|
88 |
+
Winning Track Record: 3 (Some recognition possible, but limited.)
|
89 |
+
- **Long-term**:
|
90 |
+
Financial Success: 2 (Caps out below industry or PhD levels.)
|
91 |
+
Impact on Helping Others: 3 (Niche impact possible.)
|
92 |
+
Winning Track Record: 3 (Hard to advance significantly.)
|
93 |
+
**Notes**: This path is constrained without a PhD, making it less competitive.
|
94 |
+
|
95 |
+
### Option 6: Complete or Pursue a Master’s Degree
|
96 |
+
- **Short-term**:
|
97 |
+
Financial Success: 1 (Costly unless funded; no income during study.)
|
98 |
+
Impact on Helping Others: 2 (Limited during the program.)
|
99 |
+
Winning Track Record: 3 (A degree builds your profile.)
|
100 |
+
- **Medium-term**:
|
101 |
+
Financial Success: 4 (Better job prospects post-degree.)
|
102 |
+
Impact on Helping Others: 3 (Depends on subsequent career.)
|
103 |
+
Winning Track Record: 4 (A prestigious master’s is a strong signal.)
|
104 |
+
- **Long-term**:
|
105 |
+
Financial Success: 4 (Comparable to industry paths.)
|
106 |
+
Impact on Helping Others: 4 (Depends on application.)
|
107 |
+
Winning Track Record: 4 (Opens doors, but less than a PhD.)
|
108 |
+
**Notes**: Your experience may already suffice for many jobs, but a funded program could be worth considering.
|
109 |
+
|
110 |
+
### Next Step: Rank Options with Weighted Scores
|
111 |
+
Since you’re willing to sacrifice short-term gains for long-term success, I’ll weigh long-term performance more heavily. Let’s calculate a total score for each option:
|
112 |
+
|
113 |
+
Formula: Total Score = (Short-term Avg) + (Medium-term Avg) + 2 × (Long-term Avg)
|
114 |
+
Averages are computed as (Financial + Impact + Track Record) ÷ 3.
|
115 |
+
|
116 |
+
**PhD**
|
117 |
+
- Short-term: (2+2+3)/3 = 2.33
|
118 |
+
- Medium-term: (4+4+5)/3 = 4.33
|
119 |
+
- Long-term: (4+5+5)/3 = 4.67
|
120 |
+
- Total: 2.33 + 4.33 + 2 × 4.67 = 16
|
121 |
+
|
122 |
+
**Data Science (Industry)**
|
123 |
+
- Short-term: (4+3+4)/3 = 3.67
|
124 |
+
- Medium-term: (4+4+4)/3 = 4
|
125 |
+
- Long-term: (4+4+4)/3 = 4
|
126 |
+
- Total: 3.67 + 4 + 2 × 4 = 15.67
|
127 |
+
|
128 |
+
**Quant Finance/Software Engineering**
|
129 |
+
- Short-term: (5+3+3)/3 = 3.67
|
130 |
+
- Medium-term: (5+3+4)/3 = 4
|
131 |
+
- Long-term: (5+3+4)/3 = 4
|
132 |
+
- Total: 3.67 + 4 + 2 × 4 = 15.67
|
133 |
+
|
134 |
+
**Entrepreneurship**
|
135 |
+
- Short-term: (1+4+4)/3 = 3
|
136 |
+
- Medium-term: (3+5+5)/3 = 4.33
|
137 |
+
- Long-term: (5+5+5)/3 = 5
|
138 |
+
- Total: 3 + 4.33 + 2 × 5 = 17.33
|
139 |
+
|
140 |
+
**Academia Without PhD**
|
141 |
+
- Short-term: (2+2+2)/3 = 2
|
142 |
+
- Medium-term: (2+3+3)/3 = 2.67
|
143 |
+
- Long-term: (2+3+3)/3 = 2.67
|
144 |
+
- Total: 2 + 2.67 + 2 × 2.67 = 10.01
|
145 |
+
|
146 |
+
**Master’s Degree**
|
147 |
+
- Short-term: (1+2+3)/3 = 2
|
148 |
+
- Medium-term: (4+3+4)/3 = 3.67
|
149 |
+
- Long-term: (4+4+4)/3 = 4
|
150 |
+
- Total: 2 + 3.67 + 2 × 4 = 13.67
|
151 |
+
|
152 |
+
### Final Ranking
|
153 |
+
|
154 |
+
### Final Ranking
|
155 |
+
- **Entrepreneurship**: 17.33
|
156 |
+
- **PhD**: 16
|
157 |
+
- **Data Science (Industry)**: 15.67
|
158 |
+
- **Quant Finance/Software Engineering**: 15.67
|
159 |
+
- **Master’s Degree**: 13.67
|
160 |
+
- **Academia Without PhD**: 10.01
|
161 |
+
"""
|
app/main.py
CHANGED
@@ -21,36 +21,9 @@ analyzer = CareerAnalyzer(model_name="gpt-4o-mini")
|
|
21 |
|
22 |
@monitor_api
|
23 |
def analyze_career(
|
24 |
-
|
25 |
-
achievements,
|
26 |
-
education,
|
27 |
-
edu_achievements,
|
28 |
-
goals,
|
29 |
-
insights,
|
30 |
-
time_preference,
|
31 |
-
financial_weight,
|
32 |
-
impact_weight,
|
33 |
-
opportunity_weight,
|
34 |
):
|
35 |
-
|
36 |
-
|
37 |
-
# Prepare user data
|
38 |
-
user_data = {
|
39 |
-
"roles": roles_data,
|
40 |
-
"achievements": achievements,
|
41 |
-
"educations": education,
|
42 |
-
"edu_achievements": edu_achievements,
|
43 |
-
"goals": goals,
|
44 |
-
"insights": insights,
|
45 |
-
"time_preference": time_preference,
|
46 |
-
"financial_weight": financial_weight,
|
47 |
-
"impact_weight": impact_weight,
|
48 |
-
"opportunity_weight": opportunity_weight,
|
49 |
-
}
|
50 |
-
|
51 |
-
# Get analysis
|
52 |
-
|
53 |
-
result = analyzer.analyze(user_data)
|
54 |
|
55 |
return result
|
56 |
|
@@ -60,17 +33,20 @@ with gr.Blocks(theme="soft") as demo:
|
|
60 |
# State for number of roles
|
61 |
role_count = gr.State(1) # Start with 1 role field
|
62 |
education_count = gr.State(1) # Start with 1 education field
|
|
|
|
|
63 |
professional_background = gr.State("") # State to store combined roles data
|
64 |
professional_achievements = gr.State("") # State to store combined roles data
|
65 |
education_background = gr.State("") # State to store combined education background
|
66 |
education_achievements = gr.State("") # State to store education achievements
|
67 |
goals = gr.State("") # State to store combined goals
|
68 |
insights = gr.State("") # State to store combined insights
|
69 |
-
time_preference = gr.State("") # State to store time preference
|
70 |
financial_weight = gr.State(2) # State to store financial weight
|
71 |
impact_weight = gr.State(2) # State to store impact weight
|
72 |
opportunity_weight = gr.State(2) # State to store opportunity weight
|
73 |
output = gr.State("") # State to store output
|
|
|
74 |
|
75 |
gr.Markdown(
|
76 |
"""
|
@@ -83,7 +59,7 @@ with gr.Blocks(theme="soft") as demo:
|
|
83 |
|
84 |
with gr.Row():
|
85 |
submit_btn = gr.Button("Analyze Career Paths", variant="primary", size="lg")
|
86 |
-
clear_btn = gr.Button("Clear
|
87 |
example_btn = gr.Button("Load Example", variant="secondary", size="lg")
|
88 |
|
89 |
with gr.Row():
|
@@ -117,20 +93,73 @@ with gr.Blocks(theme="soft") as demo:
|
|
117 |
@gr.render(inputs=role_count)
|
118 |
def render_roles(r_count):
|
119 |
for i in range(r_count):
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
with gr.Row():
|
|
|
121 |
role = gr.Textbox(
|
122 |
key=f"role_{i}",
|
123 |
label=f"Role {i + 1}"
|
124 |
+ (" (Current)" if i == 0 else ""),
|
125 |
placeholder=ROLE_PLACEHOLDER[i % len(ROLE_PLACEHOLDER)],
|
126 |
scale=3,
|
|
|
|
|
|
|
|
|
127 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
exp = gr.Number(
|
129 |
key=f"exp_{i}",
|
130 |
label="Years",
|
131 |
value=1,
|
132 |
minimum=0,
|
133 |
scale=1,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
)
|
135 |
|
136 |
professional_achievement = gr.Textbox(
|
@@ -138,13 +167,23 @@ with gr.Blocks(theme="soft") as demo:
|
|
138 |
label="Notable Achievements",
|
139 |
lines=2,
|
140 |
placeholder="List key accomplishments, awards, or significant contributions.",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
)
|
142 |
-
if professional_achievement.value:
|
143 |
-
professional_achievements.value += (
|
144 |
-
professional_achievement.value
|
145 |
-
)
|
146 |
-
if professional_background.value:
|
147 |
-
professional_background.value += f"{role} - {exp} years\n"
|
148 |
|
149 |
with gr.Group():
|
150 |
gr.Markdown("### Educational Background")
|
@@ -179,6 +218,11 @@ with gr.Blocks(theme="soft") as demo:
|
|
179 |
@gr.render(inputs=education_count)
|
180 |
def render_education(e_count):
|
181 |
for i in range(e_count):
|
|
|
|
|
|
|
|
|
|
|
182 |
with gr.Row():
|
183 |
education = gr.Textbox(
|
184 |
key=f"education_{i}",
|
@@ -186,17 +230,57 @@ with gr.Blocks(theme="soft") as demo:
|
|
186 |
lines=3,
|
187 |
placeholder="e.g. Bachelor of Science in Computer Science, University of Technology",
|
188 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
189 |
edu_achievement = gr.Textbox(
|
190 |
key=f"edu_achievement_{i}",
|
191 |
label="Academic Achievements",
|
192 |
lines=3,
|
193 |
placeholder="Awards, honors, notable projects or research during your education",
|
194 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
195 |
if education.value:
|
196 |
education_background.value += education.value
|
197 |
if edu_achievement.value:
|
198 |
education_achievements.value += edu_achievement.value
|
199 |
|
|
|
|
|
200 |
with gr.Group():
|
201 |
gr.Markdown("### Future Plans")
|
202 |
goals = gr.Textbox(
|
@@ -260,7 +344,13 @@ with gr.Blocks(theme="soft") as demo:
|
|
260 |
)
|
261 |
|
262 |
with gr.Column(scale=2):
|
263 |
-
output_box = gr.
|
|
|
|
|
|
|
|
|
|
|
|
|
264 |
|
265 |
with gr.Row():
|
266 |
copy_btn = gr.Button("📋 Copy to Clipboard", variant="secondary")
|
@@ -269,7 +359,7 @@ with gr.Blocks(theme="soft") as demo:
|
|
269 |
# Copy and share functionality
|
270 |
copy_btn.click(
|
271 |
copy_to_clipboard,
|
272 |
-
inputs=
|
273 |
)
|
274 |
|
275 |
# Add info section at the bottom
|
@@ -295,12 +385,10 @@ with gr.Blocks(theme="soft") as demo:
|
|
295 |
)
|
296 |
|
297 |
submit_btn.click(
|
298 |
-
export_state,
|
299 |
inputs=[
|
300 |
professional_background,
|
301 |
-
professional_achievements,
|
302 |
education_background,
|
303 |
-
education_achievements,
|
304 |
goals,
|
305 |
insights,
|
306 |
time_preference,
|
@@ -311,6 +399,17 @@ with gr.Blocks(theme="soft") as demo:
|
|
311 |
outputs=[output_box],
|
312 |
)
|
313 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
314 |
|
315 |
# Launch the app
|
316 |
if __name__ == "__main__":
|
|
|
21 |
|
22 |
@monitor_api
|
23 |
def analyze_career(
|
24 |
+
*args,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
):
|
26 |
+
result = analyzer.analyze(*args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
return result
|
29 |
|
|
|
33 |
# State for number of roles
|
34 |
role_count = gr.State(1) # Start with 1 role field
|
35 |
education_count = gr.State(1) # Start with 1 education field
|
36 |
+
professional_background_dict = gr.State({}) # State to store combined roles data
|
37 |
+
education_background_dict = gr.State({}) # State to store combined education data
|
38 |
professional_background = gr.State("") # State to store combined roles data
|
39 |
professional_achievements = gr.State("") # State to store combined roles data
|
40 |
education_background = gr.State("") # State to store combined education background
|
41 |
education_achievements = gr.State("") # State to store education achievements
|
42 |
goals = gr.State("") # State to store combined goals
|
43 |
insights = gr.State("") # State to store combined insights
|
44 |
+
time_preference = gr.State("Mid-term (10 years)") # State to store time preference
|
45 |
financial_weight = gr.State(2) # State to store financial weight
|
46 |
impact_weight = gr.State(2) # State to store impact weight
|
47 |
opportunity_weight = gr.State(2) # State to store opportunity weight
|
48 |
output = gr.State("") # State to store output
|
49 |
+
raw_list_inputs = gr.State([]) # State to store raw list inputs
|
50 |
|
51 |
gr.Markdown(
|
52 |
"""
|
|
|
59 |
|
60 |
with gr.Row():
|
61 |
submit_btn = gr.Button("Analyze Career Paths", variant="primary", size="lg")
|
62 |
+
clear_btn = gr.Button("Clear", variant="stop", size="lg")
|
63 |
example_btn = gr.Button("Load Example", variant="secondary", size="lg")
|
64 |
|
65 |
with gr.Row():
|
|
|
93 |
@gr.render(inputs=role_count)
|
94 |
def render_roles(r_count):
|
95 |
for i in range(r_count):
|
96 |
+
if i not in professional_background_dict.value:
|
97 |
+
professional_background_dict.value[i] = {
|
98 |
+
"role": "",
|
99 |
+
"exp": "",
|
100 |
+
"professional_achievement": "",
|
101 |
+
}
|
102 |
with gr.Row():
|
103 |
+
interactive = i == r_count - 1
|
104 |
role = gr.Textbox(
|
105 |
key=f"role_{i}",
|
106 |
label=f"Role {i + 1}"
|
107 |
+ (" (Current)" if i == 0 else ""),
|
108 |
placeholder=ROLE_PLACEHOLDER[i % len(ROLE_PLACEHOLDER)],
|
109 |
scale=3,
|
110 |
+
interactive=interactive,
|
111 |
+
inputs=professional_background_dict.value[i].get(
|
112 |
+
"role", ""
|
113 |
+
),
|
114 |
)
|
115 |
+
|
116 |
+
def update_professional_background_dict(key, value, i):
|
117 |
+
professional_background_dict.value[i][key] = value
|
118 |
+
final_string = ""
|
119 |
+
for i in professional_background_dict.value:
|
120 |
+
role = professional_background_dict.value[i].get(
|
121 |
+
"role"
|
122 |
+
)
|
123 |
+
exp = professional_background_dict.value[i].get(
|
124 |
+
"exp"
|
125 |
+
)
|
126 |
+
professional_achievement = (
|
127 |
+
professional_background_dict.value[i].get(
|
128 |
+
"professional_achievement"
|
129 |
+
)
|
130 |
+
)
|
131 |
+
final_string += f"{role} - {exp} years. Achieved: \n{professional_achievement}\n\n"
|
132 |
+
|
133 |
+
return professional_background_dict.value, final_string
|
134 |
+
|
135 |
+
role.input(
|
136 |
+
lambda x: update_professional_background_dict(
|
137 |
+
"role", x, i
|
138 |
+
),
|
139 |
+
inputs=[role],
|
140 |
+
outputs=[
|
141 |
+
professional_background_dict,
|
142 |
+
professional_background,
|
143 |
+
],
|
144 |
+
)
|
145 |
+
|
146 |
exp = gr.Number(
|
147 |
key=f"exp_{i}",
|
148 |
label="Years",
|
149 |
value=1,
|
150 |
minimum=0,
|
151 |
scale=1,
|
152 |
+
interactive=interactive,
|
153 |
+
)
|
154 |
+
exp.input(
|
155 |
+
lambda x: update_professional_background_dict(
|
156 |
+
"exp", x, i
|
157 |
+
),
|
158 |
+
inputs=[exp],
|
159 |
+
outputs=[
|
160 |
+
professional_background_dict,
|
161 |
+
professional_background,
|
162 |
+
],
|
163 |
)
|
164 |
|
165 |
professional_achievement = gr.Textbox(
|
|
|
167 |
label="Notable Achievements",
|
168 |
lines=2,
|
169 |
placeholder="List key accomplishments, awards, or significant contributions.",
|
170 |
+
interactive=interactive,
|
171 |
+
)
|
172 |
+
|
173 |
+
professional_achievement.input(
|
174 |
+
lambda x: update_professional_background_dict(
|
175 |
+
"professional_achievement", x, i
|
176 |
+
),
|
177 |
+
inputs=[professional_achievement],
|
178 |
+
outputs=[
|
179 |
+
professional_background_dict,
|
180 |
+
professional_background,
|
181 |
+
],
|
182 |
+
)
|
183 |
+
|
184 |
+
raw_list_inputs.value.extend(
|
185 |
+
[role, exp, professional_achievement]
|
186 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
|
188 |
with gr.Group():
|
189 |
gr.Markdown("### Educational Background")
|
|
|
218 |
@gr.render(inputs=education_count)
|
219 |
def render_education(e_count):
|
220 |
for i in range(e_count):
|
221 |
+
if i not in education_background_dict.value:
|
222 |
+
education_background_dict.value[i] = {
|
223 |
+
"education": "",
|
224 |
+
"education_achievement": "",
|
225 |
+
}
|
226 |
with gr.Row():
|
227 |
education = gr.Textbox(
|
228 |
key=f"education_{i}",
|
|
|
230 |
lines=3,
|
231 |
placeholder="e.g. Bachelor of Science in Computer Science, University of Technology",
|
232 |
)
|
233 |
+
|
234 |
+
def update_educational_background_dict(key, value, i):
|
235 |
+
education_background_dict.value[i][key] = value
|
236 |
+
final_string = ""
|
237 |
+
for i in education_background_dict.value:
|
238 |
+
education = education_background_dict.value[i].get(
|
239 |
+
"education"
|
240 |
+
)
|
241 |
+
edu_achievement = education_background_dict.value[
|
242 |
+
i
|
243 |
+
].get("edu_achievement")
|
244 |
+
final_string += f"{education}. Achieved: \n{edu_achievement}\n\n"
|
245 |
+
|
246 |
+
return education_background_dict.value, final_string
|
247 |
+
|
248 |
+
education.input(
|
249 |
+
lambda x: update_educational_background_dict(
|
250 |
+
"education", x, i
|
251 |
+
),
|
252 |
+
inputs=[education],
|
253 |
+
outputs=[
|
254 |
+
education_background_dict,
|
255 |
+
education_background,
|
256 |
+
],
|
257 |
+
)
|
258 |
+
|
259 |
edu_achievement = gr.Textbox(
|
260 |
key=f"edu_achievement_{i}",
|
261 |
label="Academic Achievements",
|
262 |
lines=3,
|
263 |
placeholder="Awards, honors, notable projects or research during your education",
|
264 |
)
|
265 |
+
|
266 |
+
edu_achievement.input(
|
267 |
+
lambda x: update_educational_background_dict(
|
268 |
+
"education_achievement", x, i
|
269 |
+
),
|
270 |
+
inputs=[education_achievements],
|
271 |
+
outputs=[
|
272 |
+
education_background_dict,
|
273 |
+
education_background,
|
274 |
+
],
|
275 |
+
)
|
276 |
+
|
277 |
if education.value:
|
278 |
education_background.value += education.value
|
279 |
if edu_achievement.value:
|
280 |
education_achievements.value += edu_achievement.value
|
281 |
|
282 |
+
raw_list_inputs.value.extend([education, edu_achievement])
|
283 |
+
|
284 |
with gr.Group():
|
285 |
gr.Markdown("### Future Plans")
|
286 |
goals = gr.Textbox(
|
|
|
344 |
)
|
345 |
|
346 |
with gr.Column(scale=2):
|
347 |
+
output_box = gr.Markdown(
|
348 |
+
output.value,
|
349 |
+
label="Career Analysis Report",
|
350 |
+
container=True,
|
351 |
+
height=614,
|
352 |
+
show_copy_button=False,
|
353 |
+
)
|
354 |
|
355 |
with gr.Row():
|
356 |
copy_btn = gr.Button("📋 Copy to Clipboard", variant="secondary")
|
|
|
359 |
# Copy and share functionality
|
360 |
copy_btn.click(
|
361 |
copy_to_clipboard,
|
362 |
+
inputs=output_box,
|
363 |
)
|
364 |
|
365 |
# Add info section at the bottom
|
|
|
385 |
)
|
386 |
|
387 |
submit_btn.click(
|
388 |
+
lambda *args: analyze_career(*args) if export_state(*args) or True else None,
|
389 |
inputs=[
|
390 |
professional_background,
|
|
|
391 |
education_background,
|
|
|
392 |
goals,
|
393 |
insights,
|
394 |
time_preference,
|
|
|
399 |
outputs=[output_box],
|
400 |
)
|
401 |
|
402 |
+
clear_btn.click(
|
403 |
+
fn=lambda: None, inputs=[], outputs=[], js="() => location.reload()"
|
404 |
+
)
|
405 |
+
|
406 |
+
"""
|
407 |
+
example_btn.click(
|
408 |
+
fn=lambda _: SAMPLE_RESULT,
|
409 |
+
inputs=[output_box],
|
410 |
+
outputs=[output_box],
|
411 |
+
)
|
412 |
+
"""
|
413 |
|
414 |
# Launch the app
|
415 |
if __name__ == "__main__":
|
app/utils.py
CHANGED
@@ -3,98 +3,21 @@ Utility functions for the LinkedIn Career Advice application.
|
|
3 |
Handles input processing, clipboard operations, and sharing functionality.
|
4 |
"""
|
5 |
|
6 |
-
import re
|
7 |
-
import urllib.parse
|
8 |
-
|
9 |
import pyperclip
|
10 |
|
11 |
|
12 |
-
def combine_roles(count, *args):
|
13 |
-
roles_text = ""
|
14 |
-
|
15 |
-
for i in range(count):
|
16 |
-
# Calculate the index in args for this role's name and experience
|
17 |
-
name_idx = i
|
18 |
-
exp_idx = i + count // 2 + 1
|
19 |
-
|
20 |
-
if name_idx < len(args) and exp_idx < len(args):
|
21 |
-
role_name = args[name_idx]
|
22 |
-
experience = args[exp_idx]
|
23 |
-
|
24 |
-
if role_name and experience is not None:
|
25 |
-
roles_text += f"{role_name} ({experience} years)\n"
|
26 |
-
|
27 |
-
return roles_text.strip()
|
28 |
-
|
29 |
-
|
30 |
-
def share_to_linkedin(text):
|
31 |
-
"""Generate a LinkedIn sharing link"""
|
32 |
-
share_link = get_share_link(text)
|
33 |
-
return f"Share on LinkedIn: {share_link}"
|
34 |
-
|
35 |
-
|
36 |
-
def combine_text_fields(text_list):
|
37 |
-
return "\n\n".join([text for text in text_list if text])
|
38 |
-
|
39 |
-
|
40 |
-
def format_for_linkedin(text):
|
41 |
-
"""Format analysis for LinkedIn sharing"""
|
42 |
-
# Extract just the summary/conclusion part to keep LinkedIn share concise
|
43 |
-
conclusion_match = re.search(
|
44 |
-
r"(?:Conclusion|Final Recommendation).*?:(.*?)(?=$)", text, re.DOTALL
|
45 |
-
)
|
46 |
-
|
47 |
-
if conclusion_match:
|
48 |
-
conclusion = conclusion_match.group(1).strip()
|
49 |
-
formatted = "I received this career path analysis insight:\n\n" + conclusion
|
50 |
-
return formatted[:500] + "..." if len(formatted) > 500 else formatted
|
51 |
-
|
52 |
-
# Fallback if no conclusion section found
|
53 |
-
shortened = text[:500] + "..." if len(text) > 500 else text
|
54 |
-
return "Career Path Analysis Results:\n\n" + shortened
|
55 |
-
|
56 |
-
|
57 |
-
def get_share_link(text):
|
58 |
-
# Create a shortened version for sharing (LinkedIn has character limits)
|
59 |
-
summary = text[:500] + "..." if len(text) > 500 else text
|
60 |
-
|
61 |
-
# Get the LinkedIn sharing URL with the text encoded
|
62 |
-
base_url = "https://www.linkedin.com/sharing/share-offsite/"
|
63 |
-
params = {
|
64 |
-
"url": "https://huggingface.co/spaces/LinkedIn-Advice/career-path-advisor",
|
65 |
-
"title": "My Career Path Analysis",
|
66 |
-
"summary": summary,
|
67 |
-
}
|
68 |
-
|
69 |
-
share_url = f"{base_url}?{urllib.parse.urlencode(params)}"
|
70 |
-
return share_url
|
71 |
-
|
72 |
-
|
73 |
-
def process_input_data(
|
74 |
-
role_count, roles_and_exps, achievements, educations, edu_achievements
|
75 |
-
):
|
76 |
-
# Process roles and experience
|
77 |
-
roles_data = combine_roles(role_count, *roles_and_exps)
|
78 |
-
|
79 |
-
# Process achievements, education, and educational achievements
|
80 |
-
achievements_data = combine_text_fields(achievements)
|
81 |
-
education_data = combine_text_fields(educations)
|
82 |
-
edu_achievements_data = combine_text_fields(edu_achievements)
|
83 |
-
|
84 |
-
return roles_data, achievements_data, education_data, edu_achievements_data
|
85 |
-
|
86 |
-
|
87 |
def copy_to_clipboard(text):
|
88 |
"""Copy text to clipboard"""
|
|
|
|
|
|
|
89 |
pyperclip.copy(text)
|
90 |
-
return
|
91 |
|
92 |
|
93 |
def export_state(
|
94 |
professional_background,
|
95 |
-
professional_achievements,
|
96 |
education_background,
|
97 |
-
education_achievements,
|
98 |
goals,
|
99 |
insights,
|
100 |
time_preference,
|
@@ -104,9 +27,7 @@ def export_state(
|
|
104 |
) -> str:
|
105 |
state_dict = {
|
106 |
"Professional Background": professional_background,
|
107 |
-
"Professional Achievements": professional_achievements,
|
108 |
"Education Background": education_background,
|
109 |
-
"Education Achievements": education_achievements,
|
110 |
"Goals": goals,
|
111 |
"Insights": insights,
|
112 |
"Time Preference": time_preference,
|
@@ -116,6 +37,5 @@ def export_state(
|
|
116 |
}
|
117 |
|
118 |
formatted_output = "\n".join(f"{key}: {value}" for key, value in state_dict.items())
|
119 |
-
print(formatted_output)
|
120 |
|
121 |
return formatted_output
|
|
|
3 |
Handles input processing, clipboard operations, and sharing functionality.
|
4 |
"""
|
5 |
|
|
|
|
|
|
|
6 |
import pyperclip
|
7 |
|
8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
def copy_to_clipboard(text):
|
10 |
"""Copy text to clipboard"""
|
11 |
+
if not pyperclip.is_available():
|
12 |
+
print("Clipboard is not available on this system")
|
13 |
+
return None
|
14 |
pyperclip.copy(text)
|
15 |
+
return None
|
16 |
|
17 |
|
18 |
def export_state(
|
19 |
professional_background,
|
|
|
20 |
education_background,
|
|
|
21 |
goals,
|
22 |
insights,
|
23 |
time_preference,
|
|
|
27 |
) -> str:
|
28 |
state_dict = {
|
29 |
"Professional Background": professional_background,
|
|
|
30 |
"Education Background": education_background,
|
|
|
31 |
"Goals": goals,
|
32 |
"Insights": insights,
|
33 |
"Time Preference": time_preference,
|
|
|
37 |
}
|
38 |
|
39 |
formatted_output = "\n".join(f"{key}: {value}" for key, value in state_dict.items())
|
|
|
40 |
|
41 |
return formatted_output
|
linkedinadvice/career_analysis.py
CHANGED
@@ -17,7 +17,17 @@ class CareerAnalyzer:
|
|
17 |
self.model_name = model_name
|
18 |
self.temperature = temperature
|
19 |
|
20 |
-
def analyze(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
"""
|
22 |
Analyze career paths based on user data
|
23 |
|
@@ -30,74 +40,48 @@ class CareerAnalyzer:
|
|
30 |
str: Formatted analysis results
|
31 |
"""
|
32 |
# Use default equal weights if none provided
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
0.33,
|
37 |
-
0.34,
|
38 |
-
] # Slightly more on opportunity to sum to 1.0
|
39 |
-
|
40 |
-
# Parse the roles data to extract current role and experience
|
41 |
-
roles_list = user_data.get("roles", "").split("\n")
|
42 |
-
|
43 |
-
# Identify current role (first one in the list)
|
44 |
-
current_role = ""
|
45 |
-
current_experience = 0
|
46 |
-
if roles_list and roles_list[0]:
|
47 |
-
parts = roles_list[0].split(" (")
|
48 |
-
if len(parts) == 2:
|
49 |
-
current_role = parts[0]
|
50 |
-
exp_part = parts[1].split(" years")[0]
|
51 |
-
try:
|
52 |
-
current_experience = int(exp_part)
|
53 |
-
except ValueError:
|
54 |
-
current_experience = 0
|
55 |
-
|
56 |
-
# Format previous roles (all except the first one)
|
57 |
-
prev_roles_str = ", ".join(roles_list[1:]) if len(roles_list) > 1 else ""
|
58 |
|
59 |
# Create the prompt with all available information
|
60 |
prompt = f"""Analyze the following career profile and generate a taxonomy of potential career paths:
|
61 |
|
62 |
Current Role: {current_role}
|
63 |
-
Years in Current Role: {current_experience}
|
64 |
"""
|
65 |
|
66 |
# Add previous roles if present
|
67 |
-
if
|
68 |
-
prompt += f"Previous Roles: {
|
69 |
|
70 |
# Add all the fields from user_data
|
71 |
-
prompt += f"""
|
72 |
-
Educational Background: {
|
73 |
-
|
74 |
-
|
75 |
-
Additional Insights: {user_data.get("insights", "")}
|
76 |
-
|
77 |
-
Time Preference: {user_data.get("time_preference")}
|
78 |
-
Financial Weight: {user_data.get("financial_weight", scoring_weights[0])}
|
79 |
-
Impact Weight: {user_data.get("impact_weight", scoring_weights[1])}
|
80 |
-
Opportunity Weight: {user_data.get("opportunity_weight", scoring_weights[2])}
|
81 |
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
|
87 |
-
|
|
|
|
|
|
|
88 |
|
89 |
-
|
90 |
-
- Financial: {scoring_weights[0]}
|
91 |
-
- Human Impact: {scoring_weights[1]}
|
92 |
-
- Opportunity Creation: {scoring_weights[2]}
|
93 |
|
94 |
-
|
|
|
|
|
|
|
95 |
|
96 |
Format the output clearly with sections for each career path, the scoring breakdown, and a final recommendation section ranking the paths from highest to lowest score.
|
97 |
"""
|
98 |
|
99 |
# Add novel paths instruction if requested
|
100 |
-
if
|
101 |
prompt += "\nPlease include at least one novel or unconventional career path in your analysis."
|
102 |
|
103 |
# Print the prompt for debugging
|
|
|
17 |
self.model_name = model_name
|
18 |
self.temperature = temperature
|
19 |
|
20 |
+
def analyze(
|
21 |
+
self,
|
22 |
+
professional_background,
|
23 |
+
education_background,
|
24 |
+
goals,
|
25 |
+
insights,
|
26 |
+
time_preference,
|
27 |
+
financial_weight,
|
28 |
+
impact_weight,
|
29 |
+
opportunity_weight,
|
30 |
+
):
|
31 |
"""
|
32 |
Analyze career paths based on user data
|
33 |
|
|
|
40 |
str: Formatted analysis results
|
41 |
"""
|
42 |
# Use default equal weights if none provided
|
43 |
+
current_role = professional_background.split("\n\n")[0]
|
44 |
+
previous_roles = "\n\n".join(professional_background.split("\n\n")[1:])
|
45 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
# Create the prompt with all available information
|
48 |
prompt = f"""Analyze the following career profile and generate a taxonomy of potential career paths:
|
49 |
|
50 |
Current Role: {current_role}
|
|
|
51 |
"""
|
52 |
|
53 |
# Add previous roles if present
|
54 |
+
if previous_roles:
|
55 |
+
prompt += f"Previous Roles: {previous_roles}\n"
|
56 |
|
57 |
# Add all the fields from user_data
|
58 |
+
prompt += f"""
|
59 |
+
Educational Background: {education_background}
|
60 |
+
Career Goals: {goals}
|
61 |
+
Additional Insights: {insights}
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
+
Time Preference: {time_preference}
|
64 |
+
Financial Weight: {financial_weight}
|
65 |
+
Impact Weight: {impact_weight}
|
66 |
+
Opportunity Weight: {opportunity_weight}
|
67 |
|
68 |
+
Help me as an expert career advisor. Provide a taxonomy to generate promising career paths on the timescale of {time_preference} and rate them them based on success on the short, medium and long term. For each path, evaluate:
|
69 |
+
1. Financial potential (scale 1-3) at 3, 10, and 10+ years
|
70 |
+
2. Human impact potential (scale 1-3) at 3, 10, and 10+ years
|
71 |
+
3. Opportunity creation potential (scale 1-3) at 3, 10, and 10+ years
|
72 |
|
73 |
+
Show your step by stepreasoning for each score.
|
|
|
|
|
|
|
74 |
|
75 |
+
Then calculate an accurate weighted average based on the following weights:
|
76 |
+
- Financial: {financial_weight}
|
77 |
+
- Human Impact: {impact_weight}
|
78 |
+
- Opportunity Creation: {opportunity_weight}
|
79 |
|
80 |
Format the output clearly with sections for each career path, the scoring breakdown, and a final recommendation section ranking the paths from highest to lowest score.
|
81 |
"""
|
82 |
|
83 |
# Add novel paths instruction if requested
|
84 |
+
if False:
|
85 |
prompt += "\nPlease include at least one novel or unconventional career path in your analysis."
|
86 |
|
87 |
# Print the prompt for debugging
|