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).
78 lines
3.6 KiB
Markdown
78 lines
3.6 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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).
|