Muhammad541 commited on
Commit
d607da0
·
verified ·
1 Parent(s): d64f9e5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +269 -158
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
- from sentence_transformers import SentenceTransformer
6
- from sklearn.feature_extraction.text import TfidfVectorizer
7
- from sklearn.metrics.pairwise import cosine_similarity
8
- import time
9
  import os
 
 
 
 
 
 
 
 
10
 
11
- # Set cache directory
12
- os.environ["HF_HOME"] = "/app/cache"
13
- os.environ["TRANSFORMERS_CACHE"] = "/app/cache"
14
-
15
- app = FastAPI()
16
-
17
- # Load datasets
18
- DATA_DIR = "/app/data/"
19
- job_df = pd.read_csv(os.path.join(DATA_DIR, "Updated_Job_Posting_Dataset.csv"), encoding="latin1")
20
- course_df = pd.read_csv(os.path.join(DATA_DIR, "coursera_course_dataset_v2_no_null.csv"))
21
- coding_df = pd.read_csv(os.path.join(DATA_DIR, "Software Questions.csv"), encoding="latin1")
22
-
23
- # Preprocess datasets
24
- coding_df = coding_df.rename(columns={
25
- 'Question': 'question',
26
- 'Answer': 'solutions',
27
- 'Category': 'category',
28
- 'Difficulty': 'difficulty'
29
- })
30
- coding_df.dropna(subset=['question', 'solutions', 'category', 'difficulty'], inplace=True)
31
- job_df.rename(columns={'company_name': 'company', 'required_skills': 'skills'}, inplace=True)
32
- course_df.rename(columns={'Title': 'course_title', 'Skills': 'skills'}, inplace=True)
33
- job_df["job_description"] = job_df["job_description"].fillna("")
34
-
35
- # Load BERT model and vectorizer
36
- bert_model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
37
- vectorizer = TfidfVectorizer()
38
-
39
- # Pydantic models for request bodies
40
- class ChallengeRequest(BaseModel):
41
- skills: List[str]
42
- difficulty: Optional[str] = None
43
-
44
- class AssessmentRequest(BaseModel):
45
- name: str
46
- skills: List[str]
47
- answers: Optional[Dict[str, Dict[str, str]]] = None
48
-
49
- # Get coding challenges
50
- def get_coding_challenges(categories: List[str], num_questions=5, difficulty: Optional[str] = None):
51
- skill_challenges = {}
52
- for category in categories:
53
- relevant = coding_df[coding_df["category"].str.contains(category, case=False, na=False)]
54
- if difficulty:
55
- relevant = relevant[relevant["difficulty"].str.lower() == difficulty.lower()]
56
- if not relevant.empty:
57
- skill_challenges[category] = relevant.sample(min(num_questions, len(relevant)))[["question", "solutions", "difficulty"]].to_dict(orient="records")
58
- else:
59
- skill_challenges[category] = []
60
- return skill_challenges
61
-
62
- # Evaluate coding answers
63
- def evaluate_coding_with_time(user_code, correct_code, start_time):
64
- end_time = time.time()
65
- execution_time = end_time - start_time
66
- vectorized = vectorizer.fit_transform([user_code, correct_code])
67
- similarity = cosine_similarity(vectorized)[0][1] * 100
68
- if execution_time > 120:
69
- similarity -= (execution_time - 120) * 0.1
70
- return round(max(similarity, 0), 2)
71
-
72
- # Assign proficiency level
73
- def get_proficiency_level(score):
74
- if score >= 80:
75
- return "Expert"
76
- elif score >= 50:
77
- return "Intermediate"
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  else:
79
- return "Beginner"
 
 
 
 
 
 
 
80
 
81
- # Recommend courses
82
- def recommend_courses(weak_skills):
83
- if not weak_skills:
84
- return []
85
- courses = course_df[course_df['skills'].str.contains('|'.join(weak_skills), case=False, na=False)]
86
- return courses[['course_title', 'Organization']].head(5).to_dict(orient="records")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- # Recommend jobs
89
- def recommend_jobs(skills):
90
- if not skills:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- if not skills:
109
- raise HTTPException(status_code=400, detail="Skills list cannot be empty")
110
-
111
- challenges = get_coding_challenges(skills, difficulty=difficulty)
112
-
113
- # Return only questions and difficulty (exclude solutions for the user)
114
- return {
115
- "challenges": {
116
- category: [
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
- # POST endpoint for assessing answers
125
- @app.post("/assess")
126
- def assess_skills(user_input: AssessmentRequest):
127
- user_name = user_input.name
128
- user_skills = user_input.skills
129
 
130
- if not user_skills:
131
- raise HTTPException(status_code=400, detail="Skills list cannot be empty")
 
 
 
 
 
 
 
 
 
 
132
 
133
- challenges = get_coding_challenges(user_skills)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  user_scores = {}
136
- for skill, challenge_list in challenges.items():
137
- if not challenge_list:
138
- user_scores[skill] = 0
139
- continue
140
-
141
- total_score = 0
142
- num_questions = len(challenge_list)
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
- total_score = 50 * num_questions # Default score for unattempted questions
157
-
158
- user_scores[skill] = round(total_score / num_questions, 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
- proficiency_levels = {skill: get_proficiency_level(score) for skill, score in user_scores.items()}
161
- weak_skills = [skill for skill, level in proficiency_levels.items() if level in ["Beginner", "Intermediate"]]
 
 
162
 
163
- courses = recommend_courses(weak_skills)
164
- jobs = recommend_jobs(user_skills)
165
 
166
- return {
167
- "name": user_name,
168
- "skills": user_skills,
169
- "scores": user_scores,
170
- "proficiency_levels": proficiency_levels,
171
- "recommended_courses": courses,
172
- "recommended_jobs": jobs
173
  }
 
174
 
175
- if __name__ == "__main__":
176
- import uvicorn
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)