samithcs commited on
Commit
08f64a2
·
verified ·
1 Parent(s): 845d793

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +176 -0
app.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ import joblib
3
+ import pandas as pd
4
+ from datetime import datetime
5
+ from typing import Literal, Annotated
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+
10
+ import os
11
+ import requests
12
+
13
+ HF_REPO = "samithcs/heart-rate-models"
14
+ HEART_MODEL_FILENAME = "Heart_Rate_Predictor_model.joblib"
15
+ ANOMALY_MODEL_FILENAME = "Anomaly_Detector_model.joblib"
16
+
17
+
18
+ MODEL_DIR = os.path.join("artifacts", "model_trainer")
19
+ os.makedirs(MODEL_DIR, exist_ok=True)
20
+
21
+ def download_from_hf(filename):
22
+ local_path = os.path.join(MODEL_DIR, filename)
23
+
24
+
25
+ if os.path.exists(local_path):
26
+ print(f"✅ {filename} already exists at {local_path}")
27
+ return local_path
28
+
29
+
30
+ url = f"https://huggingface.co/{HF_REPO}/resolve/main/{filename}"
31
+ print(f"⬇️ Downloading {filename} from {url} ...")
32
+ with requests.get(url, stream=True) as r:
33
+ r.raise_for_status()
34
+ with open(local_path, "wb") as f:
35
+ for chunk in r.iter_content(chunk_size=8192):
36
+ f.write(chunk)
37
+ print(f"✅ Downloaded {filename} to {local_path}")
38
+ return local_path
39
+
40
+
41
+
42
+ download_from_hf(HEART_MODEL_FILENAME)
43
+ download_from_hf(ANOMALY_MODEL_FILENAME)
44
+
45
+
46
+
47
+
48
+
49
+ # ===============================
50
+ # Define request schemas
51
+ # ===============================
52
+
53
+ class HeartRateInput(BaseModel):
54
+ age: Annotated[int, Field(..., gt=0, lt=120, description="The age of the user")]
55
+ gender: Annotated[Literal['M', 'F'], Field(..., description="Gender of the user")]
56
+ weight_kg: Annotated[float, Field(..., gt=0, description='Weight of the user')]
57
+ height_cm: Annotated[float, Field(..., gt=0, lt=250, description='Height of the user')]
58
+ bmi: Annotated[float, Field(..., gt=0, lt=100, description='BMI of the user')]
59
+ fitness_level: Annotated[Literal['lightly_active', 'fairly_active', 'sedentary', 'very_active'], Field(..., description="Fitness level")]
60
+ performance_level: Annotated[Literal['low', 'moderate', 'high'], Field(..., description="Performance level")]
61
+ resting_hr: Annotated[int, Field(..., gt=0, lt=120, description="Resting HR")]
62
+ max_hr: Annotated[int, Field(..., gt=0, lt=220, description="Max HR")]
63
+ activity_type: Annotated[Literal['sleeping', 'walking', 'resting', 'light', 'commuting', 'exercise'], Field(..., description="Activity type")]
64
+ activity_intensity: Annotated[float, Field(..., gt=0.0, description="Activity intensity")]
65
+ steps_5min: Annotated[int, Field(..., gt=0, description="Steps in 5 min")]
66
+ calories_5min: Annotated[float, Field(..., gt=0, description="Calories in 5 min")]
67
+ hrv_rmssd: Annotated[float, Field(..., gt=0, description="Heart rate variability RMSSD")]
68
+ stress_score: Annotated[int, Field(..., gt=0, lt=100, description="Stress score")]
69
+ signal_quality: Annotated[float, Field(..., gt=0, description="Signal quality")]
70
+ skin_temperature: Annotated[float, Field(..., gt=0, description="Skin temperature")]
71
+ device_battery: Annotated[int, Field(..., gt=0, description="Device battery")]
72
+ elevation_gain: Annotated[int, Field(..., ge=0, description="Elevation gain")]
73
+ sleep_stage: Annotated[Literal['light_sleep', 'deep_sleep', 'rem_sleep'], Field(..., description="Sleep stage")]
74
+ date: Annotated[datetime, Field(..., description="Timestamp")]
75
+
76
+
77
+ class AnomalyInput(BaseModel):
78
+ heart_rate: Annotated[float, Field(..., gt=0.0, description="Heart rate")]
79
+ resting_hr_baseline: Annotated[int, Field(..., gt=0, lt=120, description="Resting HR baseline")]
80
+ activity_type: Annotated[Literal['sleeping', 'walking', 'resting', 'light', 'commuting', 'exercise'], Field(..., description="Activity type")]
81
+ activity_intensity: Annotated[float, Field(..., gt=0, description="Activity intensity")]
82
+ steps_5min: Annotated[int, Field(..., gt=0, description="Steps in 5 min")]
83
+ calories_5min: Annotated[float, Field(..., gt=0, description="Calories in 5 min")]
84
+ hrv_rmssd: Annotated[float, Field(..., gt=0, description="Heart rate variability RMSSD")]
85
+ stress_score: Annotated[int, Field(..., gt=0, lt=100, description="Stress score")]
86
+ confidence_score: Annotated[float, Field(..., gt=0.0, description="Confidence score")]
87
+ signal_quality: Annotated[float, Field(..., gt=0, description="Signal quality")]
88
+ skin_temperature: Annotated[float, Field(..., gt=0, description="Skin temperature")]
89
+ device_battery: Annotated[int, Field(..., gt=0, description="Device battery")]
90
+ elevation_gain: Annotated[int, Field(..., ge=0, description="Elevation gain")]
91
+ sleep_stage: Annotated[Literal['light_sleep', 'deep_sleep', 'rem_sleep'], Field(..., description="Sleep stage")]
92
+ date: Annotated[datetime, Field(..., description="Timestamp")]
93
+
94
+ # ===============================
95
+ # Load models
96
+ # ===============================
97
+
98
+ MODEL_DIR = os.path.join("artifacts", "model_trainer")
99
+
100
+
101
+ HEART_MODEL_PATH = os.path.join(MODEL_DIR, "Heart_Rate_Predictor_model.joblib")
102
+ ANOMALY_MODEL_PATH = os.path.join(MODEL_DIR, "Anomaly_Detector_model.joblib")
103
+
104
+
105
+ heart_model_artifacts = joblib.load(HEART_MODEL_PATH)
106
+ heart_model = heart_model_artifacts['model']
107
+ heart_features = heart_model_artifacts['feature_columns']
108
+
109
+ anomaly_model_artifacts = joblib.load(ANOMALY_MODEL_PATH)
110
+ anomaly_model = anomaly_model_artifacts['model']
111
+ anomaly_features = anomaly_model_artifacts['feature_columns']
112
+
113
+ # ===============================
114
+ # Create FastAPI app
115
+ # ===============================
116
+ app = FastAPI(title="Health Monitoring API")
117
+
118
+ @app.get("/")
119
+ def home():
120
+ return {"message": "Health Monitoring API is running!"}
121
+
122
+ # ===============================
123
+ # Utility: preprocess features
124
+ # ===============================
125
+ def preprocess_heart_features(data_dict: dict) -> pd.DataFrame:
126
+ # Encode datetime
127
+ data_dict['date_encoded'] = data_dict['date'].timestamp()
128
+
129
+ # One-hot categorical encodings
130
+ data_dict['gender_M'] = 1 if data_dict['gender'] == 'M' else 0
131
+ data_dict['gender_F'] = 1 if data_dict['gender'] == 'F' else 0
132
+
133
+ for act in ['sleeping', 'walking', 'resting', 'light', 'commuting', 'exercise']:
134
+ data_dict[f"activity_type_{act}"] = 1 if data_dict['activity_type'] == act else 0
135
+
136
+ for stage in ['light_sleep', 'deep_sleep', 'rem_sleep']:
137
+ data_dict[f"sleep_stage_{stage}"] = 1 if data_dict['sleep_stage'] == stage else 0
138
+
139
+ # Restrict to model features only
140
+ return pd.DataFrame([{f: data_dict.get(f, 0) for f in heart_features}])
141
+
142
+
143
+ def preprocess_anomaly_features(data_dict: dict) -> pd.DataFrame:
144
+ data_dict['date_encoded'] = data_dict['date'].timestamp()
145
+
146
+ for act in ['sleeping', 'walking', 'resting', 'light', 'commuting', 'exercise']:
147
+ data_dict[f"activity_type_{act}"] = 1 if data_dict['activity_type'] == act else 0
148
+
149
+ for stage in ['light_sleep', 'deep_sleep', 'rem_sleep']:
150
+ data_dict[f"sleep_stage_{stage}"] = 1 if data_dict['sleep_stage'] == stage else 0
151
+
152
+ return pd.DataFrame([{f: data_dict.get(f, 0) for f in anomaly_features}])
153
+
154
+ # ===============================
155
+ # Endpoints
156
+ # ===============================
157
+ @app.post("/predict_heart_rate")
158
+ def predict_heart_rate(input_data: HeartRateInput):
159
+ try:
160
+ data_dict = input_data.model_dump()
161
+ X = preprocess_heart_features(data_dict)
162
+ prediction = heart_model.predict(X)[0]
163
+ return {"heart_rate_prediction": float(prediction)}
164
+ except Exception as e:
165
+ return {"error": str(e)}
166
+
167
+
168
+ @app.post("/detect_anomaly")
169
+ def detect_anomaly(input_data: AnomalyInput):
170
+ try:
171
+ data_dict = input_data.model_dump()
172
+ X = preprocess_anomaly_features(data_dict)
173
+ prediction = anomaly_model.predict(X)[0]
174
+ return {"anomaly_detected": bool(prediction)}
175
+ except Exception as e:
176
+ return {"error": str(e)}