Matthias Jacob 2370254e23 bridge: add steps and pulse via Health's Daily Values / Measurements APIs
Health v3 splits writable metrics across three distinct endpoint families
instead of one unified /entries resource (per nextcloud/health's
src/metrics.ts and src/api/*.ts, which are more current than its Markdown
docs): journal entries, one-off measurements, and per-day upserted daily
values.

- weight and steps are Daily Values (PUT /daily-values/{metricKey}/{date});
  steps are sourced from Companion's daily_totals aggregate (already
  deduplicated across sources) instead of summing raw per-window records.
- pulse (heart rate) is a Measurement (POST /measurements); handles both
  raw records and bucketed resolution windows, using the window average
  and a deterministic id for the latter since they carry no HC uuid.
- Both endpoints require an explicit unit (weight: kg, steps: steps,
  pulse: bpm) and Measurements only accept context manual/checkin/checkout,
  not an automation-specific value -- both previously caused 400s.
- sleep_duration is dropped: despite being documented, it isn't an actual
  Daily Value, Measurement, or journal metric in the shipped app (absent
  from GET /configuration entirely).
- Logging now reports per-metric success/failure counts and uses Health's
  metric-key vocabulary consistently instead of mixing in Companion's raw
  payload field names.
2026-09-17 03:48:47 +02:00

Private Health

This project aims to create the necessary building blocks to collect, aggregate, and analyze one's personal health data while maintaining data sovereignty. Instead of closed-source apps and dubious clouds, it should use a FLOSS stack.

Architecture

  1. Start with what already works. Projects like Gadgetbridge or OpenScale have lively communities and cover a lot of ground. Lean on them whenever possible instead of reinventing the collector layer.
  2. When a FLOSS bridge is missing, accept a constrained compromise—HealthConnect or a similar on-device broker—so long as it keeps data encrypted locally and gives us clear levers for sync frequency, retention, and audit logs.
  3. Only build bespoke pieces (Nextcloud health app, dashboards, data science notebooks) where there is a genuine capability gap or user story that no existing FLOSS project covers.

The diagram below mirrors that hierarchy: green blocks are “ready to plug in,” orange blocks still need shaping or configuration, and red blocks mark the blank canvas where we design from scratch. In day-to-day terms, that means we can already sync scales or fitness trackers with minimal effort, but we must design a trustworthy Nextcloud app, decide how dashboards surface trends, and write glue that keeps everything coherent.

HealthConnect remains the pragmatic bridge for now. It lives on the users phone, encrypts data, and speaks schemas for dozens of vendors. That buys time for a gentle migration: you can keep your current wearable, pull data into a local store, and steadily replace proprietary parts as FLOSS collectors mature. We should still document a path away from HealthConnect by pairing it with community-built collectors or writing our own bridge once the ecosystem catches up. Any alternative proposal should show the same benefits—broad device coverage, schema richness, and user-controlled sync rules.

flowchart LR
  scale(("bathroom<br>scale"))
  A["openscale"]
  B["openscale sync"]
  hc["HealthConnect API<br>(or similar)"]

  fitness_tracker(("fitness<br>tracker"))
  gb["Gadgetbridge"]
  gb_sync["(Gadgetbridge sync?)"]

  nc[("Nextcloud")]

  bpm(("blood<br>pressure<br> monitor"))
  --> bpm_app["BPM app"]
  --> hc

  scale-->A
  --> B
  --> hc
  
  fitness_tracker
  --> gb
  --> gb_sync
  --> hc

  hc --> ldc["Life Dashboard<br>Companion"]
  --> bridge["Bridge / Adapter<br>(auth translation +<br>schema mapping)"]
  --> health3["Nextcloud Health v3<br>(weight, steps, activity,<br>blood pressure)"]

  health3 -.->|feature request| gap["muscle mass, body water<br>(not yet supported)"]

  health3 --> nc

  nc --> cA["Web UI<br>(Dashboard, statistics)"]
  nc --> cB["mobile devices"]
  nc --> cC["...other clients..."]

  classDef green fill:#8fb935,stroke:#333;
  classDef yellow fill:#e6e22e,stroke:#333;
  classDef orange fill:#e09c3b,stroke:#333,stroke-width:4px;
  classDef red fill:#e64747,stroke:#333,stroke-width:4px;
  class A,B,gb,hc,ldc,health3 green;
  class bpm_app,gb_sync,cA,nc,bridge,gap orange;
S
Description
No description provided
Readme
95 KiB
Languages
Python 100%