Personal Data Platform

A self-hosted TimescaleDB warehouse for health, activity, environmental, and hardware-telemetry data — backfilled from exports, kept current by small monitored pollers, and dashboarded in Grafana.

Languages: Python, SQL

Storage: TimescaleDB (PostgreSQL)

License: MIT

A small self-hosted warehouse that collects data from several parts of ordinary life into one database: environmental readings from indoor sensors and an outdoor PurpleAir node, health and activity data from Fitbit and Strava, and hardware telemetry from the machines running the system. A TimescaleDB cluster on a NAS holds the data; Grafana and the API pollers run on a Raspberry Pi alongside it.

The motivation was practical rather than analytical. Consumer services each keep their own slice of the record behind their own app and their own export button. Putting all of it in a database under my control means the whole record is queryable with ordinary SQL — joins, window functions, and time-series functions — instead of being read one dashboard at a time. The Fitbit archive now reaches back roughly ten years, which makes the long horizon more interesting than the recent one.

Grafana scoreboard dashboard: step streak, today's steps, week-versus-baseline delta, personal-bests table, 30-day daily steps bar chart, week-over-week deltas, and an Active Zone Minutes gauge.

Five Grafana dashboards are provisioned from the repository — an overview, a trends view, the scoreboard above, a morning report, and a running view backed by Strava. A 30-day window is useful for immediate feedback; the value of the backfill is being able to widen it to years.

Why a shared time axis

Once the domains share a database and a common time axis, the boundaries between them stop mattering much. Questions that would otherwise need three exports and a spreadsheet become a single query: how sleep tracks against running volume, whether air quality or seasonal change lines up with shifts in activity or recovery.

That establishes correlation, not causation, and the data is a sample size of one. What changes is the cost of looking — exploration that was previously not worth the setup effort becomes a query you can write in a minute.

Design notes

  • Deliberately boring ingestion — historical exports do the backfill; small pollers keep the database current. Both paths are idempotent upserts, so re-running one is always safe.
  • A dead-man switch — the pollers check in with a heartbeat service on every run, each with a grace period matched to how often it runs: between 30 minutes and 18 hours. Miss the window and an alert goes out, so a poller that has stopped silently surfaces within a day rather than as a gap discovered months later.
  • Least-privilege roles — separate owner, read-write, and read-only roles, with a schema per project so unrelated domains can share one cluster without reaching into each other.
  • Extensions doing the heavy lifting — TimescaleDB provides hypertables and continuous aggregates; the TimescaleDB Toolkit adds statistical hyperfunctions; PostGIS stores and queries the GPS tracks from Strava.
  • Built for the API migration — the health poller targets the Google Health API rather than the legacy Fitbit Web API, which sunsets in September 2026.
  • Nightly offsite backups — the cluster is dumped and copied off the NAS on a nightly schedule.

Architecture

Sources fan into a column of small ingest services on the Raspberry Pi, each writing into the shared TimescaleDB cluster on the NAS. Grafana reads back from the same cluster; the pollers report to a heartbeat monitor, and the database is backed up offsite nightly.

Personal data platform architecture Indoor sensors, a PurpleAir API feed, the Google Health and Strava APIs, and host telemetry feed a column of ingest services running under Docker Compose on a Raspberry Pi. Those services write into a TimescaleDB cluster hosted on a NAS, which holds hypertables and continuous aggregates. Grafana, also on the Pi, reads back from the cluster; the pollers send heartbeat pings to an external monitor, and the database is copied to offsite backup nightly. Sources Raspberry Pi — Docker Compose NAS — TimescaleDB cluster Indoor sensors PurpleAir API Google Health API Strava API Host telemetry ingest_mqtt ingest_purpleair poller · health poller · strava telemetry agent Grafana TimescaleDB hypertables · continuous aggregates Offsite backup Heartbeat monitor reads nightly heartbeat

Source

The health and activity half of the platform is public: database schema, historical backfill tools, API pollers, Docker configuration, and the provisioned Grafana dashboards. The environmental half lives in its own repository, linked below. The system is in active development and its design is still settling.