|
from flask import Flask, request, jsonify, render_template |
|
import requests |
|
import os |
|
import math |
|
import logging |
|
|
|
app = Flask(__name__) |
|
app.static_folder = 'static' |
|
|
|
|
|
logging.basicConfig(level=logging.DEBUG) |
|
|
|
|
|
USDA_API_ENDPOINT = "https://api.nal.usda.gov/fdc/v1" |
|
USDA_API_KEY = os.environ.get('USDA_API_KEY') |
|
|
|
|
|
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']) |
|
target_weight = float(data['targetWeight']) |
|
waist = float(data['waist']) |
|
neck = float(data['neck']) |
|
hip = float(data['hip']) |
|
steps = int(data['steps']) |
|
standing_hours = float(data['standingHours']) |
|
|
|
|
|
height = (height_feet * 30.48) + (height_inches * 2.54) |
|
|
|
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 |
|
|
|
|
|
bmi = weight / ((height / 100) ** 2) |
|
|
|
|
|
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: |
|
|
|
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 |
|
|
|
|
|
lean_body_mass = weight * (1 - (body_fat / 100)) |
|
|
|
|
|
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: |
|
|
|
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 |
|
|
|
|
|
activity_factor = 1.2 + (steps / 10000) * 0.1 + (standing_hours / 24) * 0.1 |
|
recommended_calories = bmr * activity_factor |
|
|
|
|
|
weight_difference = abs(weight - target_weight) |
|
daily_calorie_deficit = 500 |
|
days_to_target = (weight_difference * 7700) / daily_calorie_deficit |
|
|
|
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 = [] |
|
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 = [] |
|
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) |