MarvinRoque commited on
Commit
93e01f8
·
1 Parent(s): 72ddeef
Files changed (2) hide show
  1. app.py +94 -6
  2. nutrition.py +192 -0
app.py CHANGED
@@ -7,6 +7,7 @@ import unicodedata
7
  import json
8
  import re
9
  import random
 
10
 
11
  # Carregar o JSON
12
  with open("exercicios.json", "r", encoding="utf-8") as f:
@@ -494,6 +495,27 @@ def gerar_split(dias, budget=45, objetivos=["hipertrofia"], lesoes=[]):
494
 
495
  return treino_semana
496
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
 
498
 
499
  # -------------------------
@@ -572,15 +594,81 @@ def responder(prompt: str):
572
  "treino": treino
573
  }
574
 
575
- # -------------------------------
576
  # 🚀 NUTRIÇÃO
577
  # -------------------------------
578
  if intenções["nutricao"]:
579
- # 🚨 placeholder aqui vai entrar função real depois
580
- resposta_final["nutricao"] = {
581
- "status": "em_construcao",
582
- "mensagem": "Ainda não implementei a geração de plano nutricional."
583
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
 
585
  # -------------------------------
586
  # 🚀 Recuperação (extra opcional)
 
7
  import json
8
  import re
9
  import random
10
+ from nutrition import UserProfile, build_basic_plan
11
 
12
  # Carregar o JSON
13
  with open("exercicios.json", "r", encoding="utf-8") as f:
 
495
 
496
  return treino_semana
497
 
498
+ def gerar_plano(age, sex, weight, height, activity, goal, intensity):
499
+ try:
500
+ user = UserProfile(
501
+ age=int(age),
502
+ sex=sex,
503
+ weight_kg=float(weight),
504
+ height_cm=float(height),
505
+ activity=activity,
506
+ )
507
+ plan = build_basic_plan(user, goal=goal, intensity=intensity)
508
+ return (
509
+ f"📊 **Plano Nutricional**\n\n"
510
+ f"- Calorias alvo: {plan.calories_target} kcal\n"
511
+ f"- Proteína: {plan.protein_g} g\n"
512
+ f"- Carboidratos: {plan.carbs_g} g\n"
513
+ f"- Gorduras: {plan.fats_g} g\n\n"
514
+ f"ℹ️ {plan.note}\n"
515
+ )
516
+ except Exception as e:
517
+ return f"Erro: {str(e)}"
518
+
519
 
520
 
521
  # -------------------------
 
594
  "treino": treino
595
  }
596
 
597
+ # -------------------------------
598
  # 🚀 NUTRIÇÃO
599
  # -------------------------------
600
  if intenções["nutricao"]:
601
+ # tenta extrair infos do prompt
602
+ age = 25
603
+ weight = 70
604
+ height = 175
605
+ sex = "male"
606
+ activity = "moderate"
607
+ goal = "maintenance"
608
+ intensity = "moderate"
609
+
610
+ # idade
611
+ match = re.search(r"(\d+)\s*anos", prompt_norm)
612
+ if match:
613
+ age = int(match.group(1))
614
+
615
+ # peso
616
+ match = re.search(r"(\d+)\s*(kg|quilo|kilos)", prompt_norm)
617
+ if match:
618
+ weight = float(match.group(1))
619
+
620
+ # altura
621
+ match = re.search(r"(\d+)\s*(cm|centimetro|centímetros)", prompt_norm)
622
+ if match:
623
+ height = float(match.group(1))
624
+
625
+ # sexo
626
+ if "mulher" in prompt_norm or "feminino" in prompt_norm:
627
+ sex = "female"
628
+ elif "homem" in prompt_norm or "masculino" in prompt_norm:
629
+ sex = "male"
630
+
631
+ # objetivo
632
+ if "bulking" in prompt_norm or "ganhar" in prompt_norm or "massa" in prompt_norm:
633
+ goal = "bulking"
634
+ elif "cutting" in prompt_norm or "emagrecer" in prompt_norm or "perder" in prompt_norm:
635
+ goal = "cutting"
636
+ elif "manutenção" in prompt_norm or "manter" in prompt_norm:
637
+ goal = "maintenance"
638
+
639
+ # intensidade
640
+ if "leve" in prompt_norm or "leve" in prompt_norm:
641
+ intensity = "mild"
642
+ elif "moderado" in prompt_norm or "moderada" in prompt_norm:
643
+ intensity = "moderate"
644
+ elif "agressivo" in prompt_norm or "rápido" in prompt_norm:
645
+ intensity = "aggressive"
646
+
647
+ try:
648
+ user = UserProfile(
649
+ age=age,
650
+ sex=sex,
651
+ weight_kg=weight,
652
+ height_cm=height,
653
+ activity=activity,
654
+ )
655
+ plan = build_basic_plan(user, goal=goal, intensity=intensity)
656
+
657
+ resposta_final["nutricao"] = {
658
+ "calorias_alvo": plan.calories_target,
659
+ "proteina_g": plan.protein_g,
660
+ "carboidratos_g": plan.carbs_g,
661
+ "gorduras_g": plan.fats_g,
662
+ "nota": plan.note,
663
+ "goal": goal,
664
+ "intensidade": intensity
665
+ }
666
+
667
+ except Exception as e:
668
+ resposta_final["nutricao"] = {
669
+ "erro": str(e)
670
+ }
671
+
672
 
