Spaces:
Running
Running
Hozifa Elgharbawy
commited on
Commit
·
f52f1cd
1
Parent(s):
ebc986f
add nutrition_model
Browse files- models-server/models/nutrition_model.py +69 -67
- models-server/server.py +1 -2
- src/common/models/meal-plan.model.ts +2 -0
- src/lib/models/nutrition_model.ts +46 -0
- src/modules/users/modules/meal-plans/controller/meal-plans.controller.ts +24 -0
- src/modules/users/modules/meal-plans/services/meal-plans.service.ts +70 -1
- src/modules/users/modules/user-registered-meal-plans/services/user-registered-meal-plans.service.ts +3 -1
- src/modules/users/modules/workouts/controllers/workouts.controller.ts +1 -7
- src/modules/users/modules/workouts/services/workouts.service.ts +1 -5
models-server/models/nutrition_model.py
CHANGED
@@ -1,94 +1,96 @@
|
|
1 |
-
import random
|
2 |
import pandas as pd
|
3 |
-
import numpy as np
|
4 |
import pickle
|
5 |
-
import sys
|
6 |
import os
|
7 |
-
import pickle
|
8 |
|
9 |
SERVER_FILE_DIR = os.path.dirname(os.path.abspath(__file__))
|
10 |
-
NUTRITION_MODEL_PATH = os.path.join(
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
13 |
|
|
|
14 |
|
15 |
class NutritionModel:
|
16 |
-
def
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
|
|
|
|
|
|
|
|
27 |
|
28 |
lunch_df = pd.DataFrame(lunch_attr, index=[0])
|
29 |
|
30 |
-
breakfast_attr = {
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
39 |
|
40 |
breakfast_df = pd.DataFrame(breakfast_attr, index=[0])
|
41 |
|
42 |
-
dinner_attr = {
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
|
|
|
|
51 |
|
52 |
dinner_df = pd.DataFrame(dinner_attr, index=[0])
|
53 |
|
54 |
-
snack_attr = {
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
|
|
|
|
63 |
|
64 |
snack_df = pd.DataFrame(snack_attr, index=[0])
|
65 |
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
"SodiumContent":random.uniform(3.5, 51),
|
71 |
-
"CarbohydrateContent":random.uniform(14, 30),
|
72 |
-
"FiberContent": random.uniform(0.2, 3.6),
|
73 |
-
"SugarContent": random.uniform(109, 122),
|
74 |
-
"ProteinContent":random.uniform(0.4, 6)}
|
75 |
-
|
76 |
-
drink_df = pd.DataFrame(drinks_attr, index=[0])
|
77 |
-
|
78 |
-
lunch = the_model.transform(lunch_df)
|
79 |
-
breakfast = the_model.transform(breakfast_df)
|
80 |
-
dinner = the_model.transform(dinner_df)
|
81 |
-
snack = the_model.transform(snack_df)
|
82 |
-
drink = the_model.transform(drink_df)
|
83 |
|
84 |
-
meals = np.concatenate((breakfast, lunch, dinner, snack
|
85 |
meals = np.transpose(meals)
|
86 |
-
|
87 |
-
return meals
|
88 |
-
|
89 |
|
90 |
-
|
|
|
|
|
|
|
91 |
|
|
|
|
|
|
|
92 |
with open(NUTRITION_MODEL_PATH, "rb") as f:
|
93 |
self.nutrition_model = pickle.load(f)
|
94 |
|
|
|
1 |
+
import random
|
2 |
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
import pickle
|
|
|
5 |
import os
|
|
|
6 |
|
7 |
SERVER_FILE_DIR = os.path.dirname(os.path.abspath(__file__))
|
8 |
+
NUTRITION_MODEL_PATH = os.path.join(SERVER_FILE_DIR, "../resources/models/nutrition_model.pkl")
|
9 |
+
MEALS_JSON_PATH = os.path.join(SERVER_FILE_DIR, "../../src/resources/meals.json")
|
10 |
+
|
11 |
+
# Ensure the file exists
|
12 |
+
if not os.path.exists(MEALS_JSON_PATH):
|
13 |
+
raise FileNotFoundError(f"File {MEALS_JSON_PATH} does not exist")
|
14 |
|
15 |
+
df = pd.read_json(MEALS_JSON_PATH)
|
16 |
|
17 |
class NutritionModel:
|
18 |
+
def __init__(self):
|
19 |
+
self.load()
|
20 |
+
|
21 |
+
def generate_plan(self, calories):
|
22 |
+
lunch_attr = {
|
23 |
+
"Calories": calories * 0.5,
|
24 |
+
"FatContent": random.uniform(19, 97),
|
25 |
+
"SaturatedFatContent": random.uniform(6, 12),
|
26 |
+
"CholesterolContent": random.uniform(77, 299),
|
27 |
+
"SodiumContent": random.uniform(565, 2299),
|
28 |
+
"CarbohydrateContent": random.uniform(28, 317),
|
29 |
+
"FiberContent": random.uniform(2, 38),
|
30 |
+
"SugarContent": random.uniform(0, 38),
|
31 |
+
"ProteinContent": random.uniform(20, 123)
|
32 |
+
}
|
33 |
|
34 |
lunch_df = pd.DataFrame(lunch_attr, index=[0])
|
35 |
|
36 |
+
breakfast_attr = {
|
37 |
+
"Calories": calories * 0.30,
|
38 |
+
"FatContent": random.uniform(8.7, 20),
|
39 |
+
"SaturatedFatContent": random.uniform(1.7, 3.7),
|
40 |
+
"CholesterolContent": random.uniform(0, 63),
|
41 |
+
"SodiumContent": random.uniform(163, 650),
|
42 |
+
"CarbohydrateContent": random.uniform(23, 56),
|
43 |
+
"FiberContent": random.uniform(2.6, 8),
|
44 |
+
"SugarContent": random.uniform(3.5, 13),
|
45 |
+
"ProteinContent": random.uniform(6, 25)
|
46 |
+
}
|
47 |
|
48 |
breakfast_df = pd.DataFrame(breakfast_attr, index=[0])
|
49 |
|
50 |
+
dinner_attr = {
|
51 |
+
"Calories": calories * 0.30,
|
52 |
+
"FatContent": random.uniform(8.7, 20),
|
53 |
+
"SaturatedFatContent": random.uniform(1.7, 3.7),
|
54 |
+
"CholesterolContent": random.uniform(0, 63),
|
55 |
+
"SodiumContent": random.uniform(163, 650),
|
56 |
+
"CarbohydrateContent": random.uniform(23, 56),
|
57 |
+
"FiberContent": random.uniform(2.6, 8),
|
58 |
+
"SugarContent": random.uniform(3.5, 13),
|
59 |
+
"ProteinContent": random.uniform(6, 25)
|
60 |
+
}
|
61 |
|
62 |
dinner_df = pd.DataFrame(dinner_attr, index=[0])
|
63 |
|
64 |
+
snack_attr = {
|
65 |
+
"Calories": random.uniform(90, 190),
|
66 |
+
"FatContent": random.uniform(1.7, 10),
|
67 |
+
"SaturatedFatContent": random.uniform(0.7, 3),
|
68 |
+
"CholesterolContent": random.uniform(2, 16),
|
69 |
+
"SodiumContent": random.uniform(47, 200),
|
70 |
+
"CarbohydrateContent": random.uniform(10, 31),
|
71 |
+
"FiberContent": random.uniform(0.4, 2.5),
|
72 |
+
"SugarContent": random.uniform(5.7, 21),
|
73 |
+
"ProteinContent": random.uniform(3, 20)
|
74 |
+
}
|
75 |
|
76 |
snack_df = pd.DataFrame(snack_attr, index=[0])
|
77 |
|
78 |
+
lunch = self.nutrition_model.transform(lunch_df)
|
79 |
+
breakfast = self.nutrition_model.transform(breakfast_df)
|
80 |
+
dinner = self.nutrition_model.transform(dinner_df)
|
81 |
+
snack = self.nutrition_model.transform(snack_df)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
|
83 |
+
meals = np.concatenate((breakfast, lunch, dinner, snack), axis=0)
|
84 |
meals = np.transpose(meals)
|
|
|
|
|
|
|
85 |
|
86 |
+
days = []
|
87 |
+
for i in range(7):
|
88 |
+
day_meals = df.iloc[meals[i]].to_dict(orient="records")
|
89 |
+
days.append(day_meals)
|
90 |
|
91 |
+
return days
|
92 |
+
|
93 |
+
def load(self):
|
94 |
with open(NUTRITION_MODEL_PATH, "rb") as f:
|
95 |
self.nutrition_model = pickle.load(f)
|
96 |
|
models-server/server.py
CHANGED
@@ -55,8 +55,7 @@ def nutrition_predict():
|
|
55 |
if value is None:
|
56 |
return jsonify({"error": f"{paramName} is missing"}), 400
|
57 |
params[paramName] = value
|
58 |
-
|
59 |
-
return jsonify({"result": list(nutrition_model.generate_plan(**params))})
|
60 |
|
61 |
|
62 |
if __name__ == "__main__":
|
|
|
55 |
if value is None:
|
56 |
return jsonify({"error": f"{paramName} is missing"}), 400
|
57 |
params[paramName] = value
|
58 |
+
return jsonify({"result": nutrition_model.generate_plan(**params)})
|
|
|
59 |
|
60 |
|
61 |
if __name__ == "__main__":
|
src/common/models/meal-plan.model.ts
CHANGED
@@ -16,6 +16,7 @@ export interface IMealPlan {
|
|
16 |
day_number: number;
|
17 |
meals: mongoose.Types.ObjectId[];
|
18 |
}[];
|
|
|
19 |
}
|
20 |
|
21 |
const mealPlanSchema = new Schema({
|
@@ -36,6 +37,7 @@ const mealPlanSchema = new Schema({
|
|
36 |
],
|
37 |
},
|
38 |
],
|
|
|
39 |
});
|
40 |
|
41 |
|
|
|
16 |
day_number: number;
|
17 |
meals: mongoose.Types.ObjectId[];
|
18 |
}[];
|
19 |
+
aiGenerated: boolean;
|
20 |
}
|
21 |
|
22 |
const mealPlanSchema = new Schema({
|
|
|
37 |
],
|
38 |
},
|
39 |
],
|
40 |
+
aiGenerated: { type: Boolean, required: true, default: false },
|
41 |
});
|
42 |
|
43 |
|
src/lib/models/nutrition_model.ts
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { config } from "@configs/config";
|
2 |
+
|
3 |
+
const endpoint = '/nutrition';
|
4 |
+
|
5 |
+
// Define the structure of a Nutrition Prediction Item
|
6 |
+
export interface INutritionPredictionItem {
|
7 |
+
Calories: number;
|
8 |
+
CarbohydrateContent: number;
|
9 |
+
FatContent: number;
|
10 |
+
Images: string[];
|
11 |
+
Name: string;
|
12 |
+
ProteinContent: number;
|
13 |
+
RecipeIngredientParts: string[];
|
14 |
+
RecipeInstructions: string[];
|
15 |
+
type: string;
|
16 |
+
}
|
17 |
+
|
18 |
+
export interface INParams {
|
19 |
+
calories: number
|
20 |
+
}
|
21 |
+
|
22 |
+
export class NutritionModel {
|
23 |
+
public static async predictMealPlan(
|
24 |
+
params: INParams
|
25 |
+
): Promise<INutritionPredictionItem[][]> {
|
26 |
+
const response = await fetch(
|
27 |
+
`${config.modelsServerUrl}${endpoint}`,
|
28 |
+
{
|
29 |
+
method: "POST",
|
30 |
+
headers: {
|
31 |
+
"Content-Type": "application/json",
|
32 |
+
},
|
33 |
+
body: JSON.stringify(params),
|
34 |
+
}
|
35 |
+
);
|
36 |
+
|
37 |
+
if (!response.ok) {
|
38 |
+
console.error(await response.text());
|
39 |
+
throw new Error("Failed to fetch data from the server");
|
40 |
+
}
|
41 |
+
|
42 |
+
return response.json().then((data) => {
|
43 |
+
return data.result as INutritionPredictionItem[][];
|
44 |
+
});
|
45 |
+
}
|
46 |
+
}
|
src/modules/users/modules/meal-plans/controller/meal-plans.controller.ts
CHANGED
@@ -23,6 +23,7 @@ export class UsersMealPlansController extends BaseController {
|
|
23 |
|
24 |
setRoutes(): void {
|
25 |
this.router.get("/", asyncHandler(this.list));
|
|
|
26 |
}
|
27 |
|
28 |
@SwaggerGet()
|
@@ -48,4 +49,27 @@ export class UsersMealPlansController extends BaseController {
|
|
48 |
res
|
49 |
);
|
50 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
}
|
|
|
23 |
|
24 |
setRoutes(): void {
|
25 |
this.router.get("/", asyncHandler(this.list));
|
26 |
+
this.router.get("/ss", asyncHandler(this.ss));
|
27 |
}
|
28 |
|
29 |
@SwaggerGet()
|
|
|
49 |
res
|
50 |
);
|
51 |
};
|
52 |
+
|
53 |
+
@SwaggerGet("/ss")
|
54 |
+
ss = async (req: Request, res: Response) => {
|
55 |
+
let pMealPlan = await this.mealPlansService.createModelMealPlan({
|
56 |
+
id: "664a4b170da4e428849859a0",
|
57 |
+
name: "User 0",
|
58 |
+
email: "[email protected]",
|
59 |
+
image: "https://placehold.co/300x400",
|
60 |
+
role: "user" as any,
|
61 |
+
gender: "male" as any,
|
62 |
+
dob: new Date("1990-01-31T22:00:00.000+00:00"),
|
63 |
+
height: 170,
|
64 |
+
weight: 70,
|
65 |
+
fitness_level: "beginner" as any,
|
66 |
+
});
|
67 |
+
|
68 |
+
return JsonResponse.success(
|
69 |
+
{
|
70 |
+
data: { pMealPlan: pMealPlan},
|
71 |
+
},
|
72 |
+
res
|
73 |
+
);
|
74 |
+
};
|
75 |
}
|
src/modules/users/modules/meal-plans/services/meal-plans.service.ts
CHANGED
@@ -1,4 +1,73 @@
|
|
1 |
import { MealPlan } from "@common/models/meal-plan.model";
|
2 |
import { CrudService } from "@lib/services/crud.service";
|
|
|
|
|
|
|
|
|
3 |
|
4 |
-
export class MealPlansService extends CrudService(MealPlan) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import { MealPlan } from "@common/models/meal-plan.model";
|
2 |
import { CrudService } from "@lib/services/crud.service";
|
3 |
+
import { NutritionModel, INParams, INutritionPredictionItem } from "@lib/models/nutrition_model";
|
4 |
+
import { calcAge } from "@lib/utils/age";
|
5 |
+
import { MealsService } from "../../meals/services/meals.service";
|
6 |
+
import { UserRegisteredMealPlansService } from "../../user-registered-meal-plans/services/user-registered-meal-plans.service";
|
7 |
|
8 |
+
export class MealPlansService extends CrudService(MealPlan) {
|
9 |
+
private mealsService = new MealsService();
|
10 |
+
private userRegisteredMealPlansService = new UserRegisteredMealPlansService();
|
11 |
+
|
12 |
+
public async createModelMealPlan(user: any) {
|
13 |
+
let caloriesPerDay = 0;
|
14 |
+
if (user.gender === "male") {
|
15 |
+
caloriesPerDay = 10 * user.weight + 6.25 * user.height - 5 * calcAge(user.dob) + 5;
|
16 |
+
} else {
|
17 |
+
caloriesPerDay = 10 * user.weight + 6.25 * user.height - 5 * calcAge(user.dob) - 161;
|
18 |
+
}
|
19 |
+
|
20 |
+
const params: INParams = {
|
21 |
+
calories: caloriesPerDay,
|
22 |
+
};
|
23 |
+
|
24 |
+
let pMealPlan: INutritionPredictionItem[][] = [];
|
25 |
+
for (let i = 0; i < 4; i++) {
|
26 |
+
const mealPlanChunk = await NutritionModel.predictMealPlan(params);
|
27 |
+
pMealPlan = pMealPlan.concat(mealPlanChunk);
|
28 |
+
}
|
29 |
+
|
30 |
+
const mealsNames = pMealPlan.flat().map((meal) => meal.Name);
|
31 |
+
const meals = await this.mealsService.listAll({ name: { $in: mealsNames } });
|
32 |
+
|
33 |
+
const mealPlan = await this.create({
|
34 |
+
aiGenerated: true,
|
35 |
+
image: "https://placehold.co/300x400",
|
36 |
+
description: `AI Generated Meal Plan (${new Date().toLocaleDateString()})`,
|
37 |
+
duration: 28,
|
38 |
+
level: user.fitness_level,
|
39 |
+
your_journey: `Welcome to your personalized meal plan journey! As someone with a ${user.fitness_level} fitness level, this plan is tailored to meet your specific needs.`,
|
40 |
+
key_features: [
|
41 |
+
{
|
42 |
+
title: "Balanced Nutrition",
|
43 |
+
description: `Each meal provides a well-balanced mix of nutrients to support your health goals.`,
|
44 |
+
},
|
45 |
+
{
|
46 |
+
title: "Customizable Meals",
|
47 |
+
description: `Meals can be adjusted to fit your dietary preferences.`,
|
48 |
+
},
|
49 |
+
{
|
50 |
+
title: "Easy to Prepare",
|
51 |
+
description: `All meals are designed to be quick and easy to prepare.`,
|
52 |
+
},
|
53 |
+
{
|
54 |
+
title: "Variety and Flavor",
|
55 |
+
description: `Enjoy a diverse range of delicious meals.`,
|
56 |
+
},
|
57 |
+
],
|
58 |
+
days: pMealPlan.map((day, i) => ({
|
59 |
+
day_number: i + 1,
|
60 |
+
meals: day.map((m) => meals.find((meal) => meal.name === m.Name)?._id),
|
61 |
+
})),
|
62 |
+
});
|
63 |
+
|
64 |
+
await this.userRegisteredMealPlansService.createForUser(
|
65 |
+
{
|
66 |
+
MealPlan: mealPlan._id,
|
67 |
+
},
|
68 |
+
user._id
|
69 |
+
);
|
70 |
+
|
71 |
+
return mealPlan;
|
72 |
+
}
|
73 |
+
}
|
src/modules/users/modules/user-registered-meal-plans/services/user-registered-meal-plans.service.ts
CHANGED
@@ -2,10 +2,12 @@ import { UserRegisteredMealPlan } from "@common/models/user-registered-meal-plan
|
|
2 |
import { CrudService } from "@lib/services/crud.service";
|
3 |
import { MealPlansService } from "../../meal-plans/services/meal-plans.service";
|
4 |
import { HttpError } from "@lib/error-handling/http-error";
|
|
|
|
|
5 |
|
6 |
|
7 |
export class UserRegisteredMealPlansService extends CrudService(UserRegisteredMealPlan) {
|
8 |
-
private mealPlansService
|
9 |
|
10 |
async unregisterCurrentMealPlan(userId: string) {
|
11 |
return await this.updateMany({
|
|
|
2 |
import { CrudService } from "@lib/services/crud.service";
|
3 |
import { MealPlansService } from "../../meal-plans/services/meal-plans.service";
|
4 |
import { HttpError } from "@lib/error-handling/http-error";
|
5 |
+
import { MealPlan } from "@common/models/meal-plan.model";
|
6 |
+
|
7 |
|
8 |
|
9 |
export class UserRegisteredMealPlansService extends CrudService(UserRegisteredMealPlan) {
|
10 |
+
private mealPlansService = new (CrudService(MealPlan))()
|
11 |
|
12 |
async unregisterCurrentMealPlan(userId: string) {
|
13 |
return await this.updateMany({
|
src/modules/users/modules/workouts/controllers/workouts.controller.ts
CHANGED
@@ -49,13 +49,7 @@ export class UsersWorkoutController extends BaseController {
|
|
49 |
}
|
50 |
|
51 |
const { docs, paginationData } = await this.workoutsService.list(
|
52 |
-
|
53 |
-
...filter,
|
54 |
-
$or: [
|
55 |
-
{ aiGenerated: true, created_by: req.jwtPayload.id },
|
56 |
-
{ aiGenerated: false },
|
57 |
-
],
|
58 |
-
},
|
59 |
paginationQuery
|
60 |
);
|
61 |
|
|
|
49 |
}
|
50 |
|
51 |
const { docs, paginationData } = await this.workoutsService.list(
|
52 |
+
filter,
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
paginationQuery
|
54 |
);
|
55 |
|
src/modules/users/modules/workouts/services/workouts.service.ts
CHANGED
@@ -7,11 +7,7 @@ import { calcAge } from "@lib/utils/age";
|
|
7 |
import { ExerciseService } from "../../exercises/services/exercises.service";
|
8 |
import { UserRegisteredWorkoutsService } from "../../user-registered-workouts/services/user-registered-workouts.service";
|
9 |
|
10 |
-
export class WorkoutService extends CrudService(Workout
|
11 |
-
defaultFilter: {
|
12 |
-
aiGenerated: false,
|
13 |
-
},
|
14 |
-
}) {
|
15 |
private exerciseService = new ExerciseService();
|
16 |
private userRegisteredWorkoutsService = new UserRegisteredWorkoutsService();
|
17 |
|
|
|
7 |
import { ExerciseService } from "../../exercises/services/exercises.service";
|
8 |
import { UserRegisteredWorkoutsService } from "../../user-registered-workouts/services/user-registered-workouts.service";
|
9 |
|
10 |
+
export class WorkoutService extends CrudService(Workout) {
|
|
|
|
|
|
|
|
|
11 |
private exerciseService = new ExerciseService();
|
12 |
private userRegisteredWorkoutsService = new UserRegisteredWorkoutsService();
|
13 |
|