samithcs commited on
Commit
b5186fd
·
verified ·
1 Parent(s): 91210ee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -176
app.py CHANGED
@@ -1,176 +1,148 @@
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)}
 
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
+ import os
8
+ import requests
9
+
10
+ # ===============================
11
+ # Configuration
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
+ MODEL_DIR = os.path.join("artifacts", "model_trainer")
17
+ os.makedirs(MODEL_DIR, exist_ok=True)
18
+
19
+ # ===============================
20
+ # Hugging Face download helper
21
+ # ===============================
22
+ def download_from_hf(filename):
23
+ local_path = os.path.join(MODEL_DIR, filename)
24
+ if os.path.exists(local_path):
25
+ print(f"✅ {filename} already exists at {local_path}")
26
+ return local_path
27
+
28
+ url = f"https://huggingface.co/{HF_REPO}/resolve/main/{filename}"
29
+ print(f"⬇️ Downloading {filename} from {url} ...")
30
+ with requests.get(url, stream=True) as r:
31
+ r.raise_for_status()
32
+ with open(local_path, "wb") as f:
33
+ for chunk in r.iter_content(chunk_size=8192):
34
+ f.write(chunk)
35
+ print(f"✅ Downloaded {filename} to {local_path}")
36
+ return local_path
37
+
38
+ # ===============================
39
+ # FastAPI app
40
+ # ===============================
41
+ app = FastAPI(title="Health Monitoring API")
42
+
43
+ # ===============================
44
+ # Request schemas
45
+ # ===============================
46
+ class HeartRateInput(BaseModel):
47
+ age: Annotated[int, Field(..., gt=0, lt=120)]
48
+ gender: Annotated[Literal['M', 'F'], Field(...)]
49
+ weight_kg: Annotated[float, Field(..., gt=0)]
50
+ height_cm: Annotated[float, Field(..., gt=0, lt=250)]
51
+ bmi: Annotated[float, Field(..., gt=0, lt=100)]
52
+ fitness_level: Annotated[Literal['lightly_active','fairly_active','sedentary','very_active'], Field(...)]
53
+ performance_level: Annotated[Literal['low','moderate','high'], Field(...)]
54
+ resting_hr: Annotated[int, Field(..., gt=0, lt=120)]
55
+ max_hr: Annotated[int, Field(..., gt=0, lt=220)]
56
+ activity_type: Annotated[Literal['sleeping','walking','resting','light','commuting','exercise'], Field(...)]
57
+ activity_intensity: Annotated[float, Field(..., gt=0.0)]
58
+ steps_5min: Annotated[int, Field(..., gt=0)]
59
+ calories_5min: Annotated[float, Field(..., gt=0)]
60
+ hrv_rmssd: Annotated[float, Field(..., gt=0)]
61
+ stress_score: Annotated[int, Field(..., gt=0, lt=100)]
62
+ signal_quality: Annotated[float, Field(..., gt=0)]
63
+ skin_temperature: Annotated[float, Field(..., gt=0)]
64
+ device_battery: Annotated[int, Field(..., gt=0)]
65
+ elevation_gain: Annotated[int, Field(..., ge=0)]
66
+ sleep_stage: Annotated[Literal['light_sleep','deep_sleep','rem_sleep'], Field(...)]
67
+ date: Annotated[datetime, Field(...)]
68
+
69
+ class AnomalyInput(BaseModel):
70
+ heart_rate: Annotated[float, Field(..., gt=0.0)]
71
+ resting_hr_baseline: Annotated[int, Field(..., gt=0, lt=120)]
72
+ activity_type: Annotated[Literal['sleeping','walking','resting','light','commuting','exercise'], Field(...)]
73
+ activity_intensity: Annotated[float, Field(..., gt=0)]
74
+ steps_5min: Annotated[int, Field(..., gt=0)]
75
+ calories_5min: Annotated[float, Field(..., gt=0)]
76
+ hrv_rmssd: Annotated[float, Field(..., gt=0)]
77
+ stress_score: Annotated[int, Field(..., gt=0, lt=100)]
78
+ confidence_score: Annotated[float, Field(..., gt=0.0)]
79
+ signal_quality: Annotated[float, Field(..., gt=0)]
80
+ skin_temperature: Annotated[float, Field(..., gt=0)]
81
+ device_battery: Annotated[int, Field(..., gt=0)]
82
+ elevation_gain: Annotated[int, Field(..., ge=0)]
83
+ sleep_stage: Annotated[Literal['light_sleep','deep_sleep','rem_sleep'], Field(...)]
84
+ date: Annotated[datetime, Field(...)]
85
+
86
+ # ===============================
87
+ # Startup event to download & load models
88
+ # ===============================
89
+ @app.on_event("startup")
90
+ def startup_event():
91
+ global heart_model, heart_features, anomaly_model, anomaly_features
92
+
93
+ HEART_MODEL_PATH = download_from_hf(HEART_MODEL_FILENAME)
94
+ ANOMALY_MODEL_PATH = download_from_hf(ANOMALY_MODEL_FILENAME)
95
+
96
+ heart_model_artifacts = joblib.load(HEART_MODEL_PATH)
97
+ heart_model = heart_model_artifacts['model']
98
+ heart_features = heart_model_artifacts['feature_columns']
99
+
100
+ anomaly_model_artifacts = joblib.load(ANOMALY_MODEL_PATH)
101
+ anomaly_model = anomaly_model_artifacts['model']
102
+ anomaly_features = anomaly_model_artifacts['feature_columns']
103
+
104
+ # ===============================
105
+ # Utility: preprocess features
106
+ # ===============================
107
+ def preprocess_heart_features(data_dict: dict) -> pd.DataFrame:
108
+ data_dict['date_encoded'] = data_dict['date'].timestamp()
109
+ data_dict['gender_M'] = 1 if data_dict['gender']=='M' else 0
110
+ data_dict['gender_F'] = 1 if data_dict['gender']=='F' else 0
111
+ for act in ['sleeping','walking','resting','light','commuting','exercise']:
112
+ data_dict[f"activity_type_{act}"] = 1 if data_dict['activity_type']==act else 0
113
+ for stage in ['light_sleep','deep_sleep','rem_sleep']:
114
+ data_dict[f"sleep_stage_{stage}"] = 1 if data_dict['sleep_stage']==stage else 0
115
+ return pd.DataFrame([{f: data_dict.get(f,0) for f in heart_features}])
116
+
117
+ def preprocess_anomaly_features(data_dict: dict) -> pd.DataFrame:
118
+ data_dict['date_encoded'] = data_dict['date'].timestamp()
119
+ for act in ['sleeping','walking','resting','light','commuting','exercise']:
120
+ data_dict[f"activity_type_{act}"] = 1 if data_dict['activity_type']==act else 0
121
+ for stage in ['light_sleep','deep_sleep','rem_sleep']:
122
+ data_dict[f"sleep_stage_{stage}"] = 1 if data_dict['sleep_stage']==stage else 0
123
+ return pd.DataFrame([{f: data_dict.get(f,0) for f in anomaly_features}])
124
+
125
+ # ===============================
126
+ # Endpoints
127
+ # ===============================
128
+ @app.get("/")
129
+ def home():
130
+ return {"message":"Health Monitoring API is running!"}
131
+
132
+ @app.post("/predict_heart_rate")
133
+ def predict_heart_rate(input_data: HeartRateInput):
134
+ try:
135
+ X = preprocess_heart_features(input_data.model_dump())
136
+ prediction = heart_model.predict(X)[0]
137
+ return {"heart_rate_prediction": float(prediction)}
138
+ except Exception as e:
139
+ return {"error": str(e)}
140
+
141
+ @app.post("/detect_anomaly")
142
+ def detect_anomaly(input_data: AnomalyInput):
143
+ try:
144
+ X = preprocess_anomaly_features(input_data.model_dump())
145
+ prediction = anomaly_model.predict(X)[0]
146
+ return {"anomaly_detected": bool(prediction)}
147
+ except Exception as e:
148
+ return {"error": str(e)}