673
  # -------------------------------
674
  # 🚀 Recuperação (extra opcional)
nutrition.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field, asdict
3
+ from typing import Optional, Dict, Tuple, Literal
4
+ import math
5
+
6
+ ActivityLevel = Literal[
7
+ "sedentary",
8
+ "light",
9
+ "moderate",
10
+ "active",
11
+ "very_active",
12
+ ]
13
+
14
+
15
+ @dataclass
16
+ class UserProfile:
17
+ age: int
18
+ sex: Literal["male", "female"]
19
+ weight_kg: float
20
+ height_cm: float
21
+ activity: ActivityLevel = "moderate"
22
+ body_fat_percent: Optional[float] = None
23
+
24
+ def __post_init__(self):
25
+ if self.weight_kg <= 0 or self.height_cm <= 0:
26
+ raise ValueError("Peso e altura devem ser maiores que zero")
27
+
28
+
29
+ @dataclass
30
+ class NutritionPlan:
31
+ calories_target: int
32
+ protein_g: int
33
+ carbs_g: int
34
+ fats_g: int
35
+ note: Optional[str] = None
36
+ tags: Tuple[str, ...] = field(default_factory=tuple)
37
+
38
+ def as_dict(self) -> Dict:
39
+ return asdict(self)
40
+
41
+
42
+ # ------------------- Básicas (BMR, TDEE) -------------------
43
+ def bmr_mifflin_st_jeor(profile: UserProfile) -> float:
44
+ """Calcula BMR (kcal/dia) usando Mifflin-St Jeor."""
45
+ if profile.sex == "male":
46
+ s = 5
47
+ else:
48
+ s = -161
49
+ bmr = 10 * profile.weight_kg + 6.25 * profile.height_cm - 5 * profile.age + s
50
+ return float(bmr)
51
+
52
+
53
+ _ACTIVITY_FACTORS: Dict[ActivityLevel, float] = {
54
+ "sedentary": 1.2, # pouco ou nenhum exercício
55
+ "light": 1.375, # exercício leve 1-3 dias/semana
56
+ "moderate": 1.55, # exercício moderado 3-5 dias/semana
57
+ "active": 1.725, # exercício intenso 6-7 dias/semana
58
+ "very_active": 1.9, # trabalho físico ou treino 2x/dia
59
+ }
60
+
61
+
62
+ def tdee(profile: UserProfile) -> int:
63
+ """Calcula TDEE aproximado (kcal/dia).
64
+
65
+ Nota: é uma estimativa — medir e ajustar com base na evolução real.
66
+ """
67
+ bmr = bmr_mifflin_st_jeor(profile)
68
+ factor = _ACTIVITY_FACTORS.get(profile.activity, 1.55)
69
+ return int(round(bmr * factor))
70
+
71
+
72
+ # ------------------- Metas calóricas: bulking / cutting -------------------
73
+ def calorie_target_for_goal(base_tdee: int, goal: Literal["maintenance", "bulking", "cutting"], intensity: Literal["mild", "moderate", "aggressive"] = "moderate") -> int:
74
+ """Calcula target calórico com base no objetivo e intensidade."""
75
+
76
+
77
+ adjustments = {
78
+ "bulking": {"mild": 1.05, "moderate": 1.10, "aggressive": 1.20},
79
+ "cutting": {"mild": 0.90, "moderate": 0.80, "aggressive": 0.70},
80
+ "maintenance": {"mild": 1.0, "moderate": 1.0, "aggressive": 1.0},
81
+ }
82
+ factor = adjustments[goal][intensity]
83
+ return int(round(base_tdee * factor))
84
+
85
+
86
+ # ------------------- Macros -------------------
87
+ _CAL_PER_G = {
88
+ "protein": 4,
89
+ "carb": 4,
90
+ "fat": 9,
91
+ }
92
+
93
+
94
+ def macros_from_calories(calories: int, protein_g: Optional[float] = None, protein_g_per_kg: Optional[float] = None, profile: Optional[UserProfile] = None, carb_ratio: Optional[float] = None, fat_ratio: Optional[float] = None) -> NutritionPlan:
95
+ """Cria uma divisão de macros a partir de calorias e algumas preferências.
96
+
97
+ Estratégia padrão quando não há especificações:
98
+ - Protein: se profile + protein_g_per_kg presente usa isso, senão 1.8 g/kg
99
+ - Fat: 25% das calorias
100
+ - Carb: restante
101
+
102
+ Retorna: NutritionPlan com macros em gramas.
103
+ """
104
+ if protein_g is None:
105
+ if protein_g_per_kg is None:
106
+ protein_g_per_kg = 1.8
107
+ if profile is None:
108
+ raise ValueError("Para calcular proteína por kg é necessário fornecer `profile`")
109
+ protein_g = protein_g_per_kg * profile.weight_kg
110
+
111
+ # aplicar limites sensatos
112
+ protein_g = max(0, protein_g)
113
+
114
+ if fat_ratio is None and carb_ratio is None:
115
+ fat_ratio = 0.25
116
+ elif fat_ratio is None:
117
+ fat_ratio = max(0.15, 1 - carb_ratio - (protein_g * _CAL_PER_G["protein"] / calories))
118
+
119
+ # calorias provenientes de proteína e gordura
120
+ cal_from_protein = protein_g * _CAL_PER_G["protein"]
121
+ cal_from_fat = int(round(calories * fat_ratio))
122
+ fat_g = cal_from_fat / _CAL_PER_G["fat"]
123
+
124
+ remaining_cal = calories - (cal_from_protein + cal_from_fat)
125
+ carbs_g = max(0, remaining_cal / _CAL_PER_G["carb"]) if remaining_cal > 0 else 0
126
+
127
+ return NutritionPlan(
128
+ calories_target=int(round(calories)),
129
+ protein_g=int(round(protein_g)),
130
+ carbs_g=int(round(carbs_g)),
131
+ fats_g=int(round(fat_g)),
132
+ )
133
+
134
+
135
+ def protein_recommendation(profile: UserProfile, goal: Literal["bulking", "cutting", "maintenance"] = "maintenance") -> Tuple[float, float]:
136
+ """Retorna faixa recomendada de proteína em g/kg.
137
+
138
+ - Preservação/ganho muscular: 1.6 - 2.2 g/kg (pode subir ligeiramente no cutting intenso).
139
+ """
140
+ low = 1.6
141
+ high = 2.2
142
+ if goal == "cutting":
143
+ high = 2.4
144
+ if profile.weight_kg < 60:
145
+ # ajustar para pesos muito baixos pode reduzir a faixa absoluta, porém manter g/kg
146
+ pass
147
+ return (low, high)
148
+
149
+
150
+ # ------------------- Micronutrientes (educativo) -------------------
151
+ _MICRONUTRIENT_HIGHLIGHTS = {
152
+ "vitamin_d": "Importante para saúde óssea e sistema imunitário; muitos têm défice, exposição solar e suplementação podem ser necessárias.",
153
+ "iron": "Crucial para transporte de oxigénio — atenção especial em mulheres com menstruação intensa.",
154
+ "calcium": "Saúde óssea; combinar com vitamina D.",
155
+ "magnesium": "Recuperação muscular, sono e função neuromuscular.",
156
+ }
157
+
158
+
159
+ def micronutrient_info(nutrient: str) -> str:
160
+ return _MICRONUTRIENT_HIGHLIGHTS.get(nutrient.lower(), "Informação não disponível para esse micronutriente.")
161
+
162
+
163
+ # ------------------- Suplementação (informativa) -------------------
164
+ _SUPPLEMENT_OVERVIEW = {
165
+ "whey": "Proteína de rápida digestão útil para atingir ingestão proteica diária; não é obrigatória se a dieta fornecer proteína suficiente.",
166
+ "creatine": "Creatina monohidratada: melhora força e desempenho; uma das mais estudadas e seguras.",
167
+ "caffeine": "Melhora performance e foco; usar com cuidado e atenção a tolerância/sono.",
168
+ "omega3": "Ácidos gordos essenciais benéficos para saúde cardiovascular e inflamação.",
169
+ }
170
+
171
+
172
+ def supplement_recommendation(supplement: str) -> str:
173
+ return _SUPPLEMENT_OVERVIEW.get(supplement.lower(), "Sem recomendação específica para este suplemento.")
174
+
175
+
176
+ # ------------------- Funções utilitárias / Exemplo de fluxo -------------------
177
+ def build_basic_plan(profile: UserProfile, goal: Literal["bulking", "cutting", "maintenance"] = "maintenance", intensity: Literal["mild", "moderate", "aggressive"] = "moderate") -> NutritionPlan:
178
+ """Fluxo simples que calcula TDEE, target calórico e macros padrão.
179
+
180
+ Ideal para uso no UI enquanto não existe geração completa de dietas.
181
+ """
182
+ base = tdee(profile)
183
+ calories = calorie_target_for_goal(base, goal, intensity)
184
+ prot_range = protein_recommendation(profile, goal)
185
+ # usar valor médio da faixa de proteína
186
+ protein_g_per_kg = (prot_range[0] + prot_range[1]) / 2
187
+ plan = macros_from_calories(calories, protein_g_per_kg=protein_g_per_kg, profile=profile)
188
+ plan.note = f"TDEE estimado: {base} kcal. Proteína target: {protein_g_per_kg:.2f} g/kg."
189
+ plan.tags = (goal, intensity)
190
+ return plan
191
+
192
+