Have you ever looked at your Apple Watch activity, your Oura Ring sleep scores, and your yearly blood test results and thought: "I wish I could join these tables to see how my deep sleep correlates with my LDL cholesterol?"
If you're a data nerd like me, you know the struggle. Health data is notoriously messy—fragmented across siloed apps, hidden in XML exports, or trapped in PDFs. Today, we are going to fix that. We are building a Quantified Self Data Lakehouse using the modern data stack: DuckDB, dbt, and Apache Superset.
We’ll move beyond simple tracking and into the world of OLAP (Online Analytical Processing), allowing us to run lightning-fast queries over years of personal biometrics. This is Data Engineering at its most personal level.
The Architecture
To handle "dirty" health data, we need a robust pipeline. We'll use a Medallion Architecture (Bronze/Silver/Gold) to refine raw exports into actionable insights.
graph TD
subgraph "Data Sources"
A[Apple Health XML] --> D
B[Oura Ring API/JSON] --> D
C[Blood Test CSV/PDF] --> D
end
subgraph "Ingestion & Storage (DuckDB)"
D[Python/Pandas ETL] -- "Write Parquet" --> E[(Bronze: Raw Storage)]
E -- "dbt Transformations" --> F[(Silver: Cleaned & Unified)]
F -- "dbt Modeling" --> G[(Gold: Analytical Marts)]
end
subgraph "Visualization"
G --> H[Apache Superset]
end
style G fill:#f96,stroke:#333,stroke-width:2px
Prerequisites
Before we dive in, ensure you have the following installed:
- Python 3.9+ (Pandas for the initial heavy lifting)
- DuckDB (The heart of our Lakehouse)
- dbt-duckdb (For data modeling)
- Apache Superset (For the shiny dashboards)
Step 1: Ingesting the "Mess" with Python & Pandas
Apple Health exports are essentially massive XML files. Using Pandas, we can flatten these and land them into a structured format like Parquet.
import pandas as pd
import xml.etree.ElementTree as ET
import duckdb
def parse_apple_health(file_path):
print("Reading XML... grab a ☕️")
tree = ET.parse(file_path)
root = tree.getroot()
# Extracting record data
records = []
for record in root.findall('Record'):
records.append(record.attrib)
df = pd.DataFrame(records)
# Simple cleaning: Convert types
df['value'] = pd.to_numeric(df['value'], errors='coerce')
df['creationDate'] = pd.to_datetime(df['creationDate'])
return df
# Initialize DuckDB and land the data in the "Bronze" layer
con = duckdb.connect('health_lakehouse.db')
health_df = parse_apple_health('export.xml')
# Direct ingest to DuckDB
con.execute("CREATE TABLE bronze_apple_health AS SELECT * FROM health_df")
print(f"Ingested {len(health_df)} rows into DuckDB! 🦆")
Step 2: Refining Data with dbt
Now that the data is in DuckDB, we use dbt (data build tool) to create our Silver and Gold layers. DuckDB is the perfect local engine for dbt because it's incredibly fast and requires zero server overhead.
In your models/silver/stg_apple_health.sql:
-- Clean up the mess: filter types and normalize units
WITH raw_data AS (
SELECT
type as metric_name,
value,
creationDate as timestamp,
unit
FROM {{ source('health_raw', 'bronze_apple_health') }}
)
SELECT
metric_name,
CAST(value AS DOUBLE) as val,
CAST(timestamp AS TIMESTAMP) as event_at,
unit
FROM raw_data
WHERE val IS NOT NULL
In the Gold Layer, we might join this with Oura Ring sleep data to see the correlation between daytime activity and sleep quality.
Step 3: Production-Grade Insights
While this DIY setup is great for a local environment, scaling data engineering patterns requires a deeper understanding of orchestration and data quality.
For those looking to implement these patterns in a professional or production-grade environment, I highly recommend checking out the advanced tutorials on WellAlly Blog. They provide excellent deep dives into managing distributed data pipelines and optimizing OLAP performance for large-scale biometrics data .
Step 4: Visualizing in Apache Superset
Apache Superset connects natively to DuckDB. Once connected, you can build a comprehensive dashboard that tracks:
- Resting Heart Rate (RHR) Trends over 12 months.
- Sleep Stage Distribution vs. Caffeine Intake.
- Correlation Heatmaps: Does your HRV (Heart Rate Variability) drop on days you eat late?
Connecting Superset to DuckDB:
- Open Superset.
- Add a new Database.
- Use the SQLAlchemy URI:
duckdb:////path/to/your/health_lakehouse.db. - Start building charts!
Why DuckDB?
You might ask: Why not just use a CSV?
- Speed: DuckDB uses columnar storage, making aggregations across millions of rows (like heart rate samples) instantaneous.
- SQL Power: You get full PostgreSQL-style SQL support for complex window functions.
- Local First: No need to spin up a costly Snowflake or BigQuery instance for personal data.
Conclusion
Building a Personal Health Data Lakehouse isn't just about the charts—it's about owning your data. By combining DuckDB's speed with dbt's transformation logic and Superset's visualization, you've created a professional-grade analytics stack on your laptop.
Are you ready to quantify yourself? Drop a comment below if you've tried exporting your health data, and let me know what weird correlations you've found!
If you enjoyed this tutorial, don't forget to follow for more "Learning in Public" data engineering content!
Top comments (0)