Files
private_health/bridge/README.md
T
Matthias Jacob 9865b43945 bridge: update README for the Daily Values / Measurements architecture
Replace the outdated /entries-only description with the three-endpoint
model, document daily_totals-based steps sourcing, and move sleep_duration
to "not yet covered" with an explanation of why (not implemented in
Health v3 at all, not just disabled for this user).
2026-09-17 03:49:04 +02:00

3.6 KiB

Bridge: Life Dashboard Companion → Nextcloud Health v3

Forwards the Health Connect types that are both sent by the Companion app and supported by Health v3, split across Health's three write APIs:

  • Daily Values (PUT /daily-values/{metricKey}/{date}, one upsert per calendar day): weight, steps.
  • Measurements (POST /measurements, one call per timestamped reading): pulse (heart rate).

Which metrics a given Health instance actually knows is detected at startup via GET /configuration, since shipped releases can lag behind the documented API (see "Not yet covered" below).

Requires Python 3.10+ (uses str | None type hints).

Setup

# One-time: obtain a Nextcloud app password via Login Flow v2.
# Opens a browser tab, confirm the login there, then this writes credentials.json.
uv run nc_login_flow.py https://your-nextcloud.example.com

# Set the same HMAC secret configured under "Webhook Headers" in the Companion app.
export NC_WEBHOOK_SECRET="..."
uv run server.py

Point the Companion app's webhook URL at http://<this-host>:8080/webhook/health-connect.

Set NC_TIMEOUT_SECONDS (default 30) if your Nextcloud instance responds slowly.

Scope

  • On startup, fetches GET /configuration and only forwards metrics the Health instance actually knows; unknown metrics are logged and skipped instead of failing every request with an error. Metrics it knows but has disabled for the user are enabled automatically via PUT /configuration.
  • Verifies X-Signature: sha256=<hmac> on every request; rejects with 401 on mismatch (Companion does not retry on 401).
  • Reads weight, daily_totals (for steps), and heart_rate from the payload; everything else is ignored.
  • Steps use Companion's daily_totals, which Health Connect already deduplicates across sources (phone, watch, ...) into one total per day; summing the raw per-window steps records ourselves would double-count, so those are ignored in favor of the daily total.
  • Weight uses the UTC calendar date of each reading as the Daily Value's date key (Health doesn't expose the owning user's timezone to this bridge), so a reading taken right around local midnight could land on the "wrong" day.
  • Heart rate: handles both raw records (bpm/time) and bucketed resolution windows (bucket_start/avg); bucketed windows get a deterministic id derived from the window since they carry no HC uuid.
  • Daily Value writes are idempotent by construction (one value per metric/date, last write wins); Measurement writes use the Health Connect record uuid (or a derived id for bucketed windows) as Health's operationId for idempotent retries.
  • Returns 502 on partial forwarding failure so the Companion app retries the whole payload later (transient failures are retried by the app).

Not yet covered

  • Sleep duration: despite being documented in Health v3's data model, it isn't an actual Daily Value, Measurement, or journal metric in the shipped app yet (absent from GET /configuration's metric list entirely), so there's currently no endpoint to send it to.
  • Blood pressure, body fat, blood glucose, oxygen saturation, and temperature: the Companion app doesn't send these Health Connect types at all yet.
  • Muscle mass and body water: not on Health v3's roadmap at all (see the main README's architecture diagram).
  • Hydration and exercise/movement: both apps have these concepts, but with incompatible shapes (a volume/duration in Companion vs. a closed set of event options in Health), so a faithful mapping isn't possible without lossy guessing.
  • Containerized deployment (currently a local script only, per the current iteration's scope).