## Reverse engineering the Strava Suffer Score algorithm

### 3 March 2017

A few months ago, I purchased a Suunto Ambit3 Peak after four years and thousands of kilometres with my trusty Polar RC3. One of the main attractions of this device is the ability to develop custom apps, a feature that sets it apart from its competitors.

Around the same time, Strava introduced the Suffer Score, a feature designed to quantify training effort based on heart rate, allowing athletes of various levels and disciplines to compare their performance intensity. This feature is only available for premium users, which I’m not. Fortunately, an acquaintance has recently upgraded to premium and was happy to share his training data with me. This allowed me to reverse engineer the Suffer Score algorithm and create a Movescount app to track the training intensity, even without a premium Strava membership.

Strava uses five customisable heart rate zones (only available for Premium users) that well adapts to the three most-known systems: Allen & Coggan’s five zones described in Training and Racing with a Power Meter, the simplified versions of Friel’s Training Bible seven zones and Fitzgerald’s 80/20 Endurance 5+2 zones.

## Deciphering the formula

After reading their official blogpost describing the new feature, it was clear that the Suffer Score is the sum of the time spent in each zone multiplied by a corresponding zone constant:

$score=\sum_{n=1}^{5} t_{n} \cdot z_{n}$

What I was missing were the values of $$z_{n}$$, which I was able to infer by analysing the aforementioned training data. Luckily for me the dataset was well structured and diversified, ranging from easy trainings fully spent in Z1 and Z2 to interval sessions that included all the five zones. Thanks to those easy runs I was able to calculate the value of the first two zones constants ($$z_{1}$$ and $$z_{2}$$) by using the binomial equation $$score=t_{1} \cdot z_{1}+t_{2} \cdot z_{2}$$ with $$score$$, $$t_{1}$$ and $$t_{2}$$ being known values. From there I worked my way up until I got all the five constants.

Unfortunately I’m not allowed to share the data I used since it belongs to an athlete who prefers keeping his trainings private, but these are the end results:

• Z1 - Endurance: 25 point/hour
• Z2 - Moderate: 60 point/hour
• Z3 - Tempo: 115 point/hour
• Z4 - Threshold: 250 point/hour
• Z5 - Anaerobic: 300 point/hour

## Movescount integration

Since only two fixed heart parameters were available, rest (SUUNTO_USER_REST_HR) and max (SUUNTO_USER_MAX_HR), I wasn’t able to use the lactate threshold heart rate or other advanced metrics to calculate the five zones. I instead used a more simplistic approach using the max heart rate as a reference point:

• Z1 - Endurance: ≤60%
• Z2 - Moderate: 60-70%
• Z3 - Tempo: 70-80%
• Z4 - Threshold: 80-90%
• Z5 - Anaerobic: 90-100%

The app runs once per second, providing the current heart rate (SUUNTO_HR) and showing on screen the result value (RESULT). From here, building a Movescount app was quite straightforward despite the limited syntax1.

RESULT = SCORE;
if(SUUNTO_HR >= SUUNTO_USER_REST_HR && SUUNTO_HR <= 60*SUUNTO_USER_MAX_HR/100) {
SCORE = SCORE + 25/3600;
} else if(SUUNTO_HR > 60*SUUNTO_USER_MAX_HR/100 && SUUNTO_HR <= 70*SUUNTO_USER_MAX_HR/100) {
SCORE = SCORE + 60/3600;
} else if(SUUNTO_HR > 70*SUUNTO_USER_MAX_HR/100 && SUUNTO_HR <= 80*SUUNTO_USER_MAX_HR/100) {
SCORE = SCORE + 115/3600;
} else if(SUUNTO_HR > 80*SUUNTO_USER_MAX_HR/100 && SUUNTO_HR <= 90*SUUNTO_USER_MAX_HR/100) {
SCORE = SCORE + 250/3600;
} else {
SCORE = SCORE + 300/3600;
}


You can find the app in the Movescount Apps section under the name Strava Suffer Score, or by clicking here.

1. A switch statement would have been better, unfortunately the available syntax is really limited.