from flask import Flask, request, jsonify, render_template import requests import os import math import logging app = Flask(__name__) app.static_folder = 'static' # Configure logging logging.basicConfig(level=logging.DEBUG) # Configuration USDA_API_ENDPOINT = "https://api.nal.usda.gov/fdc/v1" USDA_API_KEY = os.environ.get('USDA_API_KEY') # Error messages INVALID_INPUT_ERROR = "Invalid input" MISSING_REQUIRED_FIELDS_ERROR = "Missing required fields" FAILED_TO_FETCH_NUTRIENT_DATA_ERROR = "Failed to fetch nutrient data" @app.route('/') def index(): return render_template('index.html') @app.route('/api/calculate-metrics', methods=['POST']) def calculate_metrics(): data = request.json app.logger.debug(f"Received data for calculate_metrics: {data}") required_fields = ['age', 'gender', 'heightFeet', 'heightInches', 'weight', 'targetWeight', 'waist', 'neck', 'hip', 'steps', 'standingHours'] missing_fields = [field for field in required_fields if field not in data or data[field] is None] if missing_fields: app.logger.error(f"Missing required fields: {', '.join(missing_fields)}") return jsonify({"error": MISSING_REQUIRED_FIELDS_ERROR, "missing_fields": missing_fields}), 400 try: age = int(data['age']) gender = data['gender'] height_feet = int(data['heightFeet']) height_inches = int(data['heightInches']) weight = float(data['weight']) # in kg target_weight = float(data['targetWeight']) # in kg waist = float(data['waist']) # in cm neck = float(data['neck']) # in cm hip = float(data['hip']) # in cm steps = int(data['steps']) standing_hours = float(data['standingHours']) # Convert height to cm height = (height_feet * 30.48) + (height_inches * 2.54) # Convert to cm except (ValueError, KeyError) as e: app.logger.error(f"Invalid input: {str(e)}") return jsonify({"error": INVALID_INPUT_ERROR, "details": str(e)}), 400 if age <= 0 or height <= 0 or weight <= 0 or target_weight <= 0 or waist <= 0 or neck <= 0 or hip <= 0 or steps < 0 or standing_hours < 0: app.logger.error("Invalid input values") return jsonify({"error": INVALID_INPUT_ERROR, "details": "Input values must be positive numbers"}), 400 if gender not in ['male', 'female', 'other']: app.logger.error("Invalid gender") return jsonify({"error": INVALID_INPUT_ERROR, "details": "Gender must be 'male', 'female', or 'other'"}), 400 # Calculate BMI bmi = weight / ((height / 100) ** 2) # Calculate body fat percentage (using U.S. Navy method) if gender == 'male': body_fat = 86.010 * math.log10(waist - neck) - 70.041 * math.log10(height) + 36.76 elif gender == 'female': body_fat = 163.205 * math.log10(waist + hip - neck) - 97.684 * math.log10(height) - 78.387 else: # For 'other' gender, use an average of male and female calculations body_fat_male = 86.010 * math.log10(waist - neck) - 70.041 * math.log10(height) + 36.76 body_fat_female = 163.205 * math.log10(waist + hip - neck) - 97.684 * math.log10(height) - 78.387 body_fat = (body_fat_male + body_fat_female) / 2 # Calculate lean body mass lean_body_mass = weight * (1 - (body_fat / 100)) # Calculate recommended calorie intake (using Mifflin-St Jeor Equation) if gender == 'male': bmr = 10 * weight + 6.25 * height - 5 * age + 5 elif gender == 'female': bmr = 10 * weight + 6.25 * height - 5 * age - 161 else: # For 'other' gender, use an average of male and female calculations bmr_male = 10 * weight + 6.25 * height - 5 * age + 5 bmr_female = 10 * weight + 6.25 * height - 5 * age - 161 bmr = (bmr_male + bmr_female) / 2 # Adjust for activity level activity_factor = 1.2 + (steps / 10000) * 0.1 + (standing_hours / 24) * 0.1 recommended_calories = bmr * activity_factor # Calculate time to reach target weight weight_difference = abs(weight - target_weight) daily_calorie_deficit = 500 # Assuming a 500 calorie deficit per day days_to_target = (weight_difference * 7700) / daily_calorie_deficit # 7700 calories ≈ 1 kg of body fat response = { 'bmi': round(bmi, 2), 'bodyFatPercentage': round(body_fat, 2), 'leanBodyMass': round(lean_body_mass, 2), 'recommendedCalories': round(recommended_calories), 'timeToTargetWeight': f"{round(days_to_target)} days" } app.logger.debug(f"Calculated metrics: {response}") return jsonify(response) @app.route('/api/personalized-recommendations', methods=['POST']) def get_personalized_recommendations(): data = request.json app.logger.debug(f"Received data for personalized_recommendations: {data}") required_fields = ['age', 'gender', 'height', 'weight', 'targetWeight', 'bmi', 'bodyFatPercentage', 'recommendedCalories', 'steps', 'standingHours'] missing_fields = [field for field in required_fields if field not in data or data[field] is None] if missing_fields: app.logger.error(f"Missing required fields: {', '.join(missing_fields)}") return jsonify({"error": MISSING_REQUIRED_FIELDS_ERROR, "missing_fields": missing_fields}), 400 try: age = int(data['age']) gender = data['gender'] height = float(data['height']) weight = float(data['weight']) target_weight = float(data['targetWeight']) bmi = float(data['bmi']) body_fat_percentage = float(data['bodyFatPercentage']) recommended_calories = int(data['recommendedCalories']) steps = int(data['steps']) standing_hours = float(data['standingHours']) except (ValueError, KeyError) as e: app.logger.error(f"Invalid input: {str(e)}") return jsonify({"error": INVALID_INPUT_ERROR, "details": str(e)}), 400 # Diet recommendations diet_recommendations = [] if bmi < 18.5: diet_recommendations.append("Increase calorie intake with nutrient-dense foods to reach a healthy weight") diet_recommendations.append("Focus on foods high in healthy fats, such as avocados, nuts, and olive oil") diet_recommendations.append("Incorporate protein-rich foods like lean meats, fish, eggs, and legumes") elif 18.5 <= bmi < 25: diet_recommendations.append("Maintain a balanced diet with a focus on whole foods") diet_recommendations.append("Ensure adequate intake of fruits, vegetables, whole grains, and lean proteins") diet_recommendations.append("Monitor portion sizes to maintain your healthy weight") elif 25 <= bmi < 30: diet_recommendations.append("Slightly reduce calorie intake and focus on nutrient-dense, low-calorie foods") diet_recommendations.append("Increase fiber intake through vegetables, fruits, and whole grains") diet_recommendations.append("Choose lean proteins and limit saturated fats") else: diet_recommendations.append("Reduce calorie intake and focus on whole, unprocessed foods") diet_recommendations.append("Prioritize vegetables, lean proteins, and complex carbohydrates") diet_recommendations.append("Avoid sugary drinks and high-calorie snacks") if (gender == 'male' and body_fat_percentage > 25) or (gender == 'female' and body_fat_percentage > 32) or (gender == 'other' and body_fat_percentage > 28): diet_recommendations.append("Increase protein intake to support lean muscle mass") diet_recommendations.append("Consider adding a protein shake or Greek yogurt as a snack") diet_recommendations.append("Include more fish, chicken, turkey, or plant-based proteins in your meals") diet_recommendations.append(f"Aim for {recommended_calories} calories per day") diet_recommendations.append("Include a variety of colorful fruits and vegetables in your diet") diet_recommendations.append("Stay hydrated by drinking at least 8 glasses of water daily") diet_recommendations.append("Limit processed foods and choose whole grains over refined grains") if weight > target_weight: diet_recommendations.append("Create a calorie deficit of 500 calories per day to lose weight") diet_recommendations.append("Use smaller plates to help control portion sizes") diet_recommendations.append("Start meals with a salad or vegetable soup to increase satiety") elif weight < target_weight: diet_recommendations.append("Increase your calorie intake by 500 calories per day to gain weight") diet_recommendations.append("Add healthy, calorie-dense foods like nuts, seeds, and dried fruits to your meals") diet_recommendations.append("Consider drinking smoothies made with fruits, oats, and protein powder") else: diet_recommendations.append("Maintain your current calorie intake to maintain your weight") diet_recommendations.append("Practice mindful eating and listen to your body's hunger and fullness cues") # Exercise recommendations exercise_recommendations = [] if steps < 5000: exercise_recommendations.append("Gradually increase your daily step count to at least 7,500 steps") exercise_recommendations.append("Take short walks during breaks or after meals") exercise_recommendations.append("Use stairs instead of elevators when possible") elif 5000 <= steps < 10000: exercise_recommendations.append("Aim to reach 10,000 steps per day for better health") exercise_recommendations.append("Try brisk walking or light jogging to increase step count") exercise_recommendations.append("Consider using a treadmill desk or walking meetings") else: exercise_recommendations.append("Great job on your step count! Consider adding more intense exercises") exercise_recommendations.append("Incorporate interval training or hill walks to challenge yourself") exercise_recommendations.append("Set new step goals to maintain motivation") if standing_hours < 2: exercise_recommendations.append("Try to increase your standing time to at least 2-4 hours per day") exercise_recommendations.append("Use a standing desk or elevate your workstation for part of the day") exercise_recommendations.append("Take phone calls while standing or walking") elif 2 <= standing_hours < 4: exercise_recommendations.append("Good job on standing! Aim to increase your standing time to 4-6 hours per day") exercise_recommendations.append("Alternate between sitting and standing every 30-60 minutes") exercise_recommendations.append("Try gentle exercises or stretches while standing") else: exercise_recommendations.append("Excellent standing habits! Maintain your current standing routine") exercise_recommendations.append("Incorporate balance exercises or yoga poses while standing") exercise_recommendations.append("Consider a treadmill desk for light walking while working") exercise_recommendations.append("Include strength training exercises at least 2-3 times per week") exercise_recommendations.append("Aim for at least 150 minutes of moderate-intensity aerobic activity per week") exercise_recommendations.append("Don't forget to stretch before and after exercises to improve flexibility") if weight > target_weight: exercise_recommendations.append("Incorporate high-intensity interval training (HIIT) to boost fat burning") exercise_recommendations.append("Try circuit training to combine strength and cardio exercises") exercise_recommendations.append("Consider joining group fitness classes for motivation and guidance") elif weight < target_weight: exercise_recommendations.append("Focus on compound exercises and progressive overload to build muscle mass") exercise_recommendations.append("Incorporate resistance band exercises for muscle growth") exercise_recommendations.append("Ensure adequate rest between workouts for muscle recovery and growth") else: exercise_recommendations.append("Mix cardio and strength training to maintain your current weight and improve overall fitness") exercise_recommendations.append("Try new activities or sports to keep your routine interesting") exercise_recommendations.append("Set performance-based goals to stay motivated") if age > 50: exercise_recommendations.append("Include balance and flexibility exercises to maintain mobility") exercise_recommendations.append("Consider low-impact activities like swimming or cycling to protect joints") response = { 'dietRecommendations': diet_recommendations, 'exerciseRecommendations': exercise_recommendations } app.logger.debug(f"Generated recommendations: {response}") return jsonify(response) @app.route('/api/search-food', methods=['GET']) def search_food(): query = request.args.get('query', '') if not query: return jsonify({"error": "Missing query parameter"}), 400 try: response = requests.get( f"{USDA_API_ENDPOINT}/foods/search", params={ "api_key": USDA_API_KEY, "query": query, "dataType": ["Survey (FNDDS)"], "pageSize": 10 } ) response.raise_for_status() data = response.json() results = [] for food in data.get('foods', []): nutrients = {nutrient['nutrientName']: nutrient['value'] for nutrient in food.get('foodNutrients', [])} results.append({ 'description': food['description'], 'calories': nutrients.get('Energy', 0), 'protein': nutrients.get('Protein', 0), 'carbs': nutrients.get('Carbohydrate, by difference', 0), 'fat': nutrients.get('Total lipid (fat)', 0) }) return jsonify(results) except requests.RequestException as e: app.logger.error(f"Error fetching food data: {str(e)}") return jsonify({"error": "Failed to fetch food data"}), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=7860, debug=True)