Stop Guessing Your Health: Build a Unified Quantified Self Dashboard with Streamlit and ECharts

Published: (February 8, 2026 at 08:00 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Beck_Moulton

Introduction

Are you tired of jumping between five different apps just to see how your sleep affected your glucose levels? Welcome to the world of Quantified Self. In this era of wearable overload, the real challenge isn’t collecting data—it’s data engineering and synthesis.

In this tutorial we’ll build a unified health dashboard using Streamlit, ECharts, and Pandas. We’ll tackle the “heterogeneous data problem” by aligning time‑series data from an Oura Ring, Apple Watch, and a Continuous Glucose Monitor (CGM). By the end of this guide you’ll have a professional‑grade Quantified Self visualization tool that turns raw health metrics into actionable insights.

The Architecture: From Raw Logs to Insights

Handling health data is tricky because every device has a different sampling rate. Your CGM might ping every 5 minutes, while your Oura Ring provides a single “Sleep Score” once a day.

graph TD
    A[Oura Ring API] -->|Daily Metrics| B[(SQL Database)]
    C[Apple Watch / HealthKit] -->|Heart Rate/Steps| B
    D[Continuous Glucose Monitor] -->|5‑min Glucose| B
    B --> E{Data Alignment Layer}
    E -->|Pandas Resampling| F[Unified DataFrame]
    F --> G[Streamlit Frontend]
    G --> H[ECharts Interactive Viz]

Prerequisites

Make sure you have the following tech stack installed:

  • Python 3.9+
  • Streamlit – UI framework
  • Pandas – Data manipulation
  • SQLAlchemy – Database interface
  • streamlit‑echarts – Interactive charts
pip install streamlit pandas sqlalchemy streamlit-echarts

Step 1: The Data Layer (SQLAlchemy)

First we pull data from a centralized store. For this example we’ll assume you’ve synced your health data into a SQLite (or PostgreSQL) database.

import pandas as pd
from sqlalchemy import create_engine

# Database connection
engine = create_engine('sqlite:///health_data.db')

def load_health_data():
    # Fetch metrics from different sources
    cgm_df = pd.read_sql("SELECT timestamp, glucose_value FROM cgm_logs", engine)
    activity_df = pd.read_sql(
        "SELECT date, sleep_score, readiness_score FROM oura_metrics",
        engine,
    )

    # Ensure datetime types
    cgm_df["timestamp"] = pd.to_datetime(cgm_df["timestamp"])
    activity_df["date"] = pd.to_datetime(activity_df["date"])

    return cgm_df, activity_df

Step 2: The Alignment Magic (Pandas)

This is where the Data Engineering happens. We align the 5‑minute CGM data with the daily Oura scores using resample and merge_asof to create a continuous timeline.

def align_data(cgm, activity):
    # Resample CGM to hourly averages to reduce noise
    cgm_hourly = (
        cgm.set_index("timestamp")
        .resample("H")
        .mean()
        .reset_index()
    )

    # Merge daily activity data back into the hourly timeline
    # Left join keeps the high‑frequency timeline intact
    merged = pd.merge_asof(
        cgm_hourly.sort_values("timestamp"),
        activity.sort_values("date"),
        left_on="timestamp",
        right_on="date",
        direction="backward",
    )
    return merged

Step 3: Visualizing with ECharts

Standard charts are boring. For health data we want interactivity. ECharts lets us zoom into specific nights where sleep was poor and see exactly what the blood sugar was doing.

from streamlit_echarts import st_echarts

def render_health_chart(df):
    options = {
        "title": {"text": "Glucose vs. Sleep Correlation"},
        "tooltip": {"trigger": "axis"},
        "legend": {"data": ["Glucose", "Sleep Score"]},
        "xAxis": {
            "type": "category",
            "data": df["timestamp"]
            .dt.strftime("%m-%d %H:%M")
            .tolist(),
        },
        "yAxis": [
            {"type": "value", "name": "Glucose (mg/dL)"},
            {"type": "value", "name": "Score", "max": 100},
        ],
        "series": [
            {
                "name": "Glucose",
                "type": "line",
                "data": df["glucose_value"].tolist(),
                "smooth": True,
                "itemStyle": {"color": "#ff4d4f"},
            },
            {
                "name": "Sleep Score",
                "type": "bar",
                "yAxisIndex": 1,
                "data": df["sleep_score"].tolist(),
                "itemStyle": {"color": "#1890ff", "opacity": 0.3},
            },
        ],
        "dataZoom": [{"type": "slider"}],  # Add zoom control
    }
    st_echarts(options=options, height="500px")

The “Official” Way to Scale

A local dashboard is a great start, but moving from a weekend project to a production‑ready health‑tech platform requires:

  • Real‑time synchronization
  • HIPAA‑compliant data handling
  • Robust, scalable pipelines

For advanced architectural patterns and production‑ready examples of large‑scale time‑series data, check out the technical deep dives at the WellAlly Blog. They cover biometric data security, high‑performance modeling, and more.

Putting It All Together

Below is a minimal Streamlit app that ties everything together.

import streamlit as st

# Load and align data
cgm_df, activity_df = load_health_data()
merged_df = align_data(cgm_df, activity_df)

st.title("Quantified Self Health Dashboard")
st.subheader("Glucose vs. Sleep Score")

render_health_chart(merged_df)

Run the app with:

streamlit run your_app.py

Expanded Example

import streamlit as st

def main():
    st.set_page_config(page_title="Quantified Self Dashboard", layout="wide")
    st.title("📊 My Health Data Universe")

    st.sidebar.header("Filters")
    date_range = st.sidebar.date_input("Select Date Range")

    # Load and process
    cgm, activity = load_health_data()
    aligned_df = align_data(cgm, activity)

    # UI Layout
    col1, col2 = st.columns(2)
    with col1:
        st.metric("Avg Glucose", f"{aligned_df['glucose_value'].mean():.1f} mg/dL")
    with col2:
        st.metric("Avg Sleep Score", f"{aligned_df['sleep_score'].mean():.0f}")

    render_health_chart(aligned_df)

if __name__ == "__main__":
    main()

Conclusion

By unifying your data, you stop looking at isolated numbers and start seeing patterns. Maybe your blood sugar spikes on nights you get less than 2 hours of REM sleep? Now you have the data to prove it!

What’s next?

  • Add Anomaly Detection using Scikit‑learn to flag high‑glucose events.
  • Integrate Apple Health XML exports for even more granularity.
  • Check out the WellAlly Blog for more inspiration on health‑tech engineering.

Happy hacking!

Did you find this helpful? Drop a comment below with which health metric you track the most! 👇

0 views
Back to Blog

Related posts

Read more »