Valentina9502 commited on
Commit
a7abcf6
·
verified ·
1 Parent(s): 224142e

Delete unified_data_manager.py

Browse files
Files changed (1) hide show
  1. unified_data_manager.py +0 -413
unified_data_manager.py DELETED
@@ -1,413 +0,0 @@
1
- """
2
- Unified Data Manager for GlycoAI - FIXED VERSION
3
- Restores the original working API calls that were working before
4
- """
5
-
6
- import logging
7
- from typing import Dict, Any, Optional, Tuple
8
- import pandas as pd
9
- from datetime import datetime, timedelta
10
- from dataclasses import asdict
11
-
12
- from apifunctions import (
13
- DexcomAPI,
14
- GlucoseAnalyzer,
15
- DEMO_USERS,
16
- DemoUser
17
- )
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
- class UnifiedDataManager:
22
- """
23
- FIXED: Unified data manager that calls the API exactly as it was working before
24
- """
25
-
26
- def __init__(self):
27
- self.dexcom_api = DexcomAPI()
28
- self.analyzer = GlucoseAnalyzer()
29
-
30
- logger.info(f"UnifiedDataManager initialized - RESTORED to working version")
31
-
32
- # Single source of truth for all data
33
- self.current_user: Optional[DemoUser] = None
34
- self.raw_glucose_data: Optional[list] = None
35
- self.processed_glucose_data: Optional[pd.DataFrame] = None
36
- self.calculated_stats: Optional[Dict] = None
37
- self.identified_patterns: Optional[Dict] = None
38
-
39
- # Metadata
40
- self.data_loaded_at: Optional[datetime] = None
41
- self.data_source: str = "none" # "dexcom_api", "mock", or "none"
42
-
43
- def load_user_data(self, user_key: str, force_reload: bool = False) -> Dict[str, Any]:
44
- """
45
- FIXED: Load glucose data using the ORIGINAL WORKING method
46
- """
47
-
48
- # Check if we already have data for this user and it's recent
49
- if (not force_reload and
50
- self.current_user and
51
- self.current_user == DEMO_USERS.get(user_key) and
52
- self.data_loaded_at and
53
- (datetime.now() - self.data_loaded_at).seconds < 300): # 5 minutes cache
54
-
55
- logger.info(f"Using cached data for {user_key}")
56
- return self._build_success_response()
57
-
58
- try:
59
- if user_key not in DEMO_USERS:
60
- return {
61
- "success": False,
62
- "message": f"❌ Invalid user key '{user_key}'. Available: {', '.join(DEMO_USERS.keys())}"
63
- }
64
-
65
- logger.info(f"Loading data for user: {user_key}")
66
-
67
- # Set current user
68
- self.current_user = DEMO_USERS[user_key]
69
-
70
- # Call API EXACTLY as it was working before
71
- try:
72
- logger.info(f"Attempting Dexcom API authentication for {user_key}")
73
-
74
- # ORIGINAL WORKING METHOD: Use the simulate_demo_login exactly as before
75
- access_token = self.dexcom_api.simulate_demo_login(user_key)
76
- logger.info(f"Dexcom authentication result: {bool(access_token)}")
77
-
78
- if access_token:
79
- # ORIGINAL WORKING METHOD: Get data with 14-day range
80
- end_date = datetime.now()
81
- start_date = end_date - timedelta(days=14)
82
-
83
- # Call get_egv_data EXACTLY as it was working before
84
- self.raw_glucose_data = self.dexcom_api.get_egv_data(
85
- start_date.isoformat(),
86
- end_date.isoformat()
87
- )
88
-
89
- if self.raw_glucose_data and len(self.raw_glucose_data) > 0:
90
- self.data_source = "dexcom_api"
91
- logger.info(f"✅ Successfully loaded {len(self.raw_glucose_data)} readings from Dexcom API")
92
- else:
93
- logger.warning("Dexcom API returned empty data - falling back to mock data")
94
- raise Exception("Empty data from Dexcom API")
95
- else:
96
- logger.warning("Failed to get access token - falling back to mock data")
97
- raise Exception("Authentication failed")
98
-
99
- except Exception as api_error:
100
- logger.warning(f"Dexcom API failed ({str(api_error)}) - using mock data fallback")
101
- self.raw_glucose_data = self._generate_realistic_mock_data(user_key)
102
- self.data_source = "mock"
103
-
104
- # Process the raw data (same processing for everyone)
105
- self.processed_glucose_data = self.analyzer.process_egv_data(self.raw_glucose_data)
106
-
107
- if self.processed_glucose_data is None or self.processed_glucose_data.empty:
108
- return {
109
- "success": False,
110
- "message": "❌ Failed to process glucose data"
111
- }
112
-
113
- # Calculate statistics (single source of truth)
114
- self.calculated_stats = self._calculate_unified_stats()
115
-
116
- # Identify patterns
117
- self.identified_patterns = self.analyzer.identify_patterns(self.processed_glucose_data)
118
-
119
- # Mark when data was loaded
120
- self.data_loaded_at = datetime.now()
121
-
122
- logger.info(f"Successfully loaded and processed data for {self.current_user.name}")
123
- logger.info(f"Data source: {self.data_source}, Readings: {len(self.processed_glucose_data)}")
124
- logger.info(f"TIR: {self.calculated_stats.get('time_in_range_70_180', 0):.1f}%")
125
-
126
- return self._build_success_response()
127
-
128
- except Exception as e:
129
- logger.error(f"Failed to load user data: {e}")
130
- return {
131
- "success": False,
132
- "message": f"❌ Failed to load user data: {str(e)}"
133
- }
134
-
135
- def get_stats_for_ui(self) -> Dict[str, Any]:
136
- """Get statistics formatted for the UI display"""
137
- if not self.calculated_stats:
138
- return {}
139
-
140
- return {
141
- **self.calculated_stats,
142
- "data_source": self.data_source,
143
- "loaded_at": self.data_loaded_at.isoformat() if self.data_loaded_at else None,
144
- "user_name": self.current_user.name if self.current_user else None
145
- }
146
-
147
- def get_context_for_agent(self) -> Dict[str, Any]:
148
- """Get context formatted for the AI agent"""
149
- if not self.current_user or not self.calculated_stats:
150
- return {"error": "No user data loaded"}
151
-
152
- # Build agent context with the SAME data as UI
153
- context = {
154
- "user": {
155
- "name": self.current_user.name,
156
- "age": self.current_user.age,
157
- "diabetes_type": self.current_user.diabetes_type,
158
- "device_type": self.current_user.device_type,
159
- "years_with_diabetes": self.current_user.years_with_diabetes,
160
- "typical_pattern": getattr(self.current_user, 'typical_glucose_pattern', 'normal')
161
- },
162
- "statistics": self._safe_convert_for_json(self.calculated_stats),
163
- "patterns": self._safe_convert_for_json(self.identified_patterns),
164
- "data_points": len(self.processed_glucose_data) if self.processed_glucose_data is not None else 0,
165
- "recent_readings": self._get_recent_readings_for_agent(),
166
- "data_metadata": {
167
- "source": self.data_source,
168
- "loaded_at": self.data_loaded_at.isoformat() if self.data_loaded_at else None,
169
- "data_age_minutes": int((datetime.now() - self.data_loaded_at).total_seconds() / 60) if self.data_loaded_at else None
170
- }
171
- }
172
-
173
- return context
174
-
175
- def get_chart_data(self) -> Optional[pd.DataFrame]:
176
- """Get processed data for chart display"""
177
- return self.processed_glucose_data
178
-
179
- def _calculate_unified_stats(self) -> Dict[str, Any]:
180
- """Calculate statistics using a single, consistent method"""
181
- if self.processed_glucose_data is None or self.processed_glucose_data.empty:
182
- return {"error": "No data available"}
183
-
184
- try:
185
- # Get glucose values
186
- glucose_values = self.processed_glucose_data['value'].dropna()
187
-
188
- if len(glucose_values) == 0:
189
- return {"error": "No valid glucose values"}
190
-
191
- # Convert to numpy array for consistent calculations
192
- import numpy as np
193
- values = np.array(glucose_values.tolist(), dtype=float)
194
-
195
- # Calculate basic statistics
196
- avg_glucose = float(np.mean(values))
197
- min_glucose = float(np.min(values))
198
- max_glucose = float(np.max(values))
199
- std_glucose = float(np.std(values))
200
- total_readings = int(len(values))
201
-
202
- # Calculate time in ranges - CONSISTENT METHOD
203
- in_range_mask = (values >= 70) & (values <= 180)
204
- below_range_mask = values < 70
205
- above_range_mask = values > 180
206
-
207
- in_range_count = int(np.sum(in_range_mask))
208
- below_range_count = int(np.sum(below_range_mask))
209
- above_range_count = int(np.sum(above_range_mask))
210
-
211
- # Calculate percentages
212
- time_in_range = (in_range_count / total_readings) * 100 if total_readings > 0 else 0
213
- time_below_70 = (below_range_count / total_readings) * 100 if total_readings > 0 else 0
214
- time_above_180 = (above_range_count / total_readings) * 100 if total_readings > 0 else 0
215
-
216
- # Calculate additional metrics
217
- gmi = 3.31 + (0.02392 * avg_glucose) # Glucose Management Indicator
218
- cv = (std_glucose / avg_glucose) * 100 if avg_glucose > 0 else 0 # Coefficient of Variation
219
-
220
- stats = {
221
- "average_glucose": avg_glucose,
222
- "min_glucose": min_glucose,
223
- "max_glucose": max_glucose,
224
- "std_glucose": std_glucose,
225
- "time_in_range_70_180": time_in_range,
226
- "time_below_70": time_below_70,
227
- "time_above_180": time_above_180,
228
- "total_readings": total_readings,
229
- "gmi": gmi,
230
- "cv": cv,
231
- "in_range_count": in_range_count,
232
- "below_range_count": below_range_count,
233
- "above_range_count": above_range_count
234
- }
235
-
236
- # Log for debugging
237
- logger.info(f"Calculated stats - TIR: {time_in_range:.1f}%, Total: {total_readings}, In range: {in_range_count}")
238
-
239
- return stats
240
-
241
- except Exception as e:
242
- logger.error(f"Error calculating unified stats: {e}")
243
- return {"error": f"Statistics calculation failed: {str(e)}"}
244
-
245
- def _generate_realistic_mock_data(self, user_key: str) -> list:
246
- """Generate consistent mock data for demo users"""
247
- from mistral_chat import GlucoseDataGenerator
248
-
249
- # Map users to patterns
250
- pattern_map = {
251
- "sarah_g7": "normal",
252
- "marcus_one": "dawn_phenomenon",
253
- "jennifer_g6": "normal",
254
- "robert_receiver": "dawn_phenomenon"
255
- }
256
-
257
- user_pattern = pattern_map.get(user_key, "normal")
258
-
259
- # Generate 14 days of data
260
- mock_data = GlucoseDataGenerator.create_realistic_pattern(days=14, user_type=user_pattern)
261
-
262
- logger.info(f"Generated {len(mock_data)} mock data points for {user_key} with pattern {user_pattern}")
263
-
264
- return mock_data
265
-
266
- def _get_recent_readings_for_agent(self, count: int = 5) -> list:
267
- """Get recent readings formatted for agent context"""
268
- if self.processed_glucose_data is None or self.processed_glucose_data.empty:
269
- return []
270
-
271
- try:
272
- recent_df = self.processed_glucose_data.tail(count)
273
- readings = []
274
-
275
- for _, row in recent_df.iterrows():
276
- display_time = row.get('displayTime') or row.get('systemTime')
277
- glucose_value = row.get('value')
278
- trend_value = row.get('trend', 'flat')
279
-
280
- if pd.notna(display_time):
281
- if isinstance(display_time, str):
282
- time_str = display_time
283
- else:
284
- time_str = pd.to_datetime(display_time).isoformat()
285
- else:
286
- time_str = datetime.now().isoformat()
287
-
288
- if pd.notna(glucose_value):
289
- glucose_clean = self._safe_convert_for_json(glucose_value)
290
- else:
291
- glucose_clean = None
292
-
293
- trend_clean = str(trend_value) if pd.notna(trend_value) else 'flat'
294
-
295
- readings.append({
296
- "time": time_str,
297
- "glucose": glucose_clean,
298
- "trend": trend_clean
299
- })
300
-
301
- return readings
302
-
303
- except Exception as e:
304
- logger.error(f"Error getting recent readings: {e}")
305
- return []
306
-
307
- def _safe_convert_for_json(self, obj):
308
- """Safely convert objects for JSON serialization"""
309
- import numpy as np
310
-
311
- if obj is None:
312
- return None
313
- elif isinstance(obj, (np.integer, np.int64, np.int32)):
314
- return int(obj)
315
- elif isinstance(obj, (np.floating, np.float64, np.float32)):
316
- if np.isnan(obj):
317
- return None
318
- return float(obj)
319
- elif isinstance(obj, dict):
320
- return {key: self._safe_convert_for_json(value) for key, value in obj.items()}
321
- elif isinstance(obj, list):
322
- return [self._safe_convert_for_json(item) for item in obj]
323
- elif isinstance(obj, pd.Timestamp):
324
- return obj.isoformat()
325
- else:
326
- return obj
327
-
328
- def _build_success_response(self) -> Dict[str, Any]:
329
- """Build a consistent success response"""
330
- data_points = len(self.processed_glucose_data) if self.processed_glucose_data is not None else 0
331
- avg_glucose = self.calculated_stats.get('average_glucose', 0)
332
- time_in_range = self.calculated_stats.get('time_in_range_70_180', 0)
333
-
334
- return {
335
- "success": True,
336
- "message": f"✅ Successfully loaded data for {self.current_user.name}",
337
- "user": asdict(self.current_user),
338
- "data_points": data_points,
339
- "stats": self.calculated_stats,
340
- "data_source": self.data_source,
341
- "summary": f"📊 {data_points} readings | Avg: {avg_glucose:.1f} mg/dL | TIR: {time_in_range:.1f}% | Source: {self.data_source}"
342
- }
343
-
344
- def validate_data_consistency(self) -> Dict[str, Any]:
345
- """Validate that all components are using consistent data"""
346
- if not self.calculated_stats:
347
- return {"valid": False, "message": "No data loaded"}
348
-
349
- validation = {
350
- "valid": True,
351
- "data_source": self.data_source,
352
- "data_age_minutes": int((datetime.now() - self.data_loaded_at).total_seconds() / 60) if self.data_loaded_at else None,
353
- "total_readings": self.calculated_stats.get('total_readings', 0),
354
- "time_in_range": self.calculated_stats.get('time_in_range_70_180', 0),
355
- "average_glucose": self.calculated_stats.get('average_glucose', 0),
356
- "user": self.current_user.name if self.current_user else None
357
- }
358
-
359
- logger.info(f"Data consistency check: {validation}")
360
-
361
- return validation
362
-
363
- # ADDITIONAL: Debug function to test the API connection as it was working before
364
- def test_original_api_method():
365
- """Test the API exactly as it was working before unified data manager"""
366
- from apifunctions import DexcomAPI, DEMO_USERS
367
-
368
- print("🔍 Testing API exactly as it was working before...")
369
-
370
- api = DexcomAPI()
371
-
372
- # Test with sarah_g7 as it was working before
373
- user_key = "sarah_g7"
374
- user = DEMO_USERS[user_key]
375
-
376
- print(f"Testing with {user.name} ({user.username})")
377
-
378
- try:
379
- # Call simulate_demo_login exactly as before
380
- access_token = api.simulate_demo_login(user_key)
381
- print(f"✅ Authentication: {bool(access_token)}")
382
-
383
- if access_token:
384
- # Call get_egv_data exactly as before
385
- end_date = datetime.now()
386
- start_date = end_date - timedelta(days=14)
387
-
388
- egv_data = api.get_egv_data(
389
- start_date.isoformat(),
390
- end_date.isoformat()
391
- )
392
-
393
- print(f"✅ EGV Data: {len(egv_data)} readings")
394
-
395
- if egv_data:
396
- print(f"✅ SUCCESS! API is working as before")
397
- sample = egv_data[0] if egv_data else {}
398
- print(f"Sample reading: {sample}")
399
- return True
400
- else:
401
- print("⚠️ API authenticated but returned no data")
402
- return False
403
- else:
404
- print("❌ Authentication failed")
405
- return False
406
-
407
- except Exception as e:
408
- print(f"❌ Error: {e}")
409
- return False
410
-
411
- if __name__ == "__main__":
412
- # Test the original API method
413
- test_original_api_method()