Releasesv0.1.0

Data model and units

One canonical model, one unit per quantity, local days, provenance on every record.

Every source maps into the same records. Whatever a provider calls it, heart rate is heart_rate in bpm.

RecordHolds
QuantitySampleone scalar measurement: heart rate, steps, SpO2, weight...
CategorySamplea categorical event: a stand hour, a menstrual flow entry
SleepSession and SleepPhasea night with its duration breakdown, then each stage
Workout and RoutePointa session with its metrics, then its GPS track
NutritionLoga food or drink entry with its nutrients
DailySummarytotals per local day, from the provider or rolled up from samples
ECGRecording, HeartbeatSamplestored today; their read routes are planned
Observationa generic coded fact for the long tail; no connector writes it yet

Every field, and the full list of quantity names, is in the data model reference.

One unit per quantity

Each quantity name has one storage unit, a UCUM unit and a LOINC code where one exists. Values are converted on the way in, and what the provider sent is kept in metadata:

{
  "type": "heart_rate",
  "value": 72.5,
  "unit": "bpm",
  "metadata": { "original_unit": "count/min", "original_value": 72.5, "timezone": "Europe/Paris" }
}

GET /v1/users/{id}/samples answers 400 for a type outside the catalog (a typo such as heartrate), so a wrong name is never mistaken for missing data. The MCP tool list_quantity_types lists the catalog too.

Local days, not UTC days

A daily summary belongs to the local calendar day of its records, taken from the sample's time zone (UTC when the source sends none). A run at 23:30 in Paris counts on that day in Paris, not on the next day in UTC.

When a provider sends no daily summary of its own, step, energy, distance and flight totals are derived from samples. Where several sources report the same day, the largest per-source total wins, so an iPhone and an Apple Watch are not added together.

Provenance

Every record carries where it came from:

Prop

Type

Duplicates and plausibility

  • Records are de-duplicated by content hash: the same reading uploaded twice is stored once.
  • A value outside its hard plausible range rejects the record; outside the soft range it is stored and flagged in its metadata.
  • Rejected lines are counted in the coverage report's rejected_records. See Processing and coverage.
Edit on GitHub

On this page