healthy-meals-core 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bmr.d.ts +3 -0
- package/dist/bmr.js +14 -0
- package/dist/dailyMealPlanGenerator.d.ts +3 -0
- package/dist/dailyMealPlanGenerator.js +211 -0
- package/dist/data/detailed-recipes-bs.json +417 -0
- package/dist/data/detailed-recipes.json +744 -0
- package/dist/foodConversion.d.ts +55 -0
- package/dist/foodConversion.js +200 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +27 -0
- package/dist/planner.d.ts +2 -0
- package/dist/planner.js +24 -0
- package/dist/recipeBasedMealPlanGenerator.d.ts +17 -0
- package/dist/recipeBasedMealPlanGenerator.js +255 -0
- package/dist/recipeService.d.ts +28 -0
- package/dist/recipeService.js +136 -0
- package/dist/rules/cholesterol.d.ts +2 -0
- package/dist/rules/cholesterol.js +7 -0
- package/dist/rules/diabetes.d.ts +2 -0
- package/dist/rules/diabetes.js +7 -0
- package/dist/rules/fattyLiver.d.ts +2 -0
- package/dist/rules/fattyLiver.js +7 -0
- package/dist/rules/index.d.ts +7 -0
- package/dist/rules/index.js +47 -0
- package/dist/rules/lowCarb.d.ts +2 -0
- package/dist/rules/lowCarb.js +7 -0
- package/dist/rules/triglycerides.d.ts +2 -0
- package/dist/rules/triglycerides.js +7 -0
- package/dist/src/bmr.d.ts +3 -0
- package/dist/src/bmr.js +13 -0
- package/dist/src/dailyMealPlanGenerator.d.ts +3 -0
- package/dist/src/dailyMealPlanGenerator.js +210 -0
- package/dist/src/foodConversion.d.ts +55 -0
- package/dist/src/foodConversion.js +199 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +27 -0
- package/dist/src/planner.d.ts +2 -0
- package/dist/src/planner.js +23 -0
- package/dist/src/recipeBasedMealPlanGenerator.d.ts +17 -0
- package/dist/src/recipeBasedMealPlanGenerator.js +254 -0
- package/dist/src/recipeService.d.ts +28 -0
- package/dist/src/recipeService.js +136 -0
- package/dist/src/rules/cholesterol.d.ts +2 -0
- package/dist/src/rules/cholesterol.js +6 -0
- package/dist/src/rules/diabetes.d.ts +2 -0
- package/dist/src/rules/diabetes.js +6 -0
- package/dist/src/rules/fattyLiver.d.ts +2 -0
- package/dist/src/rules/fattyLiver.js +6 -0
- package/dist/src/rules/index.d.ts +7 -0
- package/dist/src/rules/index.js +46 -0
- package/dist/src/rules/lowCarb.d.ts +2 -0
- package/dist/src/rules/lowCarb.js +6 -0
- package/dist/src/rules/triglycerides.d.ts +2 -0
- package/dist/src/rules/triglycerides.js +6 -0
- package/dist/src/types/firestore.d.ts +100 -0
- package/dist/src/types/firestore.js +2 -0
- package/dist/src/types/openfoodfacts.d.ts +113 -0
- package/dist/src/types/openfoodfacts.js +3 -0
- package/dist/src/types/recipe.d.ts +36 -0
- package/dist/src/types/recipe.js +2 -0
- package/dist/src/types.d.ts +24 -0
- package/dist/src/types.js +2 -0
- package/dist/src/variety.d.ts +17 -0
- package/dist/src/variety.js +129 -0
- package/dist/src/weeklyMealPlanGenerator.d.ts +3 -0
- package/dist/src/weeklyMealPlanGenerator.js +468 -0
- package/dist/src/weeklyPlanner.d.ts +12 -0
- package/dist/src/weeklyPlanner.js +31 -0
- package/dist/types/firestore.d.ts +100 -0
- package/dist/types/firestore.js +2 -0
- package/dist/types/openfoodfacts.d.ts +113 -0
- package/dist/types/openfoodfacts.js +3 -0
- package/dist/types/recipe.d.ts +36 -0
- package/dist/types/recipe.js +2 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +2 -0
- package/dist/variety.d.ts +17 -0
- package/dist/variety.js +130 -0
- package/dist/weeklyMealPlanGenerator.d.ts +3 -0
- package/dist/weeklyMealPlanGenerator.js +469 -0
- package/dist/weeklyPlanner.d.ts +12 -0
- package/dist/weeklyPlanner.js +32 -0
- package/package.json +66 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shuffleArray = shuffleArray;
|
|
4
|
+
exports.preventConsecutiveRepeats = preventConsecutiveRepeats;
|
|
5
|
+
exports.ensureMealVariety = ensureMealVariety;
|
|
6
|
+
exports.calculateMealDiversity = calculateMealDiversity;
|
|
7
|
+
exports.getMealCategory = getMealCategory;
|
|
8
|
+
exports.calculateCategoryDiversity = calculateCategoryDiversity;
|
|
9
|
+
exports.hasConsecutiveRepeats = hasConsecutiveRepeats;
|
|
10
|
+
exports.scoreMealPlan = scoreMealPlan;
|
|
11
|
+
exports.selectDiverseMeals = selectDiverseMeals;
|
|
12
|
+
function shuffleArray(array) {
|
|
13
|
+
const shuffled = [...array];
|
|
14
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
15
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
16
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
17
|
+
}
|
|
18
|
+
return shuffled;
|
|
19
|
+
}
|
|
20
|
+
function preventConsecutiveRepeats(meals) {
|
|
21
|
+
const result = [];
|
|
22
|
+
const used = new Set();
|
|
23
|
+
for (const meal of meals) {
|
|
24
|
+
if (result.length > 0 && result[result.length - 1].id === meal.id) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
result.push(meal);
|
|
28
|
+
used.add(meal.id);
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
function ensureMealVariety(meals, minVariety = 3) {
|
|
33
|
+
const mealCounts = new Map();
|
|
34
|
+
meals.forEach(meal => {
|
|
35
|
+
mealCounts.set(meal.id, (mealCounts.get(meal.id) || 0) + 1);
|
|
36
|
+
});
|
|
37
|
+
const overusedMeals = Array.from(mealCounts.entries())
|
|
38
|
+
.filter(([_, count]) => count > minVariety)
|
|
39
|
+
.map(([id]) => id);
|
|
40
|
+
if (overusedMeals.length === 0) {
|
|
41
|
+
return meals;
|
|
42
|
+
}
|
|
43
|
+
return meals.filter((meal, index) => {
|
|
44
|
+
if (!overusedMeals.includes(meal.id)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const previousOccurrences = meals
|
|
48
|
+
.slice(0, index)
|
|
49
|
+
.filter(m => m.id === meal.id).length;
|
|
50
|
+
return previousOccurrences < minVariety;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function calculateMealDiversity(meals) {
|
|
54
|
+
const uniqueMeals = new Set(meals.map(m => m.id));
|
|
55
|
+
return uniqueMeals.size / meals.length;
|
|
56
|
+
}
|
|
57
|
+
function getMealCategory(recipe) {
|
|
58
|
+
if (recipe.protein > 30)
|
|
59
|
+
return 'high-protein';
|
|
60
|
+
if (recipe.carbs > 50)
|
|
61
|
+
return 'high-carb';
|
|
62
|
+
if (recipe.fat > 20)
|
|
63
|
+
return 'high-fat';
|
|
64
|
+
return 'balanced';
|
|
65
|
+
}
|
|
66
|
+
function calculateCategoryDiversity(meals) {
|
|
67
|
+
const categories = meals.map(m => getMealCategory(m));
|
|
68
|
+
const uniqueCategories = new Set(categories);
|
|
69
|
+
return uniqueCategories.size / 4;
|
|
70
|
+
}
|
|
71
|
+
function hasConsecutiveRepeats(meals) {
|
|
72
|
+
for (let i = 1; i < meals.length; i++) {
|
|
73
|
+
if (meals[i].id === meals[i - 1].id) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
function scoreMealPlan(meals) {
|
|
80
|
+
const diversity = calculateMealDiversity(meals);
|
|
81
|
+
const categoryDiversity = calculateCategoryDiversity(meals);
|
|
82
|
+
const avgProtein = meals.reduce((sum, m) => sum + m.protein, 0) / meals.length;
|
|
83
|
+
const proteinVariance = meals.reduce((sum, m) => sum + Math.pow(m.protein - avgProtein, 2), 0) / meals.length;
|
|
84
|
+
const proteinBalance = 1 / (1 + proteinVariance / 100);
|
|
85
|
+
const avgCalories = meals.reduce((sum, m) => sum + m.calories, 0) / meals.length;
|
|
86
|
+
const calorieVariance = meals.reduce((sum, m) => sum + Math.pow(m.calories - avgCalories, 2), 0) / meals.length;
|
|
87
|
+
const calorieDistribution = 1 / (1 + calorieVariance / 10000);
|
|
88
|
+
const noConsecutiveRepeats = hasConsecutiveRepeats(meals) ? 0 : 1;
|
|
89
|
+
const overallScore = (diversity * 0.3) +
|
|
90
|
+
(categoryDiversity * 0.2) +
|
|
91
|
+
(proteinBalance * 0.2) +
|
|
92
|
+
(calorieDistribution * 0.15) +
|
|
93
|
+
(noConsecutiveRepeats * 0.15);
|
|
94
|
+
return {
|
|
95
|
+
diversity,
|
|
96
|
+
categoryDiversity,
|
|
97
|
+
proteinBalance,
|
|
98
|
+
calorieDistribution,
|
|
99
|
+
noConsecutiveRepeats,
|
|
100
|
+
overallScore
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function selectDiverseMeals(availableRecipes, targetCalories, previousMeals = []) {
|
|
104
|
+
const shuffled = shuffleArray([...availableRecipes]);
|
|
105
|
+
const recentMealIds = new Set(previousMeals.slice(-3).map(m => m.id));
|
|
106
|
+
const prioritized = shuffled.sort((a, b) => {
|
|
107
|
+
const aRecent = recentMealIds.has(a.id) ? 1 : 0;
|
|
108
|
+
const bRecent = recentMealIds.has(b.id) ? 1 : 0;
|
|
109
|
+
return aRecent - bRecent;
|
|
110
|
+
});
|
|
111
|
+
let total = 0;
|
|
112
|
+
const selected = [];
|
|
113
|
+
const usedIds = new Set();
|
|
114
|
+
for (const recipe of prioritized) {
|
|
115
|
+
if (total >= targetCalories * 0.95)
|
|
116
|
+
break;
|
|
117
|
+
if (usedIds.has(recipe.id))
|
|
118
|
+
continue;
|
|
119
|
+
if (selected.length > 0 && selected[selected.length - 1].id === recipe.id) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (total + recipe.calories <= targetCalories * 1.05) {
|
|
123
|
+
selected.push(recipe);
|
|
124
|
+
usedIds.add(recipe.id);
|
|
125
|
+
total += recipe.calories;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return selected;
|
|
129
|
+
}
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateWeeklyMealPlan = generateWeeklyMealPlan;
|
|
4
|
+
const foodConversion_1 = require("./foodConversion");
|
|
5
|
+
// Extended mock food data for weekly variety
|
|
6
|
+
const mockFoods = {
|
|
7
|
+
// Breakfast foods
|
|
8
|
+
oatmeal: {
|
|
9
|
+
id: 'oatmeal',
|
|
10
|
+
name: 'Oatmeal',
|
|
11
|
+
brand: 'Generic',
|
|
12
|
+
nutritionPer100g: { calories: 379, protein: 13.2, carbs: 66.3, fat: 6.9 },
|
|
13
|
+
servingSize: 40,
|
|
14
|
+
unit: 'g'
|
|
15
|
+
},
|
|
16
|
+
banana: {
|
|
17
|
+
id: 'banana',
|
|
18
|
+
name: 'Banana',
|
|
19
|
+
brand: 'Generic',
|
|
20
|
+
nutritionPer100g: { calories: 89, protein: 1.1, carbs: 22.8, fat: 0.3 },
|
|
21
|
+
servingSize: 118,
|
|
22
|
+
unit: 'g'
|
|
23
|
+
},
|
|
24
|
+
eggs: {
|
|
25
|
+
id: 'eggs',
|
|
26
|
+
name: 'Eggs',
|
|
27
|
+
brand: 'Generic',
|
|
28
|
+
nutritionPer100g: { calories: 155, protein: 13, carbs: 1.1, fat: 11 },
|
|
29
|
+
servingSize: 100,
|
|
30
|
+
unit: 'g'
|
|
31
|
+
},
|
|
32
|
+
yogurt: {
|
|
33
|
+
id: 'yogurt',
|
|
34
|
+
name: 'Greek Yogurt',
|
|
35
|
+
brand: 'Generic',
|
|
36
|
+
nutritionPer100g: { calories: 59, protein: 10, carbs: 3.6, fat: 0.4 },
|
|
37
|
+
servingSize: 170,
|
|
38
|
+
unit: 'g'
|
|
39
|
+
},
|
|
40
|
+
berries: {
|
|
41
|
+
id: 'berries',
|
|
42
|
+
name: 'Mixed Berries',
|
|
43
|
+
brand: 'Generic',
|
|
44
|
+
nutritionPer100g: { calories: 57, protein: 0.7, carbs: 14.5, fat: 0.3 },
|
|
45
|
+
servingSize: 100,
|
|
46
|
+
unit: 'g'
|
|
47
|
+
},
|
|
48
|
+
avocado_toast: {
|
|
49
|
+
id: 'avocado_toast',
|
|
50
|
+
name: 'Avocado Toast',
|
|
51
|
+
brand: 'Generic',
|
|
52
|
+
nutritionPer100g: { calories: 160, protein: 2, carbs: 9, fat: 15 },
|
|
53
|
+
servingSize: 150,
|
|
54
|
+
unit: 'g'
|
|
55
|
+
},
|
|
56
|
+
smoothie: {
|
|
57
|
+
id: 'smoothie',
|
|
58
|
+
name: 'Protein Smoothie',
|
|
59
|
+
brand: 'Generic',
|
|
60
|
+
nutritionPer100g: { calories: 150, protein: 20, carbs: 15, fat: 3 },
|
|
61
|
+
servingSize: 300,
|
|
62
|
+
unit: 'ml'
|
|
63
|
+
},
|
|
64
|
+
// Lunch proteins
|
|
65
|
+
chicken: {
|
|
66
|
+
id: 'chicken',
|
|
67
|
+
name: 'Chicken Breast',
|
|
68
|
+
brand: 'Generic',
|
|
69
|
+
nutritionPer100g: { calories: 165, protein: 31, carbs: 0, fat: 3.6 },
|
|
70
|
+
servingSize: 100,
|
|
71
|
+
unit: 'g'
|
|
72
|
+
},
|
|
73
|
+
salmon: {
|
|
74
|
+
id: 'salmon',
|
|
75
|
+
name: 'Salmon',
|
|
76
|
+
brand: 'Generic',
|
|
77
|
+
nutritionPer100g: { calories: 206, protein: 22, carbs: 0, fat: 13 },
|
|
78
|
+
servingSize: 100,
|
|
79
|
+
unit: 'g'
|
|
80
|
+
},
|
|
81
|
+
turkey: {
|
|
82
|
+
id: 'turkey',
|
|
83
|
+
name: 'Turkey Breast',
|
|
84
|
+
brand: 'Generic',
|
|
85
|
+
nutritionPer100g: { calories: 135, protein: 30, carbs: 0, fat: 1 },
|
|
86
|
+
servingSize: 100,
|
|
87
|
+
unit: 'g'
|
|
88
|
+
},
|
|
89
|
+
tuna: {
|
|
90
|
+
id: 'tuna',
|
|
91
|
+
name: 'Tuna',
|
|
92
|
+
brand: 'Generic',
|
|
93
|
+
nutritionPer100g: { calories: 144, protein: 30, carbs: 0, fat: 1 },
|
|
94
|
+
servingSize: 100,
|
|
95
|
+
unit: 'g'
|
|
96
|
+
},
|
|
97
|
+
tofu: {
|
|
98
|
+
id: 'tofu',
|
|
99
|
+
name: 'Tofu',
|
|
100
|
+
brand: 'Generic',
|
|
101
|
+
nutritionPer100g: { calories: 76, protein: 8, carbs: 1.9, fat: 4.8 },
|
|
102
|
+
servingSize: 150,
|
|
103
|
+
unit: 'g'
|
|
104
|
+
},
|
|
105
|
+
lentils: {
|
|
106
|
+
id: 'lentils',
|
|
107
|
+
name: 'Lentils',
|
|
108
|
+
brand: 'Generic',
|
|
109
|
+
nutritionPer100g: { calories: 116, protein: 9, carbs: 20, fat: 0.4 },
|
|
110
|
+
servingSize: 200,
|
|
111
|
+
unit: 'g'
|
|
112
|
+
},
|
|
113
|
+
quinoa: {
|
|
114
|
+
id: 'quinoa',
|
|
115
|
+
name: 'Quinoa',
|
|
116
|
+
brand: 'Generic',
|
|
117
|
+
nutritionPer100g: { calories: 120, protein: 4.4, carbs: 22, fat: 1.9 },
|
|
118
|
+
servingSize: 185,
|
|
119
|
+
unit: 'g'
|
|
120
|
+
},
|
|
121
|
+
// Carbs
|
|
122
|
+
rice: {
|
|
123
|
+
id: 'rice',
|
|
124
|
+
name: 'Brown Rice',
|
|
125
|
+
brand: 'Generic',
|
|
126
|
+
nutritionPer100g: { calories: 111, protein: 2.6, carbs: 23, fat: 0.9 },
|
|
127
|
+
servingSize: 185,
|
|
128
|
+
unit: 'g'
|
|
129
|
+
},
|
|
130
|
+
pasta: {
|
|
131
|
+
id: 'pasta',
|
|
132
|
+
name: 'Whole Wheat Pasta',
|
|
133
|
+
brand: 'Generic',
|
|
134
|
+
nutritionPer100g: { calories: 124, protein: 5, carbs: 25, fat: 1.1 },
|
|
135
|
+
servingSize: 140,
|
|
136
|
+
unit: 'g'
|
|
137
|
+
},
|
|
138
|
+
sweet_potato: {
|
|
139
|
+
id: 'sweet_potato',
|
|
140
|
+
name: 'Sweet Potato',
|
|
141
|
+
brand: 'Generic',
|
|
142
|
+
nutritionPer100g: { calories: 86, protein: 1.6, carbs: 20, fat: 0.1 },
|
|
143
|
+
servingSize: 200,
|
|
144
|
+
unit: 'g'
|
|
145
|
+
},
|
|
146
|
+
bread: {
|
|
147
|
+
id: 'bread',
|
|
148
|
+
name: 'Whole Wheat Bread',
|
|
149
|
+
brand: 'Generic',
|
|
150
|
+
nutritionPer100g: { calories: 247, protein: 13, carbs: 41, fat: 3.5 },
|
|
151
|
+
servingSize: 45,
|
|
152
|
+
unit: 'g'
|
|
153
|
+
},
|
|
154
|
+
// Vegetables
|
|
155
|
+
broccoli: {
|
|
156
|
+
id: 'broccoli',
|
|
157
|
+
name: 'Broccoli',
|
|
158
|
+
brand: 'Generic',
|
|
159
|
+
nutritionPer100g: { calories: 34, protein: 2.8, carbs: 7, fat: 0.4 },
|
|
160
|
+
servingSize: 91,
|
|
161
|
+
unit: 'g'
|
|
162
|
+
},
|
|
163
|
+
spinach: {
|
|
164
|
+
id: 'spinach',
|
|
165
|
+
name: 'Spinach',
|
|
166
|
+
brand: 'Generic',
|
|
167
|
+
nutritionPer100g: { calories: 23, protein: 2.9, carbs: 3.6, fat: 0.4 },
|
|
168
|
+
servingSize: 30,
|
|
169
|
+
unit: 'g'
|
|
170
|
+
},
|
|
171
|
+
carrots: {
|
|
172
|
+
id: 'carrots',
|
|
173
|
+
name: 'Carrots',
|
|
174
|
+
brand: 'Generic',
|
|
175
|
+
nutritionPer100g: { calories: 41, protein: 0.9, carbs: 9.6, fat: 0.2 },
|
|
176
|
+
servingSize: 100,
|
|
177
|
+
unit: 'g'
|
|
178
|
+
},
|
|
179
|
+
bell_peppers: {
|
|
180
|
+
id: 'bell_peppers',
|
|
181
|
+
name: 'Bell Peppers',
|
|
182
|
+
brand: 'Generic',
|
|
183
|
+
nutritionPer100g: { calories: 31, protein: 1, carbs: 7, fat: 0.3 },
|
|
184
|
+
servingSize: 120,
|
|
185
|
+
unit: 'g'
|
|
186
|
+
},
|
|
187
|
+
zucchini: {
|
|
188
|
+
id: 'zucchini',
|
|
189
|
+
name: 'Zucchini',
|
|
190
|
+
brand: 'Generic',
|
|
191
|
+
nutritionPer100g: { calories: 17, protein: 1.2, carbs: 3.1, fat: 0.3 },
|
|
192
|
+
servingSize: 200,
|
|
193
|
+
unit: 'g'
|
|
194
|
+
},
|
|
195
|
+
// Snacks
|
|
196
|
+
apple: {
|
|
197
|
+
id: 'apple',
|
|
198
|
+
name: 'Apple',
|
|
199
|
+
brand: 'Generic',
|
|
200
|
+
nutritionPer100g: { calories: 52, protein: 0.3, carbs: 13.8, fat: 0.2 },
|
|
201
|
+
servingSize: 182,
|
|
202
|
+
unit: 'g'
|
|
203
|
+
},
|
|
204
|
+
almonds: {
|
|
205
|
+
id: 'almonds',
|
|
206
|
+
name: 'Almonds',
|
|
207
|
+
brand: 'Generic',
|
|
208
|
+
nutritionPer100g: { calories: 579, protein: 21.2, carbs: 21.6, fat: 49.9 },
|
|
209
|
+
servingSize: 28,
|
|
210
|
+
unit: 'g'
|
|
211
|
+
},
|
|
212
|
+
walnuts: {
|
|
213
|
+
id: 'walnuts',
|
|
214
|
+
name: 'Walnuts',
|
|
215
|
+
brand: 'Generic',
|
|
216
|
+
nutritionPer100g: { calories: 654, protein: 15.2, carbs: 13.7, fat: 65.2 },
|
|
217
|
+
servingSize: 28,
|
|
218
|
+
unit: 'g'
|
|
219
|
+
},
|
|
220
|
+
orange: {
|
|
221
|
+
id: 'orange',
|
|
222
|
+
name: 'Orange',
|
|
223
|
+
brand: 'Generic',
|
|
224
|
+
nutritionPer100g: { calories: 47, protein: 0.9, carbs: 11.8, fat: 0.1 },
|
|
225
|
+
servingSize: 154,
|
|
226
|
+
unit: 'g'
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
function calculateDailyNeeds(profile) {
|
|
230
|
+
const age = profile.age || 30;
|
|
231
|
+
const height = profile.height || 170;
|
|
232
|
+
const weight = profile.weight || 70;
|
|
233
|
+
const gender = profile.gender || 'male';
|
|
234
|
+
const activityLevel = profile.activityLevel || 'moderately_active';
|
|
235
|
+
const bmr = gender === 'male'
|
|
236
|
+
? 88.362 + (13.397 * weight) + (4.799 * height) - (5.677 * age)
|
|
237
|
+
: 447.593 + (9.247 * weight) + (3.098 * height) - (4.330 * age);
|
|
238
|
+
const activityMultipliers = {
|
|
239
|
+
sedentary: 1.2,
|
|
240
|
+
lightly_active: 1.375,
|
|
241
|
+
moderately_active: 1.55,
|
|
242
|
+
very_active: 1.725,
|
|
243
|
+
extremely_active: 1.9
|
|
244
|
+
};
|
|
245
|
+
const tdee = bmr * (activityMultipliers[activityLevel] || 1.2);
|
|
246
|
+
let dailyCalories = tdee;
|
|
247
|
+
if (profile.healthGoals?.includes('weight_loss')) {
|
|
248
|
+
dailyCalories -= 500;
|
|
249
|
+
}
|
|
250
|
+
else if (profile.healthGoals?.includes('weight_gain')) {
|
|
251
|
+
dailyCalories += 500;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
calories: Math.round(Math.max(dailyCalories, 1200)),
|
|
255
|
+
protein: Math.round(Math.max(dailyCalories * 0.3 / 4, 50)),
|
|
256
|
+
carbs: Math.round(Math.max(dailyCalories * 0.4 / 4, 100)),
|
|
257
|
+
fat: Math.round(Math.max(dailyCalories * 0.3 / 9, 30))
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function createMeal(name, type, foods) {
|
|
261
|
+
const meal = (0, foodConversion_1.createMealFromFoodItems)(name, type, foods.map(({ food, amount, unit }, index) => ({
|
|
262
|
+
food: food,
|
|
263
|
+
amount,
|
|
264
|
+
unit
|
|
265
|
+
})));
|
|
266
|
+
return {
|
|
267
|
+
...meal,
|
|
268
|
+
instructions: [`Prepare ${name} according to recipe`],
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
// Weekly meal plan templates
|
|
272
|
+
const weeklyMealTemplates = {
|
|
273
|
+
breakfast: [
|
|
274
|
+
{ name: 'Oatmeal with Banana', foods: [
|
|
275
|
+
{ food: mockFoods.oatmeal, amount: 50, unit: 'g' },
|
|
276
|
+
{ food: mockFoods.banana, amount: 100, unit: 'g' }
|
|
277
|
+
] },
|
|
278
|
+
{ name: 'Greek Yogurt with Berries', foods: [
|
|
279
|
+
{ food: mockFoods.yogurt, amount: 170, unit: 'g' },
|
|
280
|
+
{ food: mockFoods.berries, amount: 100, unit: 'g' }
|
|
281
|
+
] },
|
|
282
|
+
{ name: 'Scrambled Eggs', foods: [
|
|
283
|
+
{ food: mockFoods.eggs, amount: 100, unit: 'g' },
|
|
284
|
+
{ food: mockFoods.spinach, amount: 50, unit: 'g' }
|
|
285
|
+
] },
|
|
286
|
+
{ name: 'Avocado Toast', foods: [
|
|
287
|
+
{ food: mockFoods.avocado_toast, amount: 150, unit: 'g' }
|
|
288
|
+
] },
|
|
289
|
+
{ name: 'Protein Smoothie', foods: [
|
|
290
|
+
{ food: mockFoods.smoothie, amount: 300, unit: 'ml' },
|
|
291
|
+
{ food: mockFoods.berries, amount: 50, unit: 'g' }
|
|
292
|
+
] },
|
|
293
|
+
{ name: 'Oatmeal with Berries', foods: [
|
|
294
|
+
{ food: mockFoods.oatmeal, amount: 50, unit: 'g' },
|
|
295
|
+
{ food: mockFoods.berries, amount: 100, unit: 'g' }
|
|
296
|
+
] },
|
|
297
|
+
{ name: 'Yogurt Parfait', foods: [
|
|
298
|
+
{ food: mockFoods.yogurt, amount: 170, unit: 'g' },
|
|
299
|
+
{ food: mockFoods.banana, amount: 80, unit: 'g' },
|
|
300
|
+
{ food: mockFoods.almonds, amount: 15, unit: 'g' }
|
|
301
|
+
] }
|
|
302
|
+
],
|
|
303
|
+
lunch: [
|
|
304
|
+
{ name: 'Chicken Salad', foods: [
|
|
305
|
+
{ food: mockFoods.chicken, amount: 150, unit: 'g' },
|
|
306
|
+
{ food: mockFoods.spinach, amount: 100, unit: 'g' },
|
|
307
|
+
{ food: mockFoods.carrots, amount: 80, unit: 'g' }
|
|
308
|
+
] },
|
|
309
|
+
{ name: 'Salmon with Quinoa', foods: [
|
|
310
|
+
{ food: mockFoods.salmon, amount: 120, unit: 'g' },
|
|
311
|
+
{ food: mockFoods.quinoa, amount: 150, unit: 'g' },
|
|
312
|
+
{ food: mockFoods.broccoli, amount: 100, unit: 'g' }
|
|
313
|
+
] },
|
|
314
|
+
{ name: 'Turkey Wrap', foods: [
|
|
315
|
+
{ food: mockFoods.turkey, amount: 100, unit: 'g' },
|
|
316
|
+
{ food: mockFoods.bread, amount: 90, unit: 'g' },
|
|
317
|
+
{ food: mockFoods.bell_peppers, amount: 80, unit: 'g' }
|
|
318
|
+
] },
|
|
319
|
+
{ name: 'Tuna Salad', foods: [
|
|
320
|
+
{ food: mockFoods.tuna, amount: 100, unit: 'g' },
|
|
321
|
+
{ food: mockFoods.spinach, amount: 100, unit: 'g' },
|
|
322
|
+
{ food: mockFoods.sweet_potato, amount: 150, unit: 'g' }
|
|
323
|
+
] },
|
|
324
|
+
{ name: 'Tofu Stir Fry', foods: [
|
|
325
|
+
{ food: mockFoods.tofu, amount: 150, unit: 'g' },
|
|
326
|
+
{ food: mockFoods.bell_peppers, amount: 100, unit: 'g' },
|
|
327
|
+
{ food: mockFoods.rice, amount: 150, unit: 'g' }
|
|
328
|
+
] },
|
|
329
|
+
{ name: 'Lentil Bowl', foods: [
|
|
330
|
+
{ food: mockFoods.lentils, amount: 200, unit: 'g' },
|
|
331
|
+
{ food: mockFoods.carrots, amount: 100, unit: 'g' },
|
|
332
|
+
{ food: mockFoods.spinach, amount: 80, unit: 'g' }
|
|
333
|
+
] },
|
|
334
|
+
{ name: 'Chicken Pasta', foods: [
|
|
335
|
+
{ food: mockFoods.chicken, amount: 120, unit: 'g' },
|
|
336
|
+
{ food: mockFoods.pasta, amount: 140, unit: 'g' },
|
|
337
|
+
{ food: mockFoods.zucchini, amount: 150, unit: 'g' }
|
|
338
|
+
] }
|
|
339
|
+
],
|
|
340
|
+
dinner: [
|
|
341
|
+
{ name: 'Salmon with Sweet Potato', foods: [
|
|
342
|
+
{ food: mockFoods.salmon, amount: 150, unit: 'g' },
|
|
343
|
+
{ food: mockFoods.sweet_potato, amount: 200, unit: 'g' },
|
|
344
|
+
{ food: mockFoods.broccoli, amount: 100, unit: 'g' }
|
|
345
|
+
] },
|
|
346
|
+
{ name: 'Chicken with Rice', foods: [
|
|
347
|
+
{ food: mockFoods.chicken, amount: 150, unit: 'g' },
|
|
348
|
+
{ food: mockFoods.rice, amount: 150, unit: 'g' },
|
|
349
|
+
{ food: mockFoods.bell_peppers, amount: 120, unit: 'g' }
|
|
350
|
+
] },
|
|
351
|
+
{ name: 'Turkey with Quinoa', foods: [
|
|
352
|
+
{ food: mockFoods.turkey, amount: 150, unit: 'g' },
|
|
353
|
+
{ food: mockFoods.quinoa, amount: 150, unit: 'g' },
|
|
354
|
+
{ food: mockFoods.zucchini, amount: 150, unit: 'g' }
|
|
355
|
+
] },
|
|
356
|
+
{ name: 'Tofu Curry', foods: [
|
|
357
|
+
{ food: mockFoods.tofu, amount: 150, unit: 'g' },
|
|
358
|
+
{ food: mockFoods.rice, amount: 150, unit: 'g' },
|
|
359
|
+
{ food: mockFoods.spinach, amount: 100, unit: 'g' }
|
|
360
|
+
] },
|
|
361
|
+
{ name: 'Lentil Stew', foods: [
|
|
362
|
+
{ food: mockFoods.lentils, amount: 200, unit: 'g' },
|
|
363
|
+
{ food: mockFoods.carrots, amount: 100, unit: 'g' },
|
|
364
|
+
{ food: mockFoods.sweet_potato, amount: 150, unit: 'g' }
|
|
365
|
+
] },
|
|
366
|
+
{ name: 'Tuna with Pasta', foods: [
|
|
367
|
+
{ food: mockFoods.tuna, amount: 120, unit: 'g' },
|
|
368
|
+
{ food: mockFoods.pasta, amount: 140, unit: 'g' },
|
|
369
|
+
{ food: mockFoods.broccoli, amount: 100, unit: 'g' }
|
|
370
|
+
] },
|
|
371
|
+
{ name: 'Chicken Stir Fry', foods: [
|
|
372
|
+
{ food: mockFoods.chicken, amount: 150, unit: 'g' },
|
|
373
|
+
{ food: mockFoods.rice, amount: 150, unit: 'g' },
|
|
374
|
+
{ food: mockFoods.bell_peppers, amount: 100, unit: 'g' },
|
|
375
|
+
{ food: mockFoods.carrots, amount: 80, unit: 'g' }
|
|
376
|
+
] }
|
|
377
|
+
],
|
|
378
|
+
snack: [
|
|
379
|
+
{ name: 'Apple with Almonds', foods: [
|
|
380
|
+
{ food: mockFoods.apple, amount: 150, unit: 'g' },
|
|
381
|
+
{ food: mockFoods.almonds, amount: 20, unit: 'g' }
|
|
382
|
+
] },
|
|
383
|
+
{ name: 'Greek Yogurt', foods: [
|
|
384
|
+
{ food: mockFoods.yogurt, amount: 170, unit: 'g' }
|
|
385
|
+
] },
|
|
386
|
+
{ name: 'Orange with Walnuts', foods: [
|
|
387
|
+
{ food: mockFoods.orange, amount: 150, unit: 'g' },
|
|
388
|
+
{ food: mockFoods.walnuts, amount: 20, unit: 'g' }
|
|
389
|
+
] },
|
|
390
|
+
{ name: 'Berries', foods: [
|
|
391
|
+
{ food: mockFoods.berries, amount: 150, unit: 'g' }
|
|
392
|
+
] },
|
|
393
|
+
{ name: 'Banana with Almonds', foods: [
|
|
394
|
+
{ food: mockFoods.banana, amount: 118, unit: 'g' },
|
|
395
|
+
{ food: mockFoods.almonds, amount: 15, unit: 'g' }
|
|
396
|
+
] },
|
|
397
|
+
{ name: 'Carrots with Almonds', foods: [
|
|
398
|
+
{ food: mockFoods.carrots, amount: 100, unit: 'g' },
|
|
399
|
+
{ food: mockFoods.almonds, amount: 15, unit: 'g' }
|
|
400
|
+
] },
|
|
401
|
+
{ name: 'Apple', foods: [
|
|
402
|
+
{ food: mockFoods.apple, amount: 182, unit: 'g' }
|
|
403
|
+
] }
|
|
404
|
+
]
|
|
405
|
+
};
|
|
406
|
+
function generateWeeklyMealPlan(profile, userId) {
|
|
407
|
+
const needs = calculateDailyNeeds(profile);
|
|
408
|
+
const isVegetarian = profile.dietaryRestrictions?.includes('vegetarian');
|
|
409
|
+
const isVegan = profile.dietaryRestrictions?.includes('vegan');
|
|
410
|
+
const allMeals = [];
|
|
411
|
+
const daysOfWeek = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
|
412
|
+
daysOfWeek.forEach((day, dayIndex) => {
|
|
413
|
+
// Filter meal templates based on dietary restrictions
|
|
414
|
+
const getFilteredMeals = (mealType) => {
|
|
415
|
+
return weeklyMealTemplates[mealType].filter(template => {
|
|
416
|
+
if (isVegan) {
|
|
417
|
+
return !template.foods.some(f => ['chicken', 'salmon', 'turkey', 'tuna', 'eggs', 'yogurt'].includes(f.food.id));
|
|
418
|
+
}
|
|
419
|
+
if (isVegetarian) {
|
|
420
|
+
return !template.foods.some(f => ['chicken', 'salmon', 'turkey', 'tuna'].includes(f.food.id));
|
|
421
|
+
}
|
|
422
|
+
return true;
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
// Select meals for each day with variety
|
|
426
|
+
const breakfastOptions = getFilteredMeals('breakfast');
|
|
427
|
+
const lunchOptions = getFilteredMeals('lunch');
|
|
428
|
+
const dinnerOptions = getFilteredMeals('dinner');
|
|
429
|
+
const snackOptions = getFilteredMeals('snack');
|
|
430
|
+
const breakfast = createMeal(`${day} - ${breakfastOptions[dayIndex % breakfastOptions.length].name}`, 'breakfast', breakfastOptions[dayIndex % breakfastOptions.length].foods);
|
|
431
|
+
const lunch = createMeal(`${day} - ${lunchOptions[dayIndex % lunchOptions.length].name}`, 'lunch', lunchOptions[dayIndex % lunchOptions.length].foods);
|
|
432
|
+
const dinner = createMeal(`${day} - ${dinnerOptions[dayIndex % dinnerOptions.length].name}`, 'dinner', dinnerOptions[dayIndex % dinnerOptions.length].foods);
|
|
433
|
+
const snack = createMeal(`${day} - ${snackOptions[dayIndex % snackOptions.length].name}`, 'snack', snackOptions[dayIndex % snackOptions.length].foods);
|
|
434
|
+
allMeals.push(breakfast, lunch, dinner, snack);
|
|
435
|
+
});
|
|
436
|
+
const totalNutrition = allMeals.reduce((total, meal) => ({
|
|
437
|
+
calories: total.calories + (meal.nutrition?.calories || 0),
|
|
438
|
+
protein: Math.round((total.protein + (meal.nutrition?.protein || 0)) * 10) / 10,
|
|
439
|
+
carbs: total.carbs + (meal.nutrition?.carbs || 0),
|
|
440
|
+
fat: total.fat + (meal.nutrition?.fat || 0)
|
|
441
|
+
}), { calories: 0, protein: 0, carbs: 0, fat: 0 });
|
|
442
|
+
const startDate = new Date();
|
|
443
|
+
const endDate = new Date();
|
|
444
|
+
endDate.setDate(startDate.getDate() + 6);
|
|
445
|
+
return {
|
|
446
|
+
id: `weekly-plan-${Date.now()}`,
|
|
447
|
+
userId: userId,
|
|
448
|
+
name: `Weekly Meal Plan - ${startDate.toLocaleDateString()} to ${endDate.toLocaleDateString()}`,
|
|
449
|
+
description: `Personalized 7-day meal plan with variety and balanced nutrition (${Math.round(totalNutrition.calories / 7)} cal/day avg)`,
|
|
450
|
+
startDate: startDate.toISOString().split('T')[0],
|
|
451
|
+
endDate: endDate.toISOString().split('T')[0],
|
|
452
|
+
dailyPlans: [],
|
|
453
|
+
meals: allMeals,
|
|
454
|
+
totalCalories: totalNutrition.calories,
|
|
455
|
+
totalProtein: totalNutrition.protein,
|
|
456
|
+
totalCarbs: totalNutrition.carbs,
|
|
457
|
+
totalFat: totalNutrition.fat,
|
|
458
|
+
uid: userId,
|
|
459
|
+
goals: {
|
|
460
|
+
dailyCalories: Math.round(totalNutrition.calories / 7),
|
|
461
|
+
proteinPercentage: 25,
|
|
462
|
+
carbsPercentage: 45,
|
|
463
|
+
fatPercentage: 30,
|
|
464
|
+
},
|
|
465
|
+
createdAt: new Date(),
|
|
466
|
+
updatedAt: new Date()
|
|
467
|
+
};
|
|
468
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Recipe, UserProfile } from './types';
|
|
2
|
+
export interface DailyMealPlan {
|
|
3
|
+
date: string;
|
|
4
|
+
dailyCalories: number;
|
|
5
|
+
meals: Recipe[];
|
|
6
|
+
}
|
|
7
|
+
export interface WeeklyMealPlan {
|
|
8
|
+
weekCalories: number;
|
|
9
|
+
days: DailyMealPlan[];
|
|
10
|
+
diversityScore?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function generateWeeklyPlan(profile: UserProfile, recipes: Recipe[]): WeeklyMealPlan;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateWeeklyPlan = generateWeeklyPlan;
|
|
4
|
+
const bmr_1 = require("./bmr");
|
|
5
|
+
const rules_1 = require("./rules");
|
|
6
|
+
const variety_1 = require("./variety");
|
|
7
|
+
function generateWeeklyPlan(profile, recipes) {
|
|
8
|
+
const targetCalories = Math.round((0, bmr_1.calculateTDEE)(profile));
|
|
9
|
+
const filtered = (0, rules_1.applyHealthRules)(recipes, profile.conditions);
|
|
10
|
+
const days = [];
|
|
11
|
+
let weekTotal = 0;
|
|
12
|
+
let allWeekMeals = [];
|
|
13
|
+
const daysOfWeek = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
|
14
|
+
for (let i = 0; i < 7; i++) {
|
|
15
|
+
const dailyMeals = (0, variety_1.selectDiverseMeals)(filtered, targetCalories, allWeekMeals);
|
|
16
|
+
const dailyTotal = dailyMeals.reduce((sum, m) => sum + m.calories, 0);
|
|
17
|
+
days.push({
|
|
18
|
+
date: daysOfWeek[i],
|
|
19
|
+
dailyCalories: dailyTotal,
|
|
20
|
+
meals: dailyMeals
|
|
21
|
+
});
|
|
22
|
+
weekTotal += dailyTotal;
|
|
23
|
+
allWeekMeals.push(...dailyMeals);
|
|
24
|
+
}
|
|
25
|
+
const weekScore = (0, variety_1.scoreMealPlan)(allWeekMeals);
|
|
26
|
+
return {
|
|
27
|
+
weekCalories: weekTotal,
|
|
28
|
+
days,
|
|
29
|
+
diversityScore: weekScore.overallScore
|
|
30
|
+
};
|
|
31
|
+
}
|