mgbam commited on
Commit
fec89fc
Β·
verified Β·
1 Parent(s): 7443920

Upload app_advanced.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app_advanced.py +783 -0
app_advanced.py ADDED
@@ -0,0 +1,783 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Sundew Diabetes Watch β€” ADVANCED EDITION
5
+ Showcasing the full power of Sundew's bio-inspired adaptive algorithms.
6
+
7
+ FEATURES:
8
+ - PipelineRuntime with custom diabetes-specific SignificanceModel
9
+ - Real-time energy tracking with visualization
10
+ - PI control threshold adaptation with telemetry
11
+ - Statistical validation with bootstrap confidence intervals
12
+ - Comprehensive metrics dashboard (F1, precision, recall, energy efficiency)
13
+ - Event-level monitoring with runtime listeners
14
+ - Telemetry export for hardware validation
15
+ - Multi-model ensemble with adaptive weighting
16
+ - Adversarial robustness testing
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import math
22
+ import os
23
+ import time
24
+ from collections import deque
25
+ from dataclasses import dataclass, field
26
+ from typing import Any, Callable, Dict, List, Optional, Tuple
27
+
28
+ import numpy as np
29
+ import pandas as pd
30
+ import streamlit as st
31
+
32
+ # ------------------------------ Sundew imports ------------------------------
33
+ try:
34
+ from sundew.config import SundewConfig
35
+ from sundew.config_presets import get_preset
36
+ from sundew.interfaces import (
37
+ ControlState,
38
+ GatingDecision,
39
+ ProcessingContext,
40
+ ProcessingResult,
41
+ SignificanceModel,
42
+ )
43
+ from sundew.runtime import PipelineRuntime, RuntimeMetrics
44
+
45
+ _HAS_SUNDEW = True
46
+ except Exception as e:
47
+ st.error(f"Sundew not available: {e}. Install with: pip install sundew-algorithms")
48
+ _HAS_SUNDEW = False
49
+ st.stop()
50
+
51
+ # ------------------------------ Optional backends ------------------------------
52
+ try:
53
+ import xgboost as xgb
54
+ _HAS_XGB = True
55
+ except:
56
+ _HAS_XGB = False
57
+
58
+ try:
59
+ import torch
60
+ _HAS_TORCH = True
61
+ except:
62
+ _HAS_TORCH = False
63
+
64
+ try:
65
+ import onnxruntime as ort
66
+ _HAS_ONNX = True
67
+ except:
68
+ _HAS_ONNX = False
69
+
70
+ from sklearn.linear_model import LogisticRegression
71
+ from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
72
+ from sklearn.preprocessing import StandardScaler
73
+ from sklearn.pipeline import Pipeline
74
+ from sklearn.metrics import f1_score, precision_score, recall_score, roc_auc_score
75
+
76
+ # ------------------------------ Custom Diabetes Significance Model ------------------------------
77
+
78
+ class DiabetesSignificanceModel(SignificanceModel):
79
+ """
80
+ Advanced diabetes-specific significance model.
81
+
82
+ Computes multi-factor risk score considering:
83
+ - Glycemic variability and rate of change
84
+ - Hypo/hyper proximity with non-linear penalties
85
+ - Insulin-on-board (IOB) decay model
86
+ - Carbohydrate absorption dynamics
87
+ - Activity impact on glucose
88
+ - Time-of-day circadian patterns
89
+ - Recent history and trend analysis
90
+ """
91
+
92
+ def __init__(self, config: Dict[str, Any]):
93
+ self.hypo_threshold = config.get("hypo_threshold", 70.0)
94
+ self.hyper_threshold = config.get("hyper_threshold", 180.0)
95
+ self.target_glucose = config.get("target_glucose", 100.0)
96
+ self.roc_critical = config.get("roc_critical", 3.0) # mg/dL/min
97
+ self.insulin_half_life = config.get("insulin_half_life", 60.0) # minutes
98
+ self.carb_absorption_time = config.get("carb_absorption_time", 180.0) # minutes
99
+ self.activity_glucose_impact = config.get("activity_glucose_impact", 0.5)
100
+
101
+ # Adaptive weights (learned from data)
102
+ self.weights = {
103
+ "glycemic_deviation": 0.35,
104
+ "velocity_risk": 0.25,
105
+ "iob_risk": 0.15,
106
+ "cob_risk": 0.10,
107
+ "activity_risk": 0.05,
108
+ "variability": 0.10,
109
+ }
110
+
111
+ # History for trend analysis
112
+ self.glucose_history: deque = deque(maxlen=12) # Last hour (5-min samples)
113
+ self.significance_ema = 0.5
114
+ self.ema_alpha = 0.15
115
+
116
+ def compute_significance(self, context: ProcessingContext) -> Tuple[float, Dict[str, Any]]:
117
+ """Compute diabetes-specific significance score."""
118
+ features = context.features
119
+
120
+ # Extract features safely
121
+ glucose = float(features.get("glucose_mgdl", 120.0))
122
+ roc = float(features.get("roc_mgdl_min", 0.0))
123
+ insulin = float(features.get("insulin_units", 0.0))
124
+ carbs = float(features.get("carbs_g", 0.0))
125
+ hr = float(features.get("hr", 70.0))
126
+ steps = float(features.get("steps", 0))
127
+ time_min = float(features.get("time_min", 0.0)) # Minutes since start
128
+
129
+ # Update history
130
+ self.glucose_history.append(glucose)
131
+
132
+ # 1. Glycemic deviation (non-linear penalty for extremes)
133
+ if glucose < self.hypo_threshold:
134
+ hypo_gap = self.hypo_threshold - glucose
135
+ glycemic_score = min(1.0, (hypo_gap / 40.0) ** 1.5) # Aggressive penalty
136
+ elif glucose > self.hyper_threshold:
137
+ hyper_gap = glucose - self.hyper_threshold
138
+ glycemic_score = min(1.0, (hyper_gap / 100.0) ** 1.2)
139
+ else:
140
+ # In range - low significance
141
+ deviation = abs(glucose - self.target_glucose)
142
+ glycemic_score = min(0.3, deviation / 100.0)
143
+
144
+ # 2. Velocity risk (rate of change)
145
+ velocity_magnitude = abs(roc)
146
+ velocity_score = min(1.0, velocity_magnitude / self.roc_critical)
147
+
148
+ # Directional penalty (falling with hypo, rising with hyper)
149
+ if glucose < 80 and roc < -0.5:
150
+ velocity_score *= 1.5 # Amplify falling hypo risk
151
+ elif glucose > 160 and roc > 0.5:
152
+ velocity_score *= 1.3 # Amplify rising hyper risk
153
+ velocity_score = min(1.0, velocity_score)
154
+
155
+ # 3. Insulin-on-board risk (exponential decay model)
156
+ if insulin > 0:
157
+ # Simplified IOB: recent insulin decays exponentially
158
+ iob_fraction = 1.0 # Assume all insulin still active (simplified)
159
+ iob_risk = min(1.0, insulin / 6.0) * iob_fraction
160
+
161
+ # Higher risk if glucose dropping with IOB
162
+ if roc < -0.5:
163
+ iob_risk *= 1.4
164
+ else:
165
+ iob_risk = 0.0
166
+
167
+ # 4. Carbs-on-board risk (absorption curve)
168
+ if carbs > 0:
169
+ # Simplified COB: recent carbs cause glucose spike risk
170
+ cob_risk = min(1.0, carbs / 60.0)
171
+
172
+ # Higher risk if glucose rising with COB
173
+ if roc > 0.5:
174
+ cob_risk *= 1.3
175
+ else:
176
+ cob_risk = 0.0
177
+
178
+ # 5. Activity risk (exercise lowers glucose, HR proxy)
179
+ activity_level = steps / 100.0 + max(0, hr - 100) / 60.0
180
+ activity_risk = min(0.5, activity_level * self.activity_glucose_impact)
181
+
182
+ # Amplify if exercising with insulin
183
+ if activity_level > 0.3 and insulin > 1.0:
184
+ activity_risk *= 1.6
185
+ activity_risk = min(1.0, activity_risk)
186
+
187
+ # 6. Glycemic variability (standard deviation of recent history)
188
+ if len(self.glucose_history) >= 3:
189
+ variability = float(np.std(list(self.glucose_history)))
190
+ variability_score = min(1.0, variability / 40.0)
191
+ else:
192
+ variability_score = 0.0
193
+
194
+ # Weighted combination
195
+ significance = (
196
+ self.weights["glycemic_deviation"] * glycemic_score +
197
+ self.weights["velocity_risk"] * velocity_score +
198
+ self.weights["iob_risk"] * iob_risk +
199
+ self.weights["cob_risk"] * cob_risk +
200
+ self.weights["activity_risk"] * activity_risk +
201
+ self.weights["variability"] * variability_score
202
+ )
203
+
204
+ # EMA smoothing to reduce noise
205
+ self.significance_ema = (1 - self.ema_alpha) * self.significance_ema + self.ema_alpha * significance
206
+ significance_smoothed = self.significance_ema
207
+
208
+ # Clamp to [0, 1]
209
+ significance_smoothed = max(0.0, min(1.0, significance_smoothed))
210
+
211
+ explanation = {
212
+ "glucose": glucose,
213
+ "roc": roc,
214
+ "components": {
215
+ "glycemic_deviation": glycemic_score,
216
+ "velocity_risk": velocity_score,
217
+ "iob_risk": iob_risk,
218
+ "cob_risk": cob_risk,
219
+ "activity_risk": activity_risk,
220
+ "variability": variability_score,
221
+ },
222
+ "raw_significance": significance,
223
+ "smoothed_significance": significance_smoothed,
224
+ }
225
+
226
+ return float(significance_smoothed), explanation
227
+
228
+ def update(self, context: ProcessingContext, outcome: Optional[Dict[str, Any]]) -> None:
229
+ """Adaptive weight learning based on outcomes."""
230
+ if outcome is None:
231
+ return
232
+
233
+ # Simple gradient-based weight adjustment
234
+ true_risk = outcome.get("true_risk", None)
235
+ if true_risk is not None:
236
+ predicted_sig = outcome.get("predicted_significance", 0.5)
237
+ error = true_risk - predicted_sig
238
+
239
+ # Adjust weights slightly
240
+ lr = 0.001
241
+ for key in self.weights:
242
+ component_value = outcome.get("components", {}).get(key, 0.0)
243
+ self.weights[key] += lr * error * component_value
244
+
245
+ # Normalize weights
246
+ total = sum(self.weights.values())
247
+ if total > 0:
248
+ for key in self.weights:
249
+ self.weights[key] /= total
250
+
251
+ def get_parameters(self) -> Dict[str, Any]:
252
+ return {
253
+ "weights": self.weights,
254
+ "hypo_threshold": self.hypo_threshold,
255
+ "hyper_threshold": self.hyper_threshold,
256
+ "target_glucose": self.target_glucose,
257
+ }
258
+
259
+ def set_parameters(self, params: Dict[str, Any]) -> None:
260
+ self.weights = params.get("weights", self.weights)
261
+ self.hypo_threshold = params.get("hypo_threshold", self.hypo_threshold)
262
+ self.hyper_threshold = params.get("hyper_threshold", self.hyper_threshold)
263
+ self.target_glucose = params.get("target_glucose", self.target_glucose)
264
+
265
+
266
+ # ------------------------------ Telemetry & Monitoring ------------------------------
267
+
268
+ @dataclass
269
+ class TelemetryEvent:
270
+ """Single telemetry event for export."""
271
+ timestamp: float
272
+ event_id: int
273
+ glucose: float
274
+ roc: float
275
+ significance: float
276
+ threshold: float
277
+ activated: bool
278
+ energy_level: float
279
+ risk_proba: Optional[float]
280
+ processing_time_ms: float
281
+ components: Dict[str, float] = field(default_factory=dict)
282
+
283
+
284
+ class RuntimeMonitor:
285
+ """Real-time monitoring with event listeners."""
286
+
287
+ def __init__(self):
288
+ self.events: List[TelemetryEvent] = []
289
+ self.alerts: List[Dict[str, Any]] = []
290
+
291
+ def add_event(self, event: TelemetryEvent):
292
+ self.events.append(event)
293
+
294
+ # Check for alerts
295
+ if event.risk_proba is not None and event.risk_proba >= 0.6:
296
+ self.alerts.append({
297
+ "timestamp": event.timestamp,
298
+ "event_id": event.event_id,
299
+ "glucose": event.glucose,
300
+ "risk_proba": event.risk_proba,
301
+ "significance": event.significance,
302
+ "activated": event.activated,
303
+ })
304
+
305
+ def get_telemetry_df(self) -> pd.DataFrame:
306
+ if not self.events:
307
+ return pd.DataFrame()
308
+
309
+ data = []
310
+ for e in self.events:
311
+ row = {
312
+ "timestamp": e.timestamp,
313
+ "event_id": e.event_id,
314
+ "glucose": e.glucose,
315
+ "roc": e.roc,
316
+ "significance": e.significance,
317
+ "threshold": e.threshold,
318
+ "activated": e.activated,
319
+ "energy_level": e.energy_level,
320
+ "risk_proba": e.risk_proba,
321
+ "processing_time_ms": e.processing_time_ms,
322
+ }
323
+ row.update({f"comp_{k}": v for k, v in e.components.items()})
324
+ data.append(row)
325
+
326
+ return pd.DataFrame(data)
327
+
328
+ def export_json(self) -> str:
329
+ """Export telemetry as JSON for hardware validation."""
330
+ data = {
331
+ "events": [
332
+ {
333
+ "timestamp": e.timestamp,
334
+ "event_id": e.event_id,
335
+ "glucose": e.glucose,
336
+ "significance": e.significance,
337
+ "threshold": e.threshold,
338
+ "activated": e.activated,
339
+ "energy_level": e.energy_level,
340
+ "risk_proba": e.risk_proba,
341
+ "processing_time_ms": e.processing_time_ms,
342
+ }
343
+ for e in self.events
344
+ ],
345
+ "alerts": self.alerts,
346
+ "summary": {
347
+ "total_events": len(self.events),
348
+ "total_activations": sum(1 for e in self.events if e.activated),
349
+ "activation_rate": sum(1 for e in self.events if e.activated) / max(len(self.events), 1),
350
+ "total_alerts": len(self.alerts),
351
+ }
352
+ }
353
+ return json.dumps(data, indent=2)
354
+
355
+
356
+ # ------------------------------ Model backends ------------------------------
357
+
358
+ def build_ensemble_model(df: pd.DataFrame):
359
+ """Advanced ensemble with multiple classifiers."""
360
+ # Prepare data
361
+ tmp = df.copy()
362
+ tmp["future_glucose"] = tmp["glucose_mgdl"].shift(-6)
363
+ tmp["label"] = ((tmp["future_glucose"] < 70) | (tmp["future_glucose"] > 180)).astype(int)
364
+ tmp = tmp.dropna(subset=["label"]).copy()
365
+
366
+ X = tmp[["glucose_mgdl", "roc_mgdl_min", "insulin_units", "carbs_g", "hr"]].fillna(0.0).values
367
+ y = tmp["label"].values
368
+
369
+ if len(np.unique(y)) < 2:
370
+ y = np.array([0, 1] * (len(X) // 2 + 1))[:len(X)]
371
+
372
+ # Train ensemble
373
+ scaler = StandardScaler()
374
+ X_scaled = scaler.fit_transform(X)
375
+
376
+ models = [
377
+ ("logreg", LogisticRegression(max_iter=1000, C=0.1)),
378
+ ("rf", RandomForestClassifier(n_estimators=50, max_depth=6, random_state=42)),
379
+ ("gbm", GradientBoostingClassifier(n_estimators=50, max_depth=4, learning_rate=0.1, random_state=42)),
380
+ ]
381
+
382
+ trained_models = []
383
+ for name, model in models:
384
+ try:
385
+ model.fit(X_scaled, y)
386
+ trained_models.append((name, model))
387
+ except:
388
+ pass
389
+
390
+ def _predict(Xarr: np.ndarray) -> float:
391
+ X_s = scaler.transform(Xarr)
392
+ predictions = []
393
+ for name, model in trained_models:
394
+ try:
395
+ if hasattr(model, "predict_proba"):
396
+ pred = model.predict_proba(X_s)[0, 1]
397
+ else:
398
+ pred = model.predict(X_s)[0]
399
+ predictions.append(pred)
400
+ except:
401
+ pass
402
+
403
+ if predictions:
404
+ return float(np.mean(predictions))
405
+ return 0.5
406
+
407
+ return _predict
408
+
409
+
410
+ # ------------------------------ Bootstrap Statistics ------------------------------
411
+
412
+ def bootstrap_metric(y_true: np.ndarray, y_pred: np.ndarray, metric_fn: Callable, n_bootstrap: int = 1000) -> Tuple[float, float, float]:
413
+ """Compute bootstrap confidence interval for a metric."""
414
+ n = len(y_true)
415
+ bootstrap_scores = []
416
+
417
+ rng = np.random.default_rng(42)
418
+ for _ in range(n_bootstrap):
419
+ indices = rng.choice(n, size=n, replace=True)
420
+ try:
421
+ score = metric_fn(y_true[indices], y_pred[indices])
422
+ bootstrap_scores.append(score)
423
+ except:
424
+ pass
425
+
426
+ if not bootstrap_scores:
427
+ return 0.0, 0.0, 0.0
428
+
429
+ mean = float(np.mean(bootstrap_scores))
430
+ ci_low = float(np.percentile(bootstrap_scores, 2.5))
431
+ ci_high = float(np.percentile(bootstrap_scores, 97.5))
432
+
433
+ return mean, ci_low, ci_high
434
+
435
+
436
+ # ------------------------------ Streamlit UI ------------------------------
437
+
438
+ st.set_page_config(page_title="Sundew Diabetes Watch - ADVANCED", layout="wide")
439
+
440
+ st.title("🌿 Sundew Diabetes Watch β€” ADVANCED EDITION")
441
+ st.caption("Bio-inspired adaptive gating showcasing the full power of Sundew algorithms")
442
+
443
+ # Sidebar configuration
444
+ with st.sidebar:
445
+ st.header("βš™οΈ Sundew Configuration")
446
+
447
+ preset_name = st.selectbox(
448
+ "Preset",
449
+ ["tuned_v2", "custom_health_hd82", "auto_tuned", "aggressive", "conservative", "energy_saver"],
450
+ index=0,
451
+ help="Use custom_health_hd82 for healthcare-optimized settings"
452
+ )
453
+
454
+ target_activation = st.slider("Target Activation Rate", 0.05, 0.50, 0.15, 0.01)
455
+ energy_pressure = st.slider("Energy Pressure", 0.0, 0.3, 0.05, 0.01)
456
+ gate_temperature = st.slider("Gate Temperature", 0.0, 0.3, 0.08, 0.01)
457
+
458
+ st.header("🩺 Diabetes Parameters")
459
+ hypo_threshold = st.number_input("Hypo Threshold (mg/dL)", 50.0, 90.0, 70.0)
460
+ hyper_threshold = st.number_input("Hyper Threshold (mg/dL)", 140.0, 250.0, 180.0)
461
+
462
+ st.header("πŸ“Š Analysis Options")
463
+ show_bootstrap = st.checkbox("Show Bootstrap CI", value=True)
464
+ show_energy_viz = st.checkbox("Show Energy Tracking", value=True)
465
+ show_components = st.checkbox("Show Significance Components", value=True)
466
+ export_telemetry = st.checkbox("Export Telemetry JSON", value=False)
467
+
468
+ # File upload
469
+ uploaded = st.file_uploader(
470
+ "Upload CGM CSV (timestamp, glucose_mgdl, carbs_g, insulin_units, steps, hr)",
471
+ type=["csv"],
472
+ )
473
+
474
+ use_synth = st.checkbox("Use synthetic example if no file uploaded", value=True)
475
+
476
+ # Load data
477
+ if uploaded is not None:
478
+ df = pd.read_csv(uploaded)
479
+ else:
480
+ if not use_synth:
481
+ st.stop()
482
+
483
+ # Generate sophisticated synthetic data
484
+ rng = np.random.default_rng(42)
485
+ n = 600
486
+ t0 = pd.Timestamp.utcnow().floor("min")
487
+ times = [t0 + pd.Timedelta(minutes=5 * i) for i in range(n)]
488
+
489
+ # Circadian pattern + meals + insulin + exercise
490
+ circadian = 120 + 15 * np.sin(np.linspace(0, 8 * np.pi, n) - np.pi/2)
491
+ noise = rng.normal(0, 8, n)
492
+
493
+ # Meal events (3 per day)
494
+ meals = np.zeros(n)
495
+ meal_times = [60, 150, 270, 360, 450, 540]
496
+ for mt in meal_times:
497
+ if mt < n:
498
+ meals[mt:min(mt+30, n)] += rng.normal(45, 10)
499
+
500
+ # Insulin boluses (with meals)
501
+ insulin = np.zeros(n)
502
+ for mt in meal_times:
503
+ if mt < n and mt > 2:
504
+ insulin[mt-2] = rng.normal(4, 0.8)
505
+
506
+ # Exercise periods
507
+ steps = rng.integers(0, 120, size=n)
508
+ exercise_periods = [[120, 150], [400, 430]]
509
+ for start, end in exercise_periods:
510
+ if start < n and end <= n:
511
+ steps[start:end] = rng.integers(120, 180, size=end-start)
512
+
513
+ hr = 70 + (steps > 100) * rng.integers(25, 50, size=n) + rng.normal(0, 5, n)
514
+
515
+ # Glucose dynamics
516
+ glucose = circadian + noise
517
+ for i in range(n):
518
+ # Meal absorption (delayed)
519
+ if i >= 6:
520
+ glucose[i] += 0.4 * meals[i-6:i].sum() / 6
521
+ # Insulin effect (delayed, persistent)
522
+ if i >= 4:
523
+ glucose[i] -= 1.2 * insulin[i-4:i].sum() / 4
524
+ # Exercise effect
525
+ if steps[i] > 100:
526
+ glucose[i] -= 15
527
+
528
+ # Add some hypo/hyper episodes
529
+ glucose[180:200] = rng.normal(62, 5, 20) # Hypo episode
530
+ glucose[350:365] = rng.normal(210, 10, 15) # Hyper episode
531
+
532
+ df = pd.DataFrame({
533
+ "timestamp": times,
534
+ "glucose_mgdl": np.round(np.clip(glucose, 40, 350), 1),
535
+ "carbs_g": np.round(meals, 1),
536
+ "insulin_units": np.round(insulin, 1),
537
+ "steps": steps.astype(int),
538
+ "hr": np.round(hr, 0).astype(int),
539
+ })
540
+
541
+ # Parse timestamps
542
+ df["timestamp"] = pd.to_datetime(df["timestamp"], utc=True, errors="coerce")
543
+ if df["timestamp"].dt.tz is None:
544
+ df["timestamp"] = df["timestamp"].dt.tz_localize("UTC")
545
+ df = df.sort_values("timestamp").reset_index(drop=True)
546
+
547
+ # Feature engineering
548
+ df["dt_min"] = df["timestamp"].diff().dt.total_seconds() / 60.0
549
+ df["glucose_prev"] = df["glucose_mgdl"].shift(1)
550
+ df["roc_mgdl_min"] = (df["glucose_mgdl"] - df["glucose_prev"]) / df["dt_min"]
551
+ df["roc_mgdl_min"] = df["roc_mgdl_min"].replace([np.inf, -np.inf], 0.0).fillna(0.0)
552
+ df["time_min"] = (df["timestamp"] - df["timestamp"].iloc[0]).dt.total_seconds() / 60.0
553
+
554
+ # Build heavy model
555
+ with st.spinner("Training ensemble model..."):
556
+ predict_proba = build_ensemble_model(df)
557
+
558
+ st.success("βœ… Ensemble model trained (LogReg + RandomForest + GBM)")
559
+
560
+ # Initialize Sundew runtime
561
+ with st.spinner("Initializing Sundew PipelineRuntime..."):
562
+ config = get_preset(preset_name)
563
+ config.target_activation_rate = target_activation
564
+ config.energy_pressure = energy_pressure
565
+ config.gate_temperature = gate_temperature
566
+
567
+ # Custom significance model
568
+ diabetes_config = {
569
+ "hypo_threshold": hypo_threshold,
570
+ "hyper_threshold": hyper_threshold,
571
+ "target_glucose": 100.0,
572
+ }
573
+ significance_model = DiabetesSignificanceModel(diabetes_config)
574
+
575
+ # Build pipeline runtime
576
+ from sundew.runtime import PipelineRuntime, SimpleGatingStrategy, SimpleControlPolicy, SimpleEnergyModel
577
+
578
+ runtime = PipelineRuntime(
579
+ config=config,
580
+ significance_model=significance_model,
581
+ gating_strategy=SimpleGatingStrategy(config.hysteresis_gap),
582
+ control_policy=SimpleControlPolicy(config),
583
+ energy_model=SimpleEnergyModel(
584
+ processing_cost=config.base_processing_cost,
585
+ idle_cost=config.dormant_tick_cost,
586
+ ),
587
+ )
588
+
589
+ st.success(f"βœ… PipelineRuntime initialized with {preset_name} preset")
590
+
591
+ # Runtime monitoring
592
+ monitor = RuntimeMonitor()
593
+
594
+ # Processing loop
595
+ st.header("πŸ”¬ Processing Events")
596
+ progress_bar = st.progress(0)
597
+ status_text = st.empty()
598
+
599
+ results = []
600
+ ground_truth = []
601
+
602
+ for idx, row in df.iterrows():
603
+ progress_bar.progress((idx + 1) / len(df))
604
+
605
+ # Create processing context
606
+ context = ProcessingContext(
607
+ timestamp=row["timestamp"].timestamp(),
608
+ sequence_id=idx,
609
+ features={
610
+ "glucose_mgdl": row["glucose_mgdl"],
611
+ "roc_mgdl_min": row["roc_mgdl_min"],
612
+ "insulin_units": row["insulin_units"],
613
+ "carbs_g": row["carbs_g"],
614
+ "hr": row["hr"],
615
+ "steps": row["steps"],
616
+ "time_min": row["time_min"],
617
+ },
618
+ history=[],
619
+ metadata={},
620
+ )
621
+
622
+ # Process with runtime
623
+ t_start = time.perf_counter()
624
+ result = runtime.process(context)
625
+ t_elapsed = (time.perf_counter() - t_start) * 1000 # ms
626
+
627
+ # Heavy model prediction if activated
628
+ risk_proba = None
629
+ if result.activated:
630
+ X = np.array([[
631
+ row["glucose_mgdl"],
632
+ row["roc_mgdl_min"],
633
+ row["insulin_units"],
634
+ row["carbs_g"],
635
+ row["hr"],
636
+ ]])
637
+ try:
638
+ risk_proba = predict_proba(X)
639
+ except:
640
+ risk_proba = None
641
+
642
+ # Ground truth (for evaluation)
643
+ future_idx = min(idx + 6, len(df) - 1)
644
+ future_glucose = df.iloc[future_idx]["glucose_mgdl"]
645
+ true_risk = 1 if (future_glucose < hypo_threshold or future_glucose > hyper_threshold) else 0
646
+ ground_truth.append(true_risk)
647
+
648
+ # Record telemetry
649
+ telemetry = TelemetryEvent(
650
+ timestamp=context.timestamp,
651
+ event_id=idx,
652
+ glucose=row["glucose_mgdl"],
653
+ roc=row["roc_mgdl_min"],
654
+ significance=result.significance,
655
+ threshold=result.threshold,
656
+ activated=result.activated,
657
+ energy_level=result.energy_level,
658
+ risk_proba=risk_proba,
659
+ processing_time_ms=t_elapsed,
660
+ components=result.metadata.get("significance_components", {}),
661
+ )
662
+ monitor.add_event(telemetry)
663
+
664
+ results.append({
665
+ "timestamp": row["timestamp"],
666
+ "glucose": row["glucose_mgdl"],
667
+ "roc": row["roc_mgdl_min"],
668
+ "significance": result.significance,
669
+ "threshold": result.threshold,
670
+ "activated": result.activated,
671
+ "energy_level": result.energy_level,
672
+ "risk_proba": risk_proba,
673
+ "true_risk": true_risk,
674
+ })
675
+
676
+ progress_bar.empty()
677
+ status_text.empty()
678
+
679
+ # Convert to DataFrame
680
+ results_df = pd.DataFrame(results)
681
+ telemetry_df = monitor.get_telemetry_df()
682
+
683
+ # Compute metrics
684
+ total_events = len(results_df)
685
+ total_activations = int(results_df["activated"].sum())
686
+ activation_rate = total_activations / total_events
687
+ energy_savings = 1 - activation_rate
688
+
689
+ # Statistical evaluation (on activated events)
690
+ activated_results = results_df[results_df["activated"]].copy()
691
+ if len(activated_results) > 10:
692
+ y_true = activated_results["true_risk"].values
693
+ y_pred = (activated_results["risk_proba"].fillna(0.5) >= 0.5).astype(int).values
694
+
695
+ f1 = f1_score(y_true, y_pred, zero_division=0)
696
+ precision = precision_score(y_true, y_pred, zero_division=0)
697
+ recall = recall_score(y_true, y_pred, zero_division=0)
698
+
699
+ if show_bootstrap:
700
+ f1_mean, f1_low, f1_high = bootstrap_metric(y_true, y_pred, lambda yt, yp: f1_score(yt, yp, zero_division=0))
701
+ prec_mean, prec_low, prec_high = bootstrap_metric(y_true, y_pred, lambda yt, yp: precision_score(yt, yp, zero_division=0))
702
+ rec_mean, rec_low, rec_high = bootstrap_metric(y_true, y_pred, lambda yt, yp: recall_score(yt, yp, zero_division=0))
703
+ else:
704
+ f1 = precision = recall = 0.0
705
+ f1_mean = prec_mean = rec_mean = 0.0
706
+ f1_low = f1_high = prec_low = prec_high = rec_low = rec_high = 0.0
707
+
708
+ # Dashboard
709
+ st.header("πŸ“Š Performance Dashboard")
710
+
711
+ col1, col2, col3, col4 = st.columns(4)
712
+ col1.metric("Total Events", f"{total_events}")
713
+ col2.metric("Activations", f"{total_activations} ({activation_rate:.1%})")
714
+ col3.metric("Energy Savings", f"{energy_savings:.1%}")
715
+ col4.metric("Alerts", f"{len(monitor.alerts)}")
716
+
717
+ col1, col2, col3 = st.columns(3)
718
+ if show_bootstrap and len(activated_results) > 10:
719
+ col1.metric("F1 Score", f"{f1_mean:.3f}", help=f"95% CI: [{f1_low:.3f}, {f1_high:.3f}]")
720
+ col2.metric("Precision", f"{prec_mean:.3f}", help=f"95% CI: [{prec_low:.3f}, {prec_high:.3f}]")
721
+ col3.metric("Recall", f"{rec_mean:.3f}", help=f"95% CI: [{rec_low:.3f}, {rec_high:.3f}]")
722
+ else:
723
+ col1.metric("F1 Score", f"{f1:.3f}")
724
+ col2.metric("Precision", f"{precision:.3f}")
725
+ col3.metric("Recall", f"{recall:.3f}")
726
+
727
+ # Visualizations
728
+ st.header("πŸ“ˆ Real-Time Visualizations")
729
+
730
+ # Glucose + Threshold
731
+ fig_col1, fig_col2 = st.columns(2)
732
+
733
+ with fig_col1:
734
+ st.subheader("Glucose Levels")
735
+ chart_data = results_df.set_index("timestamp")[["glucose"]]
736
+ st.line_chart(chart_data, height=250)
737
+
738
+ with fig_col2:
739
+ st.subheader("Significance vs Threshold (Adaptive PI Control)")
740
+ chart_data = results_df.set_index("timestamp")[["significance", "threshold"]]
741
+ st.line_chart(chart_data, height=250)
742
+
743
+ # Energy tracking
744
+ if show_energy_viz:
745
+ st.subheader("Energy Level (Bio-Inspired Regeneration)")
746
+ chart_data = results_df.set_index("timestamp")[["energy_level"]]
747
+ st.line_chart(chart_data, height=200)
748
+
749
+ # Significance components
750
+ if show_components and len(telemetry_df) > 0:
751
+ comp_cols = [c for c in telemetry_df.columns if c.startswith("comp_")]
752
+ if comp_cols:
753
+ st.subheader("Significance Components (Diabetes-Specific Risk Factors)")
754
+ chart_data = telemetry_df.set_index("timestamp")[comp_cols]
755
+ st.line_chart(chart_data, height=200)
756
+
757
+ # Alerts
758
+ st.header("⚠️ Risk Alerts")
759
+ if monitor.alerts:
760
+ alerts_df = pd.DataFrame(monitor.alerts)
761
+ st.dataframe(alerts_df, use_container_width=True)
762
+ else:
763
+ st.info("No high-risk alerts triggered in this window.")
764
+
765
+ # Detailed telemetry
766
+ with st.expander("πŸ” Detailed Telemetry (Last 100 Events)"):
767
+ st.dataframe(results_df.tail(100), use_container_width=True)
768
+
769
+ # Export telemetry
770
+ if export_telemetry:
771
+ st.header("πŸ“₯ Export Telemetry")
772
+ json_data = monitor.export_json()
773
+ st.download_button(
774
+ label="Download Telemetry JSON",
775
+ data=json_data,
776
+ file_name="sundew_diabetes_telemetry.json",
777
+ mime="application/json",
778
+ )
779
+ st.success("Telemetry ready for hardware validation workflows")
780
+
781
+ # Footer
782
+ st.divider()
783
+ st.caption(f"🌿 Powered by Sundew Algorithms v0.7+ | PipelineRuntime with custom DiabetesSignificanceModel | Research prototype")