-
Notifications
You must be signed in to change notification settings - Fork 305
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SkillSync - Skill Gap Analysis using Gemini LLM along with Linkedin A…
…PI for Career Growth and Course Recommendation
- Loading branch information
1 parent
4a0ffb1
commit 8b95cfd
Showing
4 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
# **Skill Sync** | ||
|
||
### 🎯 **Goal** | ||
|
||
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. | ||
|
||
### 🧵 **Dataset** | ||
|
||
**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. | ||
|
||
### 🧾 **Description** | ||
|
||
**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. | ||
|
||
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. | ||
|
||
### 🧮 **What I Had Done!** | ||
|
||
- Implemented a **manual skill addition feature** where users can input their skills. | ||
- Integrated **LinkedIn API** to allow users to import their skills automatically. | ||
- Developed a **career goal matching system**, leveraging **AI** for skill gap analysis. | ||
- Suggested **relevant courses** based on skill gaps identified during analysis. | ||
|
||
### 🚀 **Models Implemented** | ||
|
||
- **Generative AI**: This model generates career insights and a comprehensive skill gap analysis based on the user’s inputs. | ||
- **Gemini AI**: Used to map and identify skill deficiencies and recommend the best learning paths. | ||
|
||
### 📚 **Libraries Needed** | ||
|
||
- Flask | ||
- requests | ||
- google-generativeai | ||
- pandas | ||
- dotenv | ||
|
||
### 📊 **Exploratory Data Analysis Results** | ||
|
||
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. | ||
|
||
### 📈 **Performance Metrics** | ||
|
||
The performance of the system is assessed through: | ||
- **Accuracy of skill analysis**: How accurately the system identifies missing skills. | ||
- **Relevance of recommended courses**: How relevant the suggested courses are to the user's career goals. | ||
- **User satisfaction**: Feedback collected on how effective the tool is in helping users bridge their skill gaps. | ||
|
||
### 💻 How to Run | ||
|
||
To get started with **Skill Sync**, follow these steps: | ||
|
||
1. Navigate to the project directory: | ||
|
||
```bash | ||
cd SkillSync | ||
``` | ||
|
||
2. (Optional) Activate a virtual environment: | ||
|
||
```bash | ||
conda create -n venv python=3.10+ | ||
conda activate venv | ||
``` | ||
|
||
3. Install dependencies: | ||
|
||
```bash | ||
pip install -r requirements.txt | ||
``` | ||
|
||
4. Configure environment variables: | ||
|
||
``` | ||
Rename `.env-sample` to `.env`. | ||
Replace with your LinkedIn and Google API Keys. | ||
``` | ||
|
||
Kindly refer to these links for getting your own API keys: | ||
- [LinkedIn API](https://developer.linkedin.com/) or [Proxy Curl API](https://nubela.co/proxycurl/linkedin) | ||
- [Google Generative AI Key](https://ai.google.dev/tutorials/setup) | ||
|
||
5. Run the application: | ||
|
||
```bash | ||
streamlit run app.py | ||
``` | ||
|
||
### 📢 **Conclusion** | ||
|
||
**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. | ||
|
||
### ✒️ **Signature** | ||
|
||
**[J B Mugundh]** | ||
GitHub: [Github](https://github.com/J-B-Mugundh) | ||
LinkedIn: [LinkedIn](https://www.linkedin.com/in/mugundhjb/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
from flask import Flask, request, render_template | ||
import google.generativeai as genai | ||
import os | ||
import json | ||
import requests | ||
from dotenv import load_dotenv | ||
|
||
# Load environment variables from .env file | ||
load_dotenv() | ||
|
||
app = Flask(__name__) | ||
|
||
# Load API keys from environment variables | ||
PROXYCURL_API_KEY = os.getenv('PROXYCURL_API_KEY') | ||
PROXYCURL_API_ENDPOINT = 'https://nubela.co/proxycurl/api/v2/linkedin' | ||
|
||
def get_gemini_response(career_goal, skills): | ||
"""Fetches response from Gemini API based on career goal and skills.""" | ||
|
||
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." | ||
|
||
model = genai.GenerativeModel('gemini-pro') | ||
api_key = os.getenv("GOOGLE_API_KEY") # Retrieve Google API key from .env | ||
genai.configure(api_key=api_key) | ||
|
||
try: | ||
response = model.generate_content(query) | ||
return response.text | ||
except Exception as e: | ||
print(f"Error occurred during Gemini API call: {e}") | ||
return "An error occurred while fetching data from Gemini. Please try again later." | ||
|
||
def get_linkedin_profile(linkedin_url): | ||
"""Fetches the LinkedIn profile using Proxycurl API.""" | ||
|
||
headers = { | ||
'Authorization': f'Bearer {PROXYCURL_API_KEY}', | ||
} | ||
|
||
params = { | ||
'linkedin_profile_url': linkedin_url, | ||
'extra': 'include', | ||
'skills': 'include', | ||
'use_cache': 'if-present', | ||
'fallback_to_cache': 'on-error', | ||
} | ||
|
||
try: | ||
response = requests.get(PROXYCURL_API_ENDPOINT, params=params, headers=headers) | ||
if response.status_code == 200: | ||
return response.json() # Return profile data as a dictionary | ||
else: | ||
print(f"Error fetching LinkedIn profile: {response.status_code}") | ||
return {"error": f"Error fetching profile, status code: {response.status_code}"} | ||
|
||
except Exception as e: | ||
print(f"Error occurred during LinkedIn API proxy call: {e}") | ||
return {"error": "An error occurred while fetching data from LinkedIn. Please try again later."} | ||
|
||
@app.route('/', methods=['GET', 'POST']) | ||
def index(): | ||
if request.method == 'POST': | ||
career_goal = request.form['careerGoal'] | ||
manual_skills = request.form.getlist('skill[]') | ||
linkedin_url = request.form.get('linkedinProfileUrl') # Changed to get LinkedIn profile URL | ||
profile_data = None | ||
|
||
if manual_skills or linkedin_url: | ||
if linkedin_url: | ||
profile_data = get_linkedin_profile(linkedin_url) | ||
if 'skills' in profile_data: | ||
skills_data = {"skills": profile_data['skills']} | ||
else: | ||
skills_data = {"skills": []} | ||
else: | ||
skills_data = {"skills": manual_skills} | ||
|
||
with open('skills.json', 'w') as json_file: | ||
json.dump(skills_data, json_file) | ||
|
||
if linkedin_url: | ||
profile_data = get_linkedin_profile(linkedin_url) | ||
elif manual_skills: | ||
profile_data = manual_skills | ||
|
||
if profile_data: | ||
if 'error' in profile_data: | ||
return render_template('index.html', error=profile_data['error']) | ||
gemini_response = get_gemini_response(career_goal, profile_data['skills'] if 'skills' in profile_data else manual_skills) | ||
return render_template('index.html', profile_data=profile_data, gemini_response=gemini_response) | ||
else: | ||
return render_template('index.html', error="Please enter your career goal and skills.") | ||
|
||
return render_template('index.html') | ||
|
||
if __name__ == '__main__': | ||
app.run(debug=True, use_reloader=False) |
5 changes: 5 additions & 0 deletions
5
Algorithms and Deep Learning Models/SkillSync/requirements.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Flask | ||
requests | ||
google-generativeai | ||
pandas | ||
dotenv |
173 changes: 173 additions & 0 deletions
173
Algorithms and Deep Learning Models/SkillSync/templates/index.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Skill Sync</title> | ||
<!-- Bootstrap CSS --> | ||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> | ||
<!-- FontAwesome Icons for a more polished UI --> | ||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> | ||
<style> | ||
body { | ||
background-color: #f8f9fa; | ||
} | ||
h1 { | ||
font-size: 2.5rem; | ||
text-align: center; | ||
margin-bottom: 40px; | ||
color: #007bff; | ||
} | ||
form { | ||
background-color: #fff; | ||
padding: 30px; | ||
border-radius: 8px; | ||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); | ||
} | ||
.form-group label { | ||
font-weight: bold; | ||
} | ||
.form-check-label { | ||
font-size: 1rem; | ||
} | ||
.btn { | ||
background-color: #007bff; | ||
border-color: #007bff; | ||
color: white; | ||
font-size: 1.1rem; | ||
padding: 10px 20px; | ||
border-radius: 5px; | ||
} | ||
.btn:hover { | ||
background-color: #0056b3; | ||
border-color: #0056b3; | ||
} | ||
.analysis-section { | ||
text-align: center; | ||
margin-top: 50px; | ||
} | ||
.skill-list { | ||
list-style: none; | ||
padding: 0; | ||
text-align: left; | ||
} | ||
.skill-list li { | ||
padding: 10px 0; | ||
border-bottom: 1px solid #ddd; | ||
} | ||
.form-group .dynamic-skill { | ||
margin-top: 10px; | ||
} | ||
.btn-add-skill { | ||
background-color: #28a745; | ||
margin-left: 10px; | ||
} | ||
#manualSkillsInput input { | ||
margin-bottom: 10px; | ||
} | ||
.btn-proceed { | ||
margin-top: 20px; | ||
background-color: #28a745; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div class="container mt-5"> | ||
<h1><i class="fas fa-sync-alt"></i> Skill Sync</h1> | ||
<form action="/" method="post"> | ||
<div class="form-group"> | ||
<label for="careerGoal"><i class="fas fa-bullseye"></i> Career Goal:</label> | ||
<input type="text" class="form-control" id="careerGoal" name="careerGoal" placeholder="Enter your career goal"> | ||
</div> | ||
|
||
<div class="form-check mb-3"> | ||
<input type="checkbox" class="form-check-input" id="manualSkills" name="manualSkills" onchange="toggleSkillsInput()"> | ||
<label class="form-check-label" for="manualSkills"><i class="fas fa-pencil-alt"></i> Manually add skills</label> | ||
</div> | ||
|
||
<div id="manualSkillsInput" style="display:none;"> | ||
<div class="form-group"> | ||
<label for="skill">Skill:</label> | ||
<input type="text" class="form-control" id="skill" name="skill[]" placeholder="Enter a skill"> | ||
<button type="button" class="btn btn-add-skill mt-2" onclick="addSkill()"><i class="fas fa-plus-circle"></i> Add Skill</button> | ||
</div> | ||
</div> | ||
|
||
<div class="form-check mb-3"> | ||
<input type="checkbox" class="form-check-input" id="linkedinSkills" name="linkedinSkills" onchange="toggleLinkedinInput()"> | ||
<label class="form-check-label" for="linkedinSkills"><i class="fab fa-linkedin"></i> Import skills from LinkedIn</label> | ||
</div> | ||
|
||
<div id="linkedinInput" style="display:none;"> | ||
<div class="form-group"> | ||
<label for="linkedinUsername"><i class="fab fa-linkedin"></i> LinkedIn Username:</label> | ||
<input type="text" class="form-control" id="linkedinUsername" name="linkedinUsername" placeholder="Enter your LinkedIn username"> | ||
</div> | ||
</div> | ||
|
||
<button type="submit" class="btn btn-primary btn-block mt-4">Submit</button> | ||
</form> | ||
|
||
{% if profile_data %} | ||
<div class="analysis-section"> | ||
{% if gemini_response %} | ||
<h2>Skill Gap Analysis</h2> | ||
<p>{{ gemini_response }}</p> | ||
{% endif %} | ||
{% if courses %} | ||
<h2>Recommended Courses</h2> | ||
<ul class="skill-list"> | ||
{% for course in courses %} | ||
<li>{{ course }}</li> | ||
{% endfor %} | ||
</ul> | ||
<button class="btn btn-proceed btn-lg" onclick="gotoHome()">Proceed to EduConnect</button> | ||
{% endif %} | ||
</div> | ||
{% endif %} | ||
</div> | ||
|
||
<!-- Bootstrap JS and jQuery --> | ||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script> | ||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> | ||
|
||
<script> | ||
function toggleSkillsInput() { | ||
var manualSkillsInput = document.getElementById("manualSkillsInput"); | ||
if (document.getElementById("manualSkills").checked) { | ||
manualSkillsInput.style.display = "block"; | ||
} else { | ||
manualSkillsInput.style.display = "none"; | ||
} | ||
} | ||
|
||
function toggleLinkedinInput() { | ||
var linkedinInput = document.getElementById("linkedinInput"); | ||
if (document.getElementById("linkedinSkills").checked) { | ||
linkedinInput.style.display = "block"; | ||
} else { | ||
linkedinInput.style.display = "none"; | ||
} | ||
} | ||
|
||
function addSkill() { | ||
var skillInput = document.createElement("input"); | ||
skillInput.setAttribute("type", "text"); | ||
skillInput.setAttribute("class", "form-control dynamic-skill"); | ||
skillInput.setAttribute("name", "skill[]"); | ||
|
||
var skill = document.getElementById("skill").value.trim(); | ||
if (skill !== "") { | ||
skillInput.value = skill; | ||
document.getElementById("skill").value = ""; | ||
document.getElementById("manualSkillsInput").appendChild(skillInput); | ||
} | ||
} | ||
|
||
function gotoHome(){ | ||
window.location.href = "http://localhost:5000"; | ||
} | ||
</script> | ||
</body> | ||
</html> |