Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,177 +1,288 @@
|
|
1 |
-
from fastapi import FastAPI, HTTPException
|
2 |
-
from pydantic import BaseModel
|
3 |
-
from typing import Optional, Dict, List
|
4 |
import pandas as pd
|
5 |
-
|
6 |
-
from
|
7 |
-
|
8 |
-
import
|
9 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
-
#
|
12 |
-
|
13 |
-
os.
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
else:
|
79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
|
88 |
-
|
89 |
-
|
90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
return []
|
92 |
-
job_df["job_embeddings"] = job_df["job_description"].apply(lambda x: bert_model.encode(str(x)))
|
93 |
-
user_embedding = bert_model.encode(" ".join(skills))
|
94 |
-
job_df["BERT_Similarity"] = job_df["job_embeddings"].apply(lambda x: cosine_similarity([x], [user_embedding])[0][0])
|
95 |
-
top_jobs = job_df.sort_values(by="BERT_Similarity", ascending=False).head(5)
|
96 |
-
return top_jobs[["job_title", "company", "location", "BERT_Similarity"]].to_dict(orient="records")
|
97 |
-
|
98 |
-
@app.get("/")
|
99 |
-
def read_root():
|
100 |
-
return {"message": "Skill Assessment API"}
|
101 |
-
|
102 |
-
# POST endpoint for fetching challenges
|
103 |
-
@app.post("/challenges")
|
104 |
-
def get_user_challenges(request: ChallengeRequest):
|
105 |
-
skills = request.skills
|
106 |
-
difficulty = request.difficulty
|
107 |
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
{"question": challenge["question"], "difficulty": challenge["difficulty"]}
|
118 |
-
for challenge in challenge_list
|
119 |
-
]
|
120 |
-
for category, challenge_list in challenges.items()
|
121 |
-
}
|
122 |
-
}
|
123 |
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
user_name = user_input.name
|
128 |
-
user_skills = user_input.skills
|
129 |
|
130 |
-
|
131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
|
133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
user_scores = {}
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
if user_input.answers and skill in user_input.answers:
|
145 |
-
for challenge in challenge_list:
|
146 |
-
question = challenge["question"]
|
147 |
-
if question in user_input.answers[skill]:
|
148 |
-
start_time = time.time() - 10 # Simulate execution time
|
149 |
-
user_code = user_input.answers[skill][question]
|
150 |
-
correct_code = challenge["solutions"]
|
151 |
-
score = evaluate_coding_with_time(user_code, correct_code, start_time)
|
152 |
-
total_score += score
|
153 |
-
else:
|
154 |
-
total_score += 0
|
155 |
else:
|
156 |
-
|
157 |
-
|
158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
|
160 |
-
|
161 |
-
|
|
|
|
|
162 |
|
163 |
-
|
164 |
-
|
165 |
|
166 |
-
|
167 |
-
"
|
168 |
-
"
|
169 |
-
"
|
170 |
-
"
|
171 |
-
"recommended_courses": courses,
|
172 |
-
"recommended_jobs": jobs
|
173 |
}
|
|
|
174 |
|
175 |
-
if __name__ ==
|
176 |
-
|
177 |
-
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
|
|
|
|
|
1 |
import pandas as pd
|
2 |
+
import torch
|
3 |
+
from sentence_transformers import SentenceTransformer, util
|
4 |
+
import faiss
|
5 |
+
import numpy as np
|
6 |
import os
|
7 |
+
import pickle
|
8 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
9 |
+
import scipy.special
|
10 |
+
from tqdm import tqdm
|
11 |
+
from tabulate import tabulate
|
12 |
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
13 |
+
from multiprocessing import Pool, cpu_count
|
14 |
+
from flask import Flask, request, jsonify
|
15 |
|
16 |
+
# Paths for saving artifacts
|
17 |
+
MODEL_DIR = "./saved_models"
|
18 |
+
UNIVERSAL_MODEL_PATH = os.path.join(MODEL_DIR, "universal_model")
|
19 |
+
DETECTOR_MODEL_PATH = os.path.join(MODEL_DIR, "detector_model")
|
20 |
+
TFIDF_PATH = os.path.join(MODEL_DIR, "tfidf_vectorizer.pkl")
|
21 |
+
SKILL_TFIDF_PATH = os.path.join(MODEL_DIR, "skill_tfidf.pkl")
|
22 |
+
QUESTION_ANSWER_PATH = os.path.join(MODEL_DIR, "question_to_answer.pkl")
|
23 |
+
FAISS_INDEX_PATH = os.path.join(MODEL_DIR, "faiss_index.index")
|
24 |
+
|
25 |
+
os.makedirs(MODEL_DIR, exist_ok=True)
|
26 |
+
|
27 |
+
# Load Datasets
|
28 |
+
def load_dataset(file_path, required_columns=[]):
|
29 |
+
try:
|
30 |
+
df = pd.read_csv(file_path)
|
31 |
+
for col in required_columns:
|
32 |
+
if col not in df.columns:
|
33 |
+
print(f"⚠ Warning: Column '{col}' missing in {file_path}. Using default values.")
|
34 |
+
df[col] = "" if col != 'level' else 'Intermediate'
|
35 |
+
return df
|
36 |
+
except FileNotFoundError:
|
37 |
+
print(f"❌ Error: Dataset not found at {file_path}. Exiting.")
|
38 |
+
return None
|
39 |
+
|
40 |
+
user_df = load_dataset("Updated_User_Profile_Dataset.csv", ["name", "skills", "level"])
|
41 |
+
questions_df = load_dataset("Generated_Skill-Based_Questions.csv", ["Skill", "Question", "Answer"])
|
42 |
+
courses_df = load_dataset("coursera_course_dataset_v2_no_null.csv", ["skills", "course_title", "Organization", "level"])
|
43 |
+
jobs_df = load_dataset("Updated_Job_Posting_Dataset.csv", ["job_title", "company_name", "location", "required_skills", "job_description"])
|
44 |
+
|
45 |
+
# Simulate courses_df with relevant skills
|
46 |
+
if courses_df is None or 'skills' not in courses_df.columns or courses_df['skills'].str.strip().eq('').all():
|
47 |
+
courses_df = pd.DataFrame({
|
48 |
+
'skills': ['Docker', 'Jenkins', 'Azure', 'Cybersecurity'],
|
49 |
+
'course_title': ['Docker Mastery', 'Jenkins CI/CD', 'Azure Fundamentals', 'Cybersecurity Basics'],
|
50 |
+
'Organization': ['Udemy', 'Coursera', 'Microsoft', 'edX'],
|
51 |
+
'level': ['Intermediate', 'Intermediate', 'Intermediate', 'Advanced'],
|
52 |
+
'popularity': [0.9, 0.85, 0.95, 0.8],
|
53 |
+
'completion_rate': [0.7, 0.65, 0.8, 0.6]
|
54 |
+
})
|
55 |
+
|
56 |
+
# Load or Initialize Models
|
57 |
+
if os.path.exists(UNIVERSAL_MODEL_PATH):
|
58 |
+
universal_model = SentenceTransformer(UNIVERSAL_MODEL_PATH)
|
59 |
+
else:
|
60 |
+
universal_model = SentenceTransformer("all-MiniLM-L6-v2")
|
61 |
+
|
62 |
+
if os.path.exists(DETECTOR_MODEL_PATH):
|
63 |
+
detector_tokenizer = AutoTokenizer.from_pretrained(DETECTOR_MODEL_PATH)
|
64 |
+
detector_model = AutoModelForSequenceClassification.from_pretrained(DETECTOR_MODEL_PATH)
|
65 |
+
else:
|
66 |
+
detector_tokenizer = AutoTokenizer.from_pretrained("roberta-base-openai-detector")
|
67 |
+
detector_model = AutoModelForSequenceClassification.from_pretrained("roberta-base-openai-detector")
|
68 |
+
|
69 |
+
# Precompute Resources with Validation
|
70 |
+
def resources_valid(saved_skills, current_skills):
|
71 |
+
return set(saved_skills) == set(current_skills)
|
72 |
+
|
73 |
+
def initialize_resources(user_skills):
|
74 |
+
global tfidf_vectorizer, skill_tfidf, question_to_answer, faiss_index, answer_embeddings
|
75 |
+
if (os.path.exists(TFIDF_PATH) and os.path.exists(SKILL_TFIDF_PATH) and
|
76 |
+
os.path.exists(QUESTION_ANSWER_PATH) and os.path.exists(FAISS_INDEX_PATH)):
|
77 |
+
with open(TFIDF_PATH, 'rb') as f:
|
78 |
+
tfidf_vectorizer = pickle.load(f)
|
79 |
+
with open(SKILL_TFIDF_PATH, 'rb') as f:
|
80 |
+
skill_tfidf = pickle.load(f)
|
81 |
+
with open(QUESTION_ANSWER_PATH, 'rb') as f:
|
82 |
+
question_to_answer = pickle.load(f)
|
83 |
+
faiss_index = faiss.read_index(FAISS_INDEX_PATH)
|
84 |
+
answer_embeddings = universal_model.encode(list(question_to_answer.values()), convert_to_tensor=True, show_progress_bar=False).cpu().numpy()
|
85 |
+
|
86 |
+
if not resources_valid(skill_tfidf.keys(), [s.lower() for s in user_skills]):
|
87 |
+
print("⚠ Saved skill TF-IDF mismatch detected. Recomputing resources.")
|
88 |
+
tfidf_vectorizer = TfidfVectorizer(stop_words='english')
|
89 |
+
all_texts = user_skills + questions_df['Answer'].fillna("").tolist() + questions_df['Question'].tolist()
|
90 |
+
tfidf_vectorizer.fit(all_texts)
|
91 |
+
skill_tfidf = {skill.lower(): tfidf_vectorizer.transform([skill.lower()]).toarray()[0] for skill in user_skills}
|
92 |
+
question_to_answer = dict(zip(questions_df['Question'], questions_df['Answer']))
|
93 |
+
answer_embeddings = universal_model.encode(list(question_to_answer.values()), convert_to_tensor=True, show_progress_bar=False).cpu().numpy()
|
94 |
+
faiss_index = faiss.IndexFlatL2(answer_embeddings.shape[1])
|
95 |
+
faiss_index.add(answer_embeddings)
|
96 |
else:
|
97 |
+
tfidf_vectorizer = TfidfVectorizer(stop_words='english')
|
98 |
+
all_texts = user_skills + questions_df['Answer'].fillna("").tolist() + questions_df['Question'].tolist()
|
99 |
+
tfidf_vectorizer.fit(all_texts)
|
100 |
+
skill_tfidf = {skill.lower(): tfidf_vectorizer.transform([skill.lower()]).toarray()[0] for skill in user_skills}
|
101 |
+
question_to_answer = dict(zip(questions_df['Question'], questions_df['Answer']))
|
102 |
+
answer_embeddings = universal_model.encode(list(question_to_answer.values()), convert_to_tensor=True, show_progress_bar=False).cpu().numpy()
|
103 |
+
faiss_index = faiss.IndexFlatL2(answer_embeddings.shape[1])
|
104 |
+
faiss_index.add(answer_embeddings)
|
105 |
|
106 |
+
with open(TFIDF_PATH, 'wb') as f:
|
107 |
+
pickle.dump(tfidf_vectorizer, f)
|
108 |
+
with open(SKILL_TFIDF_PATH, 'wb') as f:
|
109 |
+
pickle.dump(skill_tfidf, f)
|
110 |
+
with open(QUESTION_ANSWER_PATH, 'wb') as f:
|
111 |
+
pickle.dump(question_to_answer, f)
|
112 |
+
faiss.write_index(faiss_index, FAISS_INDEX_PATH)
|
113 |
+
universal_model.save_pretrained(UNIVERSAL_MODEL_PATH)
|
114 |
+
detector_model.save_pretrained(DETECTOR_MODEL_PATH)
|
115 |
+
detector_tokenizer.save_pretrained(DETECTOR_MODEL_PATH)
|
116 |
+
print(f"Models and resources saved to {MODEL_DIR}")
|
117 |
+
|
118 |
+
# Evaluate Responses
|
119 |
+
def evaluate_response(args):
|
120 |
+
skill, user_answer, question = args
|
121 |
+
if not user_answer:
|
122 |
+
return skill, 0, False
|
123 |
+
|
124 |
+
inputs = detector_tokenizer(user_answer, return_tensors="pt", truncation=True, max_length=512)
|
125 |
+
with torch.no_grad():
|
126 |
+
logits = detector_model(**inputs).logits
|
127 |
+
probs = scipy.special.softmax(logits, axis=1).tolist()[0]
|
128 |
+
is_ai_generated = probs[1] > 0.5
|
129 |
|
130 |
+
user_embedding = universal_model.encode(user_answer, convert_to_tensor=True)
|
131 |
+
expected_answer = question_to_answer.get(question, "")
|
132 |
+
expected_embedding = universal_model.encode(expected_answer, convert_to_tensor=True)
|
133 |
+
score = util.pytorch_cos_sim(user_embedding, expected_embedding).item() * 100
|
134 |
+
|
135 |
+
user_tfidf = tfidf_vectorizer.transform([user_answer]).toarray()[0]
|
136 |
+
skill_lower = skill.lower()
|
137 |
+
skill_vec = skill_tfidf.get(skill_lower, tfidf_vectorizer.transform([skill_lower]).toarray()[0])
|
138 |
+
skill_relevance = np.dot(user_tfidf, skill_vec) / (np.linalg.norm(user_tfidf) * np.linalg.norm(skill_vec) + 1e-10)
|
139 |
+
penalty = min(1.0, max(0.5, skill_relevance))
|
140 |
+
score *= penalty
|
141 |
+
|
142 |
+
return skill, round(max(0, score), 2), is_ai_generated
|
143 |
+
|
144 |
+
# Recommend Courses
|
145 |
+
def recommend_courses(skills_to_improve, user_level, upgrade=False):
|
146 |
+
if not skills_to_improve:
|
147 |
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
|
149 |
+
skill_embeddings = universal_model.encode(skills_to_improve, convert_to_tensor=True)
|
150 |
+
course_embeddings = universal_model.encode(courses_df['skills'].fillna(""), convert_to_tensor=True)
|
151 |
+
bert_similarities = util.pytorch_cos_sim(skill_embeddings, course_embeddings).numpy()
|
152 |
+
|
153 |
+
collab_scores = []
|
154 |
+
for skill in skills_to_improve:
|
155 |
+
overlap = sum(1 for user_skills_str in user_df['skills'] if pd.notna(user_skills_str) and skill.lower() in user_skills_str.lower())
|
156 |
+
collab_scores.append(overlap / len(user_df))
|
157 |
+
collab_similarities = np.array([collab_scores]).repeat(len(courses_df), axis=0).T
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
|
159 |
+
popularity = courses_df['popularity'].fillna(0.5).to_numpy()
|
160 |
+
completion = courses_df['completion_rate'].fillna(0.5).to_numpy()
|
161 |
+
total_scores = (0.6 * bert_similarities + 0.2 * collab_similarities + 0.1 * popularity + 0.1 * completion)
|
|
|
|
|
162 |
|
163 |
+
recommended_courses = []
|
164 |
+
target_level = 'Advanced' if upgrade else user_level
|
165 |
+
for i, skill in enumerate(skills_to_improve):
|
166 |
+
top_indices = total_scores[i].argsort()[-5:][::-1]
|
167 |
+
candidates = courses_df.iloc[top_indices]
|
168 |
+
candidates = candidates[candidates['skills'].str.lower() == skill.lower()]
|
169 |
+
if candidates.empty:
|
170 |
+
candidates = courses_df.iloc[top_indices]
|
171 |
+
candidates.loc[:, "level_match"] = candidates['level'].apply(lambda x: 1 if x == target_level else 0.8 if abs({'Beginner': 0, 'Intermediate': 1, 'Advanced': 2}[x] - {'Beginner': 0, 'Intermediate': 1, 'Advanced': 2}[user_level]) <= 1 else 0.5)
|
172 |
+
level_filtered = candidates.sort_values(by="level_match", ascending=False)
|
173 |
+
recommended_courses.extend(level_filtered[['course_title', 'Organization']].values.tolist()[:3])
|
174 |
+
return list(dict.fromkeys(tuple(course) for course in recommended_courses if course[0].strip()))
|
175 |
|
176 |
+
# Recommend Jobs
|
177 |
+
def recommend_jobs(user_skills, user_level):
|
178 |
+
job_field = 'required_skills' if 'required_skills' in jobs_df.columns and not jobs_df['required_skills'].str.strip().eq('').all() else 'job_description'
|
179 |
+
job_embeddings = universal_model.encode(jobs_df[job_field].fillna(""), convert_to_tensor=True)
|
180 |
+
user_embedding = universal_model.encode(" ".join(user_skills), convert_to_tensor=True)
|
181 |
+
skill_similarities = util.pytorch_cos_sim(user_embedding, job_embeddings).numpy()[0]
|
182 |
+
|
183 |
+
level_map = {'Beginner': 0, 'Intermediate': 1, 'Advanced': 2}
|
184 |
+
user_level_num = level_map[user_level]
|
185 |
+
exp_match = jobs_df['level'].fillna('Intermediate').apply(lambda x: 1 - abs(level_map.get(x, 1) - user_level_num) / 2) if 'level' in jobs_df.columns else np.ones(len(jobs_df)) * 0.5
|
186 |
+
location_pref = jobs_df['location'].apply(lambda x: 1.0 if x in ['Islamabad', 'Karachi'] else 0.7).to_numpy()
|
187 |
+
industry_embeddings = universal_model.encode(jobs_df['job_title'].fillna(""), convert_to_tensor=True)
|
188 |
+
industry_similarities = util.pytorch_cos_sim(user_embedding, industry_embeddings).numpy()[0]
|
189 |
+
|
190 |
+
total_job_scores = (0.5 * skill_similarities + 0.2 * exp_match + 0.1 * location_pref + 0.2 * industry_similarities)
|
191 |
+
top_job_indices = total_job_scores.argsort()[-5:][::-1]
|
192 |
+
return [(jobs_df.iloc[idx]['job_title'], jobs_df.iloc[idx]['company_name'], jobs_df.iloc[idx]['location']) for idx in top_job_indices]
|
193 |
+
|
194 |
+
# Main API Endpoint
|
195 |
+
app = Flask(__name__)
|
196 |
+
|
197 |
+
@app.route('/assess', methods=['POST'])
|
198 |
+
def assess_skills():
|
199 |
+
data = request.get_json()
|
200 |
+
if not data or 'user_index' not in data or 'answers' not in data:
|
201 |
+
return jsonify({"error": "Invalid input. Provide 'user_index' and 'answers' in JSON body."}), 400
|
202 |
+
|
203 |
+
user_index = int(data['user_index'])
|
204 |
+
if user_index < 0 or user_index >= len(user_df):
|
205 |
+
return jsonify({"error": "Invalid user index."}), 400
|
206 |
+
|
207 |
+
user_text = user_df.loc[user_index, 'skills']
|
208 |
+
user_skills = [skill.strip() for skill in user_text.split(",") if skill.strip()] if isinstance(user_text, str) else ["Python", "SQL"]
|
209 |
+
user_name = user_df.loc[user_index, 'name']
|
210 |
+
user_level = user_df.loc[user_index, 'level'] if 'level' in user_df.columns and pd.notna(user_df.loc[user_index, 'level']) else 'Intermediate'
|
211 |
+
|
212 |
+
initialize_resources(user_skills)
|
213 |
+
|
214 |
+
filtered_questions = questions_df[questions_df['Skill'].isin(user_skills)]
|
215 |
+
if filtered_questions.empty:
|
216 |
+
return jsonify({"error": "No matching questions found!"}), 500
|
217 |
|
218 |
+
user_questions = []
|
219 |
+
for skill in user_skills:
|
220 |
+
skill_questions = filtered_questions[filtered_questions['Skill'] == skill]
|
221 |
+
if not skill_questions.empty:
|
222 |
+
user_questions.append(skill_questions.sample(1).iloc[0])
|
223 |
+
user_questions = pd.DataFrame(user_questions)
|
224 |
+
|
225 |
+
if len(user_questions) != 4:
|
226 |
+
return jsonify({"error": "Not enough questions for all skills!"}), 500
|
227 |
+
|
228 |
+
answers = data['answers']
|
229 |
+
if len(answers) != 4:
|
230 |
+
return jsonify({"error": "Please provide exactly 4 answers."}), 400
|
231 |
+
|
232 |
+
user_responses = []
|
233 |
+
for idx, row in user_questions.iterrows():
|
234 |
+
answer = answers[idx]
|
235 |
+
if not answer or answer.lower() == 'skip':
|
236 |
+
user_responses.append((row['Skill'], None, row['Question']))
|
237 |
+
else:
|
238 |
+
user_responses.append((row['Skill'], answer, row['Question']))
|
239 |
+
|
240 |
+
with Pool(cpu_count()) as pool:
|
241 |
+
eval_args = [(skill, user_code, question) for skill, user_code, question in user_responses if user_code]
|
242 |
+
results = pool.map(evaluate_response, eval_args)
|
243 |
+
|
244 |
user_scores = {}
|
245 |
+
ai_flags = {}
|
246 |
+
scores_list = []
|
247 |
+
skipped_questions = [f"{skill} ({question})" for skill, user_code, question in user_responses if user_code is None]
|
248 |
+
for skill, score, is_ai in results:
|
249 |
+
if skill in user_scores:
|
250 |
+
user_scores[skill] = max(user_scores[skill], score)
|
251 |
+
ai_flags[skill] = ai_flags[skill] or is_ai
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
252 |
else:
|
253 |
+
user_scores[skill] = score
|
254 |
+
ai_flags[skill] = is_ai
|
255 |
+
scores_list.append(score)
|
256 |
+
|
257 |
+
mean_score = np.mean(scores_list) if scores_list else 50
|
258 |
+
dynamic_threshold = max(40, mean_score)
|
259 |
+
weak_skills = [skill for skill, score in user_scores.items() if score < dynamic_threshold]
|
260 |
+
|
261 |
+
assessment_results = [
|
262 |
+
(skill, f"{'■' * int(score//10)}{'-' * (10 - int(score//10))}", f"{score:.2f}%", "AI-Generated" if ai_flags[skill] else "Human-Written")
|
263 |
+
for skill, score in user_scores.items()
|
264 |
+
]
|
265 |
+
assessment_output = tabulate(assessment_results, headers=["Skill", "Progress", "Score", "Origin"], tablefmt="grid")
|
266 |
+
if skipped_questions:
|
267 |
+
assessment_output += f"\nSkipped Questions: {skipped_questions}"
|
268 |
+
assessment_output += f"\nMean Score: {mean_score:.2f}, Dynamic Threshold: {dynamic_threshold:.2f}"
|
269 |
+
assessment_output += f"\nWeak Skills: {weak_skills if weak_skills else 'None'}"
|
270 |
|
271 |
+
skills_to_recommend = weak_skills if weak_skills else user_skills
|
272 |
+
upgrade_flag = not weak_skills
|
273 |
+
recommended_courses = recommend_courses(skills_to_recommend, user_level, upgrade=upgrade_flag)
|
274 |
+
courses_output = tabulate(recommended_courses, headers=["Course", "Organization"], tablefmt="grid") if recommended_courses else "None"
|
275 |
|
276 |
+
recommended_jobs = recommend_jobs(user_skills, user_level)
|
277 |
+
jobs_output = tabulate(recommended_jobs, headers=["Job Title", "Company", "Location"], tablefmt="grid")
|
278 |
|
279 |
+
response = {
|
280 |
+
"user_info": f"User: {user_name}\nSkills: {user_skills}\nLevel: {user_level}",
|
281 |
+
"assessment_results": assessment_output,
|
282 |
+
"recommended_courses": courses_output,
|
283 |
+
"recommended_jobs": jobs_output
|
|
|
|
|
284 |
}
|
285 |
+
return jsonify(response)
|
286 |
|
287 |
+
if __name__ == '__main__':
|
288 |
+
app.run(host='0.0.0.0', port=7860)
|
|