Back to blog

How to Structure Health Data for AI: What Your LLM Actually Needs

Open Wearables Team · · 6 min read

Key Takeaways

  • LLMs do not need all your health data. They need the right data, in a consistent structure, with enough context to reason about it.
  • Raw wearable data from different providers uses different field names, units, and timestamp formats. An LLM receiving inconsistent data will produce inconsistent output.
  • Open Wearables normalizes all provider data into a unified model: Workout, SleepSession, ActivitySummary, and TimeSeriesSample. Every provider returns the same field names, same units, same timestamp format.
  • The MCP server exposes this normalized data through five queryable tools, so your LLM retrieves exactly what it needs for each question rather than receiving a data dump.
  • What the LLM does with that data is your responsibility. Open Wearables provides the data structure. You provide the reasoning framework.

The most common mistake in health AI integrations is treating data formatting as a solved problem. Developers spend weeks on OAuth flows and provider integrations, get data flowing, and then pass raw API responses directly into an LLM prompt. The model produces something that sounds plausible. Then a user from a different provider connects, the field names are different, the units are different, and the output breaks.

Structured health data for AI is not just about getting data into context. It is about getting consistent, queryable, well-typed data that a model can reason about reliably regardless of which device a user has.

Why Consistency Matters More Than Completeness

An LLM reasoning about health data needs to compare values over time, across sessions, across metrics. That comparison only works if the data is consistent.

Consider distance. Garmin returns distance in meters. Some providers return it in kilometers. If your prompt contains workout records from both without normalization, the model has no way to know which unit applies to which record. It will either guess or produce nonsense.

Consider timestamps. Some providers return local time. Others return UTC. If your model is answering "how did I sleep last Tuesday?" and the timestamps are not normalized to a consistent format, the answer may be wrong by hours.

Consider field names. A workout's average heart rate might be averageHR, avg_hr, heart_rate_avg, or heartRate depending on the provider. If your prompt mixes these, the model treats them as different concepts.

The solution is normalization before the data reaches the model, not prompt engineering around the inconsistency.

The Open Wearables Data Model

Open Wearables normalizes all provider data into a consistent set of types. These are the schemas your LLM receives through the API or MCP tools, defined in the backend codebase.

Workout

A discrete exercise session:

            id: UUID
type: str              # "running", "cycling", "swimming", etc.
name: str | None       # "Morning Run"
start_time: datetime   # UTC
end_time: datetime     # UTC
duration_seconds: int | None
calories_kcal: float | None
distance_meters: float | None    # always meters, regardless of provider
avg_heart_rate_bpm: int | None   # always bpm
max_heart_rate_bpm: int | None
avg_pace_sec_per_km: int | float | None
elevation_gain_meters: float | None
source: SourceMetadata           # which provider and device
          

Distance is always in meters. Heart rate is always in bpm. Timestamps are always UTC. The model does not need to handle unit conversion or field name variations.

SleepSession

A discrete sleep or nap period:

            id: UUID
start_time: datetime
end_time: datetime
duration_seconds: int
sleep_duration_seconds: int | None   # actual sleep, not just time in bed
efficiency_percent: float | None
is_nap: bool                         # distinguishes naps from main sleep
stages:
  awake_minutes: int | None
  light_minutes: int | None
  deep_minutes: int | None
  rem_minutes: int | None
source: SourceMetadata
          

The is_nap field is populated for providers that distinguish naps from main sleep sessions (Apple Health, Oura). This matters when your model is calculating sleep debt or readiness: a 25-minute afternoon nap and an 8-hour overnight session are both sleep records, but should not be treated identically.

ActivitySummary

Daily aggregate data:

            date: date
steps: int | None
distance_meters: float | None
active_calories_kcal: float | None
total_calories_kcal: float | None
active_minutes: int | None
sedentary_minutes: int | None
intensity_minutes:
  light: int | None
  moderate: int | None
  vigorous: int | None
heart_rate:
  avg_bpm: int | None
  max_bpm: int | None
  min_bpm: int | None
source: SourceMetadata
          

TimeSeriesSample

High-frequency time series data:

            timestamp: datetime
type: SeriesType     # "heart_rate", "spo2", "hrv", etc.
value: float | int
unit: str            # "bpm", "percent", "ms", etc.
source: SourceMetadata
          

This is the continuous data: heart rate every few seconds during a workout, SpO2 readings, HRV samples. For most LLM queries, you do not want to pass raw time series into context. You pass aggregates or specific samples, not thousands of rows.

What to Pass to Your LLM and What to Leave Out

The normalized data model gives you consistency. The next question is selection: what subset of that data belongs in the context for a given query?

  • For questions about training: workouts from the relevant time range, activity summaries for the same period. Avoid raw time series unless the question is specifically about intra-workout patterns.
  • For questions about recovery or readiness: sleep sessions, HRV samples if available, resting heart rate from activity summaries. Training load from recent workouts for context.
  • For questions about daily habits: activity summaries (steps, active minutes, calories). Sleep summaries. No need for individual workout records.

What to avoid: full-resolution time series (thousands of rows per session), data outside the relevant time window, fields that are null for most providers, multiple records of the same session from different providers without deduplication.

The Open Wearables MCP server handles the selection problem: the model calls a tool with a date range and the tool returns the relevant data. The model does not receive everything; it queries what it needs.

The Reasoning Layer Is Yours to Build

Open Wearables provides consistent, normalized, queryable health data. It does not tell your LLM how to reason about that data.

For a production health AI feature, you need to define: reference ranges (a resting heart rate of 58 bpm means nothing without knowing the user's baseline), domain rules (what metrics matter for your use case and how to interpret them), guardrails (what the model should not conclude without clinical context), and aggregation logic (when a user has both Garmin and Apple Health data for the same day, which takes precedence).

The data structure is the foundation. The reasoning layer is what you build on top of it.

Related articles

FAQ

Does Open Wearables handle unit normalization automatically?

Yes. All provider data is normalized to consistent units at ingestion. Distance is always in meters. Heart rate is always in bpm. Duration is always in seconds. Your LLM receives consistent units regardless of which provider a user has connected.

How do I handle users with multiple connected devices?

Open Wearables stores records from each provider separately with a source field identifying the provider and device. The platform includes configurable data priority rules for determining which source to use when multiple providers record the same data. Your application sets the priority order.

Should I pass raw time series data to my LLM?

Generally no. Full-resolution time series produces thousands of rows that exceed context limits and add noise. Pass aggregates (ActivitySummary, SleepSession) for most queries. Use TimeSeriesSample for specific lookups: peak heart rate during a workout, HRV samples around a specific event.

How does the MCP server decide what data to return?

The MCP tools are queryable by user ID and date range. The model calls the appropriate tool with the parameters it needs. It does not receive a data dump; it queries specific data for specific questions.

Can I build my own reasoning layer on top of Open Wearables data?

Yes. The normalized data model and REST API give you full access to the underlying data. You can build your own prompting strategy, aggregation logic, and domain rules entirely outside of Open Wearables. The platform handles data ingestion and normalization. The reasoning layer is yours.

Never miss an update

Stay updated with the latest in open wearables, developer tools, and health data integration.

Join our Community. No spam ever.