Skip to content

Mapping — Strava

Source: a Strava activity + its streams. Pillars: A + B. Mapper: mapStravamapStrava({ activity, streams }) → records[]. This is the cross-pillar shape end to end: telemetry becomes Pillar A Measurements, and a Pillar B Session references them via measuredBy rather than embedding them (§1.3).

Structural correspondence

StravaOpenBody
streams.heartrate / watts / cadencesingle-channel sampleArray Measurement (heart_rate /min, power W, cadence /min)
streams.latlng + altitudeone multi-channel location sampleArray (channels lat/lon/alt)
streams.timethe shared offsets array (irregular sampling)
average_heartrate / max_heartrateinterval quantity aggregates (heart_rate_mean/_max) with a derivedFrom link to the HR stream
the activity itselfSession (+ a continuous WorkUnit) with measuredBy links to every telemetry Measurement
device_nameprovenance.device ({ manufacturer: "garmin", model }); sourceApp: "strava"

Input (excerpt)

{
"activity": {
"id": 14582931007, "type": "Run", "sport_type": "Run",
"start_date": "2026-06-20T06:30:00Z", "elapsed_time": 2600,
"moving_time": 2520, "distance": 10000.0,
"average_heartrate": 152.0, "max_heartrate": 178,
"device_name": "Garmin Forerunner 965"
},
"streams": {
"time": { "data": [0, 1, 2, 3, 4] },
"heartrate": { "data": [140, 150, 152, 155, 158] },
"latlng": { "data": [[38.603734, -122.864112], ""] },
"altitude": { "data": [10.2, 10.6, 11.1, 11.0, 10.8] }
}
}

Output (selected records)

A heart-rate stream → a single-channel sampleArray Measurement:

{
"id": "strava-14582931007-hr",
"recordType": "Measurement",
"subject": "subj-001",
"type": "heart_rate",
"unit": "/min",
"sampleArray": { "offsets": [0, 1, 2, 3, 4], "dataPoints": [140, 150, 152, 155, 158] },
"startTime": "2026-06-20T06:30:00Z",
"endTime": "2026-06-20T07:13:20Z",
"provenance": { "method": "sensor", "sourceApp": "strava", "device": { "manufacturer": "garmin", "model": "Garmin Forerunner 965" } }
}

The lat/lon/alt streams → one multi-channel route Measurement:

{
"id": "strava-14582931007-route",
"recordType": "Measurement",
"subject": "subj-001",
"type": "location",
"sampleArray": {
"offsets": [0, 1, 2, 3, 4],
"channels": [{ "name": "lat", "unit": "deg" }, { "name": "lon", "unit": "deg" }, { "name": "alt", "unit": "m" }],
"dataPoints": [[38.603734, -122.864112, 10.2], ""]
},
"startTime": "2026-06-20T06:30:00Z",
"endTime": "2026-06-20T07:13:20Z",
"provenance": { "method": "sensor", "sourceApp": "strava", "device": { "manufacturer": "garmin", "model": "Garmin Forerunner 965" } }
}

The average HR → an interval quantity aggregate with derivedFrom:

{
"id": "strava-14582931007-hr-mean",
"recordType": "Measurement",
"subject": "subj-001",
"type": "heart_rate_mean",
"quantity": 152,
"unit": "/min",
"startTime": "2026-06-20T06:30:00Z",
"endTime": "2026-06-20T07:13:20Z",
"links": [{ "type": "derivedFrom", "ref": "strava-14582931007-hr" }],
"provenance": { "method": "algorithm", "sourceApp": "strava", "device": { "manufacturer": "garmin", "model": "Garmin Forerunner 965" }, "algorithm": { "name": "strava-summary", "version": "v3" } }
}

The activity → a Session whose continuous WorkUnit references the telemetry:

{
"id": "strava-14582931007",
"recordType": "Session",
"subject": "subj-001",
"disciplines": ["run"],
"intent": "train",
"startTime": "2026-06-20T06:30:00Z",
"endTime": "2026-06-20T07:13:20Z",
"workUnits": [
{
"id": "strava-14582931007-wu",
"recordType": "WorkUnit",
"scoring": "continuous",
"performance": { "distance": { "absolute": { "value": 10000, "unit": "m" } }, "time": { "absolute": { "value": 2520, "unit": "s" } } },
"links": [
{ "type": "measuredBy", "ref": "strava-14582931007-hr" },
{ "type": "measuredBy", "ref": "strava-14582931007-route" }
]
}
]
}

Notes

  • continuous scoring may carry any of distance, time, energy and requires no single metric — the endurance shape works end to end.
  • endTime is start_date + elapsed_time (2600 s → 07:13:20Z); moving_time (2520 s) is the WorkUnit’s recorded time.
  • peerSensor (a parallel co-stream, e.g. two HR straps) is distinct from sameActivityAs (whole-activity dedup across sources) — see the data model.