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¶
Sports are classified using OpenSportTaxonomy (OST), an open and hierarchical sport vocabulary. A sport is a dotted code that goes from broad to specific (cycling, cycling.road), optionally followed by one or more + modifiers describing the circumstances (cycling.road+stationary, running+race).
Everywhere the API accepts or returns a sport, it uses these OST strings. The same strings work in the Python client through sweatstack.Sport, which is OST's Sport re-exported for convenience.
cycling = sweatstack.get_activities(sports=["cycling"])
road = sweatstack.get_activities(sports=["cycling.road"])
mixed = sweatstack.get_activities(sports=["cycling.road", "running.trail"])
For the full taxonomy and the Sport API (modifiers, hierarchy, matching), see open-sport-taxonomy.sweatstack.no. Reach out to info@sweatstack.no if you need a sport that is not listed.
Source-device sports outside the taxonomy
SweatStack normalizes the source device's sport label into this taxonomy. Activities recorded as unrecognized sports arrive as sport: "generic".
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(),
)