Releasesv0.1.0

Try it without a wearable

Fill an instance with synthetic data from six sources and try every read route.

Connecting a real provider takes developer keys, an OAuth app and a device on someone's wrist. To see what the API returns before any of that, fill an instance with synthetic data:

docker compose --env-file .env -f deploy/docker-compose.yml exec ohs /ohs seed --demo

Outside Docker, ohs seed --demo needs OHS_DATABASE_URL only, like ohs admin.

The command creates a tenant named demo with one API key, printed once, and a user with external_ref demo-user. It writes 90 days of data for that user, ending yesterday, and prints the ids and a few calls to try:

tenant  6f0c...
user    2b9e...
API key, shown only this once, store it now:
ohs_sk_...

Try it:
  export OHS_URL=http://localhost:8080 OHS_API_KEY=ohs_sk_...
  curl -s -H "Authorization: Bearer $OHS_API_KEY" "$OHS_URL/v1/users/2b9e.../daily"
  curl -s -H "Authorization: Bearer $OHS_API_KEY" "$OHS_URL/v1/users/2b9e.../sleep"
  curl -s -H "Authorization: Bearer $OHS_API_KEY" "$OHS_URL/v1/users/2b9e.../fhir/Observation?code=http://loinc.org%7C8867-4&limit=5"

Set OHS_URL before the command to print another base URL in the examples.

What it writes

One invented person, living in Europe/Paris, seen through six sources that overlap the way real devices do:

SourceDevice it playsData
ouraa ring, worn at nightsleep with stages, night heart rate, resting heart rate, HRV (RMSSD), breathing rate, temperature deviation, a daily summary with readiness, sleep and activity scores
whoopa strap, worn day and nightheart rate every 10 minutes, sleep with stages and SpO2, HRV, resting heart rate, recovery and strain, the workouts it detects
withingsa scale and a blood pressure cuffweight, body fat, lean mass and BMI most mornings; blood pressure and heart rate on Sundays
fitbita band, worn in the dayhourly steps, distance, active energy and floors; heart rate every 15 minutes; daily resting heart rate; the night's SpO2
polara sports watchruns, rides, walks and strength sessions with heart rate, energy, distance, a GPS route and a VO2 max estimate after runs
apple_healtha phone in a pockethourly steps, distance and floors (it stays home during runs), a weight typed in by hand on Sundays

So heart rate comes from five sources, sleep from two, steps from two (three with the ring's daily total), weight and workouts from two each, and the same night or run appears once per source, a few minutes apart, as it would from real devices. About 35,000 quantity samples in all.

The values are plausible but made up: resting heart rate around 50 to 60 bpm, 7 to 8 hours of sleep with the usual deep, REM and light pattern, 7,000 to 12,000 steps on most days, a weight that drifts slowly, workouts on a weekly rhythm, a slow fitness cycle that moves HRV and resting heart rate together. Every sample, sleep session, workout and provider daily summary carries the metadata {"synthetic": true} (the derived daily rows carry "derived": true, as always), and the external id of every sleep and workout starts with demo-, so demo rows are easy to tell apart from real ones. None of the six connectors needs to be configured: the data is stored and read like any other.

Personas

--persona picks another kind of person. The default, mix, is the person above, seen through all six sources. The others are a person of their own each, seen through a few sources, for a demo of one kind of user or of a narrower product:

PersonaSourcesWho
mixall sixthe person above (seed 1)
runnerpolar, whoop, apple_healtha trained runner: resting heart rate about 46 bpm, HRV about 78 ms, VO2 max about 56, about 12,000 steps a day, runs on most days (easy runs, intervals, a tempo run and a long Saturday run); the phone goes on the runs
sleepoura, apple_healtha short sleeper: about 6.2 hours of sleep a night, to bed later and at more irregular times (around midnight, later at the weekend), resting heart rate about 63 bpm, HRV about 30 ms, about 6,000 steps, a walk now and then
weightwithings, fitbitsomeone watching their weight: about 92 kg, body fat about 28 percent, about 5,500 steps, a walk now and then; the weight falls slowly from mid February to mid December (about 6 kg) and comes back over the holidays, the same each year
ohs seed --demo --persona runner

Each persona has its own seed (2, 3 and 4; the mix keeps 1) and its own user by default, demo-runner, demo-sleep and demo-weight, so two personas never fill one user. --seed and --external-ref still choose another person or user; the same seed is another person in each persona. The figures above are averages: each day varies around them, and a date's records depend only on the seed and the date, never on the day the command runs.

How it is written

The records go through the same path as data from a provider: units, validation against the plausible ranges, content hash deduplication, the daily summaries derived from steps, distance and energy, coverage, and the data.written webhook events of the tenant. Only the raw store is skipped, because there is no provider payload to keep.

The data is deterministic: the same --persona and --seed give the same values for a given date. Running the command again adds nothing. Run on a later day, it adds the new days and leaves the others.

Flags

FlagDefaultMeaning
--demorequiredConfirms that synthetic data is wanted. Without it the command refuses to run.
--persona <name>mixThe kind of person: mix, runner, sleep or weight (Personas).
--tenant <id>the demo tenantWrite into an existing tenant instead. No key is created or printed.
--days <n>90Days of data, ending yesterday, at most 365.
--external-ref <ref>demo-user, or demo-<persona>The user to create, or to reuse when the tenant already has one with this ref.
--seed <n>the persona's (1 for mix)Another seed gives another person and other data.

The key of the demo tenant is printed on the first run only. To get another one later:

ohs admin key create --tenant <tenant id>

From the admin API

An instance admin key does the same over HTTP for any tenant, without a shell on the server: POST /v1/admin/tenants/{tenant_id}/demo-user with {"persona": "runner", "external_ref": "demo-runner", "days": 30} (all optional, days at most 90) writes the same data through the same code, for the persona's own seed, and answers the user with rows_written and persona. See admin-api.md.

Things to try

With OHS_URL, OHS_API_KEY and USER_ID set from the output:

H="Authorization: Bearer $OHS_API_KEY"
# Heart rate from every source, and one source at a time
curl -s -H "$H" "$OHS_URL/v1/users/$USER_ID/samples?type=heart_rate&limit=5"
# Sleep sessions, then the stages of one night
curl -s -H "$H" "$OHS_URL/v1/users/$USER_ID/sleep?limit=2"
curl -s -H "$H" "$OHS_URL/v1/users/$USER_ID/sleep/<session id>/phases"
# Workouts and the GPS route of one
curl -s -H "$H" "$OHS_URL/v1/users/$USER_ID/workouts?limit=3"
curl -s -H "$H" "$OHS_URL/v1/users/$USER_ID/workouts/<workout id>/route"
# Daily summaries: provider scores and the totals derived from samples
curl -s -H "$H" "$OHS_URL/v1/users/$USER_ID/daily?limit=7"
# Open mHealth and a CSV export
curl -s -H "$H" "$OHS_URL/v1/users/$USER_ID/omh/data-points?schema=omh:heart-rate&limit=1"
curl -sOJ -H "$H" "$OHS_URL/v1/users/$USER_ID/exports/samples?type=step_count&format=csv"

For an agent, point ohs mcp at the instance with OHS_MCP_KEY=$OHS_API_KEY (see mcp.md) and ask about the demo user's sleep or training. The routes are described in api.md; FHIR in fhir.md, Open mHealth in open-mhealth.md, exports in exports.md.

Removing it

The demo user is an ordinary user: DELETE /v1/users/{id} erases it and its data. On a development instance you can also leave the demo tenant in place; its rows never mix with another tenant's.

Edit on GitHub

On this page