> ## Documentation Index
> Fetch the complete documentation index at: https://openwearables.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Unified Health Data Model

> Open Wearables normalizes health data into one unified schema. Open algorithms, no black boxes. Covers events, timeseries, device mapping, and entities.

The Open Wearables project aims to unify health monitoring data - to do that, a stable, optimised data architecture is paramount. This problem is not trivial, as we face raw data served in heterogeneous formats, with potential erroneous observations, noise interfering with the recorded values and other such issues. On top of that, the potential volume of data is a problem on its own, and without proper structure optimization will be unacceptably memory taxing, calculation intensive, if not both.

## Technical overview

To answer this issue we propose a unified data model - designed for wearable data storage.

<img src="https://mintcdn.com/momentum-64cd1fcc/gJEFp5rFyuk4HlZo/images/current-data-model.png?fit=max&auto=format&n=gJEFp5rFyuk4HlZo&q=85&s=0d0285034d3bec74f85f2627cf7fceaf" alt="Current Data Model" width="1120" height="1020" data-path="images/current-data-model.png" />

<Warning>
  This data model structure is still under active development and is subject to change. The architecture may evolve based on feedback, performance requirements, and emerging use cases. Please refer to the roadmap section for planned changes.
</Warning>

## Design goals

The design goals behind outlined structure were as follows:

* **Uniformity** - the structures must be as consistent as possible, handling similar datatypes in similar ways. This serves two purposes
* **Ease of work** - Any developer interacting with the data needs to see a clear structure, that can be modified without excessive documentation hunting
* **Straightforward analytics** - Any service - be it internal, or working as a datasink, needs to interact with consistent data structures, that can be easily connected, offering the freedom of a full multivariate data model
* **Modularity** - The structure must be easily extendible, allowing for additional subtypes, coming from multiple providers. This is dictated by both the fast pace of the wearables market, as well as the multiple use cases for wearable data - we cannot lock ourselves into few predefined datatypes
* **Compressibility** - As the data stored will amount to fast growing, vast volumes of observations, we must optimize for storage efficiency. Time series data is notoriously storage intensive and needs to be accounted for.
* **Efficiency** - The data is to be used, not simply stored - we aim to provide a strong and efficient backbone for analytics and computation, and while memory optimisation is the primary concern, efficiency of handling is a close second.

## Data Structure

Those goals led us to employ a structure, consisting of three core components:

* **Events** (*EventRecord + EventRecordDetails*) - is meant to store singular records or events, with multiple descriptors and aggregate values that can be defined for the whole period of the event. Those are defined by a prolonged in time structure (meaning start/stop timestamps and duration).
  * Each event is assigned a type, supporting one of the activities existing in the system (i.e. workout)
  * Supporting the core EventRecord structure is an EventRecordDetails tied to it - this record is dictated by the type of the event, and holds the type specific aggregates and values
* **Series** (*DataPointSeries*) - is meant to store all the medium to high frequency time series in a shared space, demarcated by type and source device id assigned to each data point
  * The observation values are stored as float values, offering no data loss between integer and float values possible for different observation types, as well as handling categorical data as numerical encoding
  * **Note: this is a current concession as we are fully aware of the adverse effect this has on storage space optimisation - this stopgap is planned to be easily migrated as described in the roadmap section**
* **Descriptors** (*PersonalRecord*) - stores static user biometric information that changes slowly, if ever. Currently includes birth date (used for age calculation), sex, and gender. This structure is designed to be extended with additional static or slow-changing user attributes as needed.
  * The time series data will be stored in the initial release along other series - eventually, BodyState table that backreferences a user that it is tied to, and holds all the supported and potentially possible descriptors will be introduced - here null values and redundant fields are a lesser evil, as the records will be sparse, and avoiding structure bloat is a bigger gain
* **ID mapping** (*ExternalDeviceMapping*) - mapping internal reference id to a combination of user/device/provider, ensuring clear identification of the source, while preventing structure clutter with excessive IDs in other tables and reducing datapoint width, especially important for time series data
* **Series descriptors** (*SeriesTypeDefinition*)  - meant to store series related details, applicable for the entire series type (i.e. unit type in which values are stored). This way we avoid redundant fields in the time series table, eventually ending up with minimal implementation of the datapoints.

### Entity Relationship Diagram

The following diagram illustrates the current database schema and relationships between all entities:

