Skip to content

Commit 8b95cfd

Browse files
authored
SkillSync - Skill Gap Analysis using Gemini LLM along with Linkedin API for Career Growth and Course Recommendation
1 parent 4a0ffb1 commit 8b95cfd

File tree

4 files changed

+371
-0
lines changed

4 files changed

+371
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# **Skill Sync**
2+
3+
### 🎯 **Goal**
4+
5+
The primary goal of **Skill Sync** is to bridge the skill gap by analyzing an individual’s current skills against the demands of their career goals. The application helps users align their skillsets with market demands, suggesting relevant courses for upskilling and enabling seamless skill import from platforms like LinkedIn.
6+
7+
### 🧵 **Dataset**
8+
9+
**Skill Sync** does not use a pre-existing dataset. Instead, it gathers user-inputted skills or imports them from LinkedIn profiles to analyze and recommend relevant courses for skill development. The system also utilizes generative models to provide skill gap analysis and career insights.
10+
11+
### 🧾 **Description**
12+
13+
**Skill Sync** allows users to define their career goals and manually add skills or import them from LinkedIn. Using AI models, the app compares the user's current skill set with their desired career and suggests the best upskilling path.
14+
15+
The system helps streamline skill development by providing course recommendations, career insights, and gap analysis, improving the user's chances of achieving their career objectives.
16+
17+
### 🧮 **What I Had Done!**
18+
19+
- Implemented a **manual skill addition feature** where users can input their skills.
20+
- Integrated **LinkedIn API** to allow users to import their skills automatically.
21+
- Developed a **career goal matching system**, leveraging **AI** for skill gap analysis.
22+
- Suggested **relevant courses** based on skill gaps identified during analysis.
23+
24+
### 🚀 **Models Implemented**
25+
26+
- **Generative AI**: This model generates career insights and a comprehensive skill gap analysis based on the user’s inputs.
27+
- **Gemini AI**: Used to map and identify skill deficiencies and recommend the best learning paths.
28+
29+
### 📚 **Libraries Needed**
30+
31+
- Flask
32+
- requests
33+
- google-generativeai
34+
- pandas
35+
- dotenv
36+
37+
### 📊 **Exploratory Data Analysis Results**
38+
39+
Since **Skill Sync** deals with real-time input from users, traditional data analysis is not performed. However, the app provides insights based on real-time user data like skills vs. market requirements, skill deficiencies, and suggested learning paths.
40+
41+
### 📈 **Performance Metrics**
42+
43+
The performance of the system is assessed through:
44+
- **Accuracy of skill analysis**: How accurately the system identifies missing skills.
45+
- **Relevance of recommended courses**: How relevant the suggested courses are to the user's career goals.
46+
- **User satisfaction**: Feedback collected on how effective the tool is in helping users bridge their skill gaps.
47+
48+
### 💻 How to Run
49+
50+
To get started with **Skill Sync**, follow these steps:
51+
52+
1. Navigate to the project directory:
53+
54+
```bash
55+
cd SkillSync
56+
```
57+
58+
2. (Optional) Activate a virtual environment:
59+
60+
```bash
61+
conda create -n venv python=3.10+
62+
conda activate venv
63+
```
64+
65+
3. Install dependencies:
66+
67+
```bash
68+
pip install -r requirements.txt
69+
```
70+
71+
4. Configure environment variables:
72+
73+
```
74+
Rename `.env-sample` to `.env`.
75+
Replace with your LinkedIn and Google API Keys.
76+
```
77+
78+
Kindly refer to these links for getting your own API keys:
79+
- [LinkedIn API](https://developer.linkedin.com/) or [Proxy Curl API](https://nubela.co/proxycurl/linkedin)
80+
- [Google Generative AI Key](https://ai.google.dev/tutorials/setup)
81+
82+
5. Run the application:
83+
84+
```bash
85+
streamlit run app.py
86+
```
87+
88+
### 📢 **Conclusion**
89+
90+
**Skill Sync** is an effective tool for closing the skill gap by analyzing current skillsets and recommending relevant courses. It leverages AI models to provide detailed gap analysis and offers career-oriented guidance, ensuring that users stay on track to achieve their professional goals.
91+
92+
### ✒️ **Signature**
93+
94+
**[J B Mugundh]**
95+
GitHub: [Github](https://github.com/J-B-Mugundh)
96+
LinkedIn: [LinkedIn](https://www.linkedin.com/in/mugundhjb/)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from flask import Flask, request, render_template
2+
import google.generativeai as genai
3+
import os
4+
import json
5+
import requests
6+
from dotenv import load_dotenv
7+
8+
# Load environment variables from .env file
9+
load_dotenv()
10+
11+
app = Flask(__name__)
12+
13+
# Load API keys from environment variables
14+
PROXYCURL_API_KEY = os.getenv('PROXYCURL_API_KEY')
15+
PROXYCURL_API_ENDPOINT = 'https://nubela.co/proxycurl/api/v2/linkedin'
16+
17+
def get_gemini_response(career_goal, skills):
18+
"""Fetches response from Gemini API based on career goal and skills."""
19+
20+
query = f"Considering my career goal of '{career_goal}', what additional skills would I need to acquire if my current skills are {', '.join(skills)}? Just list them as a list. The skills should be actual programming or technical skills. Just give them concise, don't give extra words like Version control (eg. Git). List a maximum of 5 skills only. Display each with bulletin point."
21+
22+
model = genai.GenerativeModel('gemini-pro')
23+
api_key = os.getenv("GOOGLE_API_KEY") # Retrieve Google API key from .env
24+
genai.configure(api_key=api_key)
25+
26+
try:
27+
response = model.generate_content(query)
28+
return response.text
29+
except Exception as e:
30+
print(f"Error occurred during Gemini API call: {e}")
31+
return "An error occurred while fetching data from Gemini. Please try again later."
32+
33+
def get_linkedin_profile(linkedin_url):
34+
"""Fetches the LinkedIn profile using Proxycurl API."""
35+
36+
headers = {
37+
'Authorization': f'Bearer {PROXYCURL_API_KEY}',
38+
}
39+
40+
params = {
41+
'linkedin_profile_url': linkedin_url,
42+
'extra': 'include',
43+
'skills': 'include',
44+
'use_cache': 'if-present',
45+
'fallback_to_cache': 'on-error',
46+
}
47+
48+
try:
49+
response = requests.get(PROXYCURL_API_ENDPOINT, params=params, headers=headers)
50+
if response.status_code == 200:
51+
return response.json() # Return profile data as a dictionary
52+
else:
53+
print(f"Error fetching LinkedIn profile: {response.status_code}")
54+
return {"error": f"Error fetching profile, status code: {response.status_code}"}
55+
56+
except Exception as e:
57+
print(f"Error occurred during LinkedIn API proxy call: {e}")
58+
return {"error": "An error occurred while fetching data from LinkedIn. Please try again later."}
59+
60+
@app.route('/', methods=['GET', 'POST'])
61+
def index():
62+
if request.method == 'POST':
63+
career_goal = request.form['careerGoal']
64+
manual_skills = request.form.getlist('skill[]')
65+
linkedin_url = request.form.get('linkedinProfileUrl') # Changed to get LinkedIn profile URL
66+
profile_data = None
67+
68+
if manual_skills or linkedin_url:
69+
if linkedin_url:
70+
profile_data = get_linkedin_profile(linkedin_url)
71+
if 'skills' in profile_data:
72+
skills_data = {"skills": profile_data['skills']}
73+
else:
74+
skills_data = {"skills": []}
75+
else:
76+
skills_data = {"skills": manual_skills}
77+
78+
with open('skills.json', 'w') as json_file:
79+
json.dump(skills_data, json_file)
80+
81+
if linkedin_url:
82+
profile_data = get_linkedin_profile(linkedin_url)
83+
elif manual_skills:
84+
profile_data = manual_skills
85+
86+
if profile_data:
87+
if 'error' in profile_data:
88+
return render_template('index.html', error=profile_data['error'])
89+
gemini_response = get_gemini_response(career_goal, profile_data['skills'] if 'skills' in profile_data else manual_skills)
90+
return render_template('index.html', profile_data=profile_data, gemini_response=gemini_response)
91+
else:
92+
return render_template('index.html', error="Please enter your career goal and skills.")
93+
94+
return render_template('index.html')
95+
96+
if __name__ == '__main__':
97+
app.run(debug=True, use_reloader=False)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Flask
2+
requests
3+
google-generativeai
4+
pandas
5+
dotenv
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Skill Sync</title>
7+
<!-- Bootstrap CSS -->
8+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
9+
<!-- FontAwesome Icons for a more polished UI -->
10+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
11+
<style>
12+
body {
13+
background-color: #f8f9fa;
14+
}
15+
h1 {
16+
font-size: 2.5rem;
17+
text-align: center;
18+
margin-bottom: 40px;
19+
color: #007bff;
20+
}
21+
form {
22+
background-color: #fff;
23+
padding: 30px;
24+
border-radius: 8px;
25+
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
26+
}
27+
.form-group label {
28+
font-weight: bold;
29+
}
30+
.form-check-label {
31+
font-size: 1rem;
32+
}
33+
.btn {
34+
background-color: #007bff;
35+
border-color: #007bff;
36+
color: white;
37+
font-size: 1.1rem;
38+
padding: 10px 20px;
39+
border-radius: 5px;
40+
}
41+
.btn:hover {
42+
background-color: #0056b3;
43+
border-color: #0056b3;
44+
}
45+
.analysis-section {
46+
text-align: center;
47+
margin-top: 50px;
48+
}
49+
.skill-list {
50+
list-style: none;
51+
padding: 0;
52+
text-align: left;
53+
}
54+
.skill-list li {
55+
padding: 10px 0;
56+
border-bottom: 1px solid #ddd;
57+
}
58+
.form-group .dynamic-skill {
59+
margin-top: 10px;
60+
}
61+
.btn-add-skill {
62+
background-color: #28a745;
63+
margin-left: 10px;
64+
}
65+
#manualSkillsInput input {
66+
margin-bottom: 10px;
67+
}
68+
.btn-proceed {
69+
margin-top: 20px;
70+
background-color: #28a745;
71+
}
72+
</style>
73+
</head>
74+
<body>
75+
<div class="container mt-5">
76+
<h1><i class="fas fa-sync-alt"></i> Skill Sync</h1>
77+
<form action="/" method="post">
78+
<div class="form-group">
79+
<label for="careerGoal"><i class="fas fa-bullseye"></i> Career Goal:</label>
80+
<input type="text" class="form-control" id="careerGoal" name="careerGoal" placeholder="Enter your career goal">
81+
</div>
82+
83+
<div class="form-check mb-3">
84+
<input type="checkbox" class="form-check-input" id="manualSkills" name="manualSkills" onchange="toggleSkillsInput()">
85+
<label class="form-check-label" for="manualSkills"><i class="fas fa-pencil-alt"></i> Manually add skills</label>
86+
</div>
87+
88+
<div id="manualSkillsInput" style="display:none;">
89+
<div class="form-group">
90+
<label for="skill">Skill:</label>
91+
<input type="text" class="form-control" id="skill" name="skill[]" placeholder="Enter a skill">
92+
<button type="button" class="btn btn-add-skill mt-2" onclick="addSkill()"><i class="fas fa-plus-circle"></i> Add Skill</button>
93+
</div>
94+
</div>
95+
96+
<div class="form-check mb-3">
97+
<input type="checkbox" class="form-check-input" id="linkedinSkills" name="linkedinSkills" onchange="toggleLinkedinInput()">
98+
<label class="form-check-label" for="linkedinSkills"><i class="fab fa-linkedin"></i> Import skills from LinkedIn</label>
99+
</div>
100+
101+
<div id="linkedinInput" style="display:none;">
102+
<div class="form-group">
103+
<label for="linkedinUsername"><i class="fab fa-linkedin"></i> LinkedIn Username:</label>
104+
<input type="text" class="form-control" id="linkedinUsername" name="linkedinUsername" placeholder="Enter your LinkedIn username">
105+
</div>
106+
</div>
107+
108+
<button type="submit" class="btn btn-primary btn-block mt-4">Submit</button>
109+
</form>
110+
111+
{% if profile_data %}
112+
<div class="analysis-section">
113+
{% if gemini_response %}
114+
<h2>Skill Gap Analysis</h2>
115+
<p>{{ gemini_response }}</p>
116+
{% endif %}
117+
{% if courses %}
118+
<h2>Recommended Courses</h2>
119+
<ul class="skill-list">
120+
{% for course in courses %}
121+
<li>{{ course }}</li>
122+
{% endfor %}
123+
</ul>
124+
<button class="btn btn-proceed btn-lg" onclick="gotoHome()">Proceed to EduConnect</button>
125+
{% endif %}
126+
</div>
127+
{% endif %}
128+
</div>
129+
130+
<!-- Bootstrap JS and jQuery -->
131+
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
132+
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
133+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
134+
135+
<script>
136+
function toggleSkillsInput() {
137+
var manualSkillsInput = document.getElementById("manualSkillsInput");
138+
if (document.getElementById("manualSkills").checked) {
139+
manualSkillsInput.style.display = "block";
140+
} else {
141+
manualSkillsInput.style.display = "none";
142+
}
143+
}
144+
145+
function toggleLinkedinInput() {
146+
var linkedinInput = document.getElementById("linkedinInput");
147+
if (document.getElementById("linkedinSkills").checked) {
148+
linkedinInput.style.display = "block";
149+
} else {
150+
linkedinInput.style.display = "none";
151+
}
152+
}
153+
154+
function addSkill() {
155+
var skillInput = document.createElement("input");
156+
skillInput.setAttribute("type", "text");
157+
skillInput.setAttribute("class", "form-control dynamic-skill");
158+
skillInput.setAttribute("name", "skill[]");
159+
160+
var skill = document.getElementById("skill").value.trim();
161+
if (skill !== "") {
162+
skillInput.value = skill;
163+
document.getElementById("skill").value = "";
164+
document.getElementById("manualSkillsInput").appendChild(skillInput);
165+
}
166+
}
167+
168+
function gotoHome(){
169+
window.location.href = "http://localhost:5000";
170+
}
171+
</script>
172+
</body>
173+
</html>

0 commit comments

Comments
 (0)