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

Delete main.py

Browse files
Files changed (1) hide show
  1. main.py +0 -848
main.py DELETED
@@ -1,848 +0,0 @@
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)