* } */ public function calculate( float $weightKg, int $heightCm, CarbonInterface $dateOfBirth, UserSex $sex, PhysicalActivityLevel $activityLevel, WeightGoal $weightGoal, PacePreference $pacePreference, ): array { $sexCoefficient = match ($sex) { UserSex::MAN => 5, UserSex::WOMAN => -161, default => throw new InvalidArgumentException('A metabolic calculation reference is required.'), }; $age = (int) $dateOfBirth->diffInYears(now()); $restingMetabolism = (int) round( (10 * $weightKg) + (6.25 * $heightCm) - (5 * $age) + $sexCoefficient ); $activityFactor = $this->activityFactor($activityLevel); $maintenanceCalories = (int) (round(($restingMetabolism * $activityFactor) / 10) * 10); $adjustmentPercent = $this->calorieAdjustmentPercent($weightGoal, $pacePreference); $unboundedCalories = (int) (round(($maintenanceCalories * (1 + $adjustmentPercent)) / 10) * 10); $minimumCalories = max(1200, (int) (ceil(($restingMetabolism * 0.8) / 10) * 10)); $calorieTarget = max($minimumCalories, $unboundedCalories); $heightMeters = $heightCm / 100; $proteinReferenceWeight = min($weightKg, 25 * ($heightMeters ** 2)); $proteinFactor = $this->proteinFactor($activityLevel, $weightGoal); $proteins = max(1, (int) round($proteinReferenceWeight * $proteinFactor)); $fats = max(1, (int) round(($calorieTarget * 0.25) / 9)); $carbs = max(0, (int) round(($calorieTarget - ($proteins * 4) - ($fats * 9)) / 4)); $macroCalories = ($proteins * 4) + ($carbs * 4) + ($fats * 9); return [ 'formula_version' => self::FORMULA_VERSION, 'calories' => $macroCalories, 'proteins' => $proteins, 'carbs' => $carbs, 'fats' => $fats, 'resting_metabolism' => $restingMetabolism, 'maintenance_calories' => $maintenanceCalories, 'calorie_adjustment_percent' => $adjustmentPercent, 'calculation_details' => [ 'activityFactor' => $activityFactor, 'ageAtCalculation' => $age, 'heightCm' => $heightCm, 'inputWeightKg' => round($weightKg, 2), 'minimumCalories' => $minimumCalories, 'pacePreference' => $pacePreference->value, 'proteinFactor' => $proteinFactor, 'proteinReferenceWeightKg' => round($proteinReferenceWeight, 2), 'sexReference' => $sex->value, 'weightGoal' => $weightGoal->value, ], ]; } private function activityFactor(PhysicalActivityLevel $activityLevel): float { return match ($activityLevel) { PhysicalActivityLevel::SEDENTARY => 1.2, PhysicalActivityLevel::LIGHTLY_ACTIVE => 1.375, PhysicalActivityLevel::MODERATELY_ACTIVE => 1.55, PhysicalActivityLevel::VERY_ACTIVE => 1.725, PhysicalActivityLevel::ATHLETE => 1.9, }; } private function calorieAdjustmentPercent(WeightGoal $weightGoal, PacePreference $pacePreference): float { if ($weightGoal === WeightGoal::MAINTAIN_WEIGHT) { return 0; } $magnitude = match ($pacePreference) { PacePreference::SLOW => $weightGoal === WeightGoal::LOSE_WEIGHT ? 0.10 : 0.05, PacePreference::NORMAL => $weightGoal === WeightGoal::LOSE_WEIGHT ? 0.15 : 0.10, PacePreference::FAST => $weightGoal === WeightGoal::LOSE_WEIGHT ? 0.20 : 0.15, }; return $weightGoal === WeightGoal::LOSE_WEIGHT ? -$magnitude : $magnitude; } private function proteinFactor(PhysicalActivityLevel $activityLevel, WeightGoal $weightGoal): float { $factor = match ($activityLevel) { PhysicalActivityLevel::SEDENTARY => 1.2, PhysicalActivityLevel::LIGHTLY_ACTIVE => 1.4, PhysicalActivityLevel::MODERATELY_ACTIVE => 1.6, PhysicalActivityLevel::VERY_ACTIVE, PhysicalActivityLevel::ATHLETE => 1.8, }; $factor += match ($weightGoal) { WeightGoal::LOSE_WEIGHT => 0.2, WeightGoal::GAIN_WEIGHT => 0.1, WeightGoal::MAINTAIN_WEIGHT => 0, }; return min($factor, 2.0); } }