Skip to content

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(),
)