monthly_diet/index.php
Diogo Peralta Cordeiro 821c3c926c
integrate open food facts
fix bug on lack of food randomness
2025-01-03 22:48:46 +00:00

473 lines
18 KiB
PHP

<!DOCTYPE html>
<HTML>
<HEAD>
<META name="robots" content="noindex, nofollow" />
<META http-equiv="Content-type" content="text/html;charset=UTF-8" />
<meta charset="utf-8">
<META http-equiv="cache-control" content="no-cache" />
<META http-equiv="expires" content="0" />
<META http-equiv="pragma" content="no-cache" />
<META name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<TITLE>Plano Mensal de Dieta e Lista de Compras</TITLE>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=PT+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
<LINK rel="stylesheet" href="https://diogo.site/assets/css/main.css" />
<STYLE>
P {
text-align: justify;
}
form, input, textarea, button, select {
font-family: inherit;
font-weight: inherit;
font-size: inherit;
}
form {
margin-bottom: 15px;
}
label {
font-weight: bold;
}
input[type="text"], input[type="email"], textarea {
width: 100%;
padding: 0.5em;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
button[type="submit"] {
background-color: #1A936F;
color: white;
padding: 0.5em 1em;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 0.3em;
}
button[type="submit"]:hover {
background-color: #22be90;
}
.plate {
border: 1px dotted rgba(0, 0, 0, 0.2);
padding: 10px;
margin: 10px 0;
}
.plate p{
margin-bottom: 0.5rem;
}
.plateDetails {
color: #9cadc7;
}
</STYLE>
</HEAD>
<BODY>
<form method="get">
<label for="include_weekends">Incluir fim-de-semana:
<input type="checkbox" name="include_weekends" id="include_weekends" <?php echo isset($_GET['include_weekends']) ? 'checked' : ''; ?>>
</label>
<br>
<label for="is_female">Sexo:</label>
<select name="is_female" id="is_female">
<option value="false" <?php echo isset($_GET['is_female']) && $_GET['is_female'] == 'false' ? 'selected' : ''; ?>>Masculino</option>
<option value="true" <?php echo isset($_GET['is_female']) && $_GET['is_female'] == 'true' ? 'selected' : ''; ?>>Feminino</option>
</select>
<br>
<label for="weight">Peso (kg):</label>
<input type="number" step="0.1" name="weight" id="weight" value="<?php echo isset($_GET['weight']) ? $_GET['weight'] : ''; ?>" required>
<br>
<label for="height">Altura (cm):</label>
<input type="number" step="0.1" name="height" id="height" value="<?php echo isset($_GET['height']) ? $_GET['height'] : ''; ?>" required>
<br>
<label for="age">Idade:</label>
<input type="number" name="age" id="age" value="<?php echo isset($_GET['age']) ? $_GET['age'] : ''; ?>" required>
<br>
<label for="activity_level">Nível de atividade:</label>
<select name="activity_level" id="activity_level">
<option value="1.2" <?php echo isset($_GET['activity_level']) && $_GET['activity_level'] == '1.2' ? 'selected' : ''; ?>>Sedentário (pouco ou nenhum exercício)</option>
<option value="1.55" <?php echo isset($_GET['activity_level']) && $_GET['activity_level'] == '1.55' ? 'selected' : ''; ?>>Atividade moderada (exercício moderado)</option>
<option value="1.9" <?php echo isset($_GET['activity_level']) && $_GET['activity_level'] == '1.9' ? 'selected' : ''; ?>>Atividade intensa (exercício intenso)</option>
</select>
<br>
<label for="bmr">Meta de Calorias (opcional):</label>
<input type="number" name="bmr" id="bmr" value="<?php echo isset($_GET['bmr']) ? $_GET['bmr'] : ''; ?>">
<br>
<button type="submit">Gerar plano alimentar</button>
<button type="submit" name="rand">Forçar uma nova ementa</button>
</form>
<?php
if (!isset($_GET["rand"])) {
$currentMonth = date('n');
mt_srand($currentMonth);
}
// https://www.php.net/manual/en/function.shuffle.php#83007
function shuffle_with_keys(&$array) {
/* Auxiliary array to hold the new order */
$aux = [];
/* We work with an array of the keys */
$keys = array_keys($array);
/* We shuffle the keys */
shuffle($keys);
/* We iterate thru' the new order of the keys */
foreach($keys as $key) {
/* We insert the key, value pair in its new order */
$aux[$key] = $array[$key];
/* We remove the element from the old array to save memory */
unset($array[$key]);
}
/* The auxiliary array with the new order overwrites the old variable */
$array = $aux;
}
$mealOptionsAndIngredients = [
'Brunch' => [
'Vegetarian' => [
'Batido de iogurte grego, aveia, e frutos vermelhos congelados' => [5601009969135, 5601009967780, 5601009975327],
'Torrada integral com abacate e ovos mexidos' => [5607233572813, 5601009984329, 5601009920334],
'Omelete de espinafres e tomate com pão integral' => [5601009920334, 5601009297641, 5601009986958, 5607233572813],
'Papas de aveia com mel e frutos vermelhos congelados' => [5601009967780, 5607047004982, 5601009975327],
'Batido de whey protein, aveia e frutos vermelhos congelados' => [5601607073999, 5601009967780, 5601009975327],
'Batido de whey protein com banana e leite' => [5601607073999, 95159331, 5601049132995],
],
],
'Lunch' => [
'Poultry' => [
'Hambúrguer de peru com batatas-doce assada e salada' => [5601312306610, 5601009922116, 5601009948017],
'Wrap de frango com abacate e legumes' => [5601009984831, 2689349006997, 5601009984329, 5601009985135],
'Peito de frango grelhado com quinoa e espinafres' => [2689349006997, 5607047006467, 5601009297641],
'Wrap de peru com húmus e legumes' => [5601312306610, 5607047020265, 5601009984831, 5601009985135],
],
'Fish' => [
'Filetes de peixe branco com puré de batatas e feijão-verde' => [5601009500567, 5601009940196, 5601009500758],
'Salmão grelhado com puré de batatas e brócolos' => [5601009960439, 5601009940196, 5601312500223],
],
'Vegetarian' => [
'Tofu salteado com pimentos e quinoa' => [5607047013687, 5601009942060, 5607047006467],
],
],
'Snack' => [
'Vegetarian' => [
'Iogurte grego com frutos vermelhos congelados' => [5601009969135, 5601009975327],
'Queijo cottage com mel e frutos secos' => [5607047008058, 5607047004982, 3271670001421],
'Ovos cozidos com uvas' => [5601009920334, 7754856000204],
'Iogurte grego com granola baixa em açúcar' => [5601009969135, 5601009992706],
'Queijo cottage com uvas' => [5607047008058, 7754856000204],
],
],
'PreWorkout' => [
'Vegetarian' => [
'Barra de proteína (baixo em açúcar)' => [5607047017722],
'Tortitas de arroz com mel ou manteiga de amendoim e uma maçã' => [5607047013090, 5607047004982, 5601009997435, 5601009990597],
'Um punhado de frutos secos e uma banana' => [3271670001421, 95159331],
],
],
'Dinner' => [
'Fish' => [
'Salmão grelhado com batatas-doce e feijão-verde' => [5601009960439, 5601009922116, 5601009500758],
'Peixe grelhado com legumes salteados' => [5601009500567, 5601009985135],
],
'Meat' => [
'Salteado de carne de vaca magra com arroz e legumes' => [5600294202255, 5601009996995, 5601009985135],
'Carne picada de vaca com quinoa e pimentos' => [5601002069467, 5607047006467, 5601009942060],
],
'Vegetarian' => [
'Tofu salteado com pimentos e quinoa' => [5607047013687, 5601009942060, 5607047006467],
],
],
];
function fetchNutritionData($productId) {
// Define the directory to store JSON files
$dataDirectory = 'nutrition_data/';
// Create the directory if it doesn't exist
if (!is_dir($dataDirectory)) {
mkdir($dataDirectory, 0777, true);
}
// Define the file path where the JSON will be saved or loaded
$filePath = $dataDirectory . $productId . '.json';
// Check if the file already exists
if (file_exists($filePath)) {
// Load and return the cached data
$jsonData = file_get_contents($filePath);
$nutritionData = json_decode($jsonData, true);
//echo "Loaded from cache: $productId\n";
} else {
// Fetch the data from Open Food Facts API
$url = "https://world.openfoodfacts.org/api/v0/product/$productId.json";
// Perform the request
$response = file_get_contents($url);
// Check if the response is valid
if ($response !== false) {
// Decode the JSON response
$nutritionData = json_decode($response, true);
// Save the JSON response to the local file for future use
file_put_contents($filePath, $response);
//echo "Fetched and saved data: $productId\n";
} else {
echo "Error fetching data for product ID: $productId\n";
return null;
}
}
// Access the nutritional data
if (isset($nutritionData['product']) && isset($nutritionData['product']['nutriments'])) {
$nutrients = $nutritionData['product']['nutriments'];
return [
'calories' => $nutrients['energy-kcal_serving'] ?? $nutrients['energy-kcal_100g'],
'protein' => $nutrients['proteins_serving'] ?? $nutrients['proteins_100g'],
'fat' => $nutrients['fat_serving'] ?? $nutrients['fat_100g'],
'carbs' => $nutrients['carbohydrates_serving'] ?? $nutrients['carbohydrates_100g'],
'portion' => $nutritionData['product']['serving_quantity'] ?? 100, // Assuming values are per 100g
'unit' => $nutritionData['product']['serving_quantity_unit'] ?? 'g', // Default unit for grams
'name' => $nutritionData['product']['product_name_pt'] ?? $nutritionData['product']['product_name'] // Default unit for grams
];
}
return null;
}
// https://en.wikipedia.org/wiki/Harris%E2%80%93Benedict_equation
// https://www.nutritioncarepro.com/harris-benedict-equation
// https://www.k-state.edu/paccats/Contents/PA/PDF/Physical%20Activity%20and%20Controlling%20Weight.pdf
// https://www.healthline.com/health/what-is-basal-metabolic-rate#takeaway
function calculateBMR($is_female, $weight, $height, $age, $activity_level) {
if ($is_female)
$bmr = (9.5634 * $weight) + (1.8496 * $height) - (4.6756 * $age) + 655.0955;
else
$bmr = (13.7516 * $weight) + (5.0033 * $height) - (6.755 * $age) + 66.473;
return $bmr * $activity_level;
}
function calculateMealNutrition($ingredients, $scaleFactors = []) {
$total = ['calories' => 0, 'protein' => 0, 'fat' => 0, 'carbs' => 0];
$ingredientPortions = []; // To track the portions for the shopping list
foreach ($ingredients as $ingredient) {
$nutritionData = fetchNutritionData($ingredient);
if ($nutritionData) {
$scale = $scaleFactors[$ingredient] ?? 1;
foreach ($total as $key => $value) {
$total[$key] += $nutritionData[$key] * $scale;
}
// Track the portion of each ingredient
$ingredientPortions[$ingredient] = ($ingredientPortions[$ingredient] ?? 0) + $scale;
}
}
return ['nutrition' => $total, 'portions' => $ingredientPortions];
}
function getBalancedMeal($mealType, &$selectedMeals, &$remainingGoals) {
global $mealOptionsAndIngredients;
if (!isset($mealOptionsAndIngredients[$mealType])) {
return ["Base de Dados vazia :s", [], ['calories' => 0, 'protein' => 0, 'fat' => 0, 'carbs' => 0], []];
}
$categories = array_keys($mealOptionsAndIngredients[$mealType]);
shuffle($categories); // Shuffle categories for randomness
foreach ($categories as $category) {
$availableMeals = array_filter(
$mealOptionsAndIngredients[$mealType][$category],
function ($meal) use ($selectedMeals, $mealType) {
return !in_array($meal, $selectedMeals[$mealType]); // Exclude already selected meals
}
);
// If no meals are available, consider all meals in the category
if (empty($availableMeals)) {
$availableMeals = $mealOptionsAndIngredients[$mealType][$category];
// Reset selection of this type
$selectedMeals[$mealType] = [];
}
shuffle_with_keys($availableMeals); // Shuffle available meals for randomness
foreach ($availableMeals as $mealName => $ingredients) {
$totalCalories = 0;
$totalProtein = 0;
$totalFat = 0;
$totalCarbs = 0;
// Calculate total nutritional values for the meal dynamically
foreach ($ingredients as $ingredientId) {
$nutritionData = fetchNutritionData($ingredientId);
$totalCalories += $nutritionData['calories'];
$totalProtein += $nutritionData['protein'];
$totalFat += $nutritionData['fat'];
$totalCarbs += $nutritionData['carbs'];
}
// Calculate scale factors based on remaining goals
$scaleCalories = $totalCalories > 0 ? $remainingGoals['calories'] / $totalCalories : 1;
$scaleProtein = $totalProtein > 0 ? $remainingGoals['protein'] / $totalProtein : 1;
$scaleFat = $totalFat > 0 ? $remainingGoals['fat'] / $totalFat : 1;
$scaleCarbs = $totalCarbs > 0 ? $remainingGoals['carbs'] / $totalCarbs : 1;
// Scale the meal to fit the remaining nutritional goals
$scale = min($scaleCalories, $scaleProtein, $scaleFat, $scaleCarbs);
// If a valid scale is found, calculate the nutrition
if ($scale > 0) {
$mealNutrition = calculateMealNutrition($ingredients, $scale);
$nutrition = $mealNutrition['nutrition'];
$portions = $mealNutrition['portions'];
// Ensure the meal fits within the remaining goals
if (
$nutrition['calories'] <= $remainingGoals['calories'] &&
$nutrition['protein'] <= $remainingGoals['protein'] &&
$nutrition['fat'] <= $remainingGoals['fat'] &&
$nutrition['carbs'] <= $remainingGoals['carbs']
) {
$selectedMeals[$mealType] [] = $mealName;
foreach ($remainingGoals as $key => &$value) {
$value -= $nutrition[$key]; // Update remaining goals
}
unset($value); // Clean up reference
return [$mealName, $ingredients, $nutrition, $portions];
}
}
}
}
return ["Já comeste o suficiente, agora passa fome!", [], ['calories' => 0, 'protein' => 0, 'fat' => 0, 'carbs' => 0], []];
}
function getWeeksOfCurrentMonth() {
$currentDate = new DateTime();
$currentDate->modify('first day of this month');
$startOfMonth = $currentDate->format('Y-m-d');
$endOfMonth = $currentDate->modify('last day of this month')->format('Y-m-d');
$weeks = [];
$currentWeekStart = new DateTime($startOfMonth);
while ($currentWeekStart->format('Y-m-d') <= $endOfMonth) {
$weekStart = clone $currentWeekStart;
$weekEnd = clone $currentWeekStart;
$weekEnd->modify('next Sunday');
if ($weekEnd->format('Y-m-d') > $endOfMonth) {
$weekEnd = new DateTime($endOfMonth);
}
$weeks[] = [
'week_start' => $weekStart->format('Y-m-d'),
'week_end' => $weekEnd->format('Y-m-d'),
];
$currentWeekStart->modify('next Monday');
}
return $weeks;
}
function generateMonthPlan($includeWeekends, $is_female, $weight, $height, $age, $activity_level, $bmr = null) {
global $mealOptionsAndIngredients;
$mealtypeToPortuguese = ['Brunch' => 'Pequeno-almoço', 'Lunch' => 'Almoço', 'Snack' => 'Lanche', 'PreWorkout' => 'Pré-treino', 'Dinner' => 'Jantar'];
$bmr = is_null($bmr) ? calculateBMR($is_female, $weight, $height, $age, $activity_level) : $bmr;
// https://www.healthline.com/nutrition/best-macronutrient-ratio
// These ranges are recommended by the Institute of Medicine and are widely accepted in nutritional science.
// https://www.nal.usda.gov/programs/fnic
// Carbohydrates provide 4 calories per gram,
// protein provides 4 calories per gram,
// and fat provides 9 calories per gram.
$dailyMacronutrientGoals = [
'calories' => round($bmr),
'protein' => round($bmr * 0.3 / 4), // "10-35% from protein"
'fat' => round($bmr * 0.25 / 9), // "20-35% from fats"
'carbs' => round($bmr * 0.45 / 4), // "45-65% of your daily calories from carbs"
];
$weeks = getWeeksOfCurrentMonth();
$usedIngredients = [];
$plan = "<h1>Plano Alimentar do Mês</h1>\n";
$plan .= "<h2>Meta Diária de Macronutrientes:</h2>\n";
$plan .= "<ul><li>Calorias: {$dailyMacronutrientGoals['calories']} kcal</li>";
$plan .= "<li>Proteína: {$dailyMacronutrientGoals['protein']} g</li>";
$plan .= "<li>Gordura: {$dailyMacronutrientGoals['fat']} g</li>";
$plan .= "<li>Carboidratos: {$dailyMacronutrientGoals['carbs']} g</li></ul>";
foreach ($weeks as $week) {
$plan .= "<h2>Semana de {$week['week_start']} a {$week['week_end']}</h2>\n";
$daysOfWeek = $includeWeekends
? ['Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado', 'Domingo']
: ['Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira'];
$selectedMeals = ['Brunch' => [], 'Lunch' => [], 'Snack' => [], 'PreWorkout' => [], 'Dinner' => []]; // Reset weekly meal selection
foreach ($daysOfWeek as $day) {
$plan .= "<h3>{$day}</h3>\n";
$remainingGoals = $dailyMacronutrientGoals; // Use daily goals for each day
foreach (['Brunch', 'Lunch', 'Snack', 'PreWorkout', 'Dinner'] as $mealType) {
list($meal, $ingredients, $nutrition, $portions) = getBalancedMeal($mealType, $selectedMeals, $remainingGoals);
$plan .= "<div class=\"plate\"><p><strong>{$mealtypeToPortuguese[$mealType]}:</strong> {$meal}</p>\n";
$plan .= "<span class=\"plateDetails\"><p><strong>Ingredientes:</strong> ";
$ingredientList = [];
foreach ($ingredients as $ingredientId) {
$nutritionData = fetchNutritionData($ingredientId);
$gramsRequired = $portions[$ingredientId] * $nutritionData['portion'];
$ingredientList[] = "{$nutritionData['name']} ({$gramsRequired} {$nutritionData['unit']})";
}
$plan .= implode(", ", $ingredientList) . "</p>\n";
$plan .= "<p><strong>Informação Nutricional:</strong> {$nutrition['calories']} kcal, {$nutrition['protein']} g proteína, "
. "{$nutrition['fat']} g gordura, {$nutrition['carbs']} g carboidratos</p></span></div>\n";
// Update used ingredients and portions
foreach ($portions as $ingredientId => $portion) {
$ingredientPortions[$ingredientId] = ($ingredientPortions[$ingredientId] ?? 0) + $portion;
}
}
}
}
// Generate shopping list
$plan .= "<h1>Lista de Compras do Mês</h1><ul>\n";
foreach ($ingredientPortions as $ingredientId => $totalPortion) {
$nutritionData = fetchNutritionData($ingredientId);
$gramsRequired = $totalPortion * $nutritionData['portion'];
$plan .= "<li>{$nutritionData['name']}: {$gramsRequired} {$nutritionData['unit']}</li>\n";
}
$plan .= "</ul>\n";
return $plan;
}
// Check if all required GET parameters are set
if ($_SERVER['REQUEST_METHOD'] != 'GET' || !isset($_GET['is_female'], $_GET['weight'], $_GET['height'], $_GET['age'], $_GET['activity_level'])) {
echo "O plano será gerado assim que preencheres o formulário.";
exit;
} else {
// Process parameters
$includeWeekends = ($_GET['include_weekends'] == "on") ? true : false;
$is_female = ($_GET['is_female'] == "true");
$weight = (float) $_GET['weight'];
$height = (int) $_GET['height'];
$age = (int) $_GET['age'];
$activity_level = (float) $_GET['activity_level'];
$bmr = (isset($_GET['bmr']) && !empty($_GET['bmr'])) ? (int) $_GET['bmr'] : null;
echo generateMonthPlan($includeWeekends, $is_female, $weight, $height, $age, $activity_level, $bmr);
}