Data model¶
This page describes the core data models and nomenclature used in SweatStack and how they interrelate. Understanding this is essential for effectively using the SweatStack API and Python client library.
SweatStack organizes athletic data using several interconnected models:
- Activities: training sessions and their metadata.
- Activity data: timeseries data within an activity.
- Longitudinal data: timeseries data across multiple activities.
- Traces: manually recorded point-in-time measurements (lactate, RPE, notes).
- Tests: structured performance evaluations (lactate tests, VO2max tests, FTP tests) with a defined results schema.
- Dailies: once-per-day health and lifestyle measurements (body mass, resting heart rate, HRV, sleep).
Each model serves a specific analytical purpose. All share a consistent approach to metrics and sport categorization, described below.
Metrics¶
First of all, metrics are the fundamental units of measurement in SweatStack. They represent specific physiological, mechanical, or environmental measurements and are consistent across all data models.
| Metric | Unit |
|---|---|
power |
Watt |
heart_rate |
Beats per minute |
speed |
Meters per second |
cadence |
Revolutions or steps per minute |
altitude |
Meters |
temperature |
Degrees Celsius |
core_temperature |
Degrees Celsius |
smo2 |
Percentage |
distance |
Meters |
latitude |
Degrees |
longitude |
Degrees |
Metrics are available as an Enum in the sweatstack.Metric class:
from sweatstack import Metric
print(Metric.power)
print(Metric.heart_rate)
# etc.
Metrics can for example be used to filter longitudinal data:
# Get longitudinal data with specific metrics
longitudinal_data = sweatstack.get_longitudinal_data(
start_time="2024-01-01",
metrics=[Metric.power, Metric.heart_rate]
)
Sports¶
SweatStack uses a hierarchical sport classification system that allows for both broad and specific categorization of activities.
At the moment, these sports are supported, but more are added regularly. Reach out to [email protected] if you need a sport that is not listed.
Source-device sports outside the SweatStack taxonomy
SweatStack normalizes the source device's sport label into this taxonomy. Activities recorded as sports that don't fit (soccer, basketball, American football, climbing, and so on) arrive as sport: "unknown" or sport: "generic", and the device's original label is not preserved. To detect a run regardless of subtype (road, trail, track, treadmill), check whether the sport value starts with running. See the native-mobile-app guide for an example where this matters: GPS smoothing.
sport
├── cycling
│ ├── cycling.road
│ ├── cycling.tt
│ ├── cycling.cyclocross
│ ├── cycling.gravel
│ ├── cycling.mountainbike
│ ├── cycling.track
│ │ ├── cycling.track.250m
│ │ └── cycling.track.333m
│ └── cycling.trainer
├── running
│ ├── running.road
│ ├── running.track
│ │ ├── running.track.200m
│ │ └── running.track.400m
│ ├── running.trail
│ └── running.treadmill
├── walking
│ └── walking.hiking
├── cross_country_skiing
│ ├── cross_country_skiing.classic
│ ├── cross_country_skiing.skate
│ ├── cross_country_skiing.backcountry
│ └── cross_country_skiing.ergometer
├── rowing
│ ├── rowing.ergometer
│ ├── rowing.indoor
│ ├── rowing.regatta
│ ├── rowing.fixed-seat
│ └── rowing.coastal
├── swimming
│ ├── swimming.pool
│ │ ├── swimming.pool.50m
│ │ ├── swimming.pool.25m
│ │ ├── swimming.pool.25y
│ │ └── swimming.pool.33m
│ ├── swimming.open_water
│ └── swimming.flume
├── generic
└── unknown
These sports are available as an Enum in the sweatstack.Sport class:
from sweatstack import Sport
print(Sport.cycling)
print(Sport.cycling_road)
# etc.
To nicely format the sport name, you can use the Sport.display_name() method:
print(Sport.cycling_road.display_name())
# Output:
# cycling (road)
Sports can for example be used to filter activities:
cycling_activities = sweatstack.get_activities(sports=[Sport.cycling])
For your convenience, applicable methods also accept the sport as a string:
cycling_activities = sweatstack.get_activities(sports=["cycling"])
When querying activities, you can use root or sub categories, or mix them:
cycling_activities = sweatstack.get_activities(sports=["cycling"])
# Get only road cycling activities
road_cycling = sweatstack.get_activities(sports=["cycling.road"])
mixed = sweatstack.get_activities(sports=["cycling.road", "running.trail"])
Activities¶
SweatStack distinguishes between activity metadata and the time-series data contained within an activity.
Activity metadata¶
Activity metadata represents a single training session and contains metadata such as an id, sport type, start and end times, etc. Activities are lightweight objects that provide an overview of a training session without the full time-series data.
sweatstack.get_activities(), sweatstack.get_activity() and sweatstack.get_latest_activity() all return an Activity object with only the metadata.
activity = sweatstack.get_latest_activity()
# Access activity metadata
print(f"Activity: {activity.id}")
print(f"Sport: {activity.sport}")
# etc.
Summary statistics¶
Every activity carries a summary object with per-metric aggregates computed from the timeseries. Commonly used paths:
summary.power.mean,summary.power.max(watts)summary.heart_rate.mean,summary.heart_rate.min,summary.heart_rate.max(bpm)summary.speed.mean,summary.speed.max(m/s)summary.distance.sum(meters)summary.altitude.gain,summary.altitude.loss,summary.altitude.min,summary.altitude.max(meters)summary.cadence.mean,summary.cadence.max(rpm or steps/min, excludes stopped periods)
The same fields appear flattened on each entry of the laps array. For an inline example payload, see activities → response shape. For every available subfield, see the ActivitySummarySummary schema in the API reference.
Laps¶
When the source recording contained lap markers, the activity list and detail responses include a laps array. Each lap has start, end, duration, and the same summary.* fields as the activity itself. No detail call is needed to read laps; they ship with the list response. See the Lap schema for the full field list.
Activity data¶
Activity data refers to the time-series data associated with an activity, usually sampled at 1Hz. This includes all metrics recorded during the activity. Activity data provides the detailed information needed for in-depth analysis of a single training session.
# Get activity data as a pandas DataFrame
data = sweatstack.get_activity_data("activity-id-123")
print(data.head())
Longitudinal data¶
Longitudinal data is activity data from multiple activities.
road_cycling_data = sweatstack.get_longitudinal_data(
sport="cycling.road",
start="2024-01-01",
end="2024-03-31",
metrics=["power", "heart_rate"],
)
Traces¶
Traces are point-in-time measurements that can exist independently of activities, but are automatically associated with activities based on their timestamp. Examples of traces are blood lactate measurements and RPE measurements during activities.
Traces are particularly useful for recording metrics that aren't automatically and continuously measured during an activity.
# Create a new trace
new_trace = sweatstack.create_trace(
timestamp=datetime.now(),
lactate=2.5,
)
# Get traces from the last month as a pandas DataFrame
traces = sweatstack.get_traces(
start=date.today() - timedelta(days=30),
end=date.today(),
)