```mermaid theme={null}
erDiagram
    User ||--o| PersonalRecord : "has"
    User ||--o{ UserConnection : "has"
    User ||--o{ ExternalDeviceMapping : "has"
    
    ExternalDeviceMapping ||--o{ EventRecord : "produces"
    ExternalDeviceMapping ||--o{ DataPointSeries : "produces"
    
    EventRecord ||--o| EventRecordDetail : "has"
    EventRecordDetail ||--o| SleepDetails : "polymorphic"
    EventRecordDetail ||--o| WorkoutDetails : "polymorphic"
    
    DataPointSeries }o--|| SeriesTypeDefinition : "references"
    
    Developer ||--o{ Application : "owns"
    Developer ||--o{ ApiKey : "creates"
    Developer ||--o{ Invitation : "sends"
    
    Device ||--o{ DeviceSoftware : "has"
    
    User {
        uuid id PK
        timestamp created_at
        string first_name
        string last_name
        string email
        string external_user_id
    }
    
    PersonalRecord {
        uuid id PK
        uuid user_id FK
        date birth_date
        bool sex
        string gender
    }
    
    UserConnection {
        uuid id PK
        uuid user_id FK
        string provider
        string provider_user_id
        string access_token
        string refresh_token
        timestamp token_expires_at
        string status
    }
    
    ExternalDeviceMapping {
        uuid id PK
        uuid user_id FK
        string provider_name
        string device_id
    }
    
    EventRecord {
        uuid id PK
        uuid external_device_mapping_id FK
        string external_id
        string category
        string type
        string source_name
        int duration_seconds
        timestamp start_datetime
        timestamp end_datetime
    }
    
    EventRecordDetail {
        uuid record_id PK
        string detail_type
    }
    
    SleepDetails {
        uuid record_id PK
        int sleep_total_duration_minutes
        int sleep_time_in_bed_minutes
        decimal sleep_efficiency_score
        int sleep_deep_minutes
        int sleep_rem_minutes
        int sleep_light_minutes
        int sleep_awake_minutes
        bool is_nap
    }
    
    WorkoutDetails {
        uuid record_id PK
        int heart_rate_min
        int heart_rate_max
        decimal heart_rate_avg
        decimal energy_burned
        decimal distance
        int steps_count
        decimal max_speed
        decimal max_watts
        int moving_time_seconds
        decimal total_elevation_gain
    }
    
    DataPointSeries {
        uuid id PK
        uuid external_device_mapping_id FK
        int series_type_definition_id FK
        string external_id
        timestamp recorded_at
        decimal value
    }
    
    SeriesTypeDefinition {
        int id PK
        string code
        string unit
    }
    
    Developer {
        uuid id PK
        timestamp created_at
        timestamp updated_at
        string first_name
        string last_name
        string email
        string hashed_password
    }
    
    Application {
        uuid id PK
        uuid developer_id FK
        string app_id
        string app_secret_hash
        string name
        timestamp created_at
        timestamp updated_at
    }
    
    ApiKey {
        string id PK
        uuid created_by FK
        string name
        timestamp created_at
    }
    
    Invitation {
        uuid id PK
        uuid invited_by_id FK
        string email
        string token
        string status
        timestamp expires_at
        timestamp created_at
    }
    
    Device {
        uuid id PK
        string serial_number
        string provider_name
        string name
    }
    
    DeviceSoftware {
        uuid id PK
        uuid device_id FK
        string version
    }
    
    ProviderSetting {
        string provider PK
        bool is_enabled
        string live_sync_mode "pull | webhook | null"
    }
```

## Technical backbone

The current iteration is fully built using python native methods - utilising SQLalchemy for model declaration, pydantic for data validation and passing within the service. This layer of abstraction offers modularity and extensibility (via Alembic) in line with our stated goals.

<img src="https://mintcdn.com/momentum-64cd1fcc/gJEFp5rFyuk4HlZo/images/data-usage.png?fit=max&auto=format&n=gJEFp5rFyuk4HlZo&q=85&s=3aac772efbb5cf46f7b22aa7701c811e" alt="Data usage" width="3727" height="2814" data-path="images/data-usage.png" />

The proposed model fits with the wider open wearables environment and use cases - it serves as a strong foundation layer for future data processing, computation of complex metrics, and what is at heart of the project - ease of access to the insights and patterns within the stored data.
This data is designed to be served via multiple channels, accessible through dedicated services serving this data as either classical API endpoints, or new access methods such as MCP servers and AI agents.

## Roadmap

<img src="https://mintcdn.com/momentum-64cd1fcc/gJEFp5rFyuk4HlZo/images/data-roadmap.png?fit=max&auto=format&n=gJEFp5rFyuk4HlZo&q=85&s=80f1827eb2d0d97380d4dacb2b0ee1c3" alt="Data roadmap" width="3290" height="2553" data-path="images/data-roadmap.png" />

The presented unified data model is a first iteration of the underlying Open Wearables data structure - we fully intend to expand upon it, and introduce new features. Some of the planned stages of development are as follows:

1. **Base model architecture (starting point)** - The base data model discussed above, provided with the first release of the Open Wearables platform
2. **Wide data support** - consists of extending the existing data structures with wide support for both events and series existing in the wider wearables ecosystem
3. **Complex metrics calculation** - assumes extension of the currently existing base types of time series with complex metrics, covering both multivariate datapoints and derivative values, calculated from other datapoints. Here, reaching a compromise between incoming, registered data (come of the complex types are widely supported and will be received as raw data), and our internal calculation - the goal will be to validate and use the most trusted ones, according to either user defined priority configuration, or calculated trust scores.
4. **Data model expansion for time series** - at this stage, we assume that the time series shared model will need to be revisited. As stated in the initial description, the current approach is a stopgap, potentially sacrificing the storage optimisation - as our stated goal is to avoid unnecessary memory bloat, this will need to be resolved, using insight from the earlier stages and potential feedback. The presented structure is one of the possibilities, splitting the series according to data types, representing a compromise between classic data warehousing and relational databases. Here we can also think about separating the time series data describing the user body states into separate, snapshot-like records.
