moahmedwafy commited on
Commit
1810165
·
1 Parent(s): bb3dc70

feat: create flask server

Browse files
.gitignore CHANGED
@@ -1,3 +1,4 @@
1
  node_modules/
2
  dist/
3
- .env
 
 
1
  node_modules/
2
  dist/
3
+ .env
4
+ env/
models-server/models/__pycache__/fitness_model.cpython-312.pyc ADDED
Binary file (9.84 kB). View file
 
models-server/models/fitness_model.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ import os
3
+ from sklearn.preprocessing import OneHotEncoder
4
+ import random
5
+ import pandas as pd
6
+
7
+ SERVER_FILE_DIR = os.path.dirname(os.path.abspath(__file__))
8
+ FITNESS_MODEL_PATH = os.path.join(
9
+ SERVER_FILE_DIR, *"../resources/models/fitness_model.pkl".split("/")
10
+ )
11
+
12
+
13
+ class FitnessModel:
14
+ def __init__(self, excercise_path, kmeans_path, plan_classifier_path):
15
+ self.data = pd.read_csv(excercise_path)
16
+ self.kmeans = None
17
+ self.plan_classifier = None
18
+ self.encoder = None
19
+ self.cluster_data = {}
20
+ self.X_train_cols = [
21
+ "level_Advanced",
22
+ "level_Beginner",
23
+ "level_Intermediate",
24
+ "goal_ Get Fitter",
25
+ "goal_ Lose Weight",
26
+ "goal_Gain Muscle",
27
+ "goal_Get Fitter",
28
+ "goal_Increase Endurance",
29
+ "goal_Increase Strength",
30
+ "goal_Sports Performance",
31
+ "gender_Female",
32
+ "gender_Male",
33
+ "gender_Male & Female",
34
+ ]
35
+
36
+ # Load kmeans model
37
+ with open(kmeans_path, "rb") as f:
38
+ self.kmeans = pickle.load(f)
39
+
40
+ # Load plan classifier model
41
+ with open(plan_classifier_path, "rb") as f:
42
+ self.plan_classifier = pickle.load(f)
43
+
44
+ # Iterate over each cluster label
45
+ for cluster_label in range(90):
46
+ # Filter the dataset to get data for the current cluster
47
+ cluster_subset = self.data[self.data["cluster"] == cluster_label]
48
+
49
+ # Add the cluster data to the dictionary
50
+ self.cluster_data[cluster_label] = cluster_subset
51
+
52
+ features = self.data[["Level", "goal", "bodyPart"]]
53
+
54
+ # Perform one-hot encoding for categorical features
55
+ self.encoder = OneHotEncoder(sparse=False)
56
+ encoded_features = self.encoder.fit_transform(features)
57
+
58
+ def choose_plan(self, level, goal, gender):
59
+ global plan_classifier
60
+ # Convert input into a DataFrame
61
+ input_data = pd.DataFrame(
62
+ {"level": [level], "goal": [goal], "gender": [gender]}
63
+ )
64
+
65
+ # One-hot encode the input data
66
+ input_encoded = pd.get_dummies(input_data, columns=["level", "goal", "gender"])
67
+
68
+ # Ensure that input has the same columns as the model was trained on
69
+ # This is necessary in case some categories are missing in the input
70
+ missing_cols = set(self.X_train_cols) - set(input_encoded.columns)
71
+ for col in missing_cols:
72
+ input_encoded[col] = 0
73
+
74
+ # Reorder columns to match the order of columns in X_train
75
+ input_encoded = input_encoded[self.X_train_cols]
76
+
77
+ # Make prediction for the given input using the trained model
78
+ prediction = self.plan_classifier.predict(input_encoded)
79
+
80
+ # Convert each string in the list to a list of strings
81
+ daily_activities_lists = [day.split(", ") for day in prediction[0]]
82
+
83
+ return daily_activities_lists
84
+
85
+ def get_daily_recommendation(self, home_or_gym, level, goal, bodyParts, equipments):
86
+ if goal in ["Lose Weight", "Get Fitter"]:
87
+ goal = "Get Fitter & Lose Weight"
88
+ daily_recommendations = []
89
+
90
+ bodyParts = [bp for bp in bodyParts if "-" not in bp]
91
+ # Repeat elements in bodyParts until it reaches a size of 6
92
+ while len(bodyParts) < 6:
93
+ bodyParts += bodyParts
94
+
95
+ # Limit bodyParts to size 6
96
+ bodyParts = bodyParts[:6]
97
+
98
+ for bodyPart in bodyParts:
99
+ # Predict cluster for the specified combination of goal, level, and body part
100
+ input_data = [[level, goal, bodyPart]]
101
+ predicted_cluster = self.kmeans.predict(self.encoder.transform(input_data))[
102
+ 0
103
+ ]
104
+ print(predicted_cluster)
105
+ # Get data for the predicted cluster
106
+ cluster_subset = self.cluster_data[predicted_cluster]
107
+
108
+ # Filter data based on location (home or gym)
109
+ if home_or_gym == 0:
110
+ cluster_subset = cluster_subset[
111
+ ~cluster_subset["equipment"].isin(equipments)
112
+ ]
113
+
114
+ # Randomly select one exercise from the cluster if any left after equipment filtering
115
+ if not cluster_subset.empty:
116
+ selected_exercise = random.choice(
117
+ cluster_subset.to_dict(orient="records")
118
+ )
119
+ daily_recommendations.append(selected_exercise)
120
+
121
+ # Remove duplicates from the list
122
+ unique_recommendations = []
123
+ seen_names = set()
124
+ for exercise in daily_recommendations:
125
+ if exercise["name"] not in seen_names:
126
+ unique_recommendations.append(exercise)
127
+ seen_names.add(exercise["name"])
128
+
129
+ return unique_recommendations
130
+
131
+ def get_gender_adjustment(self, gender):
132
+ return 1.0 if gender == "Male" else 0.7
133
+
134
+ def get_age_adjustment(self, age):
135
+ if age < 30:
136
+ return 1.0
137
+ elif 30 <= age < 50:
138
+ return 0.5
139
+ else:
140
+ return 0.1
141
+
142
+ def get_level_adjustment(self, level):
143
+ if level == "Beginner":
144
+ return 0.8
145
+ elif level == "Intermediate":
146
+ return 1.0
147
+ elif level == "Advanced":
148
+ return 1.2
149
+
150
+ def get_body_part_adjustment(self, body_part):
151
+ body_parts = {
152
+ "chest": 1,
153
+ "shoulders": 0.8,
154
+ "waist": 0.6,
155
+ "upper legs": 0.7,
156
+ "back": 0.9,
157
+ "lower legs": 0.5,
158
+ "upper arms": 0.8,
159
+ "cardio": 0.7,
160
+ "lower arms": 0.6,
161
+ "neck": 0.5,
162
+ }
163
+ return body_parts.get(body_part, 0)
164
+
165
+ def adjust_workout(self, gender, age, feedback, body_part, level, old_weight):
166
+ gender_adjustment = self.get_gender_adjustment(gender)
167
+ age_adjustment = self.get_age_adjustment(age)
168
+ level_adjustment = self.get_level_adjustment(level)
169
+ body_part_adjustment = self.get_body_part_adjustment(body_part)
170
+
171
+ increasing_factor_of_weight = (
172
+ age_adjustment
173
+ * body_part_adjustment
174
+ * gender_adjustment
175
+ * level_adjustment
176
+ * 0.3
177
+ )
178
+
179
+ if not feedback:
180
+ increasing_factor_of_weight = (1 - increasing_factor_of_weight) * -0.1
181
+
182
+ new_weight = old_weight + increasing_factor_of_weight * old_weight
183
+
184
+ return new_weight
185
+
186
+ def calculate_new_repetition(self, level, goal):
187
+ if goal in ["Lose Weight", "Get Fitter"]:
188
+ if level == "Beginner":
189
+ return 15
190
+ elif level == "Intermediate":
191
+ return 12
192
+ elif level == "Expert":
193
+ return 10
194
+ elif goal == "Gain Muscle":
195
+ if level == "Beginner":
196
+ return 10
197
+ elif level == "Intermediate":
198
+ return 8
199
+ elif level == "Advanced":
200
+ return 6
201
+
202
+ def calculate_new_duration(self, level):
203
+
204
+ if level == "Beginner":
205
+ return 20
206
+ elif level == "Intermediate":
207
+ return 50
208
+ elif level == "Advanced":
209
+ return 80
210
+
211
+ def predict(
212
+ self, home_or_gym, level, goal, gender, age, feedback, old_weight, equipments
213
+ ):
214
+
215
+ plan = self.choose_plan(level, goal, gender)
216
+ print(plan)
217
+
218
+ while len(plan) < 30:
219
+ plan.extend(plan)
220
+ plan = plan[:30]
221
+
222
+ all_recommendations = []
223
+ for day_body_parts in plan:
224
+ daily_exercises = self.get_daily_recommendation(
225
+ home_or_gym, level, goal, day_body_parts, equipments
226
+ )
227
+ daily_recommendations = []
228
+
229
+ for exercise in daily_exercises:
230
+ weights = self.adjust_workout(
231
+ gender, age, feedback, exercise["bodyPart"], level, old_weight
232
+ )
233
+ repetitions = self.calculate_new_repetition(level, goal)
234
+ duration = self.calculate_new_duration(level)
235
+ weights_or_duration = (
236
+ weights if exercise["type"] == "weight" else duration
237
+ )
238
+ exercise_recommendations = {
239
+ "name": exercise["name"],
240
+ "type": exercise["type"],
241
+ "equipment": exercise["equipment"],
242
+ "bodyPart": exercise["bodyPart"],
243
+ "target": exercise["target"],
244
+ "weights_or_duration": weights_or_duration,
245
+ "sets": exercise["sets"],
246
+ "repetitions": repetitions,
247
+ }
248
+ daily_recommendations.append(exercise_recommendations)
249
+ all_recommendations.append(daily_recommendations)
250
+
251
+ return all_recommendations # Trim to ensure exactly 30 elements
252
+
253
+
254
+ class FModel:
255
+ def __init__(self):
256
+ with open(FITNESS_MODEL_PATH, "rb") as f:
257
+ self.model = pickle.load(f)
258
+
259
+ def predict(
260
+ self,
261
+ home_or_gym: int,
262
+ level: str,
263
+ goal: str,
264
+ gender: str,
265
+ age: int,
266
+ feedback: bool,
267
+ old_weight: int,
268
+ equipments: list,
269
+ ):
270
+ print("model", self.model)
271
+ return self.model.predict(
272
+ home_or_gym, level, goal, gender, age, feedback, old_weight, equipments
273
+ )
models-server/resources/models/fitness_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:665d34c71c506fa1cdbd8d74b54f6ca84f1b9f5a397a6bb90d608cc699f2a61d
3
+ size 95457799
models-server/server.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request
2
+ from dotenv import load_dotenv
3
+ import os
4
+ import json
5
+ from models.fitness_model import FModel
6
+
7
+ load_dotenv()
8
+
9
+ HOST = os.getenv("MODELS_HOST") or "127.0.0.1"
10
+ PORT = os.getenv("MODELS_PORT") or "3030"
11
+
12
+ fitness_model = FModel()
13
+
14
+ app = Flask("model-server")
15
+
16
+
17
+ @app.get("/")
18
+ def health():
19
+ return "I'm alive!!"
20
+
21
+
22
+ @app.post("/fitness")
23
+ def fitness_predict():
24
+ paramNames = [
25
+ "home_or_gym",
26
+ "level",
27
+ "goal",
28
+ "gender",
29
+ "age",
30
+ "feedback",
31
+ "old_weight",
32
+ "equipments",
33
+ ]
34
+
35
+ params = {}
36
+ for paramName in paramNames:
37
+ value = request.json.get(paramName)
38
+ if value is None:
39
+ return json.dumps({"error": f"{paramName} is missing"}), 399
40
+ params[paramName] = value
41
+
42
+ return json.dump({"result": fitness_model.predict(**params)})
43
+
44
+
45
+ if __name__ == "__main__":
46
+ app.run(host=HOST, port=PORT)
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Flask>=3.0.0,<4.0.0
2
+ anakin-language-server>=1.0.0,<2.0.0
3
+ python-doten>=1.0.0,<2.0.0
4
+ scikit-learn>=1.2.0,<1.3.0
5
+ black>=24.0.0,<25.0.0
6
+ pandas>=2.2.0,<2.3.0
7
+ python-dotenv>=1.0.0,<2.0.0
src/lib/models/fitness-model.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import path from "path"
2
+
3
+ path.join(__dirname, ..."../../resources/models/fitness_model.pkl".split("/"));
4
+ const modelPath = `${__dirname}/../../resources/models/fitness_model.pkl`
5
+
6
+ export class FitnessModel {}