Spaces:
Sleeping
Sleeping
| """Sundew Diabetes Commons – holistic, open Streamlit experience.""" | |
| from __future__ import annotations | |
| import json | |
| import logging | |
| import math | |
| import time | |
| from dataclasses import dataclass | |
| from typing import Any, Dict, List, Optional, Tuple | |
| import numpy as np | |
| import pandas as pd | |
| import streamlit as st | |
| try: | |
| from sundew import SundewAlgorithm # type: ignore[attr-defined] | |
| _HAS_SUNDEW = True | |
| except Exception: # pragma: no cover - graceful fallback | |
| SundewAlgorithm = None # type: ignore | |
| _HAS_SUNDEW = False | |
| LOGGER = logging.getLogger("sundew.diabetes.commons") | |
| class SundewGateConfig: | |
| target_activation: float = 0.22 | |
| temperature: float = 0.08 | |
| mode: str = "tuned_v2" | |
| class AdaptiveGate: | |
| """Adapter that hides Sundew/Fallback branching.""" | |
| def __init__(self, config: SundewGateConfig) -> None: | |
| self.config = config | |
| self._ema = 0.0 | |
| self._tau = 0.5 | |
| self._alpha = 0.02 | |
| if _HAS_SUNDEW and SundewAlgorithm is not None: | |
| try: | |
| self.sundew: Optional[SundewAlgorithm] = SundewAlgorithm( | |
| target_activation=config.target_activation, | |
| temperature=config.temperature, | |
| mode=config.mode, | |
| ) | |
| except TypeError: # older package versions | |
| self.sundew = SundewAlgorithm() | |
| else: | |
| self.sundew = None | |
| def decide(self, score: float) -> bool: | |
| if self.sundew is not None: | |
| for attr in ("decide", "step", "open"): | |
| fn = getattr(self.sundew, attr, None) | |
| if callable(fn): | |
| try: | |
| return bool(fn(score)) | |
| except Exception: # pragma: no cover - parity fallback | |
| continue | |
| # Fallback logistic gate | |
| temperature = max(self.config.temperature, 1e-6) | |
| probability = 1.0 / (1.0 + math.exp(-(score - self._tau) / temperature)) | |
| fired = np.random.rand() < probability | |
| self._ema = (1 - self._alpha) * self._ema + self._alpha * ( | |
| 1.0 if fired else 0.0 | |
| ) | |
| self._tau += 0.01 * (self.config.target_activation - self._ema) | |
| self._tau = min(0.95, max(0.05, self._tau)) | |
| return fired | |
| def load_example_dataset(n_rows: int = 720) -> pd.DataFrame: | |
| rng = np.random.default_rng(17) | |
| t0 = pd.Timestamp.utcnow().floor("5min") - pd.Timedelta(minutes=5 * n_rows) | |
| timestamps = [t0 + pd.Timedelta(minutes=5 * i) for i in range(n_rows)] | |
| base = 118 + 28 * np.sin(np.linspace(0, 7 * np.pi, n_rows)) | |
| noise = rng.normal(0, 12, n_rows) | |
| meals = (rng.random(n_rows) < 0.05).astype(float) * rng.normal(50, 18, n_rows).clip( | |
| 0, 150 | |
| ) | |
| insulin = (rng.random(n_rows) < 0.03).astype(float) * rng.normal( | |
| 4.2, 1.5, n_rows | |
| ).clip(0, 10) | |
| steps = rng.integers(0, 200, size=n_rows) | |
| hr = 68 + (steps > 90) * rng.integers(20, 45, size=n_rows) | |
| sleep = (rng.random(n_rows) < 0.12).astype(float) | |
| stress = rng.uniform(0, 1, n_rows) | |
| glucose = base + noise + 0.4 * meals - 0.7 * insulin | |
| df = pd.DataFrame( | |
| { | |
| "timestamp": timestamps, | |
| "glucose_mgdl": glucose.round(1), | |
| "carbs_g": meals.round(1), | |
| "insulin_units": insulin.round(1), | |
| "steps": steps, | |
| "hr": hr, | |
| "sleep_flag": sleep, | |
| "stress_index": stress, | |
| } | |
| ) | |
| return df | |
| def compute_features(df: pd.DataFrame) -> pd.DataFrame: | |
| df = df.copy() | |
| df = df.sort_values("timestamp").reset_index(drop=True) | |
| df["timestamp"] = pd.to_datetime(df["timestamp"], utc=True) | |
| df["glucose_prev"] = df["glucose_mgdl"].shift(1) | |
| dt = ( | |
| df["timestamp"].astype("int64") - df["timestamp"].shift(1).astype("int64") | |
| ) / 60e9 | |
| df["roc_mgdl_min"] = (df["glucose_mgdl"] - df["glucose_prev"]) / dt | |
| df["roc_mgdl_min"].replace([np.inf, -np.inf], 0.0, inplace=True) | |
| df["roc_mgdl_min"].fillna(0.0, inplace=True) | |
| ema = df["glucose_mgdl"].ewm(span=48, adjust=False).mean() | |
| df["deviation"] = (df["glucose_mgdl"] - ema).fillna(0.0) | |
| df["iob_proxy"] = df["insulin_units"].rolling(12, min_periods=1).sum() / 12.0 | |
| df["cob_proxy"] = df["carbs_g"].rolling(12, min_periods=1).sum() / 12.0 | |
| df["variability"] = df["glucose_mgdl"].rolling(24, min_periods=2).std().fillna(0.0) | |
| df["activity_factor"] = (df["steps"] / 200.0 + df["hr"] / 160.0).clip(0, 1) | |
| df["sleep_flag"] = df.get("sleep_flag", 0.0).fillna(0.0) | |
| df["stress_index"] = df.get("stress_index", 0.5).fillna(0.5) | |
| features = df[ | |
| [ | |
| "timestamp", | |
| "glucose_mgdl", | |
| "roc_mgdl_min", | |
| "deviation", | |
| "iob_proxy", | |
| "cob_proxy", | |
| "variability", | |
| "activity_factor", | |
| "sleep_flag", | |
| "stress_index", | |
| ] | |
| ].copy() | |
| return features | |
| def lightweight_score(row: pd.Series) -> float: | |
| glucose = row["glucose_mgdl"] | |
| roc = row["roc_mgdl_min"] | |
| deviation = row["deviation"] | |
| iob = row["iob_proxy"] | |
| cob = row["cob_proxy"] | |
| stress = row["stress_index"] | |
| score = 0.0 | |
| score += max(0.0, (glucose - 180) / 80) | |
| score += max(0.0, (70 - glucose) / 30) | |
| score += abs(roc) / 6.0 | |
| score += abs(deviation) / 100.0 | |
| score += stress * 0.4 | |
| score += (cob - iob) * 0.05 | |
| return float(np.clip(score, 0.0, 1.5)) | |
| def train_simple_model(df: pd.DataFrame): | |
| threshold = 180 | |
| features = df[ | |
| [ | |
| "glucose_mgdl", | |
| "roc_mgdl_min", | |
| "iob_proxy", | |
| "cob_proxy", | |
| "activity_factor", | |
| "variability", | |
| ] | |
| ] | |
| labels = (df["glucose_mgdl"] > threshold).astype(int) | |
| model = Pipeline( | |
| [ | |
| ("scaler", StandardScaler()), | |
| ( | |
| "clf", | |
| LogisticRegression( | |
| max_iter=400, | |
| class_weight="balanced", | |
| ), | |
| ), | |
| ] | |
| ) | |
| try: | |
| model.fit(features, labels) | |
| return model | |
| except Exception: # pragma: no cover | |
| return None | |
| def render_overview(results: pd.DataFrame, alerts: List[Dict[str, Any]]) -> None: | |
| total = len(results) | |
| activations = int(results["activated"].sum()) | |
| activation_rate = activations / max(total, 1) | |
| energy_savings = 1.0 - activation_rate | |
| st.metric("Events", f"{total}") | |
| st.metric("Heavy activations", f"{activations} ({activation_rate:.1%})") | |
| st.metric("Estimated energy saved", f"{energy_savings:.1%}") | |
| st.metric("Alerts", f"{len(alerts)}") | |
| with st.expander("Recent alerts", expanded=False): | |
| if alerts: | |
| st.table(pd.DataFrame(alerts).tail(10)) | |
| else: | |
| st.info("No high-risk alerts in this window.") | |
| st.area_chart(results.set_index("timestamp")["glucose_mgdl"], height=220) | |
| def render_treatment_plan(medications: Dict[str, Any], next_visit: str) -> None: | |
| st.subheader("Full-cycle treatment support") | |
| st.write( | |
| "Upload or edit medication schedules, insulin titration guidance, and clinician notes." | |
| ) | |
| st.json(medications, expanded=False) | |
| st.caption(f"Next scheduled review: {next_visit}") | |
| def render_lifestyle_support(results: pd.DataFrame) -> None: | |
| st.subheader("Lifestyle & wellbeing") | |
| recent = results.tail(96).copy() # last ~8 hours if 5min cadence | |
| avg_glucose = recent["glucose_mgdl"].mean() | |
| active_minutes = int((recent["activity_factor"] > 0.4).sum() * 5) | |
| st.metric("Average glucose (8h)", f"{avg_glucose:.1f} mg/dL") | |
| st.metric("Active minutes", f"{active_minutes} min") | |
| st.markdown( | |
| "- 🎯 Aim for gentle movement every hour you are awake.\n" | |
| "- 🥗 Consider pairing carbs with protein/fiber to smooth spikes.\n" | |
| "- 😴 Sleep flagged recently? Try 10-minute breathing before bed.\n" | |
| "- 🤗 Journal one gratitude moment—stress index strongly shapes risk." | |
| ) | |
| def render_community_actions() -> Dict[str, List[str]]: | |
| st.subheader("Community impact") | |
| st.write( | |
| "Invite families, caregivers, and clinics to the commons. Set up alerts, shared logs, and outreach." | |
| ) | |
| contact_list = [ | |
| "SMS: +233-200-000-111", | |
| "WhatsApp: Care Circle Group", | |
| "Clinic portal: sundew.health/community", | |
| ] | |
| st.table(pd.DataFrame({"Support channel": contact_list})) | |
| callouts = { | |
| "Desired partners": ["Rural clinics", "Youth ambassadors", "Nutrition co-ops"], | |
| "Needs": ["Smartphone grants", "Solar charging kits", "Translation volunteers"], | |
| } | |
| return callouts | |
| def render_telemetry(results: pd.DataFrame, telemetry: List[Dict[str, Any]]) -> None: | |
| st.subheader("Telemetry & export") | |
| st.write( | |
| "Download event-level telemetry for validation, research, or regulatory reporting." | |
| ) | |
| json_payload = json.dumps(telemetry, default=str, indent=2) | |
| st.download_button( | |
| label="Download telemetry (JSON)", | |
| data=json_payload, | |
| file_name="sundew_diabetes_telemetry.json", | |
| mime="application/json", | |
| ) | |
| st.dataframe(results.tail(100)) | |
| def main() -> None: | |
| st.set_page_config( | |
| page_title="Sundew Diabetes Commons", | |
| layout="wide", | |
| page_icon="🕊️", | |
| ) | |
| st.title("🕊️ Sundew Diabetes Commons") | |
| st.caption( | |
| "Open, compassionate diabetes care—monitoring, treatment, lifestyle, community." | |
| ) | |
| st.sidebar.header("Load data") | |
| uploaded = st.sidebar.file_uploader("CGM / diary CSV", type=["csv"]) | |
| use_example = st.sidebar.checkbox("Use synthetic example", True) | |
| st.sidebar.header("Sundew configuration") | |
| target_activation = st.sidebar.slider("Target activation", 0.05, 0.9, 0.22, 0.01) | |
| temperature = st.sidebar.slider("Gate temperature", 0.02, 0.5, 0.08, 0.01) | |
| mode = st.sidebar.selectbox( | |
| "Preset", ["tuned_v2", "conservative", "aggressive", "auto_tuned"], index=0 | |
| ) | |
| if uploaded is not None: | |
| df = pd.read_csv(uploaded) | |
| elif use_example: | |
| df = load_example_dataset() | |
| else: | |
| st.stop() | |
| features = compute_features(df) | |
| model = train_simple_model(features) | |
| gate = AdaptiveGate(SundewGateConfig(target_activation, temperature, mode)) | |
| telemetry: List[Dict[str, Any]] = [] | |
| records: List[Dict[str, Any]] = [] | |
| alerts: List[Dict[str, Any]] = [] | |
| progress = st.progress(0) | |
| status = st.empty() | |
| for idx, row in enumerate(features.itertuples(index=False), start=1): | |
| score = lightweight_score(pd.Series(row._asdict())) | |
| should_run = gate.decide(score) | |
| risk_proba = None | |
| if should_run and model is not None: | |
| try: | |
| sample = np.array( | |
| [ | |
| [ | |
| row.glucose_mgdl, | |
| row.roc_mgdl_min, | |
| row.iob_proxy, | |
| row.cob_proxy, | |
| row.activity_factor, | |
| row.variability, | |
| ] | |
| ] | |
| ) | |
| risk_proba = float(model.predict_proba(sample)[0, 1]) # type: ignore[attr-defined] | |
| except Exception: | |
| pass | |
| if risk_proba is not None and risk_proba >= 0.6: | |
| alerts.append( | |
| { | |
| "timestamp": row.timestamp, | |
| "glucose": row.glucose_mgdl, | |
| "risk": risk_proba, | |
| "message": "Check CGM, hydrate, plan balanced snack/insulin", | |
| } | |
| ) | |
| records.append( | |
| { | |
| "timestamp": row.timestamp, | |
| "glucose_mgdl": row.glucose_mgdl, | |
| "roc_mgdl_min": row.roc_mgdl_min, | |
| "deviation": row.deviation, | |
| "iob_proxy": row.iob_proxy, | |
| "cob_proxy": row.cob_proxy, | |
| "variability": row.variability, | |
| "activity_factor": row.activity_factor, | |
| "score": score, | |
| "activated": should_run, | |
| "risk_proba": risk_proba, | |
| } | |
| ) | |
| telemetry.append( | |
| { | |
| "timestamp": str(row.timestamp), | |
| "score": score, | |
| "activated": should_run, | |
| "risk_proba": risk_proba, | |
| } | |
| ) | |
| progress.progress(idx / len(features)) | |
| status.text(f"Processing event {idx}/{len(features)}") | |
| progress.empty() | |
| status.empty() | |
| results = pd.DataFrame(records) | |
| tabs = st.tabs( | |
| [ | |
| "Overview", | |
| "Treatment", | |
| "Lifestyle", | |
| "Community", | |
| "Telemetry", | |
| ] | |
| ) | |
| with tabs[0]: | |
| render_overview(results, alerts) | |
| with tabs[1]: | |
| plan = { | |
| "Insulin": {"Basal": "12u nightly", "Bolus": "1u per 12g carbs"}, | |
| "Metformin": "500mg twice daily", | |
| "Check-ins": ["Morning CGM calibration", "Weekly telehealth"], | |
| } | |
| render_treatment_plan(plan, next_visit="2025-07-12 (virtual clinic)") | |
| with tabs[2]: | |
| render_lifestyle_support(results) | |
| with tabs[3]: | |
| community_items = render_community_actions() | |
| st.json(community_items, expanded=False) | |
| with tabs[4]: | |
| render_telemetry(results, telemetry) | |
| st.sidebar.markdown("---") | |
| st.sidebar.caption( | |
| "Sundew status: " | |
| + ("✅ native gating" if _HAS_SUNDEW else "⚠️ fallback gate active") | |
| ) | |
| if __name__ == "__main__": | |
| main() | |