# 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://: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=` 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).