PlacementPredictor / routing_engine.py
ranilmukesh's picture
feat: update static file serving and paths for styles and scripts
dfe09a2
import pandas as pd
import networkx as nx
from collections import Counter
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import io
import base64
class RoutingEngine:
def __init__(self, data_path):
self.df = pd.read_csv(data_path)
self.df.columns = self.df.columns.str.strip()
self.G = nx.Graph()
self.all_unique_skills = set()
self.all_jobs = set()
self._build_graph()
def _build_graph(self):
self.df['All_Skills'] = (
self.df['Skills'].fillna('') + "," +
self.df.get('Programming Languages', pd.Series([''] * len(self.df))) + "," +
self.df['Tools'].fillna('')
)
# Split skills and explode
df_exploded = self.df.assign(All_Skills=self.df['All_Skills'].str.split(',')).explode('All_Skills')
df_exploded['All_Skills'] = df_exploded['All_Skills'].str.strip()
# Remove empty strings
df_exploded = df_exploded[df_exploded['All_Skills'] != '']
for _, row in df_exploded.iterrows():
job = row['Job roles']
skill = row['All_Skills']
if isinstance(skill, str) and len(skill) > 1:
self.G.add_edge(job, skill)
self.all_unique_skills.add(skill)
self.all_jobs.add(job)
def get_skill_list(self):
return sorted([str(s) for s in self.all_unique_skills if isinstance(s, str)])
def get_job_list(self):
return sorted(list(self.all_jobs))
def get_gap(self, target_job, user_skills):
if target_job not in self.G.nodes:
return []
required_skills = set(self.G.neighbors(target_job))
current_skills = set(user_skills)
missing_skills = list(required_skills - current_skills)
return missing_skills
def recommend(self, user_skills):
possible_jobs = []
for skill in user_skills:
if skill in self.G.nodes:
neighbors = list(self.G.neighbors(skill))
possible_jobs.extend(neighbors)
if not possible_jobs:
return None, []
top_jobs_counter = Counter(possible_jobs).most_common(1)
best_job = top_jobs_counter[0][0]
missing_skills = self.get_gap(best_job, user_skills)
return best_job, missing_skills
def get_career_transition_path(self, current_job, target_job):
"""Finds the shortest upskilling path between two roles via shared skills."""
if current_job not in self.G.nodes or target_job not in self.G.nodes:
return None
try:
# NetworkX finds the shortest path alternating: Job -> Skill -> Job
path = nx.shortest_path(self.G, source=current_job, target=target_job)
skills_to_learn = [node for node in path if node in self.all_unique_skills]
stepping_stones = [node for node in path if node in self.all_jobs and node not in (current_job, target_job)]
return {
"path": path,
"skills_to_learn": skills_to_learn,
"stepping_stones": stepping_stones
}
except Exception:
return None
def get_subgraph_figure_base64(self, center_node, user_skills, depth=1):
try:
if center_node not in self.G.nodes: return None
skills = list(self.G.neighbors(center_node))
subG = self.G.subgraph([center_node] + skills)
plt.figure(figsize=(8, 6), facecolor='#0f172a')
pos = nx.spring_layout(subG, seed=42, k=0.5)
nx.draw_networkx_edges(subG, pos, edge_color='#334155', alpha=0.5)
node_colors = []
for n in subG.nodes():
if n == center_node:
node_colors.append('#ffffff')
elif n in user_skills:
node_colors.append('#047857')
else:
node_colors.append('#dc2626')
nx.draw_networkx_nodes(subG, pos, node_color=node_colors, node_size=600, alpha=0.9)
labels = {n: n for n in subG.nodes()}
nx.draw_networkx_labels(subG, pos, labels=labels, font_size=8, font_color='white', font_family='sans-serif')
plt.title(f"Skill Gap Map: {center_node}", color='white')
plt.axis('off')
buf = io.BytesIO()
plt.savefig(buf, format='png', bbox_inches='tight', facecolor='#0f172a')
plt.close()
buf.seek(0)
img_base64 = base64.b64encode(buf.read()).decode('utf-8')
return f"data:image/png;base64,{img_base64}"
except Exception as e:
print(f"Graph Error: {e}")
return None