calculate( weightKg: 80, heightCm: 180, dateOfBirth: CarbonImmutable::parse('1996-08-14'), sex: UserSex::MAN, activityLevel: PhysicalActivityLevel::MODERATELY_ACTIVE, weightGoal: WeightGoal::MAINTAIN_WEIGHT, pacePreference: PacePreference::NORMAL, ); expect($plan) ->formula_version->toBe('mifflin_st_jeor_v1') ->resting_metabolism->toBe(1780) ->maintenance_calories->toBe(2760) ->calorie_adjustment_percent->toBe(0.0) ->proteins->toBe(128) ->fats->toBe(77) ->carbs->toBe(389) ->calories->toBe(2761) ->and(($plan['proteins'] * 4) + ($plan['carbs'] * 4) + ($plan['fats'] * 9)) ->toBe($plan['calories']); }); it('applies bounded loss and gain adjustments', function (WeightGoal $goal, PacePreference $pace, float $expectedAdjustment) { $plan = (new NutritionPlanCalculator)->calculate( weightKg: 60, heightCm: 165, dateOfBirth: CarbonImmutable::parse('1990-01-01'), sex: UserSex::WOMAN, activityLevel: PhysicalActivityLevel::LIGHTLY_ACTIVE, weightGoal: $goal, pacePreference: $pace, ); expect($plan['calorie_adjustment_percent'])->toBe($expectedAdjustment) ->and($plan['calories'])->toBeGreaterThanOrEqual(1200); })->with([ 'normal loss' => [WeightGoal::LOSE_WEIGHT, PacePreference::NORMAL, -0.15], 'fast gain' => [WeightGoal::GAIN_WEIGHT, PacePreference::FAST, 0.15], ]); it('rejects a non binary metabolic calculation reference', function () { (new NutritionPlanCalculator)->calculate( weightKg: 70, heightCm: 170, dateOfBirth: CarbonImmutable::parse('1990-01-01'), sex: UserSex::UNKNOWN, activityLevel: PhysicalActivityLevel::SEDENTARY, weightGoal: WeightGoal::MAINTAIN_WEIGHT, pacePreference: PacePreference::SLOW, ); })->throws(InvalidArgumentException::class);