Mapping — Strava
Source: a Strava activity + its streams. Pillars: A + B. Mapper:
mapStrava —
mapStrava({ 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
| Strava | OpenBody |
|---|---|
streams.heartrate / watts / cadence | single-channel sampleArray Measurement (heart_rate /min, power W, cadence /min) |
streams.latlng + altitude | one multi-channel location sampleArray (channels lat/lon/alt) |
streams.time | the shared offsets array (irregular sampling) |
average_heartrate / max_heartrate | interval quantity aggregates (heart_rate_mean/_max) with a derivedFrom link to the HR stream |
| the activity itself | Session (+ a continuous WorkUnit) with measuredBy links to every telemetry Measurement |
device_name | provenance.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
continuousscoring may carry any ofdistance,time,energyand requires no single metric — the endurance shape works end to end.endTimeisstart_date + elapsed_time(2600 s →07:13:20Z);moving_time(2520 s) is theWorkUnit’s recordedtime.peerSensor(a parallel co-stream, e.g. two HR straps) is distinct fromsameActivityAs(whole-activity dedup across sources) — see the data model.