Divyab2004 commited on
Commit
08298a3
·
verified ·
1 Parent(s): fd45e90

Upload 3 files

Browse files
backend/download_models.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import os
3
+
4
+ MODEL_DIR = "models"
5
+ os.makedirs(MODEL_DIR, exist_ok=True)
6
+
7
+ files = {
8
+ "preprocessor.joblib": "https://huggingface.co/<your-path>/preprocessor.joblib",
9
+ "stress_model.joblib": "https://huggingface.co/<your-path>/stress_model.joblib",
10
+ "hormones_model.joblib": "https://huggingface.co/<your-path>/hormones_model.joblib",
11
+ "wellness_model.joblib": "https://huggingface.co/<your-path>/wellness_model.joblib",
12
+ }
13
+
14
+ for filename, url in files.items():
15
+ print(f"Downloading {filename} ...")
16
+ r = requests.get(url)
17
+ with open(f"{MODEL_DIR}/{filename}", "wb") as f:
18
+ f.write(r.content)
19
+
20
+ print("✅ All models downloaded successfully.")
backend/main.py ADDED
@@ -0,0 +1,848 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GutSync FastAPI Backend
3
+ Wellness prediction using ML models with Groq LLM for personalized insights
4
+ """
5
+
6
+ import os
7
+ import json
8
+ from datetime import datetime, timedelta
9
+ from typing import Optional, List, Dict, Any
10
+ from pathlib import Path
11
+
12
+ from fastapi import FastAPI, HTTPException
13
+ from fastapi.middleware.cors import CORSMiddleware
14
+ from pydantic import BaseModel
15
+ import httpx
16
+ import numpy as np
17
+
18
+ # Try to import ML libraries - they may not be available in all environments
19
+ try:
20
+ import joblib
21
+ import pandas as pd
22
+ ML_AVAILABLE = True
23
+ except ImportError:
24
+ ML_AVAILABLE = False
25
+ print("⚠️ ML libraries (joblib, pandas) not installed. Using mock predictions.")
26
+
27
+ from dotenv import load_dotenv
28
+
29
+ load_dotenv()
30
+
31
+ app = FastAPI(
32
+ title="GutSync API",
33
+ description="Wellness prediction and AI insights API",
34
+ version="1.0.0"
35
+ )
36
+
37
+ # CORS configuration
38
+ CORS_ORIGINS = os.getenv("CORS_ORIGINS", "*").split(",")
39
+ app.add_middleware(
40
+ CORSMiddleware,
41
+ allow_origins=CORS_ORIGINS if CORS_ORIGINS[0] != "*" else ["*"],
42
+ allow_credentials=True,
43
+ allow_methods=["*"],
44
+ allow_headers=["*"],
45
+ )
46
+
47
+ # Groq API configuration
48
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
49
+ GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions"
50
+
51
+ # Model paths
52
+ MODELS_DIR = Path(os.getenv("MODELS_DIR", "models"))
53
+
54
+ # In-memory storage (replace with database in production)
55
+ profiles_db: Dict[str, Dict] = {}
56
+ logs_db: Dict[str, List[Dict]] = {}
57
+ predictions_db: Dict[str, List[Dict]] = {}
58
+
59
+
60
+ # ============ Pydantic Models ============
61
+
62
+ class Symptoms(BaseModel):
63
+ fatigue: bool = False
64
+ bloating: bool = False
65
+ anxiety: bool = False
66
+ brain_fog: bool = False
67
+ insomnia: bool = False
68
+ cramps: bool = False
69
+ joint_pain: bool = False
70
+
71
+
72
+ class DailyLog(BaseModel):
73
+ user_id: Optional[str] = None
74
+ date: str
75
+ sleep_hours: float
76
+ stress_level: int
77
+ stress_cause: Optional[str] = None
78
+ mood_category: str # Low, Meh, Okay, Good, Great
79
+ energy_level: str # Exhausted, Low, Moderate, High, Peak
80
+ caffeine_intake: bool
81
+ healthy_diet: bool
82
+ exercise_done: bool
83
+ menstrual_phase: str # NONE, Menstrual, Follicular, Ovulation, Luteal
84
+ symptoms: Symptoms
85
+
86
+
87
+ class UserProfile(BaseModel):
88
+ id: Optional[str] = None
89
+ email: Optional[str] = None
90
+ age: int
91
+ gender: str # Male, Female, Non-binary, Prefer not to say
92
+ goals: List[str] = []
93
+ notifications: bool = True
94
+
95
+
96
+ class PredictionRequest(BaseModel):
97
+ log: DailyLog
98
+ profile: UserProfile
99
+
100
+
101
+ class HormoneStability(BaseModel):
102
+ dopamine: float
103
+ cortisol: float
104
+ estrogen: float
105
+ testosterone: float
106
+ melatonin: float
107
+ serotonin: float
108
+
109
+
110
+ class PredictionResult(BaseModel):
111
+ wellness_score: float
112
+ wellness_category: str
113
+ stress_vs_sleep_score: float
114
+ hormone_stability: HormoneStability
115
+ recommendation: Optional[str] = None
116
+ key_pattern: Optional[str] = None
117
+
118
+
119
+ class InsightsRequest(BaseModel):
120
+ prediction: PredictionResult
121
+ logs: List[DailyLog]
122
+ profile: UserProfile
123
+
124
+
125
+ # ============ Model Loading ============
126
+
127
+ models = {}
128
+ model_info = {}
129
+
130
+
131
+ def validate_model(model, name: str) -> bool:
132
+ """Validate that a loaded model has the expected methods"""
133
+ if name == "preprocessor":
134
+ # Preprocessor should have transform method
135
+ if not hasattr(model, 'transform'):
136
+ print(f"❌ {name} missing 'transform' method")
137
+ return False
138
+ else:
139
+ # Other models should have predict method
140
+ if not hasattr(model, 'predict'):
141
+ print(f"❌ {name} missing 'predict' method")
142
+ return False
143
+ return True
144
+
145
+
146
+ def load_models():
147
+ """Load ML models from disk"""
148
+ global models, model_info
149
+
150
+ if not ML_AVAILABLE:
151
+ print("⚠️ ML libraries not available, using mock predictions")
152
+ return
153
+
154
+ if not MODELS_DIR.exists():
155
+ print(f"⚠️ Models directory not found: {MODELS_DIR}")
156
+ print("📁 Creating models directory...")
157
+ MODELS_DIR.mkdir(parents=True, exist_ok=True)
158
+ return
159
+
160
+ model_files = {
161
+ "preprocessor": "preprocessor.joblib",
162
+ "stress_model": "stress_model.joblib",
163
+ "hormones_model": "hormones_model.joblib",
164
+ "wellness_model": "wellness_model.joblib",
165
+ }
166
+
167
+ print(f"\n📂 Loading models from: {MODELS_DIR.absolute()}")
168
+
169
+ for name, filename in model_files.items():
170
+ path = MODELS_DIR / filename
171
+ if path.exists():
172
+ try:
173
+ model = joblib.load(path)
174
+ if validate_model(model, name):
175
+ models[name] = model
176
+
177
+ # Store model info for debugging
178
+ info = {"type": type(model).__name__}
179
+ if hasattr(model, 'feature_names_in_'):
180
+ info["features"] = list(model.feature_names_in_)
181
+ if hasattr(model, 'n_features_in_'):
182
+ info["n_features"] = model.n_features_in_
183
+ model_info[name] = info
184
+
185
+ print(f"✅ Loaded {name}: {info['type']}")
186
+ except Exception as e:
187
+ print(f"❌ Failed to load {name}: {e}")
188
+ else:
189
+ print(f"⚠️ Model file not found: {path}")
190
+
191
+ # Summary
192
+ print(f"\n📊 Models loaded: {len(models)}/4")
193
+ if len(models) < 4:
194
+ print("\n📋 Missing models. Add these files to the 'models' folder:")
195
+ for name, filename in model_files.items():
196
+ if name not in models:
197
+ print(f" - {filename}")
198
+ print("\n🔄 Using mock predictions until all models are available.\n")
199
+ else:
200
+ print("✅ All models loaded successfully!\n")
201
+
202
+ # Print preprocessor features if available
203
+ if "preprocessor" in model_info and "features" in model_info["preprocessor"]:
204
+ print("📝 Expected input features:")
205
+ for feat in model_info["preprocessor"]["features"]:
206
+ print(f" - {feat}")
207
+ print()
208
+
209
+
210
+ @app.on_event("startup")
211
+ async def startup_event():
212
+ """Load models on startup"""
213
+ print("\n" + "="*50)
214
+ print("🚀 Starting GutSync API...")
215
+ print("="*50)
216
+ load_models()
217
+ print("="*50)
218
+ print("✅ GutSync API ready!")
219
+ print("="*50 + "\n")
220
+
221
+
222
+ # ============ Helper Functions ============
223
+
224
+ # Define the expected feature order based on typical training data
225
+ EXPECTED_FEATURES = [
226
+ "Age",
227
+ "Gender",
228
+ "Sleep_hours",
229
+ "Mood_category",
230
+ "Energy_level",
231
+ "Caffeine_intake",
232
+ "Exercise_done",
233
+ "Healthy_diet_followed",
234
+ "Menstrual_phase",
235
+ "Fatigue",
236
+ "Bloating",
237
+ "Anxiety",
238
+ "Brain_fog",
239
+ "Insomnia",
240
+ "Cramps",
241
+ "Joint_pain",
242
+ ]
243
+
244
+
245
+ def prepare_input_data(log: DailyLog, profile: UserProfile) -> pd.DataFrame:
246
+ """Prepare input data for ML models as a DataFrame"""
247
+
248
+ # Map gender for model
249
+ gender_map = {
250
+ "Male": "Male",
251
+ "Female": "Female",
252
+ "Non-binary": "Female",
253
+ "Prefer not to say": "Male"
254
+ }
255
+
256
+ # Ensure menstrual phase is NONE for males
257
+ menstrual_phase = log.menstrual_phase
258
+ if profile.gender == "Male" or profile.gender == "Prefer not to say":
259
+ menstrual_phase = "NONE"
260
+
261
+ # Create data dictionary matching expected features
262
+ data = {
263
+ "Age": profile.age,
264
+ "Gender": gender_map.get(profile.gender, "Male"),
265
+ "Sleep_hours": float(log.sleep_hours),
266
+ "Mood_category": log.mood_category,
267
+ "Energy_level": log.energy_level,
268
+ "Caffeine_intake": "Yes" if log.caffeine_intake else "No",
269
+ "Exercise_done": "Yes" if log.exercise_done else "No",
270
+ "Healthy_diet_followed": "Yes" if log.healthy_diet else "No",
271
+ "Menstrual_phase": menstrual_phase,
272
+ "Fatigue": 1 if log.symptoms.fatigue else 0,
273
+ "Bloating": 1 if log.symptoms.bloating else 0,
274
+ "Anxiety": 1 if log.symptoms.anxiety else 0,
275
+ "Brain_fog": 1 if log.symptoms.brain_fog else 0,
276
+ "Insomnia": 1 if log.symptoms.insomnia else 0,
277
+ "Cramps": 1 if log.symptoms.cramps else 0,
278
+ "Joint_pain": 1 if log.symptoms.joint_pain else 0,
279
+ }
280
+
281
+ # Create DataFrame with correct column order
282
+ # If preprocessor has feature_names_in_, use that order
283
+ if "preprocessor" in models and hasattr(models["preprocessor"], 'feature_names_in_'):
284
+ columns = list(models["preprocessor"].feature_names_in_)
285
+ # Ensure all expected columns exist
286
+ for col in columns:
287
+ if col not in data:
288
+ print(f"⚠️ Missing feature: {col}, using default value")
289
+ data[col] = 0
290
+ df = pd.DataFrame([{k: data[k] for k in columns}])
291
+ else:
292
+ # Use default expected features
293
+ df = pd.DataFrame([data])
294
+
295
+ return df
296
+
297
+
298
+ def get_wellness_category(score: float) -> str:
299
+ """Get wellness category from score"""
300
+ if score >= 75:
301
+ return "Healthy"
302
+ elif score >= 50:
303
+ return "Moderate"
304
+ elif score >= 25:
305
+ return "Concern"
306
+ return "Severe"
307
+
308
+
309
+ def calculate_hormone_stability(log: DailyLog, profile: UserProfile) -> HormoneStability:
310
+ """Calculate hormone stability based on inputs (fallback when no ML model)"""
311
+ base = 60
312
+
313
+ # Cortisol: inversely related to sleep, directly to stress
314
+ cortisol = base - (log.sleep_hours - 7) * 5 + (log.stress_level - 5) * 4
315
+ cortisol = max(20, min(100, cortisol))
316
+
317
+ # Serotonin: related to mood and exercise
318
+ mood_map = {"Great": 20, "Good": 10, "Okay": 0, "Meh": -10, "Low": -20}
319
+ serotonin = base + mood_map.get(log.mood_category, 0)
320
+ if log.exercise_done:
321
+ serotonin += 10
322
+ serotonin = max(20, min(100, serotonin))
323
+
324
+ # Dopamine: related to exercise and diet
325
+ dopamine = base
326
+ if log.exercise_done:
327
+ dopamine += 15
328
+ if log.healthy_diet:
329
+ dopamine += 10
330
+ dopamine = max(20, min(100, dopamine))
331
+
332
+ # Melatonin: related to sleep
333
+ melatonin = base + (log.sleep_hours - 7) * 8
334
+ if log.symptoms.insomnia:
335
+ melatonin -= 20
336
+ melatonin = max(20, min(100, melatonin))
337
+
338
+ # Estrogen/Testosterone: affected by menstrual phase and symptoms
339
+ estrogen = base
340
+ testosterone = base
341
+
342
+ if profile.gender == "Female" or profile.gender == "Non-binary":
343
+ phase_estrogen = {
344
+ "Menstrual": -10, "Follicular": 15, "Ovulation": 25, "Luteal": 5, "NONE": 0
345
+ }
346
+ estrogen += phase_estrogen.get(log.menstrual_phase, 0)
347
+ if log.symptoms.cramps:
348
+ estrogen -= 10
349
+
350
+ if log.exercise_done:
351
+ testosterone += 10
352
+ if log.stress_level > 6:
353
+ testosterone -= 10
354
+
355
+ return HormoneStability(
356
+ dopamine=round(max(20, min(100, dopamine)), 1),
357
+ cortisol=round(max(20, min(100, cortisol)), 1),
358
+ estrogen=round(max(20, min(100, estrogen)), 1),
359
+ testosterone=round(max(20, min(100, testosterone)), 1),
360
+ melatonin=round(max(20, min(100, melatonin)), 1),
361
+ serotonin=round(max(20, min(100, serotonin)), 1),
362
+ )
363
+
364
+
365
+ def mock_prediction(log: DailyLog, profile: UserProfile) -> PredictionResult:
366
+ """Generate mock prediction when models aren't available"""
367
+
368
+ # Base wellness score
369
+ base_score = 50
370
+
371
+ # Adjust based on inputs
372
+ if log.sleep_hours >= 7:
373
+ base_score += 15
374
+ elif log.sleep_hours >= 6:
375
+ base_score += 5
376
+ else:
377
+ base_score -= 10
378
+
379
+ if log.exercise_done:
380
+ base_score += 10
381
+
382
+ if log.healthy_diet:
383
+ base_score += 10
384
+
385
+ if log.stress_level <= 3:
386
+ base_score += 10
387
+ elif log.stress_level >= 7:
388
+ base_score -= 15
389
+
390
+ # Mood adjustment
391
+ mood_adj = {"Great": 15, "Good": 10, "Okay": 0, "Meh": -5, "Low": -15}
392
+ base_score += mood_adj.get(log.mood_category, 0)
393
+
394
+ # Symptom penalties
395
+ symptom_count = sum([
396
+ log.symptoms.fatigue, log.symptoms.bloating, log.symptoms.anxiety,
397
+ log.symptoms.brain_fog, log.symptoms.insomnia, log.symptoms.cramps,
398
+ log.symptoms.joint_pain
399
+ ])
400
+ base_score -= symptom_count * 5
401
+
402
+ # Clamp score
403
+ wellness_score = max(0, min(100, base_score))
404
+
405
+ # Calculate stress vs sleep score
406
+ stress_sleep_score = max(0, 100 - (log.stress_level * 10) + (log.sleep_hours * 5))
407
+
408
+ # Calculate hormone stability
409
+ hormone_stability = calculate_hormone_stability(log, profile)
410
+
411
+ return PredictionResult(
412
+ wellness_score=round(wellness_score, 1),
413
+ wellness_category=get_wellness_category(wellness_score),
414
+ stress_vs_sleep_score=round(min(100, stress_sleep_score), 1),
415
+ hormone_stability=hormone_stability
416
+ )
417
+
418
+
419
+ def ml_prediction(log: DailyLog, profile: UserProfile) -> PredictionResult:
420
+ """Generate prediction using ML models"""
421
+
422
+ try:
423
+ # Prepare input DataFrame
424
+ df = prepare_input_data(log, profile)
425
+ print(f"📊 Input DataFrame shape: {df.shape}")
426
+ print(f"📊 Input columns: {list(df.columns)}")
427
+
428
+ # Step 1: Preprocess the data
429
+ preprocessor = models["preprocessor"]
430
+ X_prep = preprocessor.transform(df)
431
+ print(f"✅ Preprocessed shape: {X_prep.shape}")
432
+
433
+ # Ensure X_prep is 2D numpy array
434
+ if hasattr(X_prep, 'toarray'):
435
+ X_prep = X_prep.toarray()
436
+ X_prep = np.atleast_2d(X_prep)
437
+
438
+ # Step 2: Predict stress score
439
+ stress_model = models["stress_model"]
440
+ stress_pred = stress_model.predict(X_prep)
441
+ stress_score = float(stress_pred[0]) if hasattr(stress_pred, '__len__') else float(stress_pred)
442
+ print(f"✅ Stress prediction: {stress_score}")
443
+
444
+ # Step 3: Predict hormone stability
445
+ hormones_model = models["hormones_model"]
446
+ hormone_pred = hormones_model.predict(X_prep)
447
+
448
+ # Handle different output formats
449
+ if hasattr(hormone_pred, '__len__') and len(hormone_pred) > 0:
450
+ if hasattr(hormone_pred[0], '__len__'):
451
+ # 2D array: [[d, c, e, t, m, s]]
452
+ h = hormone_pred[0]
453
+ else:
454
+ # 1D array or single prediction repeated
455
+ h = hormone_pred
456
+ else:
457
+ h = [65, 70, 60, 65, 55, 68] # fallback
458
+
459
+ # Ensure we have 6 values for hormones
460
+ if len(h) >= 6:
461
+ hormone_stability = HormoneStability(
462
+ dopamine=round(float(max(0, min(100, h[0]))), 1),
463
+ cortisol=round(float(max(0, min(100, h[1]))), 1),
464
+ estrogen=round(float(max(0, min(100, h[2]))), 1),
465
+ testosterone=round(float(max(0, min(100, h[3]))), 1),
466
+ melatonin=round(float(max(0, min(100, h[4]))), 1),
467
+ serotonin=round(float(max(0, min(100, h[5]))), 1),
468
+ )
469
+ else:
470
+ print(f"⚠️ Unexpected hormone prediction shape: {hormone_pred}")
471
+ hormone_stability = calculate_hormone_stability(log, profile)
472
+
473
+ print(f"✅ Hormone predictions: {hormone_stability}")
474
+
475
+ # Step 4: Predict wellness score
476
+ wellness_model = models["wellness_model"]
477
+
478
+ # Stack features for wellness model: [preprocessed, stress, hormones]
479
+ hormone_array = np.array([[
480
+ hormone_stability.dopamine,
481
+ hormone_stability.cortisol,
482
+ hormone_stability.estrogen,
483
+ hormone_stability.testosterone,
484
+ hormone_stability.melatonin,
485
+ hormone_stability.serotonin,
486
+ ]])
487
+ stress_array = np.array([[stress_score]])
488
+
489
+ # Concatenate all features
490
+ X_wellness = np.hstack([X_prep, stress_array, hormone_array])
491
+ print(f"✅ Wellness input shape: {X_wellness.shape}")
492
+
493
+ wellness_pred = wellness_model.predict(X_wellness)
494
+ wellness_score = float(wellness_pred[0]) if hasattr(wellness_pred, '__len__') else float(wellness_pred)
495
+ wellness_score = max(0, min(100, wellness_score))
496
+ print(f"✅ Wellness prediction: {wellness_score}")
497
+
498
+ # Calculate stress vs sleep score
499
+ stress_sleep_score = max(0, min(100, 100 - stress_score))
500
+
501
+ return PredictionResult(
502
+ wellness_score=round(wellness_score, 1),
503
+ wellness_category=get_wellness_category(wellness_score),
504
+ stress_vs_sleep_score=round(stress_sleep_score, 1),
505
+ hormone_stability=hormone_stability
506
+ )
507
+
508
+ except Exception as e:
509
+ print(f"❌ ML prediction error: {e}")
510
+ import traceback
511
+ traceback.print_exc()
512
+ raise
513
+
514
+
515
+ async def get_groq_insights(prediction: PredictionResult, logs: List[DailyLog], profile: UserProfile) -> Dict[str, str]:
516
+ """Get personalized insights from Groq LLM"""
517
+
518
+ if not GROQ_API_KEY or GROQ_API_KEY == "your_groq_api_key_here":
519
+ return {
520
+ "recommendation": generate_default_recommendation(prediction),
521
+ "key_pattern": generate_default_pattern(prediction)
522
+ }
523
+
524
+ context = f"""
525
+ User Profile:
526
+ - Age: {profile.age}
527
+ - Gender: {profile.gender}
528
+ - Goals: {', '.join(profile.goals) if profile.goals else 'Not specified'}
529
+
530
+ Current Wellness Status:
531
+ - Wellness Score: {prediction.wellness_score}/100
532
+ - Category: {prediction.wellness_category}
533
+ - Stress vs Sleep Score: {prediction.stress_vs_sleep_score}
534
+
535
+ Hormone Stability:
536
+ - Dopamine: {prediction.hormone_stability.dopamine}%
537
+ - Cortisol: {prediction.hormone_stability.cortisol}%
538
+ - Serotonin: {prediction.hormone_stability.serotonin}%
539
+ - Melatonin: {prediction.hormone_stability.melatonin}%
540
+ - Estrogen: {prediction.hormone_stability.estrogen}%
541
+ - Testosterone: {prediction.hormone_stability.testosterone}%
542
+ """
543
+
544
+ prompt = f"""Based on this wellness data, provide:
545
+ 1. A personalized recommendation (2-3 sentences) for improving wellness
546
+ 2. A key pattern detected in the data (1-2 sentences)
547
+
548
+ {context}
549
+
550
+ Respond ONLY with valid JSON in this exact format:
551
+ {{"recommendation": "your recommendation here", "key_pattern": "your pattern here"}}
552
+ """
553
+
554
+ try:
555
+ async with httpx.AsyncClient() as client:
556
+ response = await client.post(
557
+ GROQ_API_URL,
558
+ headers={
559
+ "Authorization": f"Bearer {GROQ_API_KEY}",
560
+ "Content-Type": "application/json"
561
+ },
562
+ json={
563
+ "model": "mixtral-8x7b-32768",
564
+ "messages": [
565
+ {"role": "system", "content": "You are a wellness expert. Respond only with valid JSON."},
566
+ {"role": "user", "content": prompt}
567
+ ],
568
+ "temperature": 0.7,
569
+ "max_tokens": 500
570
+ },
571
+ timeout=30.0
572
+ )
573
+
574
+ if response.status_code == 402:
575
+ raise HTTPException(status_code=402, detail="Insufficient credits")
576
+
577
+ if response.status_code != 200:
578
+ print(f"Groq API error: {response.status_code} - {response.text}")
579
+ raise Exception(f"Groq API error: {response.status_code}")
580
+
581
+ data = response.json()
582
+ content = data["choices"][0]["message"]["content"]
583
+
584
+ # Parse JSON response
585
+ try:
586
+ # Clean up common issues
587
+ content = content.strip()
588
+ if content.startswith("```json"):
589
+ content = content[7:]
590
+ if content.startswith("```"):
591
+ content = content[3:]
592
+ if content.endswith("```"):
593
+ content = content[:-3]
594
+ content = content.strip()
595
+
596
+ insights = json.loads(content)
597
+ return insights
598
+ except json.JSONDecodeError as e:
599
+ print(f"JSON parse error: {e}")
600
+ print(f"Raw content: {content}")
601
+ return {
602
+ "recommendation": generate_default_recommendation(prediction),
603
+ "key_pattern": generate_default_pattern(prediction)
604
+ }
605
+
606
+ except httpx.TimeoutException:
607
+ print("Groq API timeout")
608
+ return {
609
+ "recommendation": generate_default_recommendation(prediction),
610
+ "key_pattern": generate_default_pattern(prediction)
611
+ }
612
+ except HTTPException:
613
+ raise
614
+ except Exception as e:
615
+ print(f"Groq API error: {e}")
616
+ if "credit" in str(e).lower() or "402" in str(e):
617
+ raise HTTPException(status_code=402, detail="Insufficient credits")
618
+ return {
619
+ "recommendation": generate_default_recommendation(prediction),
620
+ "key_pattern": generate_default_pattern(prediction)
621
+ }
622
+
623
+
624
+ def generate_default_recommendation(prediction: PredictionResult) -> str:
625
+ """Generate default recommendation without LLM"""
626
+ if prediction.wellness_score >= 75:
627
+ return "You're doing great! Keep maintaining your current healthy habits. Consider adding mindfulness or meditation to further optimize your wellbeing."
628
+ elif prediction.wellness_score >= 50:
629
+ return "You're doing well overall, but there's room for optimization. Focus on getting consistent sleep and consider adding more physical activity to your routine."
630
+ elif prediction.wellness_score >= 25:
631
+ return "Your wellness needs attention. Prioritize sleep quality, reduce stress where possible, and consider speaking with a healthcare provider about your symptoms."
632
+ return "Your wellness score indicates significant concern. Please consult with a healthcare provider and focus on basic self-care: rest, hydration, and stress reduction."
633
+
634
+
635
+ def generate_default_pattern(prediction: PredictionResult) -> str:
636
+ """Generate default pattern insight without LLM"""
637
+ if prediction.stress_vs_sleep_score > 70:
638
+ return "Your mood consistently improves 24-48 hours after getting 7+ hours of sleep. Prioritizing sleep on weeknights could boost your weekday productivity by ~20%."
639
+ elif prediction.hormone_stability.cortisol > 70:
640
+ return "High cortisol levels correlate with your stress patterns. Consider stress-reduction techniques like deep breathing or short walks."
641
+ elif prediction.hormone_stability.serotonin < 50:
642
+ return "Lower serotonin levels detected. Regular exercise and sunlight exposure can naturally boost serotonin production."
643
+ return "Your energy levels peak when you combine good sleep with morning exercise. This pattern suggests optimizing your morning routine."
644
+
645
+
646
+ # ============ API Endpoints ============
647
+
648
+ @app.get("/")
649
+ async def root():
650
+ """Root endpoint"""
651
+ return {
652
+ "name": "GutSync API",
653
+ "version": "1.0.0",
654
+ "status": "running",
655
+ "docs": "/docs"
656
+ }
657
+
658
+
659
+ @app.get("/health")
660
+ async def health_check():
661
+ """Health check endpoint"""
662
+ return {
663
+ "status": "healthy",
664
+ "ml_available": ML_AVAILABLE,
665
+ "models_loaded": len(models),
666
+ "models_required": 4,
667
+ "models_ready": len(models) == 4,
668
+ "loaded_models": list(models.keys()),
669
+ "groq_configured": bool(GROQ_API_KEY and GROQ_API_KEY != "your_groq_api_key_here")
670
+ }
671
+
672
+
673
+ @app.get("/models/info")
674
+ async def models_info():
675
+ """Get information about loaded models"""
676
+ return {
677
+ "models_dir": str(MODELS_DIR.absolute()),
678
+ "ml_available": ML_AVAILABLE,
679
+ "models": model_info,
680
+ "expected_features": EXPECTED_FEATURES
681
+ }
682
+
683
+
684
+ @app.post("/profile", response_model=UserProfile)
685
+ async def create_profile(profile: UserProfile):
686
+ """Create a new user profile"""
687
+ profile_id = profile.id or str(datetime.now().timestamp())
688
+ profile.id = profile_id
689
+ profiles_db[profile_id] = profile.dict()
690
+ return profile
691
+
692
+
693
+ @app.get("/profile/{user_id}", response_model=UserProfile)
694
+ async def get_profile(user_id: str):
695
+ """Get user profile"""
696
+ if user_id not in profiles_db:
697
+ raise HTTPException(status_code=404, detail="Profile not found")
698
+ return UserProfile(**profiles_db[user_id])
699
+
700
+
701
+ @app.put("/profile/{user_id}", response_model=UserProfile)
702
+ async def update_profile(user_id: str, profile: UserProfile):
703
+ """Update user profile"""
704
+ if user_id not in profiles_db:
705
+ profiles_db[user_id] = {}
706
+ profiles_db[user_id].update(profile.dict(exclude_unset=True))
707
+ profiles_db[user_id]["id"] = user_id
708
+ return UserProfile(**profiles_db[user_id])
709
+
710
+
711
+ @app.post("/logs", response_model=DailyLog)
712
+ async def create_log(log: DailyLog):
713
+ """Create a new daily log"""
714
+ user_id = log.user_id or "default"
715
+ if user_id not in logs_db:
716
+ logs_db[user_id] = []
717
+ logs_db[user_id].insert(0, log.dict())
718
+ return log
719
+
720
+
721
+ @app.get("/logs/{user_id}")
722
+ async def get_logs(user_id: str, limit: int = 30):
723
+ """Get user's daily logs"""
724
+ if user_id not in logs_db:
725
+ return []
726
+ return logs_db[user_id][:limit]
727
+
728
+
729
+ @app.post("/predict", response_model=PredictionResult)
730
+ async def predict(request: PredictionRequest):
731
+ """Generate wellness prediction from daily log"""
732
+ log = request.log
733
+ profile = request.profile
734
+
735
+ # Check if all models are loaded
736
+ required_models = ["preprocessor", "stress_model", "hormones_model", "wellness_model"]
737
+ all_models_loaded = all(m in models for m in required_models)
738
+
739
+ if all_models_loaded and ML_AVAILABLE:
740
+ try:
741
+ print("\n" + "="*40)
742
+ print("🔮 Running ML Prediction")
743
+ print("="*40)
744
+ result = ml_prediction(log, profile)
745
+ print("✅ ML prediction successful!")
746
+ print("="*40 + "\n")
747
+ return result
748
+ except Exception as e:
749
+ print(f"❌ ML prediction failed, falling back to mock: {e}")
750
+ return mock_prediction(log, profile)
751
+ else:
752
+ if not ML_AVAILABLE:
753
+ print("⚠️ ML libraries not available, using mock prediction")
754
+ else:
755
+ missing = [m for m in required_models if m not in models]
756
+ print(f"⚠️ Missing models: {missing}, using mock prediction")
757
+ return mock_prediction(log, profile)
758
+
759
+
760
+ @app.post("/insights")
761
+ async def get_insights(request: InsightsRequest):
762
+ """Get AI-powered insights"""
763
+ try:
764
+ insights = await get_groq_insights(
765
+ request.prediction,
766
+ request.logs,
767
+ request.profile
768
+ )
769
+ return insights
770
+ except HTTPException:
771
+ raise
772
+ except Exception as e:
773
+ print(f"Insights error: {e}")
774
+ if "credit" in str(e).lower():
775
+ raise HTTPException(status_code=402, detail="Insufficient credits")
776
+ return {
777
+ "recommendation": generate_default_recommendation(request.prediction),
778
+ "key_pattern": generate_default_pattern(request.prediction)
779
+ }
780
+
781
+
782
+ @app.get("/trends/{user_id}")
783
+ async def get_trends(user_id: str, days: int = 7):
784
+ """Get trend data for user"""
785
+ # Check if we have actual logs
786
+ if user_id in logs_db and len(logs_db[user_id]) > 0:
787
+ user_logs = logs_db[user_id][:days]
788
+ trends = []
789
+ for log in reversed(user_logs):
790
+ try:
791
+ log_date = datetime.strptime(log["date"], "%Y-%m-%d")
792
+ date_str = log_date.strftime("%a")
793
+ except:
794
+ date_str = log.get("date", "Day")
795
+
796
+ mood_map = {"Great": 100, "Good": 80, "Okay": 60, "Meh": 40, "Low": 20}
797
+ trends.append({
798
+ "date": date_str,
799
+ "wellness_score": 70,
800
+ "mood_score": mood_map.get(log.get("mood_category", "Okay"), 60),
801
+ "stress_level": log.get("stress_level", 5) * 10,
802
+ "sleep_hours": log.get("sleep_hours", 7)
803
+ })
804
+ return trends
805
+
806
+ # Return mock trend data
807
+ base_date = datetime.now()
808
+ trends = []
809
+
810
+ for i in range(days):
811
+ date = base_date - timedelta(days=days - 1 - i)
812
+ trends.append({
813
+ "date": date.strftime("%a"),
814
+ "wellness_score": int(np.random.randint(65, 90)),
815
+ "mood_score": int(np.random.randint(60, 85)),
816
+ "stress_level": int(np.random.randint(20, 45)),
817
+ "sleep_hours": round(float(np.random.uniform(6, 9)), 1)
818
+ })
819
+
820
+ return trends
821
+
822
+
823
+ # ============ Debug Endpoints ============
824
+
825
+ @app.post("/debug/test-input")
826
+ async def debug_test_input(request: PredictionRequest):
827
+ """Debug endpoint to see how input data is prepared"""
828
+ if not ML_AVAILABLE:
829
+ return {"error": "ML libraries not available"}
830
+
831
+ df = prepare_input_data(request.log, request.profile)
832
+ return {
833
+ "columns": list(df.columns),
834
+ "values": df.to_dict(orient="records")[0],
835
+ "shape": list(df.shape)
836
+ }
837
+
838
+
839
+ if __name__ == "__main__":
840
+ import uvicorn
841
+
842
+ host = os.getenv("HOST", "0.0.0.0")
843
+ port = int(os.getenv("PORT", 8000))
844
+
845
+ print(f"\n🌐 Starting server at http://{host}:{port}")
846
+ print(f"📚 API docs available at http://{host}:{port}/docs\n")
847
+
848
+ uvicorn.run(app, host=host, port=port)
backend/models/readme.md ADDED
File without changes