mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
True up the integration references
- ai-providers: /api/ai/test returns provider type, model, token usage and a sample with a reasonCode on failure; drop drifted line numbers - apple-health: source-priority overrides moved to /settings/sources; cite the constant instead of line numbers - data-import: four import paths (medication dose history added); ECG and metric-typed clinical records are imported; measurement externalId documented; CSV re-uploads dedupe on the natural key; mood loggedAt defaults to the start of the given date; the type table is a curated subset with /api/meta/capabilities authoritative - fitbit: the source-priority ladder ships, FITBIT is ranked in every default ladder - google-health: Sources is its own settings section, not nested under Integrations - withings: disconnect unsubscribes the webhook and deletes stored tokens without revoking the Withings-side grant; the connection test is self-serve, not admin-only; the safety-net cron is hourly; refresh stale references
This commit is contained in:
1 parent
123fcc4ec3
commit
b2ee7f11d1
6 files changed
+53
-41
No files matched your search
@@ -45,8 +45,8 @@ fallback for users who skip the setup.
|
||||
## The fallback chain
|
||||
|
||||
`User.aiProviderChain` is a JSON array of providers tried in priority
|
||||
order. The default is encoded in
|
||||
`src/lib/ai/provider-chain.ts:60-66`:
|
||||
order. The default is encoded as `PROVIDER_CHAIN_DEFAULT` in
|
||||
`src/lib/ai/provider-chain.ts`:
|
||||
|
||||
1. `codex` — ChatGPT OAuth (covered by your ChatGPT Plus / Pro
|
||||
subscription; no per-token cost on top).
|
||||
@@ -79,7 +79,7 @@ the default rather than 500-ing.
|
||||
4. In HealthLog, open `/settings/ai`. Set provider to **OpenAI**,
|
||||
paste the key, optionally override the model.
|
||||
|
||||
The default model is `gpt-4o` (`src/lib/ai/provider.ts:80`) — a
|
||||
The default model is `gpt-4o` (see `src/lib/ai/provider.ts`) — a
|
||||
full-size model that keeps the headline Insights assessments grounded
|
||||
and well-reasoned out of the box. Override to a lighter model such as
|
||||
`gpt-4o-mini` if you want to trade some reasoning headroom for a lower
|
||||
@@ -234,7 +234,7 @@ UI setup:
|
||||
`connected`.
|
||||
5. Token refresh runs automatically; the client triggers a refresh
|
||||
on 401 and persists the new tokens transparently
|
||||
(`src/lib/ai/provider.ts:122-217`).
|
||||
(`src/lib/ai/provider.ts`).
|
||||
|
||||
Disconnect cleanly from `/settings/ai` — `POST /api/auth/codex/disconnect`
|
||||
clears the encrypted token columns. The wire-level protocol is
|
||||
@@ -410,12 +410,12 @@ All three variables must also be listed in the compose
|
||||
## Connection test
|
||||
|
||||
`POST /api/ai/test` runs a one-shot probe against the resolved
|
||||
provider and returns the latency in milliseconds plus the model the
|
||||
provider routed to. The Settings UI surfaces a **Test connection**
|
||||
button next to each provider section that fires this endpoint. A
|
||||
failed probe surfaces a localisable `errorCode` so the UI renders
|
||||
the failure in the user's language rather than the raw upstream
|
||||
error.
|
||||
provider and returns the resolved provider type, the model, the token
|
||||
usage, and a short response sample. The Settings UI surfaces a
|
||||
**Test connection** button next to each provider section that fires
|
||||
this endpoint. A failed probe carries a categorised `reasonCode` so
|
||||
the UI renders the failure in the user's language rather than the raw
|
||||
upstream error.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -265,7 +265,8 @@ IMPORT** for cumulative metrics (steps, active energy, distance,
|
||||
flights, sleep, HRV, resting HR) and **WITHINGS ≻ APPLE_HEALTH ≻
|
||||
MANUAL** for point measurements where the Withings device is the
|
||||
primary sensor (weight, BP, body fat, body temperature, SpO₂, VO₂
|
||||
max). The defaults live in `src/lib/validations/source-priority.ts:205-220`.
|
||||
max). The defaults live in `DEFAULT_SOURCE_PRIORITY` in
|
||||
`src/lib/validations/source-priority.ts`.
|
||||
|
||||
Concrete consequences:
|
||||
|
||||
@@ -283,7 +284,7 @@ Concrete consequences:
|
||||
source tag — the lowest rank — is reserved for legacy CSV/JSON
|
||||
imports that pre-date the Apple Health passthrough.
|
||||
|
||||
Override per-user via the Sources section of `/settings/thresholds`.
|
||||
Override per-user via Settings → Sources (`/settings/sources`).
|
||||
|
||||
## Failure modes
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Data import
|
||||
|
||||
HealthLog has three ways to bring data in, all reachable from
|
||||
HealthLog has four ways to bring data in, all reachable from
|
||||
**Settings → Export & Import**:
|
||||
|
||||
1. An **Apple Health `export.zip`** upload — the full archive from the
|
||||
@@ -12,8 +12,11 @@ HealthLog has three ways to bring data in, all reachable from
|
||||
from another tracker. This page documents that format.
|
||||
3. A **CSV import** — one measurement per row, for a spreadsheet or a
|
||||
meter / sensor export. Documented in section 3 below.
|
||||
4. A **medication dose-history import** — its own card in the same
|
||||
panel, posting to `POST /api/medications/intake/dose-history-import`
|
||||
with its own `?dryRun=1` preview.
|
||||
|
||||
All three paths skip rows that already exist, so re-running an import is
|
||||
All paths skip rows that already exist, so re-running an import is
|
||||
safe — it merges rather than duplicating.
|
||||
|
||||
Every import reports what it actually wrote. A file where nothing was
|
||||
@@ -33,9 +36,10 @@ the browser — and a background job parses it while a progress indicator
|
||||
polls for the imported / skipped counts.
|
||||
|
||||
Re-uploading the same archive resolves to the same job by content hash,
|
||||
so it merges instead of creating duplicates. Clinical records
|
||||
(electrocardiograms, lab documents) are intentionally skipped. The full
|
||||
type mapping lives in [`apple-health.md`](./apple-health.md).
|
||||
so it merges instead of creating duplicates. ECG CSVs and metric-typed
|
||||
clinical records are imported too; see
|
||||
[`apple-health.md`](./apple-health.md) for the details and the full
|
||||
type mapping.
|
||||
|
||||
## 2. Generic JSON import
|
||||
|
||||
@@ -65,6 +69,7 @@ skipped and counted under `skipped` in the response.
|
||||
| `source` | no | string | Free-text origin label. Imported rows are tagged `IMPORT` server-side regardless. |
|
||||
| `notes` | no | string | Optional free text. |
|
||||
| `glucoseContext` | no | enum | Only for `BLOOD_GLUCOSE`. See [Glucose context](#glucose-context). |
|
||||
| `externalId` | no | string | A source-stable id (1–120 chars). When present, a re-import upserts on `(user, type, source, externalId)` so an edit upstream is reflected rather than skipped. |
|
||||
|
||||
### Glucose context
|
||||
|
||||
@@ -95,7 +100,7 @@ moment you know it.
|
||||
| `mood` | yes | enum | One of `SUPER_GUT`, `GUT`, `OKAY`, `SCHLECHT`, `LAUSIG`. |
|
||||
| `score` | yes | integer | `1`–`5`. |
|
||||
| `tags` | no | string | Comma-separated tags. |
|
||||
| `loggedAt` | no | string | ISO-8601 datetime; defaults to now. |
|
||||
| `loggedAt` | no | string | ISO-8601 datetime; defaults to the start of the given `date` in the default timezone. |
|
||||
| `externalId` | no | string | A source-stable id (1–120 chars). When present, a re-import upserts on `(user, source, externalId)` so an edit upstream is reflected rather than skipped. |
|
||||
|
||||
### `MeasurementType` values and canonical units
|
||||
@@ -172,9 +177,10 @@ hand-authored.
|
||||
| `WALKING_STEADINESS_EVENT` | event |
|
||||
| `BREATHING_DISTURBANCE_EVENT` | event |
|
||||
|
||||
The live, machine-readable list of writable types and their units is
|
||||
also served by `GET /api/meta/capabilities` under `ingest.quantityTypes`
|
||||
— the authoritative source if this table ever lags a release.
|
||||
The table is a curated subset and lags the enum by a few
|
||||
device-derived types. The live, machine-readable list of writable
|
||||
types and their units is served by `GET /api/meta/capabilities` under
|
||||
`ingest.quantityTypes` and is the authoritative source.
|
||||
|
||||
### Worked example
|
||||
|
||||
@@ -242,7 +248,7 @@ matching is case-insensitive.
|
||||
| `measuredAt` | yes | ISO-8601 **with an explicit offset** (`Z` or `±HH:MM`, and `±HHMM` is accepted). No offset, no import. |
|
||||
| `glucoseContext` | no | Only for `BLOOD_GLUCOSE`; blank means no context. See [Glucose context](#glucose-context). |
|
||||
| `notes` | no | Free text, up to 200 characters. |
|
||||
| `externalId` | no | A source-stable id. With it, a re-upload updates the same row; without it, a re-upload creates a new one. |
|
||||
| `externalId` | no | A source-stable id. With it, a re-upload updates the same row even when the value changed; without it, an identical re-upload is skipped via the natural key `(user, type, measuredAt, source)`. |
|
||||
|
||||
A sensor export therefore needs no glucose column at all:
|
||||
|
||||
|
||||
@@ -93,8 +93,9 @@ The Fitbit client id/secret are per-user (Settings), not env vars.
|
||||
|
||||
If Fitbit and another source (Withings, WHOOP, Apple Health) both supply the same
|
||||
vital — resting heart rate, blood oxygen, body temperature or respiratory rate —
|
||||
you may see both values until a future update settles on a single preferred
|
||||
source.
|
||||
the source priority ladder in **Settings → Sources** decides which one wins
|
||||
per metric. FITBIT is ranked in every default metric ladder; override
|
||||
per metric on that page.
|
||||
|
||||
## Disconnect
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ triggers the verification + CASA requirements above.)
|
||||
|
||||
If Google Health and another source (Withings, WHOOP, Apple Health) both supply
|
||||
the same vital — resting heart rate, blood oxygen, respiratory rate — the source
|
||||
priority ladder in **Settings → Integrations → Sources** decides which one wins
|
||||
priority ladder in **Settings → Sources** decides which one wins
|
||||
per metric.
|
||||
|
||||
## Disconnect
|
||||
|
||||
@@ -73,7 +73,7 @@ only when the OAuth callback must differ from that default.
|
||||
|
||||
When HealthLog calls Withings `Notify.subscribe()` for a newly-linked
|
||||
account, it hands Withings the callback URL constructed by
|
||||
`getWithingsWebhookCallbackUrl()` (`src/lib/withings/sync.ts:38-48`).
|
||||
`getWithingsWebhookCallbackUrl()` (`src/lib/withings/sync.ts`).
|
||||
The shape is:
|
||||
|
||||
```
|
||||
@@ -107,8 +107,9 @@ With the credentials saved in Settings, open
|
||||
5. The settings page now shows **Connected** with the timestamp of
|
||||
the last successful sync.
|
||||
|
||||
Subsequent syncs run automatically on every webhook delivery plus a
|
||||
safety-net cron pull every few hours.
|
||||
Subsequent syncs run automatically on every webhook delivery plus an
|
||||
hourly safety-net cron pull (measurements at :00, sleep at :15, ECG
|
||||
at :41).
|
||||
|
||||
## Source-priority interaction
|
||||
|
||||
@@ -117,7 +118,7 @@ for point measurements (weight, BP, pulse, body fat, body
|
||||
temperature, SpO₂, VO₂ max) and **APPLE_HEALTH ≻ WITHINGS ≻ MANUAL**
|
||||
for cumulative metrics (steps, active energy, distance, flights,
|
||||
sleep, HRV, resting HR). The defaults live in
|
||||
`src/lib/validations/source-priority.ts:205-220`.
|
||||
`DEFAULT_SOURCE_PRIORITY` in `src/lib/validations/source-priority.ts`.
|
||||
|
||||
Concrete consequences if you run Withings alongside an Apple Health
|
||||
import:
|
||||
@@ -130,7 +131,7 @@ import:
|
||||
when Apple Health is also linked, so the Apple Health stream wins
|
||||
for the cumulative axis. The Withings rows stay in the DB.
|
||||
|
||||
Override per-user via the Sources section of `/settings/thresholds`.
|
||||
Override per-user via Settings → Sources (`/settings/sources`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -169,7 +170,7 @@ fixes the mismatch by updating the URL Withings holds.
|
||||
Withings revokes refresh tokens on password change, app un-authorise,
|
||||
or after extended inactivity. The `WithingsConnection` row flips to
|
||||
`error_reauth` status and the scheduled sync no-ops until the user
|
||||
re-runs the OAuth flow (`src/lib/withings/sync.ts:155-161`).
|
||||
re-runs the OAuth flow (`src/lib/withings/sync.ts`).
|
||||
Click **Connect Withings** again from the settings page; the
|
||||
callback exchanges fresh tokens and the next sync runs through the
|
||||
last 30 days of history to catch up.
|
||||
@@ -179,20 +180,23 @@ last 30 days of history to catch up.
|
||||
The Settings page **Disconnect** button:
|
||||
|
||||
1. Calls `POST /api/withings/disconnect`.
|
||||
2. Server-side: revokes the access token via Withings'
|
||||
`Notify.revoke`, deletes the `WithingsConnection` row, and audits
|
||||
the action.
|
||||
2. Server-side: unsubscribes the webhook categories (best-effort
|
||||
Withings `Notify` revoke per category), deletes the
|
||||
`WithingsConnection` row with its stored encrypted tokens, and
|
||||
audits the action. The Withings-side grant itself persists until
|
||||
the user un-authorises HealthLog in their Withings account.
|
||||
3. Existing `Measurement` rows tagged `source = WITHINGS` stay in
|
||||
the database — disconnect does not delete data. Re-linking the
|
||||
same Withings account merges back onto the same metric
|
||||
timeline; the upsert path matches on `(userId, type, measuredAt,
|
||||
source)` so previously-synced rows update in place.
|
||||
|
||||
## Admin probe
|
||||
## Connection test
|
||||
|
||||
`POST /api/integrations/withings/test` (admin-only) runs a
|
||||
read-only round-trip against the user's stored credentials and
|
||||
returns the latency in milliseconds. Use it from the admin panel
|
||||
when a user reports stale data — a green test plus a stale
|
||||
`lastSyncedAt` usually means the webhook URL Withings holds is out
|
||||
of sync (re-subscribe per the steps above).
|
||||
`POST /api/integrations/withings/test` runs a read-only round-trip
|
||||
against the calling user's own stored credentials and returns the
|
||||
latency in milliseconds (rate-limited to 5/min per user). The
|
||||
Withings settings card surfaces it as the connection test. Use it
|
||||
when data looks stale — a green test plus a stale `lastSyncedAt`
|
||||
usually means the webhook URL Withings holds is out of sync
|
||||
(re-subscribe per the steps above).
|
||||
Reference in new issue
Block a user